opencos-eda 0.2.48__tar.gz → 0.2.49__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. {opencos_eda-0.2.48/opencos_eda.egg-info → opencos_eda-0.2.49}/PKG-INFO +1 -1
  2. opencos_eda-0.2.49/opencos/__init__.py +6 -0
  3. opencos_eda-0.2.49/opencos/_version.py +19 -0
  4. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/flist.py +8 -7
  5. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/multi.py +13 -14
  6. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/sweep.py +3 -2
  7. opencos_eda-0.2.49/opencos/deps/defaults.py +69 -0
  8. opencos_eda-0.2.49/opencos/deps/deps_commands.py +419 -0
  9. opencos_eda-0.2.49/opencos/deps/deps_file.py +326 -0
  10. opencos_eda-0.2.49/opencos/deps/deps_processor.py +670 -0
  11. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/deps_schema.py +7 -8
  12. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda.py +84 -64
  13. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_base.py +572 -316
  14. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_config.py +80 -14
  15. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_extract_targets.py +22 -14
  16. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_tool_helper.py +33 -7
  17. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/export_helper.py +166 -86
  18. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/export_json_convert.py +31 -23
  19. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/files.py +2 -1
  20. {opencos_eda-0.2.48/opencos → opencos_eda-0.2.49/opencos/hw}/oc_cli.py +9 -4
  21. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/names.py +0 -4
  22. opencos_eda-0.2.49/opencos/peakrdl_cleanup.py +26 -0
  23. opencos_eda-0.2.49/opencos/seed.py +36 -0
  24. opencos_eda-0.2.49/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  25. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/helpers.py +3 -2
  26. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_deps_helpers.py +35 -32
  27. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_eda.py +36 -29
  28. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_eda_elab.py +5 -3
  29. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_eda_synth.py +1 -1
  30. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_oc_cli.py +1 -1
  31. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_tools.py +3 -2
  32. opencos_eda-0.2.49/opencos/tools/__init__.py +0 -0
  33. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/iverilog.py +2 -2
  34. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/modelsim_ase.py +2 -2
  35. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/riviera.py +1 -1
  36. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/slang.py +1 -1
  37. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/surelog.py +1 -1
  38. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/verilator.py +1 -1
  39. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/vivado.py +1 -1
  40. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/yosys.py +4 -3
  41. opencos_eda-0.2.49/opencos/util.py +737 -0
  42. opencos_eda-0.2.49/opencos/utils/__init__.py +0 -0
  43. opencos_eda-0.2.49/opencos/utils/markup_helpers.py +98 -0
  44. opencos_eda-0.2.49/opencos/utils/str_helpers.py +111 -0
  45. opencos_eda-0.2.49/opencos/utils/subprocess_helpers.py +108 -0
  46. {opencos_eda-0.2.48 → opencos_eda-0.2.49/opencos_eda.egg-info}/PKG-INFO +1 -1
  47. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos_eda.egg-info/SOURCES.txt +12 -3
  48. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos_eda.egg-info/entry_points.txt +1 -1
  49. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/pyproject.toml +8 -3
  50. opencos_eda-0.2.48/opencos/__init__.py +0 -4
  51. opencos_eda-0.2.48/opencos/_version.py +0 -16
  52. opencos_eda-0.2.48/opencos/deps_helpers.py +0 -1346
  53. opencos_eda-0.2.48/opencos/peakrdl_cleanup.py +0 -20
  54. opencos_eda-0.2.48/opencos/seed.py +0 -28
  55. opencos_eda-0.2.48/opencos/util.py +0 -831
  56. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/LICENSE +0 -0
  57. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/LICENSE.spdx +0 -0
  58. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/README.md +0 -0
  59. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/_waves_pkg.sv +0 -0
  60. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/__init__.py +0 -0
  61. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/build.py +0 -0
  62. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/elab.py +0 -0
  63. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/export.py +0 -0
  64. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/lec.py +0 -0
  65. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/open.py +0 -0
  66. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/proj.py +0 -0
  67. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/shell.py +0 -0
  68. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/sim.py +0 -0
  69. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/synth.py +0 -0
  70. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/targets.py +0 -0
  71. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/upload.py +0 -0
  72. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/commands/waves.py +0 -0
  73. {opencos_eda-0.2.48/opencos/tests → opencos_eda-0.2.49/opencos/deps}/__init__.py +0 -0
  74. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_config_defaults.yml +0 -0
  75. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_config_max_verilator_waivers.yml +0 -0
  76. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_config_reduced.yml +0 -0
  77. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/eda_deps_bash_completion.bash +0 -0
  78. {opencos_eda-0.2.48/opencos/tools → opencos_eda-0.2.49/opencos/hw}/__init__.py +0 -0
  79. {opencos_eda-0.2.48/opencos → opencos_eda-0.2.49/opencos/hw}/pcie.py +0 -0
  80. /opencos_eda-0.2.48/opencos/tests/deps_files/no_deps_here/DEPS.yml → /opencos_eda-0.2.49/opencos/tests/__init__.py +0 -0
  81. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/custom_config.yml +0 -0
  82. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
  83. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
  84. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
  85. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
  86. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
  87. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
  88. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_build.py +0 -0
  89. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tests/test_deps_schema.py +0 -0
  90. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/invio.py +0 -0
  91. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/invio_helpers.py +0 -0
  92. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/invio_yosys.py +0 -0
  93. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/questa.py +0 -0
  94. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/slang_yosys.py +0 -0
  95. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos/tools/tabbycad_yosys.py +0 -0
  96. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos_eda.egg-info/dependency_links.txt +0 -0
  97. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos_eda.egg-info/requires.txt +0 -0
  98. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/opencos_eda.egg-info/top_level.txt +0 -0
  99. {opencos_eda-0.2.48 → opencos_eda-0.2.49}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.48
