opencos-eda 0.2.48__py3-none-any.whl → 0.2.49__py3-none-any.whl
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/__init__.py +4 -2
- opencos/_version.py +10 -7
- opencos/commands/flist.py +8 -7
- opencos/commands/multi.py +13 -14
- opencos/commands/sweep.py +3 -2
- opencos/deps/__init__.py +0 -0
- opencos/deps/defaults.py +69 -0
- opencos/deps/deps_commands.py +419 -0
- opencos/deps/deps_file.py +326 -0
- opencos/deps/deps_processor.py +670 -0
- opencos/deps_schema.py +7 -8
- opencos/eda.py +84 -64
- opencos/eda_base.py +572 -316
- opencos/eda_config.py +80 -14
- opencos/eda_extract_targets.py +22 -14
- opencos/eda_tool_helper.py +33 -7
- opencos/export_helper.py +166 -86
- opencos/export_json_convert.py +31 -23
- opencos/files.py +2 -1
- opencos/hw/__init__.py +0 -0
- opencos/{oc_cli.py → hw/oc_cli.py} +9 -4
- opencos/names.py +0 -4
- opencos/peakrdl_cleanup.py +13 -7
- opencos/seed.py +19 -11
- opencos/tests/helpers.py +3 -2
- opencos/tests/test_deps_helpers.py +35 -32
- opencos/tests/test_eda.py +36 -29
- opencos/tests/test_eda_elab.py +5 -3
- opencos/tests/test_eda_synth.py +1 -1
- opencos/tests/test_oc_cli.py +1 -1
- opencos/tests/test_tools.py +3 -2
- opencos/tools/iverilog.py +2 -2
- opencos/tools/modelsim_ase.py +2 -2
- opencos/tools/riviera.py +1 -1
- opencos/tools/slang.py +1 -1
- opencos/tools/surelog.py +1 -1
- opencos/tools/verilator.py +1 -1
- opencos/tools/vivado.py +1 -1
- opencos/tools/yosys.py +4 -3
- opencos/util.py +374 -468
- opencos/utils/__init__.py +0 -0
- opencos/utils/markup_helpers.py +98 -0
- opencos/utils/str_helpers.py +111 -0
- opencos/utils/subprocess_helpers.py +108 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/METADATA +1 -1
- opencos_eda-0.2.49.dist-info/RECORD +88 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/entry_points.txt +1 -1
- opencos/deps_helpers.py +0 -1346
- opencos_eda-0.2.48.dist-info/RECORD +0 -79
- /opencos/{pcie.py → hw/pcie.py} +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/top_level.txt +0 -0
opencos/deps_schema.py
CHANGED
|
@@ -133,7 +133,8 @@ import sys
|
|
|
133
133
|
|
|
134
134
|
from schema import Schema, Or, Optional, SchemaError
|
|
135
135
|
|
|
136
|
-
from opencos import util
|
|
136
|
+
from opencos import util
|
|
137
|
+
from opencos.deps import deps_file
|
|
137
138
|
|
|
138
139
|
# Because we deal with YAML, where a Table Key with dangling/empty value is allowed
|
|
139
140
|
# and we have things like SystemVerilog defines where there's a Table key with no Value,
|
|
@@ -312,21 +313,19 @@ FILE_SIMPLIFIED = Schema(
|
|
|
312
313
|
|
|
313
314
|
def check(data: dict, schema_obj=FILE) -> (bool, str):
|
|
314
315
|
'''Returns (bool, str) for checking dict against FILE schema'''
|
|
315
|
-
|
|
316
|
-
|
|
317
316
|
try:
|
|
318
317
|
schema_obj.validate(data)
|
|
319
318
|
return True, None
|
|
320
319
|
except SchemaError as e:
|
|
321
|
-
return False,
|
|
322
|
-
except Exception as e:
|
|
320
|
+
return False, f'SchemaError: {e}'
|
|
321
|
+
except Exception as e:
|
|
323
322
|
return False, str(e)
|
|
324
323
|
|
|
325
324
|
|
|
326
325
|
def deps_markup_safe_load(deps_filepath: str) -> (bool, dict):
|
|
327
326
|
'''Returns tuple (bool False if took errors, dict of markp data)'''
|
|
328
327
|
current_errors = util.args['errors']
|
|
329
|
-
data =
|
|
328
|
+
data = deps_file.deps_markup_safe_load(deps_filepath)
|
|
330
329
|
if util.args['errors'] > current_errors:
|
|
331
330
|
return False, data
|
|
332
331
|
return True, data
|
|
@@ -337,10 +336,10 @@ def check_file(filepath: str, schema_obj=FILE) -> (bool, str, str):
|
|
|
337
336
|
|
|
338
337
|
deps_filepath = filepath
|
|
339
338
|
if os.path.isdir(filepath):
|
|
340
|
-
deps_filepath =
|
|
339
|
+
deps_filepath = deps_file.get_deps_markup_file(base_path=filepath)
|
|
341
340
|
|
|
342
341
|
# get deps file
|
|
343
|
-
if not os.path.
|
|
342
|
+
if not os.path.isfile(deps_filepath):
|
|
344
343
|
print(f'ERROR: internal error(s) no DEPS.[yml|..] found in {filepath=}')
|
|
345
344
|
return False, '', deps_filepath
|
|
346
345
|
|
opencos/eda.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
'''opencos.eda is an executable script (as `eda <command> ...`)
|
|
4
|
+
|
|
5
|
+
This is the entrypoint for tool discovery, and running targets from command line or DEPS
|
|
6
|
+
markup files
|
|
7
|
+
'''
|
|
4
8
|
|
|
5
9
|
import subprocess
|
|
6
10
|
import os
|
|
@@ -13,9 +17,7 @@ import shlex
|
|
|
13
17
|
import importlib.util
|
|
14
18
|
|
|
15
19
|
import opencos
|
|
16
|
-
from opencos import util,
|
|
17
|
-
from opencos import eda_config
|
|
18
|
-
from opencos import eda_base
|
|
20
|
+
from opencos import util, eda_config, eda_base
|
|
19
21
|
from opencos.eda_base import Tool, which_tool
|
|
20
22
|
|
|
21
23
|
# Configure util:
|
|
@@ -46,7 +48,7 @@ def init_config(
|
|
|
46
48
|
else:
|
|
47
49
|
config['command_handler'][command] = cls
|
|
48
50
|
|
|
49
|
-
config['auto_tools_found'] =
|
|
51
|
+
config['auto_tools_found'] = {}
|
|
50
52
|
config['tools_loaded'] = set()
|
|
51
53
|
if run_auto_tool_setup:
|
|
52
54
|
config = auto_tool_setup(config=config, quiet=quiet, tool=tool)
|
|
@@ -55,7 +57,7 @@ def init_config(
|
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def usage(tokens: list, config: dict, command=""):
|
|
60
|
+
def usage(tokens: list, config: dict, command="") -> int:
|
|
59
61
|
'''Returns an int shell return code, given remaining args (tokens list) and eda command.
|
|
60
62
|
|
|
61
63
|
config is the config dict. Used to check valid commands in config['command_handler']
|
|
@@ -101,41 +103,44 @@ And <files|targets, ...> is one or more source file or DEPS markup file target,
|
|
|
101
103
|
)
|
|
102
104
|
eda_base.print_base_help()
|
|
103
105
|
return 0
|
|
104
|
-
|
|
106
|
+
|
|
107
|
+
if command in config['command_handler'].keys():
|
|
105
108
|
sco = config['command_handler'][command](config=config) # sub command object
|
|
106
109
|
sco.help(tokens=tokens)
|
|
107
110
|
return util.exit(0)
|
|
108
|
-
else:
|
|
109
|
-
util.info(f"Valid commands are: ")
|
|
110
|
-
for k in sorted(config['command_handler'].keys()):
|
|
111
|
-
util.info(f" {k:20}")
|
|
112
|
-
return util.error(f"Cannot provide help, don't understand command: '{command}'")
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
util.info("Valid commands are:")
|
|
113
|
+
for k in sorted(config['command_handler'].keys()):
|
|
114
|
+
util.info(f" {k:20}")
|
|
115
|
+
return util.error(f"Cannot provide help, don't understand command: '{command}'")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def interactive(config: dict) -> int:
|
|
119
|
+
'''Returns bash/sh return code, entry point for running interactively in CLI if
|
|
120
|
+
|
|
121
|
+
args are not present to directly call a command handler, --help, etc
|
|
122
|
+
'''
|
|
123
|
+
rc = 0
|
|
116
124
|
while True:
|
|
117
|
-
|
|
118
|
-
line = f.readline()
|
|
119
|
-
if line:
|
|
120
|
-
print("%s->%s" % (fname, line), end="")
|
|
121
|
-
else:
|
|
122
|
-
read_file = False
|
|
123
|
-
f.close()
|
|
124
|
-
continue
|
|
125
|
-
else:
|
|
126
|
-
line = input('EDA->')
|
|
125
|
+
line = input('EDA->')
|
|
127
126
|
m = re.match(r'^([^\#]*)\#.*$', line)
|
|
128
|
-
if m:
|
|
127
|
+
if m:
|
|
128
|
+
line = m.group(1)
|
|
129
129
|
tokens = line.split()
|
|
130
130
|
original_args = tokens.copy()
|
|
131
131
|
# NOTE: interactive will not correctly handle --config-yml arg (from eda_config.py),
|
|
132
132
|
# but we should do a best effor to re-parse args from util.py, such as
|
|
133
133
|
# --quiet, --color, --fancy, --logfile, --debug or --debug-level, etc
|
|
134
134
|
_, tokens = util.process_tokens(tokens)
|
|
135
|
-
|
|
135
|
+
rc = process_tokens(
|
|
136
|
+
tokens=tokens, original_args=original_args, config=config, is_interactive=True
|
|
137
|
+
)
|
|
138
|
+
return rc
|
|
136
139
|
|
|
137
140
|
|
|
138
|
-
def auto_tool_setup(
|
|
141
|
+
def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
142
|
+
warnings: bool = True, config = None, quiet: bool = False, tool: str = ''
|
|
143
|
+
) -> dict:
|
|
139
144
|
'''Returns an updated config, uses config['auto_tools_order'][0] dict, calls tool_setup(..)
|
|
140
145
|
|
|
141
146
|
-- adds items to config['tools_loaded'] set
|
|
@@ -151,8 +156,8 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
151
156
|
)
|
|
152
157
|
|
|
153
158
|
assert 'auto_tools_order' in config
|
|
154
|
-
assert
|
|
155
|
-
assert
|
|
159
|
+
assert isinstance(config['auto_tools_order'], list)
|
|
160
|
+
assert isinstance(config['auto_tools_order'][0], dict)
|
|
156
161
|
|
|
157
162
|
for name, value in config['auto_tools_order'][0].items():
|
|
158
163
|
if tool and tool != name:
|
|
@@ -160,12 +165,13 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
160
165
|
|
|
161
166
|
util.debug(f"Checking for ability to run tool: {name}")
|
|
162
167
|
exe = value.get('exe', str())
|
|
163
|
-
if
|
|
168
|
+
if isinstance(exe, list):
|
|
164
169
|
exe_list = exe
|
|
165
|
-
elif
|
|
170
|
+
elif isinstance(exe, str):
|
|
166
171
|
exe_list = [exe] # make it a list
|
|
167
172
|
else:
|
|
168
|
-
util.error(f'eda.py: config["auto_tools_order"][0] for {name=} {value=} has bad type
|
|
173
|
+
util.error(f'eda.py: config["auto_tools_order"][0] for {name=} {value=} has bad type'
|
|
174
|
+
f'for {exe=}')
|
|
169
175
|
continue
|
|
170
176
|
|
|
171
177
|
has_all_py = True
|
|
@@ -201,14 +207,16 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
201
207
|
for cmd in requires_cmd_list:
|
|
202
208
|
cmd_list = shlex.split(cmd)
|
|
203
209
|
try:
|
|
204
|
-
proc = subprocess.run(cmd_list, capture_output=True,
|
|
210
|
+
proc = subprocess.run(cmd_list, capture_output=True, check=False,
|
|
211
|
+
input=b'exit\n\n')
|
|
205
212
|
if proc.returncode != 0:
|
|
206
213
|
if not quiet:
|
|
207
|
-
util.debug(f"For tool {name} missing required command
|
|
214
|
+
util.debug(f"For tool {name} missing required command",
|
|
215
|
+
f"({proc.returncode=}): {cmd_list=}")
|
|
208
216
|
has_all_exe = False
|
|
209
|
-
except:
|
|
217
|
+
except Exception as e:
|
|
210
218
|
has_all_exe = False
|
|
211
|
-
util.debug(f"... No, exception running {cmd_list}")
|
|
219
|
+
util.debug(f"... No, exception {e} running {cmd_list}")
|
|
212
220
|
|
|
213
221
|
|
|
214
222
|
if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path]):
|
|
@@ -263,12 +271,11 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
|
|
|
263
271
|
if warnings:
|
|
264
272
|
util.warning(f"tool_setup: {auto_setup=} already setup for {tool}?")
|
|
265
273
|
|
|
266
|
-
entry = config['auto_tools_order'][0].get(tool,
|
|
267
|
-
tool_cmd_handler_dict = entry.get('handlers',
|
|
274
|
+
entry = config['auto_tools_order'][0].get(tool, {})
|
|
275
|
+
tool_cmd_handler_dict = entry.get('handlers', {})
|
|
268
276
|
|
|
269
277
|
for command, str_class_name in tool_cmd_handler_dict.items():
|
|
270
278
|
current_handler_cls = config['command_handler'].get(command, None)
|
|
271
|
-
ext_mod = None
|
|
272
279
|
|
|
273
280
|
if auto_setup and current_handler_cls is not None and issubclass(current_handler_cls, Tool):
|
|
274
281
|
# If we're not in auto_setup, then always override (aka arg --tool=<this tool>)
|
|
@@ -277,19 +284,28 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
|
|
|
277
284
|
|
|
278
285
|
cls = util.import_class_from_string(str_class_name)
|
|
279
286
|
|
|
280
|
-
assert issubclass(cls, Tool),
|
|
287
|
+
assert issubclass(cls, Tool), \
|
|
288
|
+
f'{str_class_name=} is does not have Tool class associated with it'
|
|
281
289
|
util.debug(f'Setting {cls=} for {command=} in config.command_handler')
|
|
282
290
|
config['command_handler'][command] = cls
|
|
283
291
|
|
|
284
292
|
config['tools_loaded'].add(tool)
|
|
285
293
|
|
|
286
294
|
|
|
287
|
-
def process_tokens(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
def process_tokens( # pylint: disable=too-many-branches,too-many-statements
|
|
296
|
+
tokens: list, original_args: list, config: dict, is_interactive=False
|
|
297
|
+
) -> int:
|
|
298
|
+
'''Returns bash/sh style return code int (0 pass, non-zero fail).
|
|
299
|
+
|
|
300
|
+
This is the top level token processing function, and entry point (after util and eda_config
|
|
301
|
+
have performed their argparsing). Tokens can come from command line, DEPS target markup file,
|
|
302
|
+
or interactively. We do one pass through all the tokens, triaging them into:
|
|
303
|
+
- those we can execute immediate (help, quit, and global opens like --debug, --color)
|
|
304
|
+
-- some of this has already been performed in main() by util for --color.
|
|
305
|
+
- a command (sim, synth, etc)
|
|
306
|
+
- command arguments (--seed, +define, +incdir, etc) which will be deferred and processed by
|
|
307
|
+
the command. Some are processed here (--tool)
|
|
308
|
+
'''
|
|
293
309
|
|
|
294
310
|
deferred_tokens = []
|
|
295
311
|
command = ""
|
|
@@ -329,7 +345,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
|
|
|
329
345
|
unparsed.remove(value) # remove command (flist, export, targets, etc)
|
|
330
346
|
break
|
|
331
347
|
|
|
332
|
-
if not
|
|
348
|
+
if not is_interactive:
|
|
333
349
|
# Run init_config() now, we deferred it in main(), but only run it
|
|
334
350
|
# for this tool (or tool=None to figure it out)
|
|
335
351
|
config = init_config(
|
|
@@ -337,7 +353,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
|
|
|
337
353
|
run_auto_tool_setup=run_auto_tool_setup
|
|
338
354
|
)
|
|
339
355
|
if not config:
|
|
340
|
-
util.error(f'eda.py main: problem loading config, {
|
|
356
|
+
util.error(f'eda.py main: problem loading config, {tokens=}')
|
|
341
357
|
return 3
|
|
342
358
|
|
|
343
359
|
# Deal with help, now that we have the command (if it was set).
|
|
@@ -355,9 +371,9 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
|
|
|
355
371
|
tool_setup(parsed.tool, config=config)
|
|
356
372
|
|
|
357
373
|
deferred_tokens = unparsed
|
|
358
|
-
if command
|
|
374
|
+
if not command:
|
|
359
375
|
util.error("Didn't get a command!")
|
|
360
|
-
return
|
|
376
|
+
return 2
|
|
361
377
|
|
|
362
378
|
sco = config['command_handler'][command](config=config) # sub command object
|
|
363
379
|
util.debug(f'{command=}')
|
|
@@ -378,16 +394,23 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
|
|
|
378
394
|
# Add the original, nothing-parsed args to the Command.config dict.
|
|
379
395
|
sco.config['eda_original_args'] = original_args
|
|
380
396
|
|
|
381
|
-
setattr(sco, 'command_name', command) # as a safeguard,
|
|
397
|
+
setattr(sco, 'command_name', command) # as a safeguard, 'command' is not always passed to 'sco'
|
|
382
398
|
unparsed = sco.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
|
|
383
399
|
|
|
384
|
-
# query the status from the Command object (0 is pass, > 0 is fail
|
|
385
|
-
rc
|
|
400
|
+
# query the status from the Command object (0 is pass, > 0 is fail, but we'd prefer to avoid
|
|
401
|
+
# rc=1 because that's the python exception rc)
|
|
402
|
+
rc = getattr(sco, 'status', 2)
|
|
386
403
|
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
|
|
387
404
|
return rc
|
|
388
405
|
|
|
389
406
|
|
|
390
407
|
def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> int:
|
|
408
|
+
'''Returns bash/sh return code, checks that a command handling class has all
|
|
409
|
+
|
|
410
|
+
internal CHECK_REQUIRES list items. For example, sim.py has CHECK_REQUIRES=[Tool],
|
|
411
|
+
so if a 'sim' command handler does not also inherit a Tool class, then reports this as an
|
|
412
|
+
error.
|
|
413
|
+
'''
|
|
391
414
|
sco = command_obj
|
|
392
415
|
for cls in getattr(sco, 'CHECK_REQUIRES', []):
|
|
393
416
|
if not isinstance(sco, cls):
|
|
@@ -405,7 +428,8 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
|
|
|
405
428
|
# **************************************************************
|
|
406
429
|
# **** Interrupt Handler
|
|
407
430
|
|
|
408
|
-
def signal_handler(sig, frame):
|
|
431
|
+
def signal_handler(sig, frame) -> None: # pylint: disable=unused-argument
|
|
432
|
+
'''Handles Ctrl-C, called by main_cli() if running from command line'''
|
|
409
433
|
util.fancy_stop()
|
|
410
434
|
util.info('Received Ctrl+C...', start='\nINFO: [EDA] ')
|
|
411
435
|
util.exit(-1)
|
|
@@ -437,6 +461,8 @@ def main(*args):
|
|
|
437
461
|
|
|
438
462
|
if not util.args['quiet']:
|
|
439
463
|
util.info(f'eda: version {opencos.__version__}')
|
|
464
|
+
# And show the command that was run (all args):
|
|
465
|
+
util.info(f"main: eda {' '.join(args)}; (run from {os.getcwd()})")
|
|
440
466
|
|
|
441
467
|
# Handle --config-yml= arg
|
|
442
468
|
config, unparsed = eda_config.get_eda_config(unparsed)
|
|
@@ -449,15 +475,16 @@ def main(*args):
|
|
|
449
475
|
if len(args) == 0 or (len(args) == 1 and '--debug' in args):
|
|
450
476
|
# special snowflake case if someone called with a singular arg --debug
|
|
451
477
|
# (without --help or exit)
|
|
452
|
-
util.debug(
|
|
478
|
+
util.debug("Starting automatic tool setup: init_config()")
|
|
453
479
|
config = init_config(config=config)
|
|
454
480
|
if not config:
|
|
455
481
|
util.error(f'eda.py main: problem loading config, {args=}')
|
|
456
482
|
return 3
|
|
457
483
|
main_ret = interactive(config=config)
|
|
458
484
|
else:
|
|
459
|
-
main_ret = process_tokens(
|
|
460
|
-
|
|
485
|
+
main_ret = process_tokens(
|
|
486
|
+
tokens=list(unparsed), original_args=original_args, config=config
|
|
487
|
+
)
|
|
461
488
|
# Stop the util log, needed for pytests that call eda.main directly that otherwise
|
|
462
489
|
# won't close the log file via util's atexist.register(stop_log)
|
|
463
490
|
util.global_log.stop()
|
|
@@ -475,10 +502,3 @@ def main_cli() -> None:
|
|
|
475
502
|
|
|
476
503
|
if __name__ == '__main__':
|
|
477
504
|
main_cli()
|
|
478
|
-
|
|
479
|
-
# IDEAS:
|
|
480
|
-
# * options with no default (i.e. if user doesn't override, THEN we set it, like "seed" or "work-dir") can be given a
|
|
481
|
-
# special type (DefaultVar) versus saying "None" so that help can say more about it (it's a string, it's default val
|
|
482
|
-
# is X, etc) and it can be queried as to whether it's really a default val. This avoids having to avoid default vals
|
|
483
|
-
# that user can never set (-1, None, etc) which make it hard to infer the type. this same object can be given help
|
|
484
|
-
# info and simply "render" to the expected type (str, integer, etc) when used.
|