opencos-eda 0.3.5__tar.gz → 0.3.6__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.3.5/opencos_eda.egg-info → opencos_eda-0.3.6}/PKG-INFO +4 -3
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/sim.py +16 -6
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps/deps_processor.py +13 -2
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda.py +6 -5
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_base.py +77 -29
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_config.py +2 -1
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_config_defaults.yml +5 -1
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/export_helper.py +89 -31
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/files.py +3 -1
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/helpers.py +23 -17
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/cocotb.py +94 -21
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/verilator.py +5 -2
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/util.py +73 -50
- {opencos_eda-0.3.5 → opencos_eda-0.3.6/opencos_eda.egg-info}/PKG-INFO +4 -3
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos_eda.egg-info/requires.txt +3 -2
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/pyproject.toml +5 -3
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/LICENSE +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/LICENSE.spdx +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/README.md +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/_version.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/_waves_pkg.sv +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/build.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/deps_help.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/elab.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/export.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/flist.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/lec.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/lint.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/multi.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/open.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/proj.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/shell.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/sweep.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/synth.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/targets.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/upload.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/commands/waves.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps/defaults.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps/deps_commands.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps/deps_file.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/deps_schema.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_config_max_verilator_waivers.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_config_reduced.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_deps_bash_completion.bash +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_deps_sanitize.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_extract_targets.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/eda_tool_helper.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/export_json_convert.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/hw/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/hw/oc_cli.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/hw/pcie.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/names.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/peakrdl_cleanup.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/seed.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/custom_config.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_build.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_deps_helpers.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_deps_schema.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_eda.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_eda_elab.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_eda_synth.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_oc_cli.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tests/test_tools.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/invio.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/invio_helpers.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/invio_yosys.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/iverilog.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/modelsim_ase.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/quartus.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/questa.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/questa_fse.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/riviera.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/slang.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/slang_yosys.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/surelog.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/tabbycad_yosys.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/vivado.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/tools/yosys.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/__init__.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/markup_helpers.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/status_constants.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/str_helpers.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/subprocess_helpers.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/vscode_helper.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos/utils/vsim_helper.py +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos_eda.egg-info/SOURCES.txt +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos_eda.egg-info/dependency_links.txt +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos_eda.egg-info/entry_points.txt +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/opencos_eda.egg-info/top_level.txt +0 -0
- {opencos_eda-0.3.5 → opencos_eda-0.3.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
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
|
|
@@ -16,11 +16,12 @@ Requires-Dist: schema>=0.7.7
|
|
|
16
16
|
Requires-Dist: toml>=0.10.2
|
|
17
17
|
Requires-Dist: yamllint>=1.35.1
|
|
18
18
|
Requires-Dist: PySerial>=3.5
|
|
19
|
-
Requires-Dist: cocotb>=2.0
|
|
20
19
|
Requires-Dist: supports_color>=0.2.0
|
|
20
|
+
Requires-Dist: cocotb>=2.0
|
|
21
|
+
Requires-Dist: pytest>=8.3.5
|
|
22
|
+
Requires-Dist: coverage>=7.6.1
|
|
21
23
|
Provides-Extra: dev
|
|
22
24
|
Requires-Dist: pylint>=3.0.0; extra == "dev"
|
|
23
|
-
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
24
25
|
Provides-Extra: docs
|
|
25
26
|
Requires-Dist: mkdocs; extra == "docs"
|
|
26
27
|
Requires-Dist: mkdocs-material; extra == "docs"
|
|
@@ -145,6 +145,7 @@ class CommandSim(CommandDesign):
|
|
|
145
145
|
self.run_dep_commands()
|
|
146
146
|
self.do_it()
|
|
147
147
|
self.run_post_tool_dep_commands()
|
|
148
|
+
self.report_pass_fail()
|
|
148
149
|
return unparsed
|
|
149
150
|
|
|
150
151
|
|
|
@@ -198,6 +199,9 @@ class CommandSim(CommandDesign):
|
|
|
198
199
|
|
|
199
200
|
clist = list(obj).copy()
|
|
200
201
|
tee_fpath = getattr(obj, 'tee_fpath', None)
|
|
202
|
+
work_dir = getattr(obj, 'work_dir', None)
|
|
203
|
+
if not work_dir:
|
|
204
|
+
work_dir = self.args['work-dir']
|
|
201
205
|
|
|
202
206
|
util.debug(f'run_commands_check_logs: {clist=}, {tee_fpath=}')
|
|
203
207
|
|
|
@@ -207,24 +211,28 @@ class CommandSim(CommandDesign):
|
|
|
207
211
|
if log_filename:
|
|
208
212
|
log_fname = log_filename
|
|
209
213
|
|
|
214
|
+
|
|
210
215
|
_, stdout, _ = self.exec(
|
|
211
|
-
work_dir=
|
|
216
|
+
work_dir=work_dir, command_list=clist, tee_fpath=tee_fpath
|
|
212
217
|
)
|
|
213
218
|
|
|
214
219
|
if check_logs and log_fname:
|
|
215
220
|
# Note this call will check on stdout if not GUI, not opening the log_fname,
|
|
216
221
|
# but if this is GUI we normally lose stdout and have to open the log.
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
if self.args.get('gui', False):
|
|
223
|
+
file_contents_str = ''
|
|
224
|
+
else:
|
|
225
|
+
file_contents_str = stdout
|
|
226
|
+
|
|
219
227
|
self.check_logs_for_errors(
|
|
220
|
-
filename=os.path.join(
|
|
228
|
+
filename=os.path.join(work_dir, log_fname),
|
|
221
229
|
file_contents_str=file_contents_str,
|
|
222
230
|
bad_strings=bad_strings, must_strings=must_strings,
|
|
223
231
|
use_bad_strings=use_bad_strings, use_must_strings=use_must_strings
|
|
224
232
|
)
|
|
225
233
|
if log_fname:
|
|
226
234
|
self.artifacts_add(
|
|
227
|
-
name=os.path.join(
|
|
235
|
+
name=os.path.join(work_dir, log_fname),
|
|
228
236
|
typ='text', description='Simulator stdout/stderr log file'
|
|
229
237
|
)
|
|
230
238
|
|
|
@@ -253,6 +261,7 @@ class CommandSim(CommandDesign):
|
|
|
253
261
|
tool = self.args.get('tool', None)
|
|
254
262
|
# Certain args are allow-listed here
|
|
255
263
|
deps_file_args = []
|
|
264
|
+
print(f'SUPER DREW DEBUG: {self.get_command_line_args()=}')
|
|
256
265
|
for a in self.get_command_line_args():
|
|
257
266
|
if any(a.startswith(x) for x in [
|
|
258
267
|
'--compile-args',
|
|
@@ -265,7 +274,8 @@ class CommandSim(CommandDesign):
|
|
|
265
274
|
'--stop-',
|
|
266
275
|
'--lint-',
|
|
267
276
|
'--verilate',
|
|
268
|
-
'--verilator'
|
|
277
|
+
'--verilator',
|
|
278
|
+
'--cocotb-test-']):
|
|
269
279
|
deps_file_args.append(a)
|
|
270
280
|
|
|
271
281
|
export_obj.run(
|
|
@@ -10,7 +10,7 @@ import os
|
|
|
10
10
|
from opencos import files
|
|
11
11
|
from opencos import eda_config
|
|
12
12
|
from opencos.util import debug, info, warning, error, read_tokens_from_dot_f, \
|
|
13
|
-
patch_args_for_dir
|
|
13
|
+
patch_args_for_dir, load_env_file
|
|
14
14
|
from opencos.utils.str_helpers import dep_str2list
|
|
15
15
|
from opencos.deps.deps_file import deps_target_get_deps_list
|
|
16
16
|
from opencos.deps.deps_commands import deps_commands_handler
|
|
@@ -192,9 +192,15 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
192
192
|
# Since some args (util.py, eda_config.py, eda.py) can only be handled from command
|
|
193
193
|
# line, it would be nice if -f or --input-file is handled from DEPS, so we'll special
|
|
194
194
|
# case that now. Recursively resolve -f / --input-file.
|
|
195
|
+
# Do similary for --env-file (also only supported in util.py)
|
|
195
196
|
parser = argparse.ArgumentParser(
|
|
196
197
|
prog='deps_processor -f/--input-file', add_help=False, allow_abbrev=False
|
|
197
198
|
)
|
|
199
|
+
parser.add_argument('--env-file', default=[], action='append',
|
|
200
|
+
help=(
|
|
201
|
+
"dotenv file(s) to pass ENV vars, (default: .env loaded first,"
|
|
202
|
+
" subsequent files' vars override .env"
|
|
203
|
+
))
|
|
198
204
|
parser.add_argument('-f', '--input-file', default=[], action='append',
|
|
199
205
|
help=(
|
|
200
206
|
'Input .f file to be expanded as eda args, defines, incdirs,'
|
|
@@ -225,7 +231,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
225
231
|
# recurse until we've resolved nested .f files.
|
|
226
232
|
return self.apply_args(args_list=tokens2)
|
|
227
233
|
|
|
228
|
-
|
|
234
|
+
if parsed.env_file:
|
|
235
|
+
for env_file in parsed.env_file:
|
|
236
|
+
load_env_file(env_file)
|
|
237
|
+
|
|
238
|
+
# if no --input-file/--env-file values, keep parsing the remaining tokens2:
|
|
239
|
+
tokens = tokens2
|
|
229
240
|
|
|
230
241
|
# We have to special-case anything with --tool[=value] in tokens, otherwise
|
|
231
242
|
# the user may think they were allowed to set --tool, but in our flow the Command handler
|
|
@@ -20,6 +20,7 @@ from pathlib import Path
|
|
|
20
20
|
|
|
21
21
|
import opencos
|
|
22
22
|
from opencos import util, eda_config, eda_base
|
|
23
|
+
from opencos.util import safe_emoji
|
|
23
24
|
from opencos.eda_base import Tool, which_tool, get_eda_exec
|
|
24
25
|
from opencos.utils import vsim_helper, vscode_helper
|
|
25
26
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
@@ -95,15 +96,15 @@ def usage(tokens: list, config: dict, command: str = "", tool: str = "") -> int:
|
|
|
95
96
|
|
|
96
97
|
if command == "":
|
|
97
98
|
print(
|
|
98
|
-
"""
|
|
99
|
-
Usage:
|
|
99
|
+
f"""
|
|
100
|
+
{safe_emoji("🔦 ")}Usage:
|
|
100
101
|
eda [<options>] <command> [options] <files|targets, ...>
|
|
101
102
|
"""
|
|
102
103
|
)
|
|
103
104
|
print(get_all_commands_help_str(config=config))
|
|
104
105
|
print(
|
|
105
|
-
"""
|
|
106
|
-
|
|
106
|
+
f"""
|
|
107
|
+
{safe_emoji("❕ ")}where <files|targets, ...> is one or more source file or DEPS markup file target,
|
|
107
108
|
such as .v, .sv, .vhd[l], .cpp files, or a target key in a DEPS.[yml|yaml|toml|json].
|
|
108
109
|
Note that you can prefix source files with `sv@`, `v@`, `vhdl@` or `cpp@` to
|
|
109
110
|
force use that file as systemverilog, verilog, vhdl, or C++, respectively.
|
|
@@ -495,7 +496,7 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
|
|
|
495
496
|
def signal_handler(sig, frame) -> None: # pylint: disable=unused-argument
|
|
496
497
|
'''Handles Ctrl-C, called by main_cli() if running from command line'''
|
|
497
498
|
util.fancy_stop()
|
|
498
|
-
util.
|
|
499
|
+
util.error(f'{safe_emoji("❌ ")}Received Ctrl+C...', start='\nINFO: [EDA] ')
|
|
499
500
|
subprocess_helpers.cleanup_all()
|
|
500
501
|
util.exit(-1)
|
|
501
502
|
|
|
@@ -24,7 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
from opencos import seed, util, files
|
|
25
25
|
from opencos import eda_config
|
|
26
26
|
|
|
27
|
-
from opencos.util import Colors
|
|
27
|
+
from opencos.util import Colors, safe_emoji
|
|
28
28
|
from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or_space, \
|
|
29
29
|
indent_wrap_long_text, pretty_list_columns_manual
|
|
30
30
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
@@ -44,7 +44,9 @@ def print_base_help() -> None:
|
|
|
44
44
|
|
|
45
45
|
def get_argparser() -> argparse.ArgumentParser:
|
|
46
46
|
'''Returns the ArgumentParser for general eda CLI'''
|
|
47
|
-
parser = argparse.ArgumentParser(
|
|
47
|
+
parser = argparse.ArgumentParser(
|
|
48
|
+
prog=f'{safe_emoji("🔎 ")}eda options', add_help=False, allow_abbrev=False
|
|
49
|
+
)
|
|
48
50
|
parser.add_argument('-q', '--quit', action='store_true',
|
|
49
51
|
help=(
|
|
50
52
|
'For interactive mode (eda called with no options, command, or'
|
|
@@ -313,6 +315,18 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
313
315
|
util.error(f"command '{self.command_name}' has previous errors")
|
|
314
316
|
return self.status > 0
|
|
315
317
|
|
|
318
|
+
def report_pass_fail(self) -> None:
|
|
319
|
+
'''Reports an INFO line with pass/fail information'''
|
|
320
|
+
job_name = ' - '.join(
|
|
321
|
+
x for x in (self.command_name, self.args.get('tool', ''),
|
|
322
|
+
self.args.get('top', '')) if x
|
|
323
|
+
)
|
|
324
|
+
if self.status_any_error():
|
|
325
|
+
util.info(f'{safe_emoji("❌ ")}{job_name}: Errors observed.', color=Colors.red)
|
|
326
|
+
else:
|
|
327
|
+
util.info(f'{safe_emoji("✅ ")}{job_name}: No errors observed.')
|
|
328
|
+
|
|
329
|
+
|
|
316
330
|
def which_tool(self, command:str) -> str:
|
|
317
331
|
'''Returns a str for the tool name used for the requested command'''
|
|
318
332
|
return which_tool(command, config=self.config)
|
|
@@ -462,7 +476,8 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
462
476
|
if not tee_fpath and getattr(command_list, 'tee_fpath', None):
|
|
463
477
|
tee_fpath = getattr(command_list, 'tee_fpath', '')
|
|
464
478
|
if not quiet:
|
|
465
|
-
util.info(f"exec: {' '.join(command_list)}
|
|
479
|
+
util.info(f"{safe_emoji('⏩ ')}exec: {' '.join(command_list)}",
|
|
480
|
+
f"(in {work_dir}, {tee_fpath=})")
|
|
466
481
|
|
|
467
482
|
stdout, stderr, return_code = subprocess_run_background(
|
|
468
483
|
work_dir=work_dir,
|
|
@@ -484,7 +499,8 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
484
499
|
self.error(f"exec: returned with error (return code: {return_code})",
|
|
485
500
|
error_code=self.status)
|
|
486
501
|
else:
|
|
487
|
-
util.debug(f"exec: returned with error (return code:
|
|
502
|
+
util.debug(f"{safe_emoji('❌ ')}exec: returned with error (return code:",
|
|
503
|
+
f"{return_code})")
|
|
488
504
|
else:
|
|
489
505
|
util.debug(f"exec: returned without error (return code: {return_code})")
|
|
490
506
|
return stderr, stdout, return_code
|
|
@@ -564,7 +580,9 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
564
580
|
# parsed.args-with-dashes is not legal python. Some of self.args.keys() still have - or _,
|
|
565
581
|
# so this will handle both.
|
|
566
582
|
# Also, preference is for self.args.keys(), to be str with - dashes
|
|
567
|
-
parser = argparse.ArgumentParser(
|
|
583
|
+
parser = argparse.ArgumentParser(
|
|
584
|
+
prog=f'{safe_emoji("🔎 ")}eda', add_help=False, allow_abbrev=False
|
|
585
|
+
)
|
|
568
586
|
bool_action_kwargs = util.get_argparse_bool_action_kwargs()
|
|
569
587
|
|
|
570
588
|
if not parser_arg_list:
|
|
@@ -797,7 +815,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
797
815
|
util.info('Help:')
|
|
798
816
|
# using bare 'print' here, since help was requested, avoids --color and --quiet
|
|
799
817
|
print()
|
|
800
|
-
print('Usage:')
|
|
818
|
+
print(f'{safe_emoji("🔦 ")}Usage:')
|
|
801
819
|
if no_targets:
|
|
802
820
|
print(f' eda [options] {self.command_name} [options]')
|
|
803
821
|
else:
|
|
@@ -811,10 +829,10 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
811
829
|
return
|
|
812
830
|
|
|
813
831
|
if self.command_name:
|
|
814
|
-
lines.append(f"Generic help for command='{self.command_name}'"
|
|
832
|
+
lines.append(f"{safe_emoji('🔧 ')}Generic help for command='{self.command_name}'"
|
|
815
833
|
f" (using '{self.__class__.__name__}')")
|
|
816
834
|
else:
|
|
817
|
-
lines.append("Generic help (from class Command):")
|
|
835
|
+
lines.append("{safe_emoji('🔧 ')}Generic help (from class Command):")
|
|
818
836
|
|
|
819
837
|
# Attempt to run argparser on args, but don't error if it fails.
|
|
820
838
|
unparsed = []
|
|
@@ -980,6 +998,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
980
998
|
self.files_vhd = []
|
|
981
999
|
self.files_cpp = []
|
|
982
1000
|
self.files_sdc = []
|
|
1001
|
+
self.files_py = []
|
|
1002
|
+
self.files_makefile = []
|
|
983
1003
|
self.files_non_source = []
|
|
984
1004
|
self.files_caller_info = {}
|
|
985
1005
|
self.dep_shell_commands = [] # each list entry is a {}
|
|
@@ -1139,7 +1159,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1139
1159
|
self.files[new_key] = True
|
|
1140
1160
|
|
|
1141
1161
|
my_file_lists_list = [self.files_v, self.files_sv, self.files_vhd, self.files_cpp,
|
|
1142
|
-
self.files_sdc]
|
|
1162
|
+
self.files_sdc, self.files_py, self.files_makefile]
|
|
1143
1163
|
for my_file_list in my_file_lists_list:
|
|
1144
1164
|
for i,value in enumerate(my_file_list):
|
|
1145
1165
|
if value and isinstance(value, str) and \
|
|
@@ -1157,9 +1177,11 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1157
1177
|
need to be copied or linked to the work-dir. For example, if some SV assumes it
|
|
1158
1178
|
can $readmemh('file_that_is_here.txt') but we're running out of work-dir. Linking
|
|
1159
1179
|
is the easy work-around vs trying to run-in-place of all SV files.
|
|
1180
|
+
|
|
1181
|
+
Note that we also include .py and Makefile(s) in this.
|
|
1160
1182
|
'''
|
|
1161
1183
|
|
|
1162
|
-
for fname in self.files_non_source:
|
|
1184
|
+
for fname in self.files_non_source + self.files_py + self.files_makefile:
|
|
1163
1185
|
_, leaf_fname = os.path.split(fname)
|
|
1164
1186
|
destfile = os.path.join(self.args['work-dir'], leaf_fname)
|
|
1165
1187
|
relfname = os.path.relpath(fname)
|
|
@@ -1518,7 +1540,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1518
1540
|
# if we've found any target since being called, it means we found the one we were called for
|
|
1519
1541
|
return found_target
|
|
1520
1542
|
|
|
1521
|
-
def add_file( # pylint: disable=too-many-locals,too-many-branches
|
|
1543
|
+
def add_file( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
1522
1544
|
self, filename: str, use_abspath: bool = True, add_to_non_sources: bool = False,
|
|
1523
1545
|
caller_info: str = '', forced_extension: str = ''
|
|
1524
1546
|
) -> str:
|
|
@@ -1543,6 +1565,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1543
1565
|
cpp_file_ext_list = known_file_ext_dict.get('cpp', [])
|
|
1544
1566
|
sdc_file_ext_list = known_file_ext_dict.get('synth_constraints', [])
|
|
1545
1567
|
dotf_file_ext_list = known_file_ext_dict.get('dotf', [])
|
|
1568
|
+
py_file_ext_list = known_file_ext_dict.get('python', [])
|
|
1569
|
+
makefile_ext_list = known_file_ext_dict.get('makefile', [])
|
|
1546
1570
|
|
|
1547
1571
|
if forced_extension:
|
|
1548
1572
|
# If forced_extension='systemverilog', then use the first known extension for
|
|
@@ -1582,6 +1606,12 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1582
1606
|
caller_info=caller_info)
|
|
1583
1607
|
dp.apply_args(args_list=[f'-f={file_abspath}'])
|
|
1584
1608
|
del dp
|
|
1609
|
+
elif file_ext in py_file_ext_list:
|
|
1610
|
+
self.files_py.append(file_abspath)
|
|
1611
|
+
util.debug(f"Added Python file {filename} as {file_abspath}")
|
|
1612
|
+
elif file_ext in makefile_ext_list or os.path.split(filename)[1] == 'Makefile':
|
|
1613
|
+
self.files_makefile.append(file_abspath)
|
|
1614
|
+
util.debug(f"Added Makefile {filename} as {file_abspath}")
|
|
1585
1615
|
else:
|
|
1586
1616
|
# unknown file extension. In these cases we link the file to the working directory
|
|
1587
1617
|
# so it is available (for example, a .mem file that is expected to exist with relative
|
|
@@ -2114,21 +2144,22 @@ class CommandParallel(Command):
|
|
|
2114
2144
|
work_queue.put((jobs_launched, command_list, job['name'], cwd))
|
|
2115
2145
|
suffix = "<START>"
|
|
2116
2146
|
if fancy_mode:
|
|
2117
|
-
util.fancy_print(job_text+suffix, worker)
|
|
2147
|
+
util.fancy_print(job_text + suffix, worker)
|
|
2118
2148
|
elif failed_jobs:
|
|
2119
2149
|
# if we aren't in fancy mode, we will print a START line, periodic RUNNING
|
|
2120
2150
|
# lines, and PASS/FAIL line per-job
|
|
2121
|
-
util.
|
|
2151
|
+
util.print_yellow(job_text, end='')
|
|
2152
|
+
util.print_foreground_color(suffix)
|
|
2122
2153
|
else:
|
|
2123
|
-
util.
|
|
2154
|
+
util.print_foreground_color(job_text + suffix)
|
|
2124
2155
|
else:
|
|
2125
2156
|
# single-threaded job launch, we are going to print out job info as we start
|
|
2126
2157
|
# each job... no newline. since non-verbose silences the job and prints only
|
|
2127
2158
|
# <PASS>/<FAIL> after the trailing "..." we leave here
|
|
2128
2159
|
if failed_jobs:
|
|
2129
|
-
util.print_orange(job_text, end="")
|
|
2130
|
-
else:
|
|
2131
2160
|
util.print_yellow(job_text, end="")
|
|
2161
|
+
else:
|
|
2162
|
+
util.print_foreground_color(job_text, end="")
|
|
2132
2163
|
job_done_number = jobs_launched
|
|
2133
2164
|
job_done_name = job['name']
|
|
2134
2165
|
job_start_time = time.time()
|
|
@@ -2182,9 +2213,10 @@ class CommandParallel(Command):
|
|
|
2182
2213
|
if fancy_mode:
|
|
2183
2214
|
util.fancy_print(f"{job_text}{suffix}", t['worker'])
|
|
2184
2215
|
elif failed_jobs:
|
|
2185
|
-
util.
|
|
2216
|
+
util.print_yellow(job_text, end='')
|
|
2217
|
+
util.print_foreground_color(suffix)
|
|
2186
2218
|
else:
|
|
2187
|
-
util.
|
|
2219
|
+
util.print_foreground_color(job_text + suffix)
|
|
2188
2220
|
|
|
2189
2221
|
# shared job completion code
|
|
2190
2222
|
# single or multi-threaded, we can arrive here to harvest <= 1 jobs, and need
|
|
@@ -2192,36 +2224,52 @@ class CommandParallel(Command):
|
|
|
2192
2224
|
# printed, ready for pass/fail
|
|
2193
2225
|
if job_done:
|
|
2194
2226
|
jobs_complete += 1
|
|
2227
|
+
this_job_failed = False
|
|
2195
2228
|
if job_done_return_code is None or job_done_return_code:
|
|
2196
|
-
# embed the color code, to change color of pass/fail during the
|
|
2197
|
-
# util.print_orange/yellow below
|
|
2198
2229
|
if job_done_return_code == 124:
|
|
2199
2230
|
# bash uses 124 for bash timeout errors, if that was preprended to the
|
|
2200
2231
|
# command list.
|
|
2201
|
-
suffix =
|
|
2232
|
+
suffix = (
|
|
2233
|
+
f"<TOUT{safe_emoji(' ❌')}:"
|
|
2234
|
+
f" {sprint_time(job_done_run_time)}>"
|
|
2235
|
+
)
|
|
2236
|
+
this_job_failed = True
|
|
2202
2237
|
else:
|
|
2203
|
-
suffix =
|
|
2238
|
+
suffix = (
|
|
2239
|
+
f"<FAIL{safe_emoji(' ❌')}:"
|
|
2240
|
+
f" {sprint_time(job_done_run_time)}>"
|
|
2241
|
+
)
|
|
2242
|
+
this_job_failed = True
|
|
2204
2243
|
failed_jobs.append(job_done_name)
|
|
2205
2244
|
else:
|
|
2206
|
-
suffix =
|
|
2245
|
+
suffix = (
|
|
2246
|
+
f"<PASS{safe_emoji(' ✅')}:"
|
|
2247
|
+
f" {sprint_time(job_done_run_time)}>"
|
|
2248
|
+
)
|
|
2207
2249
|
passed_jobs.append(job_done_name)
|
|
2208
2250
|
# we want to print in one shot, because in fancy modes that's all that we're allowed
|
|
2209
2251
|
job_done_text = "" if job_done_quiet else sprint_job_line(job_done_number,
|
|
2210
2252
|
job_done_name)
|
|
2211
|
-
if
|
|
2212
|
-
util.
|
|
2213
|
-
|
|
2253
|
+
if this_job_failed:
|
|
2254
|
+
util.print_red(f"{job_done_text}{suffix}")
|
|
2255
|
+
elif failed_jobs:
|
|
2214
2256
|
util.print_yellow(f"{job_done_text}{suffix}")
|
|
2257
|
+
else:
|
|
2258
|
+
util.print_green(f"{job_done_text}{suffix}")
|
|
2215
2259
|
self.jobs_status[job_done_number-1] = job_done_return_code
|
|
2216
2260
|
|
|
2217
2261
|
if not anything_done:
|
|
2218
2262
|
time.sleep(0.25) # if nothing happens for an iteration, chill out a bit
|
|
2219
2263
|
|
|
2220
2264
|
if total_jobs:
|
|
2221
|
-
|
|
2222
|
-
|
|
2265
|
+
if len(passed_jobs) == total_jobs:
|
|
2266
|
+
emojitxt = safe_emoji('😀', ':)')
|
|
2267
|
+
else:
|
|
2268
|
+
emojitxt = safe_emoji('😦', ':(')
|
|
2269
|
+
util.info(sprint_job_line(final=True, job_name="jobs passed") + f"< {emojitxt} >",
|
|
2270
|
+
start="")
|
|
2223
2271
|
else:
|
|
2224
|
-
util.info("Parallel: <No jobs found>")
|
|
2272
|
+
util.info(f"Parallel: <{safe_emoji('❓ ')}No jobs found>")
|
|
2225
2273
|
# Make sure all jobs have a set status:
|
|
2226
2274
|
for i, rc in enumerate(self.jobs_status):
|
|
2227
2275
|
if rc is None or not isinstance(rc, int):
|
|
@@ -15,6 +15,7 @@ import shutil
|
|
|
15
15
|
import mergedeep
|
|
16
16
|
|
|
17
17
|
from opencos import util
|
|
18
|
+
from opencos.util import safe_emoji
|
|
18
19
|
from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
|
|
19
20
|
|
|
20
21
|
class Defaults:
|
|
@@ -231,7 +232,7 @@ def get_config_merged_with_defaults(config:dict) -> dict:
|
|
|
231
232
|
def get_argparser() -> argparse.ArgumentParser:
|
|
232
233
|
'''Returns an ArgumentParser, handles --config-yml=<filename> arg'''
|
|
233
234
|
parser = argparse.ArgumentParser(
|
|
234
|
-
prog='opencos eda config options', add_help=False, allow_abbrev=False
|
|
235
|
+
prog=f'{safe_emoji("🔎 ")}opencos eda config options', add_help=False, allow_abbrev=False
|
|
235
236
|
)
|
|
236
237
|
parser.add_argument('--config-yml', type=str, default=Defaults.config_yml,
|
|
237
238
|
help=('YAML filename to use for configuration (default'
|
|
@@ -94,6 +94,11 @@ file_extensions:
|
|
|
94
94
|
dotf:
|
|
95
95
|
- .f
|
|
96
96
|
- .vc
|
|
97
|
+
python:
|
|
98
|
+
- .py
|
|
99
|
+
makefile:
|
|
100
|
+
- .mk
|
|
101
|
+
|
|
97
102
|
|
|
98
103
|
inferred_top:
|
|
99
104
|
# file extensions that we can infer "top" module from, if --top omitted.
|
|
@@ -332,7 +337,6 @@ tools:
|
|
|
332
337
|
- "COCOTB_TEST_FAILED"
|
|
333
338
|
log-must-strings:
|
|
334
339
|
- "passed"
|
|
335
|
-
- "Cocotb test completed successfully!"
|
|
336
340
|
|
|
337
341
|
|
|
338
342
|
quartus:
|
|
@@ -42,12 +42,18 @@ def json_paths_to_jsonl(
|
|
|
42
42
|
with open(output_json_path, 'w', encoding='utf-8') as outf:
|
|
43
43
|
|
|
44
44
|
# jsonl is every line of the file is a json.
|
|
45
|
+
# We would expect the JSON to have "tests": [ ... ] with >= 1 test.
|
|
45
46
|
for json_file_path in json_file_paths:
|
|
46
47
|
with open(json_file_path, encoding='utf-8') as f:
|
|
47
48
|
data = json.load(f)
|
|
48
49
|
if len(assert_json_types) > 0 and type(data) not in assert_json_types:
|
|
49
50
|
error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
|
|
50
|
-
|
|
51
|
+
if 'tests' in data and isinstance(data['tests'], list):
|
|
52
|
+
for test in data['tests']:
|
|
53
|
+
json.dump(test, outf)
|
|
54
|
+
else:
|
|
55
|
+
json.dump(data, outf)
|
|
56
|
+
|
|
51
57
|
outf.write('\n')
|
|
52
58
|
info(f'Wrote {len(json_file_paths)} tests to {output_json_path=}')
|
|
53
59
|
|
|
@@ -78,7 +84,12 @@ def json_paths_to_single_json(
|
|
|
78
84
|
data = json.load(f)
|
|
79
85
|
if len(assert_json_types) > 0 and type(data) not in assert_json_types:
|
|
80
86
|
error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
|
|
81
|
-
|
|
87
|
+
if 'tests' in data and isinstance(data['tests'], list):
|
|
88
|
+
for test in data['tests']:
|
|
89
|
+
out_json_data['tests'].append(test)
|
|
90
|
+
else:
|
|
91
|
+
out_json_data['tests'].append(data)
|
|
92
|
+
|
|
82
93
|
json.dump(out_json_data, outf)
|
|
83
94
|
outf.write('\n')
|
|
84
95
|
info(f'Wrote {len(json_file_paths)} tests {output_json_path=}')
|
|
@@ -471,7 +482,52 @@ class ExportHelper:
|
|
|
471
482
|
yaml_safe_writer(data=data, filepath=dst)
|
|
472
483
|
|
|
473
484
|
|
|
474
|
-
def
|
|
485
|
+
def create_combined_env_file_in_out_dir(self) -> list:
|
|
486
|
+
''' Returns list of all .env lines to be put in output directory
|
|
487
|
+
|
|
488
|
+
Creates single exported .env file if any --env-file(s)
|
|
489
|
+
were loaded by CLI or DEPS targets. --input-file/-f was handled as args
|
|
490
|
+
already, but --env-files need to be special cased. We do not add this
|
|
491
|
+
combined ".env" file to the all_files list, just build it on the fly if needed.
|
|
492
|
+
'''
|
|
493
|
+
env_lines = []
|
|
494
|
+
for filepath in util.env_files_loaded:
|
|
495
|
+
with open(filepath, encoding='utf-8') as f:
|
|
496
|
+
env_lines.extend(f.readlines() + ['\n'])
|
|
497
|
+
|
|
498
|
+
if env_lines:
|
|
499
|
+
dst = os.path.join(self.out_dir, '.env')
|
|
500
|
+
|
|
501
|
+
# Write this to the export directory too:
|
|
502
|
+
with open(dst, 'w', encoding='utf-8') as f:
|
|
503
|
+
for lineno, line in enumerate(env_lines):
|
|
504
|
+
|
|
505
|
+
# modify VERILOG_SOURCES line if present, although it is known via DEPS.yml,
|
|
506
|
+
# we can set it according to files_v and files_sv:
|
|
507
|
+
if line.strip().startswith('VERILOG_SOURCES'):
|
|
508
|
+
base_verilog_filenames = [
|
|
509
|
+
os.path.split(x)[1] for x in \
|
|
510
|
+
self.cmd_design_obj.files_v + self.cmd_design_obj.files_sv
|
|
511
|
+
]
|
|
512
|
+
env_lines[lineno] = (
|
|
513
|
+
f'VERILOG_SOURCES = {" ".join(base_verilog_filenames)}'
|
|
514
|
+
)
|
|
515
|
+
continue
|
|
516
|
+
|
|
517
|
+
# remove PYTHONPATH from .env
|
|
518
|
+
if line.strip().startswith('PYTHONPATH'):
|
|
519
|
+
env_lines[lineno] = ''
|
|
520
|
+
continue
|
|
521
|
+
|
|
522
|
+
f.write(line)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
info(f'export_helper: Wrote {dst}')
|
|
526
|
+
|
|
527
|
+
return env_lines
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def create_export_json_in_out_dir( # pylint: disable=unused-argument,too-many-locals,too-many-branches
|
|
475
531
|
self, eda_config:dict={}, **kwargs
|
|
476
532
|
) -> None:
|
|
477
533
|
'''Optionally creates an exported JSON file in the output directory'''
|
|
@@ -482,40 +538,43 @@ class ExportHelper:
|
|
|
482
538
|
# assumes we've run self.create_deps_yml_in_out_dir():
|
|
483
539
|
assert self.target
|
|
484
540
|
assert self.out_deps_file
|
|
541
|
+
full_eda_cmd_list = ['eda', self.eda_command]
|
|
542
|
+
if self.args.get('waves', False):
|
|
543
|
+
full_eda_cmd_list.append('--waves')
|
|
544
|
+
if self.args.get('tool', ''):
|
|
545
|
+
full_eda_cmd_list.append(f'--tool={self.args["tool"]}')
|
|
546
|
+
full_eda_cmd_list.append(self.target)
|
|
485
547
|
|
|
486
548
|
data = {
|
|
487
|
-
'
|
|
488
|
-
'
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
'targets': [self.target],
|
|
493
|
-
'args': [],
|
|
494
|
-
'waves': self.args.get('waves', False),
|
|
495
|
-
# tool - eda.CommandSimVerilator has this set in self.args:
|
|
496
|
-
'tool': self.args.get('tool', None),
|
|
497
|
-
},
|
|
498
|
-
'files': [],
|
|
549
|
+
'correlationId': self.target,
|
|
550
|
+
'jobType': 'edaCmd',
|
|
551
|
+
'cmd': ' '.join(full_eda_cmd_list),
|
|
552
|
+
'timeout': 600,
|
|
553
|
+
'filesList': [], # filename (str), content (str)
|
|
499
554
|
}
|
|
500
555
|
|
|
501
|
-
# allow caller to override eda - tool, or eda - args, etc.
|
|
502
|
-
for k,v in eda_config.items():
|
|
503
|
-
if k in data['eda'] and v is not None:
|
|
504
|
-
data['eda'][k] = v
|
|
505
|
-
|
|
506
556
|
# Note that args may already be set via:
|
|
507
557
|
# create_deps_yml_in_out_dir(deps_file_args=some_list)
|
|
508
558
|
# For example, eda.CommandSim.do_export() will set certain allow-listed
|
|
509
559
|
# args if present with non-default values.
|
|
510
560
|
|
|
511
|
-
|
|
512
561
|
all_files = [self.out_deps_file] + self.included_files \
|
|
513
562
|
+ self.cmd_design_obj.files_sv + self.cmd_design_obj.files_v \
|
|
514
563
|
+ self.cmd_design_obj.files_vhd + self.cmd_design_obj.files_cpp \
|
|
564
|
+
+ self.cmd_design_obj.files_sdc + self.cmd_design_obj.files_py \
|
|
565
|
+
+ self.cmd_design_obj.files_makefile + self.cmd_design_obj.files_non_source
|
|
566
|
+
|
|
567
|
+
all_files = list(dict.fromkeys(all_files)) # uniqify list.
|
|
568
|
+
|
|
569
|
+
# The last file we handle is a single exported .env file if any --env-file(s)
|
|
570
|
+
env_lines = self.create_combined_env_file_in_out_dir()
|
|
571
|
+
if env_lines:
|
|
572
|
+
# write the updated env_lines to the export.json file.
|
|
573
|
+
data['filesList'].append({
|
|
574
|
+
'filename': '.env',
|
|
575
|
+
'content': ''.join(env_lines), # already has \n per line
|
|
576
|
+
})
|
|
515
577
|
|
|
516
|
-
for x in self.cmd_design_obj.files_non_source:
|
|
517
|
-
if x not in all_files:
|
|
518
|
-
all_files.append(x)
|
|
519
578
|
|
|
520
579
|
for somefile in all_files:
|
|
521
580
|
|
|
@@ -532,18 +591,17 @@ class ExportHelper:
|
|
|
532
591
|
|
|
533
592
|
assert os.path.exists(somefile)
|
|
534
593
|
with open(somefile, encoding='utf-8') as f:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
'
|
|
538
|
-
'content': filestr,
|
|
594
|
+
data['filesList'].append({
|
|
595
|
+
'filename': os.path.split(somefile)[1],
|
|
596
|
+
'content': ''.join(f.readlines()),
|
|
539
597
|
})
|
|
540
598
|
|
|
541
|
-
|
|
599
|
+
test_runner_data = {'tests': [data]} # single test for test runner.
|
|
542
600
|
dst = os.path.join(self.out_dir, 'export.json')
|
|
543
601
|
with open(dst, 'w', encoding='utf-8') as f:
|
|
544
|
-
json.dump(
|
|
602
|
+
json.dump(test_runner_data, f)
|
|
545
603
|
f.write('\n')
|
|
546
|
-
info(f'export_helper: Wrote {dst
|
|
604
|
+
info(f'export_helper: Wrote {dst}')
|
|
547
605
|
|
|
548
606
|
# If this was from an `export` command, and the self.out_dir != self.args['work-dir'], then
|
|
549
607
|
# copy the export.json to the work-dir:
|