3
+ Version: 0.2.49
4
4
  Summary: A simple Python package for wrapping RTL simuliatons and synthesis
5
5
  Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
6
6
  Project-URL: Homepage, https://github.com/cognichip/opencos
@@ -0,0 +1,6 @@
1
+ '''__init__ package for opencos.'''
2
+
3
+ from ._version import VERSION, NAME
4
+
5
+ __version__ = VERSION
6
+ __pyproject_name__ = NAME
@@ -0,0 +1,19 @@
1
+ ''' private helper package for `eda --version`'''
2
+
3
+ import sys
4
+
5
+ NAME = 'opencos-eda'
6
+
7
+ # Use standard library metadata module starting Python 3.8
8
+ if sys.version_info >= (3, 8):
9
+
10
+ from importlib import metadata
11
+ try:
12
+ VERSION = metadata.version(NAME)
13
+ except metadata.PackageNotFoundError:
14
+ # Handle case where the package is not installed (e.g., running from source checkout)
15
+ VERSION = "0.0.0"
16
+
17
+ else:
18
+ # This package only supports >= 3.8, so not doing the importlib_metadata method.
19
+ VERSION = "unknown" # Or raise an error, or handle differently
@@ -9,6 +9,7 @@ import os
9
9
 
10
10
  from opencos import util
11
11
  from opencos.eda_base import CommandDesign
12
+ from opencos.utils.str_helpers import strip_all_quotes
12
13
 
13
14
  class CommandFList(CommandDesign):
14
15
  '''Base class command handler for: eda flist ...'''
@@ -140,14 +141,14 @@ class CommandFList(CommandDesign):
140
141
  if self.args['emit-non-sources']:
141
142
  if self.files_non_source:
142
143
  print('## reqs (non-source files that are dependencies):', file=fo)
143
- prefix = util.strip_all_quotes(self.args['prefix-non-sources'])
144
+ prefix = strip_all_quotes(self.args['prefix-non-sources'])
144
145
  for f in self.files_non_source:
145
146
  if self.args['emit-rel-path']:
146
147
  f = os.path.relpath(f)
147
148
  print('## ' + prefix + pq1 + f + pq2, file=fo)
148
149
 
149
150
  if self.args['emit-define']:
150
- prefix = util.strip_all_quotes(self.args['prefix-define'])
151
+ prefix = strip_all_quotes(self.args['prefix-define'])
151
152
  for d, value in self.defines.items():
152
153
  if value is None:
153
154
  newline = prefix + d
@@ -176,31 +177,31 @@ class CommandFList(CommandDesign):
176
177
  print(newline, file=fo)
177
178
 
178
179
  if self.args['emit-incdir']:
179
- prefix = util.strip_all_quotes(self.args['prefix-incdir'])
180
+ prefix = strip_all_quotes(self.args['prefix-incdir'])
180
181
  for i in self.incdirs:
181
182
  if self.args['emit-rel-path']:
182
183
  i = os.path.relpath(i)
183
184
  print(prefix + pq1 + i + pq2, file=fo)
184
185
  if self.args['emit-v']:
185
- prefix = util.strip_all_quotes(self.args['prefix-v'])
186
+ prefix = strip_all_quotes(self.args['prefix-v'])
186
187
  for f in self.files_v:
187
188
  if self.args['emit-rel-path']:
