opencos-eda 0.2.47__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.47/opencos_eda.egg-info → opencos_eda-0.2.48}/PKG-INFO +1 -1
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/multi.py +23 -5
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/sweep.py +6 -2
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/waves.py +1 -1
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda.py +9 -4
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_base.py +58 -21
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/helpers.py +24 -12
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_eda.py +11 -12
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/util.py +131 -80
- {opencos_eda-0.2.47 → opencos_eda-0.2.48/opencos_eda.egg-info}/PKG-INFO +1 -1
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/pyproject.toml +1 -1
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/LICENSE +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/LICENSE.spdx +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/README.md +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/__init__.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/_version.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/_waves_pkg.sv +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/__init__.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/build.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/elab.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/export.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/flist.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/lec.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/open.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/proj.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/shell.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/sim.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/synth.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/targets.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/commands/upload.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/deps_helpers.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/deps_schema.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_config.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_config_defaults.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_config_max_verilator_waivers.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_config_reduced.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_deps_bash_completion.bash +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_extract_targets.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/eda_tool_helper.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/export_helper.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/export_json_convert.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/files.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/names.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/oc_cli.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/pcie.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/peakrdl_cleanup.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/seed.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/__init__.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/custom_config.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_build.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_deps_helpers.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_deps_schema.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_eda_elab.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_eda_synth.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_oc_cli.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tests/test_tools.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/__init__.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/invio.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/invio_helpers.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/invio_yosys.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/iverilog.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/modelsim_ase.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/questa.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/riviera.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/slang.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/slang_yosys.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/surelog.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/tabbycad_yosys.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/verilator.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/vivado.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos/tools/yosys.py +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos_eda.egg-info/SOURCES.txt +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos_eda.egg-info/dependency_links.txt +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos_eda.egg-info/entry_points.txt +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos_eda.egg-info/requires.txt +0 -0
- {opencos_eda-0.2.47 → opencos_eda-0.2.48}/opencos_eda.egg-info/top_level.txt +0 -0
- {opencos_eda-0.2.47 → 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):
|
|
@@ -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('/', '\\')
|
|
@@ -422,7 +422,6 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
|
|
|
422
422
|
class TestMissingDepsFileErrorMessages(Helpers):
|
|
423
423
|
'''Test for missing DEPS.yml file, using 'eda export' to avoid tools.'''
|
|
424
424
|
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'no_deps_here', 'empty')
|
|
425
|
-
DEFAULT_LOG = 'eda.log'
|
|
426
425
|
|
|
427
426
|
def test_bad0(self):
|
|
428
427
|
'''Looks for target_bad0, but there is no DEPS file in .'''
|
|
@@ -444,7 +443,6 @@ class TestDepsResolveErrorMessages(Helpers):
|
|
|
444
443
|
linenumber information is printed when available.'''
|
|
445
444
|
|
|
446
445
|
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'error_msgs')
|
|
447
|
-
DEFAULT_LOG = 'eda.log'
|
|
448
446
|
|
|
449
447
|
# files foo.sv, foo2.sv, target_bad0.sv, and target_bad1.sv exist.
|
|
450
448
|
# files missing*.sv and targets missing* do not exist.
|
|
@@ -741,13 +739,15 @@ def test_deps_command_order(command):
|
|
|
741
739
|
work_dir = os.path.join(
|
|
742
740
|
THISPATH, 'deps_files', 'command_order', 'eda.work', f'target_test.{command}'
|
|
743
741
|
)
|
|
742
|
+
# Note that eda will write out the returncode INFO line to tee'd log files, so
|
|
743
|
+
# there is more in the log file than "hi" or "bye".
|
|
744
744
|
with open(os.path.join(work_dir, 'target_echo_hi__shell_0.log'), encoding='utf-8') as f:
|
|
745
|
-
text = ''.join(f.readlines()).strip()
|
|
746
|
-
assert text in ['hi', '"hi"', '\\"hi\\"']
|
|
745
|
+
text = ' '.join(f.readlines()).strip()
|
|
746
|
+
assert any(text.startswith(x) for x in ['hi', '"hi"', '\\"hi\\"'])
|
|
747
747
|
# Added check, one of the targets uses a custom 'tee' file name, instead of the default log.
|
|
748
748
|
with open(os.path.join(work_dir, 'custom_tee_echo_bye.log'), encoding='utf-8') as f:
|
|
749
749
|
text = ''.join(f.readlines()).strip()
|
|
750
|
-
assert text in ['bye', '"bye"', '\\"bye\\"']
|
|
750
|
+
assert any(text.startswith(x) for x in ['bye', '"bye"', '\\"bye\\"'])
|
|
751
751
|
|
|
752
752
|
|
|
753
753
|
@pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
|
|
@@ -833,14 +833,13 @@ class TestDepsNoFilesTargets(Helpers):
|
|
|
833
833
|
class TestDepsTags(Helpers):
|
|
834
834
|
'''Series of tests for DEPS - target - tags, in ./deps_files/tags_with_tools'''
|
|
835
835
|
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'tags_with_tools')
|
|
836
|
-
DEFAULT_LOG = 'eda.log'
|
|
837
836
|
|
|
838
837
|
@pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
|
|
839
838
|
def test_tags_with_tools_verilator(self):
|
|
840
839
|
'''test for DEPS target that hits with-tools: verilator, so that
|
|
841
840
|
additional args are applied from the DEPS tag.'''
|
|
842
841
|
self.chdir()
|
|
843
|
-
logfile = 'verilator_eda.log'
|
|
842
|
+
logfile = '.pytest.verilator_eda.log'
|
|
844
843
|
rc = self.log_it('sim --tool verilator target_test', logfile=logfile)
|
|
845
844
|
assert rc == 0
|
|
846
845
|
|
|
@@ -859,7 +858,7 @@ class TestDepsTags(Helpers):
|
|
|
859
858
|
AKA, lets you replace all the Verilator waivers to the DEPS target that only affect
|
|
860
859
|
with-tools: verilator'''
|
|
861
860
|
self.chdir()
|
|
862
|
-
logfile = 'target_with_replace_config_tools_test.log'
|
|
861
|
+
logfile = '.pytest.target_with_replace_config_tools_test.log'
|
|
863
862
|
rc = self.log_it('sim --tool verilator target_with_replace_config_tools_test',
|
|
864
863
|
logfile=logfile)
|
|
865
864
|
assert rc == 0
|
|
@@ -877,7 +876,7 @@ class TestDepsTags(Helpers):
|
|
|
877
876
|
AKA, lets you add Verilator waivers to the DEPS target that only affect
|
|
878
877
|
with-tools: verilator'''
|
|
879
878
|
self.chdir()
|
|
880
|
-
logfile = 'target_with_additive_config_tools_test.log'
|
|
879
|
+
logfile = '.pytest.target_with_additive_config_tools_test.log'
|
|
881
880
|
rc = self.log_it('sim --tool verilator --debug target_with_additive_config_tools_test',
|
|
882
881
|
logfile=logfile)
|
|
883
882
|
assert rc == 0
|
|
@@ -897,7 +896,7 @@ class TestDepsTags(Helpers):
|
|
|
897
896
|
apply the arg --lint-only in the DEPS tag, and instead run the
|
|
898
897
|
full simulation.'''
|
|
899
898
|
self.chdir()
|
|
900
|
-
logfile = 'vivado_eda.log'
|
|
899
|
+
logfile = '.pytest.vivado_eda.log'
|
|
901
900
|
rc = self.log_it('sim --tool vivado target_test', logfile=logfile)
|
|
902
901
|
assert rc == 0
|
|
903
902
|
|
|
@@ -916,7 +915,7 @@ class TestDepsTags(Helpers):
|
|
|
916
915
|
def test_tags_with_tools_add_incdirs(self):
|
|
917
916
|
'''test for DEPS target with tag that adds incdirs'''
|
|
918
917
|
self.chdir()
|
|
919
|
-
logfile = 'target_foo_sv_add_incdirs.log'
|
|
918
|
+
logfile = '.pytest.target_foo_sv_add_incdirs.log'
|
|
920
919
|
rc = self.log_it('elab --tool verilator target_foo_sv_add_incdirs',
|
|
921
920
|
logfile=logfile)
|
|
922
921
|
assert rc == 0
|
|
@@ -931,7 +930,7 @@ class TestDepsTags(Helpers):
|
|
|
931
930
|
def test_tags_with_tools_add_defines(self):
|
|
932
931
|
'''test for DEPS target with tag that adds defines'''
|
|
933
932
|
self.chdir()
|
|
934
|
-
logfile = 'target_foo_sv_add_defines.log'
|
|
933
|
+
logfile = '.pytest.target_foo_sv_add_defines.log'
|
|
935
934
|
rc = self.log_it('elab --tool verilator --debug target_foo_sv_add_defines',
|
|
936
935
|
logfile=logfile)
|
|
937
936
|
assert rc == 0
|
|
@@ -19,10 +19,85 @@ from opencos import eda_config
|
|
|
19
19
|
global_exit_allowed = False
|
|
20
20
|
progname = "UNKNOWN"
|
|
21
21
|
progname_in_message = True
|
|
22
|
-
logfile = None
|
|
23
|
-
loglast = 0
|
|
24
22
|
debug_level = 0
|
|
25
23
|
|
|
24
|
+
class UtilLogger:
|
|
25
|
+
file = None
|
|
26
|
+
filepath = ''
|
|
27
|
+
time_last = 0 #timestamp via time.time()
|
|
28
|
+
|
|
29
|
+
# disabled by default, eda.py enables it. Can also be disabled via
|
|
30
|
+
# util's argparser: --no-default-log, --logfile=<name>, or --force-logfile=<name>
|
|
31
|
+
default_log_enabled = False
|
|
32
|
+
default_log_filepath = os.path.join('eda.work', 'eda.log')
|
|
33
|
+
|
|
34
|
+
def clear(self) -> None:
|
|
35
|
+
self.file = None
|
|
36
|
+
self.filepath = ''
|
|
37
|
+
self.time_last = 0
|
|
38
|
+
|
|
39
|
+
def stop(self) -> None:
|
|
40
|
+
if self.file:
|
|
41
|
+
self.write_timestamp(f'stop - {self.filepath}')
|
|
42
|
+
info(f"Closing logfile: {self.filepath}")
|
|
43
|
+
self.file.close()
|
|
44
|
+
self.clear()
|
|
45
|
+
|
|
46
|
+
def start(self, filename: str, force: bool = False) -> None:
|
|
47
|
+
if not filename:
|
|
48
|
+
error(f'Trying to start a logfile, but filename is missing')
|
|
49
|
+
return
|
|
50
|
+
if os.path.exists(filename):
|
|
51
|
+
if force:
|
|
52
|
+
debug(f"Overwriting logfile '{filename}', which exists, due to --force-logfile.")
|
|
53
|
+
else:
|
|
54
|
+
error(f"The --logfile path '{filename}' exists. Use --force-logfile",
|
|
55
|
+
"(vs --logfile) to override.")
|
|
56
|
+
return
|
|
57
|
+
else:
|
|
58
|
+
safe_mkdir_for_file(filename)
|
|
59
|
+
try:
|
|
60
|
+
self.file = open(filename, 'w')
|
|
61
|
+
debug(f"Opened logfile '{filename}' for writing")
|
|
62
|
+
self.filepath = filename
|
|
63
|
+
self.write_timestamp(f'start - {self.filepath}')
|
|
64
|
+
except Exception as e:
|
|
65
|
+
error(f"Error opening '{filename}' for writing, {e}")
|
|
66
|
+
self.clear()
|
|
67
|
+
|
|
68
|
+
def write_timestamp(self, text: str = "") -> None:
|
|
69
|
+
dt = datetime.datetime.now().ctime()
|
|
70
|
+
print(f"INFO: [{progname}] Time: {dt} {text}", file=self.file)
|
|
71
|
+
self.time_last = time.time()
|
|
72
|
+
|
|
73
|
+
def write(self, text: str, end: str) -> None:
|
|
74
|
+
sw = text.startswith(f"INFO: [{progname}]")
|
|
75
|
+
if (((time.time() - self.time_last) > 10) and
|
|
76
|
+
(text.startswith(f"DEBUG: [{progname}]") or
|
|
77
|
+
text.startswith(f"INFO: [{progname}]") or
|
|
78
|
+
text.startswith(f"WARNING: [{progname}]") or
|
|
79
|
+
text.startswith(f"ERROR: [{progname}]"))):
|
|
80
|
+
self.write_timestamp()
|
|
81
|
+
print(text, end=end, file=self.file)
|
|
82
|
+
self.file.flush()
|
|
83
|
+
os.fsync(self.file)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
global_log = UtilLogger()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def start_log(filename, force=False):
|
|
90
|
+
global_log.start(filename=filename, force=force)
|
|
91
|
+
|
|
92
|
+
def write_log(text, end):
|
|
93
|
+
global_log.write(text=text, end=end)
|
|
94
|
+
|
|
95
|
+
def stop_log():
|
|
96
|
+
global_log.stop()
|
|
97
|
+
|
|
98
|
+
atexit.register(stop_log)
|
|
99
|
+
|
|
100
|
+
|
|
26
101
|
EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
|
|
27
102
|
|
|
28
103
|
args = {
|
|
@@ -137,45 +212,6 @@ def yaml_safe_writer(data:dict, filepath:str) -> None:
|
|
|
137
212
|
warning(f'{filepath=} to be written for this extension not implemented.')
|
|
138
213
|
|
|
139
214
|
|
|
140
|
-
|
|
141
|
-
def start_log(filename, force=False):
|
|
142
|
-
global logfile, loglast
|
|
143
|
-
if os.path.exists(filename):
|
|
144
|
-
if force:
|
|
145
|
-
info(f"Overwriting '{filename}', which exists, due to --force-logfile.")
|
|
146
|
-
else:
|
|
147
|
-
error(f"The --logfile path '{filename}' exists. Use --force-logfile (vs --logfile) to override.")
|
|
148
|
-
try:
|
|
149
|
-
logfile = open( filename, 'w')
|
|
150
|
-
debug(f"Opened logfile '{filename}' for writing")
|
|
151
|
-
except Exception as e:
|
|
152
|
-
error(f"Error opening '{filename}' for writing!")
|
|
153
|
-
|
|
154
|
-
def write_log(text, end):
|
|
155
|
-
global logfile, loglast
|
|
156
|
-
sw = text.startswith(f"INFO: [{progname}]")
|
|
157
|
-
if (((time.time() - loglast) > 10) and
|
|
158
|
-
(text.startswith(f"DEBUG: [{progname}]") or
|
|
159
|
-
text.startswith(f"INFO: [{progname}]") or
|
|
160
|
-
text.startswith(f"WARNING: [{progname}]") or
|
|
161
|
-
text.startswith(f"ERROR: [{progname}]"))):
|
|
162
|
-
dt = datetime.datetime.now().ctime()
|
|
163
|
-
print(f"INFO: [{progname}] Time: {dt}", file=logfile)
|
|
164
|
-
loglast = time.time()
|
|
165
|
-
print(text, end=end, file=logfile)
|
|
166
|
-
logfile.flush()
|
|
167
|
-
os.fsync(logfile)
|
|
168
|
-
|
|
169
|
-
def stop_log():
|
|
170
|
-
global logfile, loglast
|
|
171
|
-
if logfile:
|
|
172
|
-
debug(f"Closing logfile")
|
|
173
|
-
logfile.close()
|
|
174
|
-
logfile = None
|
|
175
|
-
loglast = 0
|
|
176
|
-
|
|
177
|
-
atexit.register(stop_log)
|
|
178
|
-
|
|
179
215
|
def get_argparse_bool_action_kwargs() -> dict:
|
|
180
216
|
bool_kwargs = dict()
|
|
181
217
|
x = getattr(argparse, 'BooleanOptionalAction', None)
|
|
@@ -205,12 +241,19 @@ def get_argparser() -> argparse.ArgumentParser:
|
|
|
205
241
|
help='Display additional debug messaging level 1 or higher')
|
|
206
242
|
parser.add_argument('--debug-level', type=int, default=0,
|
|
207
243
|
help='Set debug level messaging (default: 0)')
|
|
208
|
-
parser.add_argument('--logfile', type=str, default=
|
|
209
|
-
help='Write eda messaging to logfile
|
|
210
|
-
|
|
244
|
+
parser.add_argument('--logfile', type=str, default=None,
|
|
245
|
+
help=('Write eda messaging to safe logfile that will not be overwritten'
|
|
246
|
+
' (default disabled)'))
|
|
247
|
+
parser.add_argument('--force-logfile', type=str, default=None,
|
|
211
248
|
help='Set to force overwrite the logfile')
|
|
249
|
+
parser.add_argument('--default-log', **bool_action_kwargs,
|
|
250
|
+
default=global_log.default_log_enabled,
|
|
251
|
+
help=('Enable/Disable default logging to'
|
|
252
|
+
f' {global_log.default_log_filepath}. Default logging is disabled'
|
|
253
|
+
' if --logfile or --force-logfile is set'))
|
|
212
254
|
parser.add_argument('--no-respawn', action='store_true',
|
|
213
|
-
help='Legacy mode (default respawn disabled) for respawning eda.py
|
|
255
|
+
help=('Legacy mode (default respawn disabled) for respawning eda.py'
|
|
256
|
+
' using $OC_ROOT/bin'))
|
|
214
257
|
return parser
|
|
215
258
|
|
|
216
259
|
def get_argparser_short_help(parser=None) -> str:
|
|
@@ -251,10 +294,15 @@ def process_tokens(tokens:list) -> (argparse.Namespace, list):
|
|
|
251
294
|
|
|
252
295
|
debug(f'util.process_tokens: {parsed=} {unparsed=} from {tokens=}')
|
|
253
296
|
|
|
254
|
-
if parsed.force_logfile
|
|
297
|
+
if parsed.force_logfile:
|
|
255
298
|
start_log(parsed.force_logfile, force=True)
|
|
256
|
-
elif parsed.logfile
|
|
299
|
+
elif parsed.logfile:
|
|
257
300
|
start_log(parsed.logfile, force=False)
|
|
301
|
+
elif parsed.default_log and \
|
|
302
|
+
(parsed.force_logfile is None and parsed.logfile is None):
|
|
303
|
+
# Use a forced logfile in the eda.work/eda.log:
|
|
304
|
+
start_log(global_log.default_log_filepath, force=True)
|
|
305
|
+
|
|
258
306
|
|
|
259
307
|
parsed_as_dict = vars(parsed)
|
|
260
308
|
for key,value in parsed_as_dict.items():
|
|
@@ -339,7 +387,7 @@ def print_post(text, end):
|
|
|
339
387
|
print(fancy_lines_[x],flush=True,end=('' if x==(len(fancy_lines_)-1) else '\n'))
|
|
340
388
|
#time.sleep(1)
|
|
341
389
|
print("\033[1G", end="", flush=True)
|
|
342
|
-
if
|
|
390
|
+
if global_log.file: write_log(text, end=end)
|
|
343
391
|
|
|
344
392
|
string_red = f"\x1B[31m"
|
|
345
393
|
string_green = f"\x1B[32m"
|
|
@@ -520,6 +568,11 @@ def safe_rmdir(path):
|
|
|
520
568
|
error(f"An error occurred while removing the directory '{path}': {e}")
|
|
521
569
|
|
|
522
570
|
def safe_mkdir(path : str):
|
|
571
|
+
if os.path.exists(path):
|
|
572
|
+
return
|
|
573
|
+
left, right = os.path.split(os.path.relpath(path))
|
|
574
|
+
if left and left not in ['.', '..', os.path.sep]:
|
|
575
|
+
safe_mkdir(left)
|
|
523
576
|
try:
|
|
524
577
|
os.mkdir(path)
|
|
525
578
|
except FileExistsError:
|
|
@@ -534,6 +587,11 @@ def safe_mkdirs(base : str, new_dirs : list):
|
|
|
534
587
|
for p in new_dirs:
|
|
535
588
|
safe_mkdir( os.path.join(base, p) )
|
|
536
589
|
|
|
590
|
+
def safe_mkdir_for_file(filepath: str):
|
|
591
|
+
left, right = os.path.split(filepath)
|
|
592
|
+
if left:
|
|
593
|
+
safe_mkdir(left)
|
|
594
|
+
|
|
537
595
|
|
|
538
596
|
def import_class_from_string(full_class_name):
|
|
539
597
|
"""
|
|
@@ -707,9 +765,8 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
|
|
|
707
765
|
|
|
708
766
|
debug(f'util.subprocess_run_background: {background=} {tee_fpath=} {shell=}')
|
|
709
767
|
|
|
710
|
-
if fake
|
|
711
|
-
#
|
|
712
|
-
# printed out (stdout and tee_fpath).
|
|
768
|
+
if fake:
|
|
769
|
+
# let subprocess_run handle it (won't run anything)
|
|
713
770
|
rc = subprocess_run(work_dir, command_list, fake=fake, shell=shell)
|
|
714
771
|
return '', '', rc
|
|
715
772
|
|
|
@@ -734,38 +791,32 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
|
|
|
734
791
|
debug(f"util.subprocess_run_background: about to call subprocess.Popen({c}, **{proc_kwargs})")
|
|
735
792
|
proc = subprocess.Popen(c, **proc_kwargs)
|
|
736
793
|
|
|
794
|
+
stdout = ''
|
|
795
|
+
stderr = ''
|
|
796
|
+
tee_fpath_f = None
|
|
737
797
|
if tee_fpath:
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
798
|
+
try:
|
|
799
|
+
tee_fpath_f = open(tee_fpath, 'w')
|
|
800
|
+
except Exception as e:
|
|
801
|
+
error(f'Unable to open file "{tee_fpath}" for writing, {e}')
|
|
802
|
+
|
|
803
|
+
for line in iter(proc.stdout.readline, b''):
|
|
804
|
+
line = line.rstrip().decode("utf-8", errors="replace")
|
|
805
|
+
if not background:
|
|
806
|
+
print(line)
|
|
807
|
+
if tee_fpath_f:
|
|
808
|
+
tee_fpath_f.write(line + '\n')
|
|
809
|
+
if global_log.file:
|
|
810
|
+
global_log.write(line, '\n')
|
|
811
|
+
stdout += line + '\n'
|
|
812
|
+
|
|
813
|
+
proc.communicate()
|
|
814
|
+
rc = proc.returncode
|
|
815
|
+
if tee_fpath_f:
|
|
816
|
+
tee_fpath_f.write(f'INFO: [{progname}] util.subprocess_run_background: returncode={rc}\n')
|
|
817
|
+
tee_fpath_f.close()
|
|
750
818
|
info('util.subprocess_run_background: wrote: ' + os.path.abspath(tee_fpath))
|
|
751
819
|
|
|
752
|
-
|
|
753
|
-
else:
|
|
754
|
-
|
|
755
|
-
debug(f"util.subprocess_run_background: about to call proc.communicate()")
|
|
756
|
-
stdout, stderr = proc.communicate()
|
|
757
|
-
rc = proc.returncode
|
|
758
|
-
|
|
759
|
-
stdout = stdout.decode('utf-8', errors="replace") if stdout else ""
|
|
760
|
-
stderr = stderr.decode('utf-8', errors="replace") if stderr else ""
|
|
761
|
-
debug(f"shell_run_background: {rc=}")
|
|
762
|
-
if stdout:
|
|
763
|
-
for lineno, line in enumerate(stdout.strip().split('\n')):
|
|
764
|
-
debug(f"stdout:{lineno+1}: {line}")
|
|
765
|
-
if stderr:
|
|
766
|
-
for lineno, line in enumerate(stdout.strip().split('\n')):
|
|
767
|
-
debug(f"stderr:{lineno+1}: {line}")
|
|
768
|
-
|
|
769
820
|
return stdout, stderr, rc
|
|
770
821
|
|
|
771
822
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|