opencos-eda 0.2.46__tar.gz → 0.2.48__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.
- {opencos_eda-0.2.46/opencos_eda.egg-info → opencos_eda-0.2.48}/PKG-INFO +1 -1
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/multi.py +23 -5
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/sweep.py +6 -2
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/waves.py +1 -1
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/deps_helpers.py +71 -28
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/deps_schema.py +8 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda.py +9 -4
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_base.py +58 -21
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/helpers.py +24 -12
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_deps_helpers.py +56 -19
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_eda.py +11 -12
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/vivado.py +18 -12
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/util.py +131 -80
- {opencos_eda-0.2.46 → opencos_eda-0.2.48/opencos_eda.egg-info}/PKG-INFO +1 -1
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/pyproject.toml +1 -1
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/LICENSE +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/LICENSE.spdx +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/README.md +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/__init__.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/_version.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/_waves_pkg.sv +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/__init__.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/build.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/elab.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/export.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/flist.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/lec.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/open.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/proj.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/shell.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/sim.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/synth.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/targets.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/commands/upload.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_config.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_config_defaults.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_config_max_verilator_waivers.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_config_reduced.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_deps_bash_completion.bash +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_extract_targets.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/eda_tool_helper.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/export_helper.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/export_json_convert.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/files.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/names.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/oc_cli.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/pcie.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/peakrdl_cleanup.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/seed.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/__init__.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/custom_config.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_build.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_deps_schema.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_eda_elab.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_eda_synth.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_oc_cli.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tests/test_tools.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/__init__.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/invio.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/invio_helpers.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/invio_yosys.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/iverilog.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/modelsim_ase.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/questa.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/riviera.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/slang.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/slang_yosys.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/surelog.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/tabbycad_yosys.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/verilator.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos/tools/yosys.py +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos_eda.egg-info/SOURCES.txt +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos_eda.egg-info/dependency_links.txt +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos_eda.egg-info/entry_points.txt +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos_eda.egg-info/requires.txt +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/opencos_eda.egg-info/top_level.txt +0 -0
- {opencos_eda-0.2.46 → opencos_eda-0.2.48}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.48
|
|
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
|
|
@@ -68,7 +68,7 @@ class CommandMulti(CommandParallel):
|
|
|
68
68
|
return command, level
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def resolve_path_and_target_patterns(
|
|
71
|
+
def resolve_path_and_target_patterns( # pylint: disable=too-many-locals
|
|
72
72
|
self, base_path: str, target: str, level: int = -1
|
|
73
73
|
) -> dict:
|
|
74
74
|
'''Returns a dict of: key = matching path, value = set of matched targets.
|
|
@@ -91,10 +91,20 @@ class CommandMulti(CommandParallel):
|
|
|
91
91
|
|
|
92
92
|
matching_targets_dict = {}
|
|
93
93
|
|
|
94
|
+
# Let's not glob.glob if the path_pattern and target_pattern are
|
|
95
|
+
# exact, aka if it does not have special characters for glob: * or ?
|
|
96
|
+
# for the target, we also support re, so: * + ?
|
|
97
|
+
if any(x in path_pattern for x in ['*', '?']):
|
|
98
|
+
paths_from_pattern = list(glob.glob(path_pattern, recursive=True))
|
|
99
|
+
else:
|
|
100
|
+
paths_from_pattern = [path_pattern]
|
|
101
|
+
|
|
102
|
+
target_pattern_needs_lookup = any(x in target_pattern for x in ['*', '?', '+'])
|
|
103
|
+
|
|
94
104
|
# resolve the path_pattern portion using glob.
|
|
95
105
|
# we'll have to check for DEPS markup files in path_pattern, to match the target_wildcard
|
|
96
106
|
# using fnmatch or re.
|
|
97
|
-
for path in
|
|
107
|
+
for path in paths_from_pattern:
|
|
98
108
|
|
|
99
109
|
if self.path_hidden_or_work_dir(path):
|
|
100
110
|
continue
|
|
@@ -108,7 +118,11 @@ class CommandMulti(CommandParallel):
|
|
|
108
118
|
debug(f'in {rel_path=} looking for {target_pattern=} in {deps_targets=}')
|
|
109
119
|
|
|
110
120
|
for t in deps_targets:
|
|
111
|
-
if
|
|
121
|
+
if target_pattern_needs_lookup:
|
|
122
|
+
matched = deps_helpers.fnmatch_or_re(pattern=target_pattern, string=t)
|
|
123
|
+
else:
|
|
124
|
+
matched = t == target_pattern
|
|
125
|
+
if matched:
|
|
112
126
|
if rel_path not in matching_targets_dict:
|
|
113
127
|
matching_targets_dict[rel_path] = set()
|
|
114
128
|
matching_targets_dict[rel_path].add(t)
|
|
@@ -392,9 +406,13 @@ class CommandMulti(CommandParallel):
|
|
|
392
406
|
# Special case for 'multi' --export-jsonl, run reach child with --export-json
|
|
393
407
|
command_list.append('--export-json')
|
|
394
408
|
if tool and len(all_multi_tools) > 1:
|
|
395
|
-
|
|
409
|
+
jobname = f'{short_target}.{command}.{tool}'
|
|
396
410
|
else:
|
|
397
|
-
|
|
411
|
+
jobname = f'{short_target}.{command}'
|
|
412
|
+
command_list.append(f'--job-name={jobname}')
|
|
413
|
+
logfile = os.path.join(self.args['eda-dir'], f'eda.{jobname}.log')
|
|
414
|
+
command_list.append(f'--force-logfile={logfile}')
|
|
415
|
+
|
|
398
416
|
|
|
399
417
|
def append_jobs_from_targets(self, args:list):
|
|
400
418
|
'''Helper method in CommandMulti to apply 'args' (list) to all self.targets,
|
|
@@ -206,14 +206,18 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
206
206
|
snapshot_name = snapshot_name.replace(os.sep, '_') \
|
|
207
207
|
+ f'.{self.single_command}{sweep_string}'
|
|
208
208
|
eda_path = get_eda_exec('sweep')
|
|
209
|
+
logfile = os.path.join(self.args['eda-dir'], f'eda.{snapshot_name}.log')
|
|
209
210
|
self.jobs.append({
|
|
210
211
|
'name' : snapshot_name,
|
|
211
212
|
'index' : len(self.jobs),
|
|
212
213
|
'command': self.single_command,
|
|
213
214
|
'target': self.sweep_target,
|
|
214
215
|
'command_list' : (
|
|
215
|
-
[
|
|
216
|
-
|
|
216
|
+
[
|
|
217
|
+
eda_path, self.single_command, self.sweep_target,
|
|
218
|
+
f'--job-name={snapshot_name}',
|
|
219
|
+
f'--force-logfile={logfile}',
|
|
220
|
+
] + arg_tokens
|
|
217
221
|
)
|
|
218
222
|
})
|
|
219
223
|
return
|
|
@@ -143,7 +143,7 @@ class CommandWaves(CommandDesign):
|
|
|
143
143
|
else:
|
|
144
144
|
self.error(f"Don't know how to open {wave_file} without GtkWave in PATH")
|
|
145
145
|
elif wave_file.endswith('.vcd'):
|
|
146
|
-
if 'gtkwave' in self.config['tools_loaded'] and shutil.which('
|
|
146
|
+
if 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
|
|
147
147
|
command_list = ['gtkwave', wave_file]
|
|
148
148
|
self.exec(os.path.dirname(wave_file), command_list)
|
|
149
149
|
elif self._vsim_available(from_tools=self.VSIM_VCD_TOOLS):
|
|
@@ -68,7 +68,9 @@ class Defaults:
|
|
|
68
68
|
'shell',
|
|
69
69
|
'work-dir-add-srcs', 'work-dir-add-sources',
|
|
70
70
|
'peakrdl',
|
|
71
|
-
'run-from-work-dir',
|
|
71
|
+
'run-from-work-dir', # default True
|
|
72
|
+
'filepath-subst-target-dir', # default True
|
|
73
|
+
'dirpath-subst-target-dir', # default False
|
|
72
74
|
'var-subst-args',
|
|
73
75
|
'var-subst-os-env',
|
|
74
76
|
'tee',
|
|
@@ -384,7 +386,13 @@ def deps_list_target_sanitize(entry, default_key:str='deps', target_node:str='',
|
|
|
384
386
|
assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
|
|
385
387
|
|
|
386
388
|
|
|
387
|
-
def path_substitutions_relative_to_work_dir(
|
|
389
|
+
def path_substitutions_relative_to_work_dir(
|
|
390
|
+
exec_list: list, info_str: str, target_path: str,
|
|
391
|
+
enable_filepath_subst: bool, enable_dirpath_subst: bool
|
|
392
|
+
) -> list:
|
|
393
|
+
|
|
394
|
+
if not enable_filepath_subst and not enable_dirpath_subst:
|
|
395
|
+
return exec_list
|
|
388
396
|
|
|
389
397
|
# Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
|
|
390
398
|
# files should be relative to our target_path.
|
|
@@ -398,10 +406,18 @@ def path_substitutions_relative_to_work_dir(exec_list : list, info_str : str, ta
|
|
|
398
406
|
# need this, and we can't assume dir levels in the work-dir.
|
|
399
407
|
try:
|
|
400
408
|
try_path = os.path.abspath(os.path.join(os.path.abspath(target_path), m.group(1)))
|
|
401
|
-
if os.path.isfile(try_path)
|
|
409
|
+
if enable_filepath_subst and os.path.isfile(try_path):
|
|
410
|
+
# make the substitution
|
|
411
|
+
exec_list[i] = word.replace(m.group(1), try_path)
|
|
412
|
+
debug(f'file path substitution {info_str=} {target_path=}: replaced - {word=}'
|
|
413
|
+
f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
|
|
414
|
+
'"filepath-subst-targetdir: false"')
|
|
415
|
+
elif enable_dirpath_subst and os.path.isdir(try_path):
|
|
402
416
|
# make the substitution
|
|
403
417
|
exec_list[i] = word.replace(m.group(1), try_path)
|
|
404
|
-
debug(f'path substitution {info_str=} {target_path=}: replaced - {word=}
|
|
418
|
+
debug(f'dir path substitution {info_str=} {target_path=}: replaced - {word=}'
|
|
419
|
+
f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
|
|
420
|
+
'"dirpath-subst-targetdir: false"')
|
|
405
421
|
except:
|
|
406
422
|
pass
|
|
407
423
|
|
|
@@ -1034,7 +1050,10 @@ class DepsProcessor:
|
|
|
1034
1050
|
|
|
1035
1051
|
|
|
1036
1052
|
|
|
1037
|
-
def parse_deps_shell_str(line
|
|
1053
|
+
def parse_deps_shell_str(line: str, target_path: str, target_node: str,
|
|
1054
|
+
enable_filepath_subst_target_dir: bool = True,
|
|
1055
|
+
enable_dirpath_subst_target_dir: bool = False,
|
|
1056
|
+
enable: bool = True) -> dict:
|
|
1038
1057
|
'''Returns None or a dict of a possible shell command from line (str)
|
|
1039
1058
|
|
|
1040
1059
|
Examples of 'line' str:
|
|
@@ -1050,25 +1069,30 @@ def parse_deps_shell_str(line : str, target_path : str, target_node : str, enabl
|
|
|
1050
1069
|
target_node (str) -- from dependency parsing, the target containing this 'line' str.
|
|
1051
1070
|
'''
|
|
1052
1071
|
if not enable:
|
|
1053
|
-
return
|
|
1072
|
+
return {}
|
|
1054
1073
|
|
|
1055
1074
|
m = re.match(r'^\s*shell\@(.*)\s*$', line)
|
|
1056
1075
|
if not m:
|
|
1057
|
-
return
|
|
1076
|
+
return {}
|
|
1058
1077
|
|
|
1059
1078
|
exec_str = m.group(1)
|
|
1060
1079
|
exec_list = exec_str.split()
|
|
1061
1080
|
|
|
1062
1081
|
# Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
|
|
1063
1082
|
# files should be relative to our target_path.
|
|
1064
|
-
|
|
1083
|
+
# Note this can be disable in DEPS via path-subst-target-dir=False
|
|
1084
|
+
exec_list = path_substitutions_relative_to_work_dir(
|
|
1085
|
+
exec_list=exec_list, info_str='shell@', target_path=target_path,
|
|
1086
|
+
enable_filepath_subst=enable_filepath_subst_target_dir,
|
|
1087
|
+
enable_dirpath_subst=enable_dirpath_subst_target_dir
|
|
1088
|
+
)
|
|
1065
1089
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1090
|
+
return {
|
|
1091
|
+
'target_path': os.path.abspath(target_path),
|
|
1092
|
+
'target_node': target_node,
|
|
1093
|
+
'run_from_work_dir': True, # may be overriden later.
|
|
1094
|
+
'exec_list': exec_list,
|
|
1095
|
+
}
|
|
1072
1096
|
|
|
1073
1097
|
|
|
1074
1098
|
def parse_deps_work_dir_add_srcs(line : str, target_path : str, target_node : str, enable : bool = True):
|
|
@@ -1100,8 +1124,12 @@ def parse_deps_work_dir_add_srcs(line : str, target_path : str, target_node : st
|
|
|
1100
1124
|
return d
|
|
1101
1125
|
|
|
1102
1126
|
|
|
1103
|
-
def parse_deps_peakrdl(
|
|
1104
|
-
|
|
1127
|
+
def parse_deps_peakrdl(
|
|
1128
|
+
line: str, target_path: str, target_node: str, enable: bool = True,
|
|
1129
|
+
enable_filepath_subst_target_dir: bool = True,
|
|
1130
|
+
enable_dirpath_subst_target_dir: bool = False,
|
|
1131
|
+
tool: str = ''
|
|
1132
|
+
) -> dict:
|
|
1105
1133
|
'''Returns None or a dict describing a PeakRDL CSR register generator dependency
|
|
1106
1134
|
|
|
1107
1135
|
Examples of 'line' str:
|
|
@@ -1172,10 +1200,13 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
|
|
|
1172
1200
|
# Make these look like a dep_shell_command:
|
|
1173
1201
|
for one_cmd_as_list in shell_commands:
|
|
1174
1202
|
ret_dict['shell_commands_list'].append(
|
|
1175
|
-
parse_deps_shell_str(
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1203
|
+
parse_deps_shell_str(
|
|
1204
|
+
line=(' shell@ ' + ' '.join(one_cmd_as_list)),
|
|
1205
|
+
target_path=target_path,
|
|
1206
|
+
target_node=target_node,
|
|
1207
|
+
enable_filepath_subst_target_dir=enable_filepath_subst_target_dir,
|
|
1208
|
+
enable_dirpath_subst_target_dir=enable_dirpath_subst_target_dir
|
|
1209
|
+
)
|
|
1179
1210
|
)
|
|
1180
1211
|
|
|
1181
1212
|
# Make the work_dir_add_srcs dict:
|
|
@@ -1237,14 +1268,22 @@ def deps_commands_handler(config: dict, eda_args: dict,
|
|
|
1237
1268
|
var_subst_dict = eda_args
|
|
1238
1269
|
|
|
1239
1270
|
tee_fpath = command.get('tee', None)
|
|
1240
|
-
|
|
1271
|
+
|
|
1272
|
+
# These are both optional bools, default True, would have to explicitly be set to False
|
|
1273
|
+
# to take effect:
|
|
1274
|
+
run_from_work_dir = command.get('run-from-work-dir', True)
|
|
1275
|
+
filepath_subst_target_dir = command.get('filepath-subst-target-dir', True)
|
|
1276
|
+
dirpath_subst_target_dir = command.get('dirpath-subst-target-dir', False)
|
|
1241
1277
|
|
|
1242
1278
|
for key,item in command.items():
|
|
1243
1279
|
|
|
1244
|
-
# skip the var-subst-* keys, since these types are bools
|
|
1245
|
-
if key
|
|
1246
|
-
|
|
1247
|
-
|
|
1280
|
+
# skip the tee and var-subst-* keys, since these types are bools and not commands.
|
|
1281
|
+
if key in ['tee',
|
|
1282
|
+
'var-subst-os-env',
|
|
1283
|
+
'var-subst-args',
|
|
1284
|
+
'run-from-work-dir',
|
|
1285
|
+
'filepath-subst-target-dir',
|
|
1286
|
+
'dirpath-subst-target-dir']:
|
|
1248
1287
|
continue
|
|
1249
1288
|
|
|
1250
1289
|
# Optional variable substituion in commands
|
|
@@ -1255,9 +1294,11 @@ def deps_commands_handler(config: dict, eda_args: dict,
|
|
|
1255
1294
|
if key == 'shell':
|
|
1256
1295
|
# For now, piggyback on parse_deps_shell_str:
|
|
1257
1296
|
ret_dict = parse_deps_shell_str(
|
|
1258
|
-
line
|
|
1259
|
-
target_path
|
|
1260
|
-
target_node
|
|
1297
|
+
line=('shell@ ' + item),
|
|
1298
|
+
target_path=target_path,
|
|
1299
|
+
target_node=target_node,
|
|
1300
|
+
enable_filepath_subst_target_dir=filepath_subst_target_dir,
|
|
1301
|
+
enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
|
|
1261
1302
|
enable=config['dep_command_enables']['shell'],
|
|
1262
1303
|
)
|
|
1263
1304
|
# To support 'tee: <some-file>' need to append it to last
|
|
@@ -1286,6 +1327,8 @@ def deps_commands_handler(config: dict, eda_args: dict,
|
|
|
1286
1327
|
line = 'peakrdl@ ' + item,
|
|
1287
1328
|
target_path = target_path,
|
|
1288
1329
|
target_node = target_node,
|
|
1330
|
+
enable_filepath_subst_target_dir=filepath_subst_target_dir,
|
|
1331
|
+
enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
|
|
1289
1332
|
enable=config['dep_command_enables']['peakrdl'],
|
|
1290
1333
|
tool=eda_args.get('tool', '')
|
|
1291
1334
|
)
|
|
@@ -71,6 +71,12 @@ my_target_name:
|
|
|
71
71
|
var-subst-os-env: <---- bool, perform var substitution using os.environ
|
|
72
72
|
run-from-work-dir: <---- bool, default True, if False runs from target dir
|
|
73
73
|
instead of work-dir.
|
|
74
|
+
filepath-subst-target-dir: <---- bool, default True, if False does not perform
|
|
75
|
+
file path substitution relative to target dir
|
|
76
|
+
(if substituted file exists).
|
|
77
|
+
dirpath-subst-target-dir: <---- bool, default False, if True performs
|
|
78
|
+
directory path substitution relative to target dir
|
|
79
|
+
(if substituted directory exists).
|
|
74
80
|
tee: <---- string, filename to write logs to
|
|
75
81
|
- work-dir-add-sources: <---- work-dir-add-sources, optional list (or string)
|
|
76
82
|
- some_file_gen_from_sh.sv <---- string filename that we created with sh command
|
|
@@ -153,6 +159,8 @@ DEPS_COMMANDS_LIST = [
|
|
|
153
159
|
Optional('var-subst-args'): bool,
|
|
154
160
|
Optional('var-subst-os-env'): bool,
|
|
155
161
|
Optional('run-from-work-dir'): bool,
|
|
162
|
+
Optional('filepath-subst-target-dir'): bool,
|
|
163
|
+
Optional('dirpath-subst-target-dir'): bool,
|
|
156
164
|
Optional('tee'): Or(str, type(None)),
|
|
157
165
|
},
|
|
158
166
|
{
|
|
@@ -18,9 +18,10 @@ from opencos import eda_config
|
|
|
18
18
|
from opencos import eda_base
|
|
19
19
|
from opencos.eda_base import Tool, which_tool
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
21
|
+
# Configure util:
|
|
23
22
|
util.progname = "EDA"
|
|
23
|
+
util.global_log.default_log_enabled = True
|
|
24
|
+
util.global_log.default_log_filepath = os.path.join('eda.work', 'eda.log')
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
# ******************************************************************************
|
|
@@ -453,10 +454,14 @@ def main(*args):
|
|
|
453
454
|
if not config:
|
|
454
455
|
util.error(f'eda.py main: problem loading config, {args=}')
|
|
455
456
|
return 3
|
|
456
|
-
|
|
457
|
+
main_ret = interactive(config=config)
|
|
457
458
|
else:
|
|
458
|
-
|
|
459
|
+
main_ret = process_tokens(tokens=list(unparsed), original_args=original_args,
|
|
459
460
|
config=config)
|
|
461
|
+
# Stop the util log, needed for pytests that call eda.main directly that otherwise
|
|
462
|
+
# won't close the log file via util's atexist.register(stop_log)
|
|
463
|
+
util.global_log.stop()
|
|
464
|
+
return main_ret
|
|
460
465
|
|
|
461
466
|
|
|
462
467
|
def main_cli() -> None:
|
|
@@ -212,12 +212,30 @@ class Command:
|
|
|
212
212
|
self.target = ""
|
|
213
213
|
self.target_path = ""
|
|
214
214
|
self.status = 0
|
|
215
|
+
self.errors_log_f = None
|
|
215
216
|
|
|
216
|
-
def error(self, *args, **kwargs):
|
|
217
|
-
'''Returns None, child classes can call self.error(..) instead of util.error,
|
|
217
|
+
def error(self, *args, **kwargs) -> None:
|
|
218
|
+
'''Returns None, child classes can call self.error(..) instead of util.error,
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
which updates their self.status. Also will write out to eda.errors.log if the work-dir
|
|
221
|
+
exists. Please consider using Command.error(..) (or self.error(..)) in place of util.error
|
|
222
|
+
so self.status is updated.
|
|
220
223
|
'''
|
|
224
|
+
|
|
225
|
+
if self.args['work-dir']:
|
|
226
|
+
if not self.errors_log_f:
|
|
227
|
+
try:
|
|
228
|
+
self.errors_log_f = open(
|
|
229
|
+
os.path.join(self.args['work-dir'], 'eda.errors.log'), 'w'
|
|
230
|
+
)
|
|
231
|
+
except:
|
|
232
|
+
pass
|
|
233
|
+
if self.errors_log_f:
|
|
234
|
+
print(
|
|
235
|
+
f'ERROR: [eda] ({self.command_name}) {" ".join(list(args))}',
|
|
236
|
+
file=self.errors_log_f
|
|
237
|
+
)
|
|
238
|
+
|
|
221
239
|
self.status = util.error(*args, **kwargs)
|
|
222
240
|
|
|
223
241
|
def status_any_error(self, report=True) -> bool:
|
|
@@ -1480,6 +1498,9 @@ class CommandParallel(Command):
|
|
|
1480
1498
|
jobs_launched += 1
|
|
1481
1499
|
anything_done = True
|
|
1482
1500
|
job = self.jobs.pop(0)
|
|
1501
|
+
# TODO(drew): it might be nice to pass more items on 'job' dict, like
|
|
1502
|
+
# logfile or job-name, so CommandSweep or CommandMulti don't have to set
|
|
1503
|
+
# via args. on their command_list.
|
|
1483
1504
|
if job['name'].startswith(multi_cwd): job['name'] = job['name'][len(multi_cwd):]
|
|
1484
1505
|
# in all but fancy mode, we will print this text at the launch of a job. It may get a newline below
|
|
1485
1506
|
job_text = sprint_job_line(jobs_launched, job['name'], hide_stats=run_parallel)
|
|
@@ -1663,34 +1684,36 @@ class CommandParallel(Command):
|
|
|
1663
1684
|
'''Examines list self.jobs, and if leaf target names are duplicate will
|
|
1664
1685
|
patch each command's job-name to:
|
|
1665
1686
|
--job-name=path.leaf.command[.tool]
|
|
1687
|
+
|
|
1688
|
+
Also do for --force-logfile
|
|
1666
1689
|
'''
|
|
1667
1690
|
|
|
1668
|
-
def
|
|
1669
|
-
'''Fishes the
|
|
1691
|
+
def get_job_arg(job_dict: dict, arg_name: str) -> str:
|
|
1692
|
+
'''Fishes the arg_name out of an entry in self.jobs'''
|
|
1670
1693
|
for i, item in enumerate(job_dict['command_list']):
|
|
1671
|
-
if item.startswith('--
|
|
1672
|
-
_, name = item.split('--
|
|
1694
|
+
if item.startswith(f'--{arg_name}='):
|
|
1695
|
+
_, name = item.split(f'--{arg_name}=')
|
|
1673
1696
|
return name
|
|
1674
|
-
elif item == '--
|
|
1697
|
+
elif item == f'--{arg_name}':
|
|
1675
1698
|
return job_dict['command_list'][i + 1]
|
|
1676
1699
|
return ''
|
|
1677
1700
|
|
|
1678
|
-
def
|
|
1679
|
-
'''Replaces the
|
|
1701
|
+
def replace_job_arg(job_dict: dict, arg_name: str, new_value: str) -> bool:
|
|
1702
|
+
'''Replaces the arg_name's value in an entry in self.jobs'''
|
|
1680
1703
|
for i, item in enumerate(job_dict['command_list']):
|
|
1681
|
-
if item.startswith('--
|
|
1682
|
-
job_dict['command_list'][i] = '--
|
|
1683
|
-
return
|
|
1684
|
-
elif item == '--
|
|
1685
|
-
job_dict['command_list'][i + 1] =
|
|
1686
|
-
return
|
|
1687
|
-
return
|
|
1704
|
+
if item.startswith(f'--{arg_name}='):
|
|
1705
|
+
job_dict['command_list'][i] = f'--{arg_name}=' + new_value
|
|
1706
|
+
return True
|
|
1707
|
+
elif item == f'--{arg_name}':
|
|
1708
|
+
job_dict['command_list'][i + 1] = new_value
|
|
1709
|
+
return True
|
|
1710
|
+
return False
|
|
1688
1711
|
|
|
1689
1712
|
|
|
1690
1713
|
job_names_count_dict = {}
|
|
1691
1714
|
for job_dict in self.jobs:
|
|
1692
1715
|
|
|
1693
|
-
key =
|
|
1716
|
+
key = get_job_arg(job_dict, arg_name='job-name')
|
|
1694
1717
|
if not key:
|
|
1695
1718
|
self.error(f'{job_dict=} needs to have a --job-name= arg attached')
|
|
1696
1719
|
if key not in job_names_count_dict:
|
|
@@ -1699,7 +1722,7 @@ class CommandParallel(Command):
|
|
|
1699
1722
|
job_names_count_dict[key] += 1
|
|
1700
1723
|
|
|
1701
1724
|
for i, job_dict in enumerate(self.jobs):
|
|
1702
|
-
key =
|
|
1725
|
+
key = get_job_arg(job_dict, 'job-name')
|
|
1703
1726
|
if job_names_count_dict[key] < 2:
|
|
1704
1727
|
continue
|
|
1705
1728
|
|
|
@@ -1709,6 +1732,20 @@ class CommandParallel(Command):
|
|
|
1709
1732
|
patched_sub_work_dir = False
|
|
1710
1733
|
patched_target_path = os.path.relpath(tpath).replace(os.sep, '_')
|
|
1711
1734
|
new_job_name = f'{patched_target_path}.{key}'
|
|
1712
|
-
|
|
1713
|
-
|
|
1735
|
+
replace_job_arg(job_dict, arg_name='job-name', new_value=new_job_name)
|
|
1736
|
+
|
|
1737
|
+
# prepend path information to force-logfile (if present):
|
|
1738
|
+
force_logfile = get_job_arg(job_dict, arg_name='force-logfile')
|
|
1739
|
+
if force_logfile:
|
|
1740
|
+
left, right = os.path.split(force_logfile)
|
|
1741
|
+
new_force_logfile = os.path.join(left, f'{patched_target_path}.{right}')
|
|
1742
|
+
replace_job_arg(
|
|
1743
|
+
job_dict, arg_name='force-logfile', new_value=new_force_logfile
|
|
1744
|
+
)
|
|
1745
|
+
util.debug(
|
|
1746
|
+
f'Patched job {job_dict["name"]}: --force-logfile={new_force_logfile}'
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1749
|
+
|
|
1750
|
+
self.jobs[i] = job_dict
|
|
1714
1751
|
util.debug(f'Patched job {job_dict["name"]}: --job-name={new_job_name}')
|
|
@@ -124,20 +124,35 @@ def assert_export_jsonl_good(filepath:str, jsonl:bool=True) -> list:
|
|
|
124
124
|
class Helpers:
|
|
125
125
|
'''We do so much with logging in this file, might as well make it reusable'''
|
|
126
126
|
DEFAULT_DIR = ''
|
|
127
|
-
|
|
127
|
+
DEFAULT_LOG_DIR = os.getcwd()
|
|
128
|
+
DEFAULT_LOG = os.path.join(DEFAULT_LOG_DIR, '.pytest.eda.log')
|
|
128
129
|
def chdir(self):
|
|
129
130
|
'''Changes directory to self.DEFAULT_DIR and removes eda.work, eda.export paths'''
|
|
130
131
|
chdir_remove_work_dir('', self.DEFAULT_DIR)
|
|
131
132
|
|
|
133
|
+
def _resolve_logfile(self, logfile=None) -> str:
|
|
134
|
+
'''Returns the logfile's filepath'''
|
|
135
|
+
ret = logfile
|
|
136
|
+
if ret is None:
|
|
137
|
+
ret = self.DEFAULT_LOG
|
|
138
|
+
else:
|
|
139
|
+
left, right = os.path.split(logfile)
|
|
140
|
+
if not left or left in [os.path.sep, '.', '..']:
|
|
141
|
+
# relative logfile put in DEFAULT_LOG_DIR:
|
|
142
|
+
ret = os.path.join(self.DEFAULT_LOG_DIR, right)
|
|
143
|
+
return ret
|
|
144
|
+
|
|
132
145
|
def log_it(self, command_str:str, logfile=None, use_eda_wrap=True) -> int:
|
|
133
146
|
'''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
|
|
134
147
|
|
|
135
148
|
Usage:
|
|
136
|
-
rc = self.log_it('sim foo'
|
|
149
|
+
rc = self.log_it('sim foo')
|
|
137
150
|
assert rc == 0
|
|
151
|
+
|
|
152
|
+
Note this will run with --no-default-log to avoid a Windows problem with stomping
|
|
153
|
+
on a log file.
|
|
138
154
|
'''
|
|
139
|
-
|
|
140
|
-
logfile = self.DEFAULT_LOG
|
|
155
|
+
logfile = self._resolve_logfile(logfile)
|
|
141
156
|
rc = 50
|
|
142
157
|
|
|
143
158
|
# TODO(drew): There are some issues with log_it redirecting stdout from vivado
|
|
@@ -148,16 +163,15 @@ class Helpers:
|
|
|
148
163
|
with open(logfile, 'w', encoding='utf-8') as f:
|
|
149
164
|
with redirect_stdout(f), redirect_stderr(f):
|
|
150
165
|
if use_eda_wrap:
|
|
151
|
-
rc = eda_wrap(*(command_str.split()))
|
|
166
|
+
rc = eda_wrap('--no-default-log', *(command_str.split()))
|
|
152
167
|
else:
|
|
153
|
-
rc = eda.main(*(command_str.split()))
|
|
168
|
+
rc = eda.main('--no-default-log', *(command_str.split()))
|
|
154
169
|
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
155
170
|
return rc
|
|
156
171
|
|
|
157
172
|
def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
|
|
158
173
|
'''Check if any of want_str args are in the logfile, or self.DEFAULT_LOG'''
|
|
159
|
-
|
|
160
|
-
logfile = self.DEFAULT_LOG
|
|
174
|
+
logfile = self._resolve_logfile(logfile)
|
|
161
175
|
want_str0 = ' '.join(list(want_str))
|
|
162
176
|
want_str1 = want_str0.replace('/', '\\')
|
|
163
177
|
with open(logfile, encoding='utf-8') as f:
|
|
@@ -169,8 +183,7 @@ class Helpers:
|
|
|
169
183
|
|
|
170
184
|
def get_log_lines_with(self, *want_str, logfile=None, windows_path_support=False):
|
|
171
185
|
'''gets all log lines with any of want_str args are in the logfile, or self.DEFAULT_LOG'''
|
|
172
|
-
|
|
173
|
-
logfile = self.DEFAULT_LOG
|
|
186
|
+
logfile = self._resolve_logfile(logfile)
|
|
174
187
|
ret_list = []
|
|
175
188
|
want_str0 = ' '.join(list(want_str))
|
|
176
189
|
want_str1 = want_str0.replace('/', '\\')
|
|
@@ -186,8 +199,7 @@ class Helpers:
|
|
|
186
199
|
'''gets all log lines with any of *want_str within a single word
|
|
187
200
|
in the logfile or self.DEFAULT_LOG
|
|
188
201
|
'''
|
|
189
|
-
|
|
190
|
-
logfile = self.DEFAULT_LOG
|
|
202
|
+
logfile = self._resolve_logfile(logfile)
|
|
191
203
|
ret_list = []
|
|
192
204
|
want_str0 = ' '.join(list(want_str))
|
|
193
205
|
want_str1 = want_str0.replace('/', '\\')
|