188
189
  f = os.path.relpath(f)
189
190
  print(prefix + pq1 + f + pq2, file=fo)
190
191
  if self.args['emit-sv']:
191
- prefix = util.strip_all_quotes(self.args['prefix-sv'])
192
+ prefix = strip_all_quotes(self.args['prefix-sv'])
192
193
  for f in self.files_sv:
193
194
  if self.args['emit-rel-path']:
194
195
  f = os.path.relpath(f)
195
196
  print(prefix + pq1 + f + pq2, file=fo)
196
197
  if self.args['emit-vhd']:
197
- prefix = util.strip_all_quotes(self.args['prefix-vhd'])
198
+ prefix = strip_all_quotes(self.args['prefix-vhd'])
198
199
  for f in self.files_vhd:
199
200
  if self.args['emit-rel-path']:
200
201
  f = os.path.relpath(f)
201
202
  print(prefix + pq1 + f + pq2, file=fo)
202
203
  if self.args['emit-cpp']:
203
- prefix = util.strip_all_quotes(self.args['prefix-cpp'])
204
+ prefix = strip_all_quotes(self.args['prefix-cpp'])
204
205
  for f in self.files_cpp:
205
206
  if self.args['emit-rel-path']:
206
207
  f = os.path.relpath(f)
@@ -9,10 +9,12 @@ import os
9
9
  import shutil
10
10
  from pathlib import Path
11
11
 
12
- from opencos import util, eda_base, deps_helpers, eda_config, export_helper, \
12
+ from opencos import util, eda_base, eda_config, export_helper, \
13
13
  eda_tool_helper
14
- from opencos.deps_helpers import get_deps_markup_file, deps_markup_safe_load
15
14
  from opencos.eda_base import CommandParallel, get_eda_exec
15
+ from opencos.deps.deps_file import get_deps_markup_file, deps_markup_safe_load, \
16
+ deps_data_get_all_targets, deps_list_target_sanitize
17
+ from opencos.utils.str_helpers import fnmatch_or_re, dep_str2list
16
18
 
17
19
  class CommandMulti(CommandParallel):
18
20
  '''eda.py command handler for: eda multi <command> <args,targets,target-globs,...>'''
@@ -109,17 +111,17 @@ class CommandMulti(CommandParallel):
109
111
  if self.path_hidden_or_work_dir(path):
110
112
  continue
111
113
 
112
- deps_markup_file = deps_helpers.get_deps_markup_file(path)
114
+ deps_markup_file = get_deps_markup_file(path)
113
115
  if deps_markup_file:
114
- data = deps_helpers.deps_markup_safe_load(deps_markup_file)
115
- deps_targets = deps_helpers.deps_data_get_all_targets(data)
116
+ data = deps_markup_safe_load(deps_markup_file)
117
+ deps_targets = deps_data_get_all_targets(data)
116
118
  rel_path = os.path.relpath(path)
117
119
 
118
120
  debug(f'in {rel_path=} looking for {target_pattern=} in {deps_targets=}')
119
121
 
120
122
  for t in deps_targets:
121
123
  if target_pattern_needs_lookup:
122
- matched = deps_helpers.fnmatch_or_re(pattern=target_pattern, string=t)
124
+ matched = fnmatch_or_re(pattern=target_pattern, string=t)
123
125
  else:
124
126
  matched = t == target_pattern
125
127
  if matched:
@@ -176,14 +178,11 @@ class CommandMulti(CommandParallel):
176
178
  all_multi_tools = self.multi_which_tools(command)
177
179
 
178
180
  deps_file = get_deps_markup_file(base_path)
179
- data = None
181
+ data = {}
180
182
  if self.config['deps_markup_supported'] and deps_file:
181
183
  data = deps_markup_safe_load(deps_file)
182
184
 
183
- if data is None:
184
- data = {}
185
-
186
- deps_targets = deps_helpers.deps_data_get_all_targets(data)
185
+ deps_targets = deps_data_get_all_targets(data)
187
186
  deps_file_defaults = data.get('DEFAULTS', {})
188
187
 
189
188
  # Loop through all the targets in DEPS.yml, skipping DEFAULTS
@@ -196,7 +195,7 @@ class CommandMulti(CommandParallel):
196
195
  # Since we support a few schema flavors for a target (our
197
196
  # 'target_node' key in a DEPS.yml file) santize the entry
198
197
  # so it's a {} with a 'deps' key:
199
- entry_sanitized = deps_helpers.deps_list_target_sanitize(
198
+ entry_sanitized = deps_list_target_sanitize(
200
199
  entry, target_node=target_node, deps_file=deps_file
201
200
  )
202
201
 
@@ -229,8 +228,8 @@ class CommandMulti(CommandParallel):
229
228
 
230
229
  commands = x.get('commands', [])
231
230
  tools = x.get('tools', [])
232
- ignore_commands_list = deps_helpers.dep_str2list(commands)
233
- ignore_tools_list = deps_helpers.dep_str2list(tools)
231
+ ignore_commands_list = dep_str2list(commands)
232
+ ignore_tools_list = dep_str2list(tools)
234
233
 
235
234
  debug(f"{ignore_tools_list=}, {ignore_commands_list=} {target_node=}")
236
235
  debug(f"{command=} --> {all_multi_tools=}")
@@ -8,6 +8,7 @@ import re
8
8
 
9
9
  from opencos import util
10
10
  from opencos.eda_base import CommandDesign, CommandParallel, get_eda_exec, which_tool
11
+ from opencos.utils.str_helpers import strip_outer_quotes
11
12
 
12
13
  class CommandSweep(CommandDesign, CommandParallel):
13
14
  '''Command handler for: eda sweep ...'''
@@ -77,7 +78,7 @@ class CommandSweep(CommandDesign, CommandParallel):
77
78
 
78
79
  for sweep_arg_value in self.args['sweep']:
79
80
  # Deal with --sweep= args we already parsed, but haven't expanded yet.
80
- sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
81
+ sweep_arg_value = strip_outer_quotes(sweep_arg_value)
81
82
  sweep_axis_list_entry = self._process_sweep_arg(sweep_arg_value=sweep_arg_value)
82
83
  if sweep_axis_list_entry:
83
84
  sweep_axis_list.append(sweep_axis_list_entry)
@@ -136,7 +137,7 @@ class CommandSweep(CommandDesign, CommandParallel):
136
137
  Return value is {} or {'lhs': str, 'operator': str (+ or =), 'values': list}
137
138
  '''
138
139
 
139
- sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
140
+ sweep_arg_value = strip_outer_quotes(sweep_arg_value)
140
141
 
141
142
  util.debug(f'{sweep_arg_value=}')
142
143
  # Try to match a sweep range expansion:
@@ -0,0 +1,69 @@
1
+ ''' opencos.deps.defaults -- pymodule for defaults referenced by other modules here'''
2
+
3
+
4
+ DEPS_FILE_EXTS = set([
5
+ '.yml', '.yaml', '.toml', '.json',
6
+ # Treat no extension DEPS as YAML.
7
+ ''
8
+ ])
9
+
10
+ ROOT_TABLE_KEYS_NOT_TARGETS = set([
11
+ "DEFAULTS",
12
+ "METADATA",
13
+ ])
14
+
15
+ KNOWN_EDA_COMMANDS = set([
16
+ "sim",
17
+ "elab",
18
+ "synth",
19
+ "flist",
20
+ "proj",
21
+ "multi",
22
+ "tools-multi",
23
+ "sweep",
24
+ "build",
25
+ "waves",
26
+ "upload",
27
+ "open",
28
+ "export",
29
+ ])
30
+
31
+ SUPPORTED_TARGET_TABLE_KEYS = set([
32
+ 'args',
33
+ 'defines',
34
+ 'incdirs',
35
+ 'top',
36
+ 'deps',
37
+ 'reqs',
38
+ 'multi',
39
+ 'tags',
40
+ 'commands'] + list(KNOWN_EDA_COMMANDS))
41
+
42
+ SUPPORTED_DEP_KEYS_BY_TYPE = {
43
+ dict: set(['commands']),
44
+ str: set(['*']),
45
+ }
46
+
47
+ SUPPORTED_TAG_KEYS = set([
48
+ 'with-tools',
49
+ 'with-args',
50
+ 'args',
51
+ 'deps',
52
+ 'reqs',
53
+ 'defines',
54
+ 'incdirs',
55
+ 'replace-config-tools',
56
+ 'additive-config-tools',
57
+ ])
58
+
59
+ SUPPORTED_COMMAND_KEYS = set([
60
+ 'shell',
61
+ 'work-dir-add-srcs', 'work-dir-add-sources',
62
+ 'peakrdl',
63
+ 'run-from-work-dir', # default True
64
+ 'filepath-subst-target-dir', # default True
65
+ 'dirpath-subst-target-dir', # default False
66
+ 'var-subst-args',
67
+ 'var-subst-os-env',
68
+ 'tee',
69
+ ])
@@ -0,0 +1,419 @@
1
+ '''opencos.deps.deps_commands - pymodule for processing DEPS markup deps-commands
2
+
3
+ This supports things like:
4
+ my_target:
5
+ - deps:
6
+ - commands:
7
+ - shell: <string>
8
+ - peakrdl: <string>
9
+ - work-dir-add-sources: <list>
10
+ '''
11
+
12
+
13
+ import os
14
+ from pathlib import Path
15
+ import re
16
+ import shutil
17
+
18
+ from opencos.util import debug, error, warning, ShellCommandList
19
+ from opencos.deps.defaults import SUPPORTED_COMMAND_KEYS
20
+
21
+ THISPATH = os.path.dirname(__file__)
22
+ PEAKRDL_CLEANUP_PY = os.path.join(THISPATH, '..', 'peakrdl_cleanup.py')
23
+
24
+ def path_substitutions_relative_to_work_dir(
25
+ exec_list: list, info_str: str, target_path: str,
26
+ enable_filepath_subst: bool, enable_dirpath_subst: bool
27
+ ) -> list:
28
+ '''For shell commands, since eda.py Command objects operate out of a "work-dir", it can
29
+ make shell commands confusing with additional ../.. to get to the calling directory, and
30
+ the original target directory is completely lost otherwise.
31
+
32
+ By default, if a FILE exists in the calling target's path, we will substitute that file
33
+ instead of looking in the work-dir. (can be disabled via filepath-subst-target-dir: False)
34
+
35
+ Optionally if a DIR exists in the calling target's path, will will substitute that DIR
36
+ instead of relative to work-dir. This can be annoying with relative paths like ../ (which
37
+ would exist in both) so this is disabled by default (can be enabled with
38
+ dirpath-subst-target-dir: True)
39
+ '''
40
+
41
+ if not enable_filepath_subst and not enable_dirpath_subst:
42
+ return exec_list
43
+
44
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
45
+ # files should be relative to our target_path.
46
+ for i,word in enumerate(exec_list):
47
+ m = re.search(r'(\.+\/+[^"\;\:\|\<\>\*]*)$', word)
48
+ if m:
49
+ # ./, ../, file=./../whatever It might be a filepath.
50
+ # [^"\;\:\|\<\>\*] is looking for non-path like characters, so we dont' have a trailing
51
+ # " : ; < > |
52
+ # try and see if this file or dir exists. Note that files in the
53
+ # self.args['work-dir'] don't need this, and we can't assume dir levels in the work-dir.
54
+ try:
55
+ try_path = os.path.abspath(os.path.join(os.path.abspath(target_path), m.group(1)))
56
+ if enable_filepath_subst and os.path.isfile(try_path):
57
+ # make the substitution
58
+ exec_list[i] = word.replace(m.group(1), try_path)
59
+ debug(f'file path substitution {info_str=} {target_path=}: replaced - {word=}'
60
+ f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
61
+ '"filepath-subst-targetdir: false"')
62
+ elif enable_dirpath_subst and os.path.isdir(try_path):
63
+ # make the substitution
64
+ exec_list[i] = word.replace(m.group(1), try_path)
65
+ debug(f'dir path substitution {info_str=} {target_path=}: replaced - {word=}'
66
+ f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
67
+ '"dirpath-subst-targetdir: false"')
68
+ except Exception:
69
+ pass
70
+
71
+ return exec_list
72
+
73
+
74
+ def line_with_var_subst( # pylint: disable=dangerous-default-value
75
+ line : str, replace_vars_dict: dict = {},
76
+ replace_vars_os_env: bool = False,
77
+ target_node: str = '', target_path: str = ''
78
+ ) -> str:
79
+ '''Given a line (often from a shell style command) perform var substitution'''
80
+
81
+ # We can try for replacing any formatted strings, using self.args, and os.environ?
82
+ # We have to do this per-word, so that missing replacements or tcl-like things, such
83
+ # as '{}' wouldn't bail if trying to do line.format(**dict)
84
+ if '{' not in line:
85
+ return line
86
+
87
+ if replace_vars_os_env:
88
+ replace_dict = {}
89
+ replace_dict.update(os.environ)
90
+ replace_dict.update(replace_vars_dict)
91
+ else:
92
+ replace_dict = replace_vars_dict
93
+
94
+ words = line.split()
95
+ for i,word in enumerate(words):
96
+ try:
97
+ words[i] = word.format(**replace_dict)
98
+ except Exception:
99
+ # this was an attempt, no need to catch exceptions if we couldn't replace the word.
100
+ pass
101
+
102
+ new_line = ' '.join(words)
103
+ if new_line != line:
104
+ debug(f'{target_node=} {target_path=} performed string format replacement,',
105
+ f'{line=} {new_line=}')
106
+ return new_line
107
+
108
+ debug(f'{target_node=} {target_path=} string format replacement attempted,',
109
+ f'no replacement. {line=}')
110
+ return line
111
+
112
+
113
+ def parse_deps_shell_str(
114
+ line: str, target_path: str, target_node: str,
115
+ enable_filepath_subst_target_dir: bool = True,
116
+ enable_dirpath_subst_target_dir: bool = False,
117
+ enable: bool = True
118
+ ) -> dict:
119
+ '''Returns None or a dict of a possible shell command from line (str)
120
+
121
+ Examples of 'line' str:
122
+ shell@echo "hello world" > hello.txt
123
+ shell@ generate_something.sh
124
+ shell@ generate_this.py --input=some_data.json
125
+ shell@ cp ./some_file.txt some_file_COPY.txt
126
+ shell@ vivado -mode tcl -script ./some.tcl -tclargs foo_ip 1.2 foo_part foo_our_name \
127
+ {property value}
128
+
129
+ Returns None if no parsing was performed, or if enable is False
130
+
131
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
132
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
133
+ '''
134
+ if not enable:
135
+ return {}
136
+
137
+ m = re.match(r'^\s*shell\@(.*)\s*$', line)
138
+ if not m:
139
+ return {}
140
+
141
+ exec_str = m.group(1)
142
+ exec_list = exec_str.split()
143
+
144
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
145
+ # files should be relative to our target_path.
146
+ # Note this can be disable in DEPS via path-subst-target-dir=False
147
+ exec_list = path_substitutions_relative_to_work_dir(
148
+ exec_list=exec_list, info_str='shell@', target_path=target_path,
149
+ enable_filepath_subst=enable_filepath_subst_target_dir,
150
+ enable_dirpath_subst=enable_dirpath_subst_target_dir
151
+ )
152
+
153
+ return {
154
+ 'target_path': os.path.abspath(target_path),
155
+ 'target_node': target_node,
156
+ 'run_from_work_dir': True, # may be overriden later.
157
+ 'exec_list': exec_list,
158
+ }
159
+
160
+
161
+ def parse_deps_work_dir_add_srcs(
162
+ line : str, target_path : str, target_node : str, enable : bool = True
163
+ ) -> dict:
164
+ '''Returns None or a dict describing source files to add from the work-dir path
165
+
166
+ Examples of 'line' str:
167
+ work_dir_add_srcs@ my_csrs.sv
168
+ work_dir_add_srcs@ some_generated_file.sv some_dir/some_other.v ./gen-vhd-dir/even_more.vhd
169
+
170
+ Returns None if no parsing was performed, or if enable is False
171
+
172
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
173
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
174
+ '''
175
+ if not enable:
176
+ return {}
177
+
178
+ m = re.match(r'^\s*work_dir_add_srcs\@(.*)\s*$', line)
179
+ if not m:
180
+ return {}
181
+
182
+ files_str = m.group(1)
183
+ file_list = files_str.split()
184
+
185
+ d = {'target_path': os.path.abspath(target_path),
186
+ 'target_node': target_node,
187
+ 'file_list': file_list,
188
+ }
189
+ return d
190
+
191
+
192
+ def parse_deps_peakrdl( # pylint: disable=too-many-locals
193
+ line: str, target_path: str, target_node: str, enable: bool = True,
194
+ enable_filepath_subst_target_dir: bool = True,
195
+ enable_dirpath_subst_target_dir: bool = False
196
+ ) -> dict:
197
+ '''Returns None or a dict describing a PeakRDL CSR register generator dependency
198
+
199
+ Examples of 'line' str:
200
+ peakrdl@ --cpuif axi4-lite-flat --top oc_eth_10g_1port_csrs ./oc_eth_10g_csrs.rdl
201
+
202
+ Returns None if no parsing was performed, or if enable=False
203
+
204
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
205
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
206
+ '''
207
+
208
+ m = re.match(r'^\s*peakrdl\@(.*)\s*$', line)
209
+ if not m:
210
+ return None
211
+
212
+ if not enable:
213
+ warning(f'peakrdl: encountered peakrdl command in {target_path=} {target_node=},' \
214
+ + ' however it is not enabled in edy.py - eda.config[dep_command_enables]')
215
+ return None
216
+
217
+ if not shutil.which('peakrdl'):
218
+ error('peakrdl: is not present in shell path, or the python package is not avaiable,' \
219
+ + f' yet we encountered a peakrdl command in {target_path=} {target_node=}')
220
+ return None
221
+
222
+
223
+ args_str = m.group(1)
224
+ args_list = args_str.split()
225
+
226
+ # Fish out the .rdl name
227
+ # If there is --top=value or --top value, then fish out that value (that will be the
228
+ # value.sv and value_pkg.sv generated names.
229
+
230
+ sv_files = []
231
+ top = ''
232
+ for i,str_value in enumerate(args_list):
233
+ if '--top=' in str_value:
234
+ _, top = str_value.split('=')
235
+ elif '--top' in str_value:
236
+ if i + 1 < len(args_list):
237
+ top = args_list[i + 1]
238
+
239
+ for str_item in args_list:
240
+ if str_item[-4:] == '.rdl':
241
+ _, rdl_fileonly = os.path.split(str_item) # strip all path info
242
+ rdl_filebase, _ = os.path.splitext(rdl_fileonly) # strip .rdl ext
243
+ if not top:
244
+ top = rdl_filebase
245
+
246
+ assert top != '', \
247
+ f'peakrdl@ DEP, could not determine value {top=}: {line=}, {target_path=}, {target_node=}'
248
+
249
+ sv_files += [ f'peakrdl/{top}_pkg.sv', f'peakrdl/{top}.sv' ]
250
+
251
+
252
+ shell_commands = [
253
+ [ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
254
+ # Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
255
+ [ 'python3', PEAKRDL_CLEANUP_PY, str(Path(f'peakrdl/{top}.sv')),
256
+ str(Path(f'peakrdl/{top}.sv')) ],
257
+ ]
258
+
259
+ ret_dict = {
260
+ 'shell_commands_list': [], # Entry needs target_path, target_node, exec_list
261
+ 'work_dir_add_srcs': {}, # Single dict needs target_path, target_node, file_list
262
+ }
263
+
264
+ # Make these look like a dep_shell_command:
265
+ for one_cmd_as_list in shell_commands:
266
+ ret_dict['shell_commands_list'].append(
267
+ parse_deps_shell_str(
268
+ line=(' shell@ ' + ' '.join(one_cmd_as_list)),
269
+ target_path=target_path,
270
+ target_node=target_node,
271
+ enable_filepath_subst_target_dir=enable_filepath_subst_target_dir,
272
+ enable_dirpath_subst_target_dir=enable_dirpath_subst_target_dir
273
+ )
274
+ )
275
+
276
+ # Make the work_dir_add_srcs dict:
277
+ ret_dict['work_dir_add_srcs'] = parse_deps_work_dir_add_srcs(
278
+ line = ' work_dir_add_srcs@ ' + ' '.join(sv_files),
279
+ target_path = target_path,
280
+ target_node = target_node
281
+ )
282
+
283
+ return ret_dict
284
+
285
+
286
+
287
+ def deps_commands_handler( #pylint: disable=too-many-locals,too-many-branches
288
+ config: dict, eda_args: dict,
289
+ dep : str, deps_file : str, target_node : str, target_path : str,
290
+ commands : list
291
+ ) -> (list, list):
292
+ ''' Returns a tuple of (shell_commands_list, work_dir_add_srcs_list), from processing
293
+ a DEPS.yml entry for something like:
294
+
295
+ target_foo:
296
+ deps:
297
+ - some_file
298
+ - commands: # (list of dicts) These are directly in a 'deps' list.
299
+ - shell: ...
300
+ - peakrdl: ...
301
+ - work-dir-add-sources: ...
302
+ - shell: ...
303
+
304
+ target_foo:
305
+ commands: # (list of dicts) These are in a target, but not ordered with other deps
306
+ - shell: ...
307
+ - peakrdl: ...
308
+ - work-dir-add-sources: ...
309
+ - shell: ...
310
+
311
+ We'd like to handle the list in a 'commands' entry, supporting it in a few places in a
312
+ DEPS.yml, so this this a generic way to do that. Currently these are broken down into Shell
313
+ commands and Files that will be later added to our sources (b/c we haven't run the Shell
314
+ commands yet, and the Files aren't present yet but we'd like them in our eda.py filelist in
315
+ order.
316
+
317
+ '''
318
+
319
+ shell_commands_list = []
320
+ work_dir_add_srcs_list = []
321
+
322
+ for command in commands:
323
+ assert isinstance(command, dict), \
324
+ (f'{type(command)=} must be dict, for {deps_file=} {target_node=} {target_path=} with'
325
+ f'{commands=}')
326
+
327
+ for key in command.keys():
328
+ if key not in SUPPORTED_COMMAND_KEYS:
329
+ error(f'deps_commands.process_commands - command {key=} not in',
330
+ f'{SUPPORTED_COMMAND_KEYS=}')
331
+
332
+ var_subst_dict = {} # this is per-command.
333
+ if config['dep_command_enables'].get('var_subst_os_env', False) and \
334
+ command.get('var-subst-os-env', False):
335
+ var_subst_dict.update(dict(os.environ))
336
+ if config['dep_command_enables'].get('var_subst_args', False) and \
337
+ command.get('var-subst-args', False):
338
+ var_subst_dict = eda_args
339
+
340
+ tee_fpath = command.get('tee', None)
341
+
342
+ # These are both optional bools, default True, would have to explicitly be set to False
343
+ # to take effect:
344
+ run_from_work_dir = command.get('run-from-work-dir', True)
345
+ filepath_subst_target_dir = command.get('filepath-subst-target-dir', True)
346
+ dirpath_subst_target_dir = command.get('dirpath-subst-target-dir', False)
347
+
348
+ for key,item in command.items():
349
+
350
+ # skip the tee and var-subst-* keys, since these types are bools and not commands.
351
+ if key in ['tee',
352
+ 'var-subst-os-env',
353
+ 'var-subst-args',
354
+ 'run-from-work-dir',
355
+ 'filepath-subst-target-dir',
356
+ 'dirpath-subst-target-dir']:
357
+ continue
358
+
359
+ # Optional variable substituion in commands
360
+ if isinstance(item, str):
361
+ item = line_with_var_subst(item, replace_vars_dict=var_subst_dict,
362
+ target_node=target_node, target_path=deps_file)
363
+
364
+ if key == 'shell':
365
+ # For now, piggyback on parse_deps_shell_str:
366
+ ret_dict = parse_deps_shell_str(
367
+ line=('shell@ ' + item),
368
+ target_path=target_path,
369
+ target_node=target_node,
370
+ enable_filepath_subst_target_dir=filepath_subst_target_dir,
371
+ enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
372
+ enable=config['dep_command_enables']['shell'],
373
+ )
374
+ # To support 'tee: <some-file>' need to append it to last
375
+ # list item in ret_dict['exec_list'], and make it a util.ShellCommandList.
376
+ if tee_fpath:
377
+ ret_dict['exec_list'] = ShellCommandList(
378
+ ret_dict['exec_list'], tee_fpath=tee_fpath
379
+ )
380
+ ret_dict['run_from_work_dir'] = run_from_work_dir
381
+ assert ret_dict, f'shell command failed in {dep=} {target_node=} in {deps_file=}'
382
+ shell_commands_list.append(ret_dict) # process this later, append to ret tuple
383
+
384
+ elif key in ['work-dir-add-srcs', 'work-dir-add-sources']:
385
+ # For now, piggyback on parse_deps_work_dir_add_srcs:
386
+ ret_dict = parse_deps_work_dir_add_srcs(
387
+ line = 'work_dir_add_srcs@ ' + item,
388
+ target_path = target_path,
389
+ target_node = target_node,
390
+ enable=config['dep_command_enables']['work_dir_add_srcs'],
391
+ )
392
+ assert ret_dict, \
393
+ f'work-dir-add-srcs command failed in {dep=} {target_node=} in {deps_file=}'
394
+
395
+ work_dir_add_srcs_list.append(ret_dict) # process this later, append to ret tuple
396
+
397
+ elif key == 'peakrdl':
398
+ # for now, piggyback on parse_deps_peakrdl:
399
+ ret_dict = parse_deps_peakrdl(
400
+ line = 'peakrdl@ ' + item,
401
+ target_path = target_path,
402
+ target_node = target_node,
403
+ enable_filepath_subst_target_dir=filepath_subst_target_dir,
404
+ enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
405
+ enable=config['dep_command_enables']['peakrdl']
406
+ )
407
+ assert ret_dict, f'peakrdl command failed in {dep=} {target_node=} in {deps_file=}'
408
+
409
+ # add all the shell commands:
410
+ shell_commands_list += ret_dict['shell_commands_list'] # several entries.
411
+ # all the work_dir_add_srcs:
412
+ work_dir_add_srcs_list += [ ret_dict['work_dir_add_srcs'] ] # single entry append
413
+
414
+
415
+ else:
416
+ assert False, \
417
+ f'unknown {key=} in {command=}, {item=} {dep=} {target_node=} in {deps_file=}'
418
+
419
+ return (shell_commands_list, work_dir_add_srcs_list)