opencos-eda 0.3.0__py3-none-any.whl → 0.3.2__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/deps/deps_file.py CHANGED
@@ -249,8 +249,11 @@ class DepsFile:
249
249
  self.rel_deps_file = os.path.join(os.path.relpath(deps_path), deps_leaf)
250
250
 
251
251
  self.error = getattr(command_design_ref, 'error', None)
252
+ self.error_ifarg = getattr(command_design_ref, 'error_ifarg', None)
252
253
  if not self.error:
253
254
  self.error = util.error
255
+ if not self.error_ifarg:
256
+ self.error_ifarg = util.error
254
257
 
255
258
 
256
259
  def found(self) -> bool:
@@ -287,7 +290,9 @@ class DepsFile:
287
290
  f'{targets_str}'))
288
291
 
289
292
 
290
- def lookup(self, target_node: str, caller_info: str) -> bool:
293
+ def lookup( # pylint: disable=too-many-branches
294
+ self, target_node: str, caller_info: str
295
+ ) -> bool:
291
296
  '''Returns True if the target_node is in the DEPS markup file. If not, error with
292
297
 
293
298
  some caller_info(str). This is more useful for YAML or TOML markup where we have
@@ -295,16 +300,20 @@ class DepsFile:
295
300
  '''
296
301
  if target_node not in self.data:
297
302
  found_target = False
298
- # For error printing, prefer relative paths:
299
- t_path = os.path.relpath(self.target_path) + os.path.sep
300
- t_node = target_node
301
- t_full = os.path.join(t_path, t_node)
302
303
 
303
304
  if target_node.startswith('-'):
304
305
  # likely an unparsed arg that made it this far.
305
306
  util.warning(f"Ignoring unparsed argument '{target_node}'")
306
307
  return False
307
308
 
309
+ # For error printing, prefer relative paths:
310
+ if self.target_path:
311
+ t_path = os.path.relpath(self.target_path) + os.path.sep
312
+ else:
313
+ t_path = ''
314
+ t_node = target_node
315
+ t_full = os.path.join(t_path, t_node)
316
+
308
317
  if not is_valid_target_name(target_node):
309
318
  util.warning(
310
319
  f"In file {self.rel_deps_file}, {target_node} {VALID_TARGET_INFO_STR}"
@@ -314,19 +323,28 @@ class DepsFile:
314
323
  # If we don't have caller_info, likely came from command line (or DEPS JSON data):
315
324
  if '.' in target_node:
316
325
  # Likely a filename (target_node does not include path)
317
- self.error(f'Trying to resolve command-line target={t_full} (file?):',
318
- f'File={t_node} not found in directory={t_path}',
319
- error_code=EDA_DEPS_FILE_NOT_FOUND)
326
+ self.error_ifarg(
327
+ f'Trying to resolve command-line target={t_full} (file?):',
328
+ f'File={t_node} not found in directory={t_path}',
329
+ arg='error-unknown-args',
330
+ error_code=EDA_DEPS_FILE_NOT_FOUND
331
+ )
320
332
  elif not self.rel_deps_file:
321
333
  # target, but there's no DEPS file
322
- self.error(f'Trying to resolve command-line target={t_full}:',
323
- f'but path {t_path} has no DEPS markup file (DEPS.yml)',
324
- error_code=EDA_DEPS_FILE_NOT_FOUND)
334
+ self.error_ifarg(
335
+ f'Trying to resolve command-line target={t_full}:',
336
+ f'but path {t_path} has no DEPS markup file (DEPS.yml)',
337
+ arg='error-unknown-args',
338
+ error_code=EDA_DEPS_FILE_NOT_FOUND
339
+ )
325
340
  else:
326
341
  self.warning_show_available_targets()
327
- self.error(f'Trying to resolve command-line target={t_full}:',
328
- f'was not found in deps_file={self.rel_deps_file}',
329
- error_code=EDA_DEPS_TARGET_NOT_FOUND)
342
+ self.error_ifarg(
343
+ f'Trying to resolve command-line target={t_full}:',
344
+ f'was not found in deps_file={self.rel_deps_file}',
345
+ arg='error-unknown-args',
346
+ error_code=EDA_DEPS_TARGET_NOT_FOUND
347
+ )
330
348
 
331
349
  else:
332
350
  # If we have caller_info, then this was a recursive call from another
@@ -334,22 +352,31 @@ class DepsFile:
334
352
 
335
353
  if '.' in target_node:
336
354
  # Likely a filename (target_node does not include path)
337
- self.error(f'Trying to resolve target={t_full} (file?):',
338
- f'called from {caller_info},',
339
- f'File={t_node} not found in directory={t_path}',
340
- error_code=EDA_DEPS_FILE_NOT_FOUND)
355
+ self.error_ifarg(
356
+ f'Trying to resolve target={t_full} (file?):',
357
+ f'called from {caller_info},',
358
+ f'File={t_node} not found in directory={t_path}',
359
+ arg='error-unknown-args',
360
+ error_code=EDA_DEPS_FILE_NOT_FOUND
361
+ )
341
362
  elif not self.rel_deps_file:
342
363
  # target, but there's no DEPS file
343
- self.error(f'Trying to resolve target={t_full}:',
344
- f'called from {caller_info},',
345
- f'but {t_path} has no DEPS markup file (DEPS.yml)',
346
- error_code=EDA_DEPS_FILE_NOT_FOUND)
364
+ self.error_ifarg(
365
+ f'Trying to resolve target={t_full}:',
366
+ f'called from {caller_info},',
367
+ f'but {t_path} has no DEPS markup file (DEPS.yml)',
368
+ arg='error-unknown-args',
369
+ error_code=EDA_DEPS_FILE_NOT_FOUND
370
+ )
347
371
  else:
348
372
  self.warning_show_available_targets()
349
- self.error(f'Trying to resolve target={t_full}:',
350
- f'called from {caller_info},',
351
- f'Target not found in deps_file={self.rel_deps_file}',
352
- error_code=EDA_DEPS_TARGET_NOT_FOUND)
373
+ self.error_ifarg(
374
+ f'Trying to resolve target={t_full}:',
375
+ f'called from {caller_info},',
376
+ f'Target not found in deps_file={self.rel_deps_file}',
377
+ arg='error-unknown-args',
378
+ error_code=EDA_DEPS_TARGET_NOT_FOUND
379
+ )
353
380
  else:
354
381
  debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
355
382
  found_target = True
@@ -4,11 +4,13 @@ a DEPS markup files targets (applying deps, reqs, commands, tags, incdirs, defin
4
4
  CommandDesign ref object
5
5
  '''
6
6
 
7
+ import argparse
7
8
  import os
8
9
 
9
10
  from opencos import files
10
11
  from opencos import eda_config
11
- from opencos.util import debug, info, warning, error
12
+ from opencos.util import debug, info, warning, error, read_tokens_from_dot_f, \
13
+ patch_args_for_dir
12
14
  from opencos.utils.str_helpers import dep_str2list
13
15
  from opencos.deps.deps_file import deps_target_get_deps_list
14
16
  from opencos.deps.deps_commands import deps_commands_handler
@@ -48,6 +50,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
48
50
  self.target_path = target_path
49
51
  self.target_node = target_node # for debug
50
52
  self.deps_file = deps_file # for debug
53
+ self.deps_dir, _ = os.path.split(deps_file)
51
54
  self.caller_info = caller_info
52
55
 
53
56
  assert isinstance(deps_entry, dict), \
@@ -150,7 +153,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
150
153
  return tokens
151
154
 
152
155
 
153
- def apply_args(self, args_list:list) -> list:
156
+ def apply_args( # pylint: disable=too-many-locals,too-many-branches
157
+ self, args_list:list
158
+ ) -> list:
154
159
  '''Given args_list, applies them to our self.command_design_ref obj
155
160
 
156
161
  This will return unparsed args that weren't in the self.command_design_ref.args keys
@@ -162,10 +167,54 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
162
167
  f"in {self.caller_info}")
163
168
  tokens = dep_str2list(args_list)
164
169
 
170
+ # patch args relative to the DEPS (if self.deps_dir exists) so things like
171
+ # --build-tcl=<file> for relative <file> works when calling targets from any directory.
172
+ tokens = patch_args_for_dir(
173
+ tokens=tokens, patch_dir=self.deps_dir, caller_info=self.caller_info
174
+ )
175
+
165
176
  # We're going to run an ArgumentParser here, which is not the most efficient
166
177
  # thing to do b/c it runs on all of self.command_design_ref.args (dict) even
167
178
  # if we're applying a single token.
168
179
 
180
+ # Since some args (util.py, eda_config.py, eda.py) can only be handled from command
181
+ # line, it would be nice if -f or --input-file is handled from DEPS, so we'll special
182
+ # case that now. Recursively resolve -f / --input-file.
183
+ parser = argparse.ArgumentParser(
184
+ prog='deps_processor -f/--input-file', add_help=False, allow_abbrev=False
185
+ )
186
+ parser.add_argument('-f', '--input-file', default=[], action='append',
187
+ help=(
188
+ 'Input .f file to be expanded as eda args, defines, incdirs,'
189
+ ' files, or targets.'
190
+ ))
191
+ try:
192
+ parsed, unparsed = parser.parse_known_args(tokens + [''])
193
+ tokens2 = list(filter(None, unparsed))
194
+ except argparse.ArgumentError:
195
+ error('deps_processor -f/--input-file, problem attempting to parse_known_args for:',
196
+ f'{tokens}')
197
+ tokens2 = tokens
198
+
199
+ if parsed.input_file:
200
+ dotf_tokens = []
201
+ for filepath in parsed.input_file:
202
+ # Since this isn't command line, we have to assume the path is relative
203
+ # to this DEPS file.
204
+ if not os.path.isabs(filepath):
205
+ filepath = os.path.join(self.deps_dir, filepath)
206
+ dotf_tokens.extend(read_tokens_from_dot_f(
207
+ filepath=filepath, caller_info=self.caller_info, verbose=True
208
+ ))
209
+
210
+ # put the .f files before the unparsed args.
211
+ tokens2 = dotf_tokens + tokens2
212
+
213
+ # recurse until we've resolved nested .f files.
214
+ return self.apply_args(args_list=tokens2)
215
+
216
+ tokens = tokens2 # if no --input-file values, keep parsing the remaining tokens2
217
+
169
218
  # We have to special-case anything with --tool[=value] in tokens, otherwise
170
219
  # the user may think they were allowed to set --tool, but in our flow the Command handler
171
220
  # (self.command_design_ref) has already been chosen, so setting the tool can have
@@ -180,11 +229,38 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
180
229
  _, unparsed = self.command_design_ref.run_argparser_on_list(
181
230
  tokens=tokens
182
231
  )
232
+
183
233
  # Annoying, but check for plusargs in unparsed, and have referenced CommandDesign
184
234
  # or CommandSim class handle it with process_plusarg.
185
- for arg in unparsed:
235
+ for arg in list(unparsed):
186
236
  if arg.startswith('+'):
187
237
  self.command_design_ref.process_plusarg(plusarg=arg, pwd=self.target_path)
238
+ unparsed.remove(arg)
239
+
240
+ # For any leftover files, or targets, attempt to process those too:
241
+ for arg in list(unparsed):
242
+ # Since this isn't command line, we have to assume for files, the path is relative
243
+ # to this DEPS file.
244
+ if os.path.isabs(arg):
245
+ target = arg
246
+ else:
247
+ target = os.path.join(self.deps_dir, arg)
248
+
249
+ file_exists, fpath, forced_extension = files.get_source_file(target)
250
+ if file_exists:
251
+ _, file_ext = os.path.splitext(fpath)
252
+ if forced_extension or file_ext:
253
+ self.command_design_ref.add_file(fpath, caller_info=self.caller_info,
254
+ forced_extension=forced_extension)
255
+ unparsed.remove(arg)
256
+
257
+ else:
258
+ if not os.path.isdir(target) and \
259
+ self.command_design_ref.resolve_target_core(
260
+ target=target, no_recursion=False, caller_info=self.caller_info,
261
+ error_on_not_found=False
262
+ ):
263
+ unparsed.remove(arg)
188
264
 
189
265
  if unparsed:
190
266
  # This is only a warning - because things like CommandFlist may not have every
opencos/eda_base.py CHANGED
@@ -230,6 +230,7 @@ class Command: # pylint: disable=too-many-public-methods
230
230
  'enable-tags': [],
231
231
  'disable-tags': [],
232
232
  'test-mode': False,
233
+ 'error-unknown-args': True,
233
234
  })
234
235
  self.args_help.update({
235
236
  'stop-before-compile': ('stop this run before any compile (if possible for tool) and'
@@ -249,6 +250,9 @@ class Command: # pylint: disable=too-many-public-methods
249
250
  ' --disable-tags has higher precedence than --enable-tags.'),
250
251
  'test-mode': ('command and tool dependent, usually stops the command early without'
251
252
  ' executing.'),
253
+ 'error-unknown-args': (
254
+ 'Enable errors on unknown/unparsable args, or unknown/nonexistent files, or targets'
255
+ ),
252
256
  })
253
257
  self.modified_args = {}
254
258
  self.config = copy.deepcopy(config) # avoid external modifications.
@@ -495,9 +499,12 @@ class Command: # pylint: disable=too-many-public-methods
495
499
 
496
500
  # Do some minimal type handling, preserving the type(self.args[key])
497
501
  if key not in self.args:
498
- self.error(f'set_arg, {key=} not in self.args {value=}',
499
- f'({self.command_name=}, {self.__class__.__name__=})',
500
- error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
502
+ self.error_ifarg(
503
+ f'set_arg, {key=} not in self.args {value=}',
504
+ f'({self.command_name=}, {self.__class__.__name__=})',
505
+ arg='error-unknown-args',
506
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR
507
+ )
501
508
 
502
509
  cur_value = self.args[key]
503
510
 
@@ -692,9 +699,12 @@ class Command: # pylint: disable=too-many-public-methods
692
699
  _, unparsed = self.run_argparser_on_list(tokens)
693
700
  if process_all and unparsed:
694
701
  self.warning_show_known_args()
695
- self.error(f"Didn't understand argument: '{unparsed=}' in",
696
- f" {self.command_name=} context, {pwd=}",
697
- error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
702
+ self.error_ifarg(
703
+ f"Didn't understand argument: '{unparsed=}' in",
704
+ f"{self.command_name=} context, {pwd=}",
705
+ arg='error-unknown-args',
706
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR
707
+ )
698
708
 
699
709
  return unparsed
700
710
 
@@ -910,6 +920,20 @@ class Command: # pylint: disable=too-many-public-methods
910
920
  lines.append(self.pretty_str_known_args(command=commands[-1])) # use last command if > 1
911
921
  util.warning("\n".join(lines))
912
922
 
923
+ def error_ifarg(
924
+ self, *msg, arg: str, error_code: int = status_constants.EDA_COMMAND_OR_ARGS_ERROR
925
+ ) -> None:
926
+ '''For errors involving an unknown --arg, they can be optionally disabled
927
+
928
+ using CLI: --no-error-unknown-args, and this method arg='error-uknown-arg'
929
+
930
+ Note if arg is not present in self.args, the error is enabled.
931
+ '''
932
+ if self.args.get(arg, True):
933
+ self.error(*msg, error_code=error_code)
934
+ else:
935
+ util.warning(*msg)
936
+
913
937
 
914
938
  class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
915
939
  '''CommandDesign is the eda base class for command handlers that need to track files.
@@ -1392,7 +1416,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1392
1416
  return self.resolve_target_core(target, no_recursion, caller_info)
1393
1417
 
1394
1418
  def resolve_target_core( # pylint: disable=too-many-locals,too-many-branches
1395
- self, target: str, no_recursion: bool, caller_info: str = ''
1419
+ self, target: str, no_recursion: bool, caller_info: str = '',
1420
+ error_on_not_found: bool = True
1396
1421
  ) -> bool:
1397
1422
  '''Returns True if target is found. recursive point for resolving path or DEPS markup
1398
1423
  target names.'''
@@ -1481,14 +1506,18 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1481
1506
  self.add_file(try_file, caller_info=f'n/a::{target}::n/a')
1482
1507
  found_target = True
1483
1508
  break # move on to the next target
1484
- if not found_target: # if STILL not found_this_target...
1485
- self.error(f"Unable to resolve {target=}",
1486
- error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND)
1509
+ if not found_target and error_on_not_found: # if STILL not found_this_target...
1510
+ # allow this if --no-error-unknown-args:
1511
+ self.error_ifarg(
1512
+ f"Unable to resolve {target=}",
1513
+ arg='error-unknown-args',
1514
+ error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND
1515
+ )
1487
1516
 
1488
1517
  # if we've found any target since being called, it means we found the one we were called for
1489
1518
  return found_target
1490
1519
 
1491
- def add_file(
1520
+ def add_file( # pylint: disable=too-many-locals,too-many-branches
1492
1521
  self, filename: str, use_abspath: bool = True, add_to_non_sources: bool = False,
1493
1522
  caller_info: str = '', forced_extension: str = ''
1494
1523
  ) -> str:
@@ -1512,6 +1541,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1512
1541
  vhdl_file_ext_list = known_file_ext_dict.get('vhdl', [])
1513
1542
  cpp_file_ext_list = known_file_ext_dict.get('cpp', [])
1514
1543
  sdc_file_ext_list = known_file_ext_dict.get('synth_constraints', [])
1544
+ dotf_file_ext_list = known_file_ext_dict.get('dotf', [])
1515
1545
 
1516
1546
  if forced_extension:
1517
1547
  # If forced_extension='systemverilog', then use the first known extension for
@@ -1544,6 +1574,13 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1544
1574
  elif file_ext in sdc_file_ext_list:
1545
1575
  self.files_sdc.append(file_abspath)
1546
1576
  util.debug(f"Added SDC file {filename} as {file_abspath}")
1577
+ elif file_ext in dotf_file_ext_list:
1578
+ # a stray .f file as a source file, sure why not support it:
1579
+ dp = DepsProcessor(command_design_ref=self, deps_entry={}, target='',
1580
+ target_path='', target_node='', deps_file='',
1581
+ caller_info=caller_info)
1582
+ dp.apply_args(args_list=[f'-f={file_abspath}'])
1583
+ del dp
1547
1584
  else:
1548
1585
  # unknown file extension. In these cases we link the file to the working directory
1549
1586
  # so it is available (for example, a .mem file that is expected to exist with relative
@@ -1637,9 +1674,12 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1637
1674
  if process_all and possible_unparsed_args:
1638
1675
  _tool = self.safe_which_tool()
1639
1676
  self.warning_show_known_args()
1640
- self.error(f"Didn't understand unparsed args: {possible_unparsed_args}, for command",
1641
- f"'{self.command_name}', tool '{_tool}'",
1642
- error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1677
+ self.error_ifarg(
1678
+ f"Didn't understand unparsed args: {possible_unparsed_args}, for command",
1679
+ f"'{self.command_name}', tool '{_tool}'",
1680
+ arg='error-unknown-args',
1681
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR
1682
+ )
1643
1683
 
1644
1684
  remove_list = []
1645
1685
  last_potential_top_file = ('', '') # (top, fpath)
@@ -1697,9 +1737,12 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1697
1737
  # we were unable to figure out what this command line token is for...
1698
1738
  if process_all and unparsed:
1699
1739
  self.warning_show_known_args()
1700
- self.error(f"Didn't understand remaining args or targets {unparsed=} for command",
1701
- f"'{self.command_name}'",
1702
- error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1740
+ self.error_ifarg(
1741
+ f"Didn't understand remaining args or targets {unparsed=} for command",
1742
+ f"'{self.command_name}'",
1743
+ arg='error-unknown-args',
1744
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR
1745
+ )
1703
1746
 
1704
1747
  # handle a missing self.args['top'] with last filepath or last target:
1705
1748
  if not self.args.get('top', ''):
@@ -2244,9 +2287,12 @@ class CommandParallel(Command):
2244
2287
  bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
2245
2288
  if bad_remaining_args:
2246
2289
  self.warning_show_known_args(command=f'{self.command_name} {command}')
2247
- self.error(f'for {self.command_name} {command=} the following args are unknown',
2248
- f'{bad_remaining_args}',
2249
- error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
2290
+ self.error_ifarg(
2291
+ f'for {self.command_name} {command=} the following args are unknown',
2292
+ f'{bad_remaining_args}',
2293
+ arg='error-unknown-args',
2294
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR
2295
+ )
2250
2296
 
2251
2297
  # Remove unparsed args starting with '+', since those are commonly sent downstream to
2252
2298
  # single job (example, CommandSim plusargs).
@@ -91,6 +91,9 @@ file_extensions:
91
91
  synth_constraints:
92
92
  - .sdc
93
93
  - .xdc
94
+ dotf:
95
+ - .f
96
+ - .vc
94
97
 
95
98
  inferred_top:
96
99
  # file extensions that we can infer "top" module from, if --top omitted.
opencos/files.py CHANGED
@@ -24,6 +24,7 @@ FORCE_PREFIX_DICT = {
24
24
  'vhdl@': 'vhdl',
25
25
  'cpp@': 'cpp',
26
26
  'sdc@': 'synth_constraints',
27
+ 'f@': 'dotf'
27
28
  }
28
29
 
29
30
  ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))
opencos/tests/helpers.py CHANGED
@@ -9,12 +9,14 @@ from pathlib import Path
9
9
 
10
10
  from contextlib import redirect_stdout, redirect_stderr
11
11
 
12
- from opencos import eda
13
- from opencos import deps_schema
12
+ from opencos import eda, eda_tool_helper, deps_schema
14
13
  from opencos.utils.markup_helpers import yaml_safe_load
15
14
  from opencos.utils import status_constants
16
15
  from opencos.utils.subprocess_helpers import subprocess_run_background
17
16
 
17
+ # Figure out what tools the system has available, without calling eda.main(..)
18
+ config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
19
+
18
20
 
19
21
  def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
20
22
  '''Because eda_wrap calls eda_main(..) and will continue running
@@ -30,11 +32,11 @@ def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
30
32
  status_constants.EDA_DEFAULT_ERROR
31
33
  )
32
34
 
33
- def can_run_eda_command(*commands, config: dict) -> bool:
35
+ def can_run_eda_command(*commands, cfg: dict = config) -> bool:
34
36
  '''Returns True if we have any installed tool that can run: eda <command>'''
35
37
  runnable = []
36
38
  for command in list(commands):
37
- handler = config['command_handler'].get(command, None)
39
+ handler = cfg['command_handler'].get(command, None)
38
40
  if not handler:
39
41
  return False
40
42
  if handler and getattr(handler, 'CHECK_REQUIRES', []):
@@ -44,7 +46,7 @@ def can_run_eda_command(*commands, config: dict) -> bool:
44
46
  # We cannot run tools that have disable-auto set:
45
47
  tool = getattr(handler, '_TOOL', '')
46
48
  if handler and tool:
47
- entry = config['auto_tools_order'][0].get(tool, {})
49
+ entry = cfg['auto_tools_order'][0].get(tool, {})
48
50
  if entry and entry.get('disable-auto', False):
49
51
  # This tool cannot automatically run our command.
50
52
  return False
@@ -52,6 +54,14 @@ def can_run_eda_command(*commands, config: dict) -> bool:
52
54
  runnable.append(True)
53
55
  return runnable and all(runnable)
54
56
 
57
+ def can_run_eda_sim(cfg: dict = config) -> bool:
58
+ '''Returns True if we have any installed tool that can run: eda sim'''
59
+ return can_run_eda_command('sim', cfg=cfg)
60
+
61
+ def can_run_eda_elab(cfg: dict = config) -> bool:
62
+ '''Returns True if we have any installed tool that can run: eda elab'''
63
+ return can_run_eda_command('elab', cfg=cfg)
64
+
55
65
  def chdir_remove_work_dir(startpath, relpath):
56
66
  '''Changes dir to startpath/relpath, removes the work directories (eda.work, eda.export*)'''
57
67
  os.chdir(os.path.join(str(Path(startpath)), str(Path(relpath))))
@@ -171,6 +181,7 @@ class Helpers:
171
181
  '''Changes directory to self.DEFAULT_DIR and removes eda.work, eda.export paths'''
172
182
  chdir_remove_work_dir('', self.DEFAULT_DIR)
173
183
 
184
+
174
185
  def _resolve_logfile(self, logfile=None) -> str:
175
186
  '''Returns the logfile's filepath'''
176
187
  ret = logfile
@@ -232,7 +243,7 @@ class Helpers:
232
243
  return rc
233
244
 
234
245
  def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
235
- '''Check if any of want_str args are in the logfile, or self.DEFAULT_LOG'''
246
+ '''Check if want_str (joined) is in the logfile, or self.DEFAULT_LOG'''
236
247
  logfile = self._resolve_logfile(logfile)
237
248
  want_str0 = ' '.join(list(want_str))
238
249
  want_str1 = want_str0.replace('/', '\\')
opencos/tests/test_eda.py CHANGED
@@ -23,11 +23,12 @@ import subprocess
23
23
 
24
24
  import pytest
25
25
 
26
- from opencos import eda, eda_tool_helper
26
+ from opencos import eda
27
27
  from opencos.utils.markup_helpers import yaml_safe_load
28
28
  from opencos.tests import helpers
29
29
  from opencos.tests.helpers import eda_wrap, eda_sim_wrap, eda_elab_wrap, \
30
- Helpers
30
+ Helpers, tools_loaded, can_run_eda_sim
31
+
31
32
 
32
33
 
33
34
  THISPATH = os.path.dirname(__file__)
@@ -36,17 +37,6 @@ def chdir_remove_work_dir(relpath):
36
37
  '''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
37
38
  return helpers.chdir_remove_work_dir(THISPATH, relpath)
38
39
 
39
- # Figure out what tools the system has available, without calling eda.main(..)
40
- config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
41
-
42
- def can_run_eda_sim() -> bool:
43
- '''Returns True if we have any installed tool that can run: eda sim'''
44
- return helpers.can_run_eda_command('sim', config=config)
45
-
46
- def can_run_eda_elab() -> bool:
47
- '''Returns True if we have any installed tool that can run: eda elab'''
48
- return helpers.can_run_eda_command('elab', config=config)
49
-
50
40
  @pytest.mark.skipif(
51
41
  'verilator' not in tools_loaded and 'vivado' not in tools_loaded,
52
42
  reason="requires verilator OR vivado"
@@ -6,25 +6,23 @@ import os
6
6
  import sys
7
7
  import pytest
8
8
 
9
- from opencos import eda, eda_tool_helper, eda_base
9
+ from opencos import eda, eda_base
10
10
 
11
11
  from opencos.tools.verilator import ToolVerilator
12
12
  from opencos.tools.vivado import ToolVivado
13
13
  from opencos.tools.cocotb import ToolCocotb
14
14
  from opencos.tests import helpers
15
- from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail
15
+ from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail, config, tools_loaded
16
16
  from opencos.utils.markup_helpers import yaml_safe_load
17
17
  from opencos.utils import status_constants
18
18
 
19
19
 
20
- thispath = os.path.dirname(__file__)
20
+ THISPATH = os.path.dirname(__file__)
21
21
 
22
22
  def chdir_remove_work_dir(relpath):
23
23
  '''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
24
- return helpers.chdir_remove_work_dir(thispath, relpath)
24
+ return helpers.chdir_remove_work_dir(THISPATH, relpath)
25
25
 
26
- # Figure out what tools the system has available, without calling eda.main(..)
27
- config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
28
26
 
29
27
  def test_tools_loaded():
30
28
  '''Does not directly call 'eda.main' instead create a few Tool
@@ -37,7 +35,7 @@ def test_tools_loaded():
37
35
  # It's possible we're running in some container or install that has no tools, for example,
38
36
  # Windows.
39
37
  if sys.platform.startswith('win') and \
40
- not helpers.can_run_eda_command('elab', 'sim', config=config):
38
+ not helpers.can_run_eda_command('elab', 'sim', cfg=config):
41
39
  # Windows, not handlers for elab or sim:
42
40
  pass
43
41
  else:
@@ -129,7 +127,7 @@ def test_sim_elab_tools_pass_or_fail(command, tool, target, sim_expect_pass, add
129
127
  added_args = added_sim_args_str.split()
130
128
 
131
129
  relative_dir = "deps_files/test_err_fatal"
132
- os.chdir(os.path.join(thispath, relative_dir))
130
+ os.chdir(os.path.join(THISPATH, relative_dir))
133
131
  rc = eda.main(command, '--tool', tool, *(added_args), target)
134
132
  print(f'{rc=}')
135
133
  if command != 'sim' or sim_expect_pass:
opencos/util.py CHANGED
@@ -435,25 +435,67 @@ def load_env_file(env_file: str) -> None:
435
435
  else:
436
436
  warning(f'--env-file {env_file} does not exist and is not loaded.')
437
437
 
438
- def read_tokens_from_dot_f(filepath: str) -> list:
438
+ def patch_args_for_dir(tokens: list, patch_dir: str, caller_info: str) -> list:
439
+ '''Given list of args, attempt to correct for relative dir'''
440
+
441
+ # deal with relative path args or files.
442
+ # Note the dot-f file could have been in a different directory (-f=path/to/my.f)
443
+ # As a workaround to deal with relative paths, attempt to replace relative
444
+ # path args within this dotf contents - so they are relative the dotf dir:
445
+ if not os.path.isdir(patch_dir):
446
+ return tokens
447
+
448
+ ret = []
449
+ for word in tokens:
450
+ if word.startswith('-') and '=' in word:
451
+ parts = word.split('=')
452
+ leftarg = parts[0] + '='
453
+ word = '='.join(parts[1:])
454
+ elif word.startswith('+incdir+'):
455
+ # do for +incdir+ too
456
+ leftarg = '+incdir+'
457
+ word = word[len('+incdir+'):]
458
+ else:
459
+ leftarg = ''
460
+
461
+ if word and not os.path.isabs(word) and \
462
+ os.path.exists(os.path.join(patch_dir, word)):
463
+ # fix relative path of word, or --arg=word
464
+ word = os.path.abspath(os.path.join(patch_dir, word))
465
+ info(f'Using relative path {patch_dir} for arg/token: {leftarg}{word}',
466
+ f'{caller_info}')
467
+ ret.append(f'{leftarg}{word}')
468
+ return ret
469
+
470
+ def read_tokens_from_dot_f(filepath: str, caller_info: str = '', verbose: bool = False) -> list:
439
471
  '''Returns list of tokens from a .f file, with ENV vars expanded'''
440
472
 
441
473
  # Let's defer 'info' printing out what input files were opened until after
442
474
  # args['quiet'] and debug is resolved (which may be in these .f files)
443
- debug(f"Opening -f / --input-file '{filepath}' for contents")
475
+ start_str = f"Opening -f / --input-file '{filepath}' for contents {caller_info}"
476
+ if verbose:
477
+ info(start_str)
478
+ else:
479
+ debug(start_str)
444
480
  if not os.path.isfile(filepath):
445
- error(f'-f (or --input-file): {filepath} does not exist',
481
+ error(f'-f (or --input-file): {filepath} does not exist {caller_info}',
446
482
  error_code=status_constants.EDA_GENERAL_FILE_NOT_FOUND)
447
483
  return []
448
484
  if os.path.abspath(filepath) in dot_f_files_expanded:
449
485
  error(f'-f (or --input-file): {filepath} has already been expanded',
450
- 'cannot traverse again (duplicate arg or nested .f files)')
486
+ f'cannot traverse again (duplicate arg or nested .f files) {caller_info}')
451
487
  dot_f_files_expanded.add(os.path.abspath(filepath))
452
488
  tokens = []
489
+ dotf_file_dir, _ = os.path.split(filepath)
453
490
  with open(filepath, encoding='utf-8') as f:
454
491
  for line in f:
455
492
  line = os.path.expandvars(line.strip())
456
- tokens.extend(line.split())
493
+ if not line or line.startswith('#') or line.startswith('//'):
494
+ continue
495
+ words = line.split()
496
+ tokens.extend(patch_args_for_dir(
497
+ tokens=words, patch_dir=dotf_file_dir, caller_info=f"(from dotf {filepath})"
498
+ ))
457
499
  return tokens
458
500
 
459
501
 
@@ -471,7 +513,7 @@ def process_debug_args(parsed: argparse.Namespace) -> None:
471
513
 
472
514
 
473
515
  def process_tokens( # pylint: disable=too-many-branches
474
- tokens: list
516
+ tokens: list, caller_info: str = ''
475
517
  ) -> (argparse.Namespace, list):
476
518
  '''Processes tokens (unparsed args list) on util's ArgumentParser
477
519
 
@@ -517,13 +559,15 @@ def process_tokens( # pylint: disable=too-many-branches
517
559
  if parsed.input_file:
518
560
  dotf_tokens = []
519
561
  for filepath in parsed.input_file:
520
- dotf_tokens.extend(read_tokens_from_dot_f(filepath))
562
+ dotf_tokens.extend(read_tokens_from_dot_f(
563
+ filepath=filepath, caller_info=caller_info
564
+ ))
521
565
 
522
566
  # put the .f files before the unparsed args.
523
567
  tokens2 = dotf_tokens + tokens2
524
568
 
525
569
  # recurse until we've resolved nested .f files.
526
- return process_tokens(tokens=tokens2)
570
+ return process_tokens(tokens=tokens2, caller_info=f'(from {parsed.input_file[-1]})')
527
571
 
528
572
 
529
573
  # Continue with all normal parsing beyond --debug and -f/--input-file,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -3,9 +3,9 @@ opencos/_version.py,sha256=KaWIjS0c08g-C0fgYY1kXwSPqhOFxaq5pYEeoZhOR_I,617
3
3
  opencos/_waves_pkg.sv,sha256=TL5YT9lT-fn2FD54MbVVZROmZ7vtW3ScA_rM2eRzKmU,2068
4
4
  opencos/deps_schema.py,sha256=VUdXuq43mKfM-U4x7DSA28-MH1Xqxre6V7Ttw2DeOqI,16762
5
5
  opencos/eda.py,sha256=91E-EsyZS-uRadApP-h2onW6rpvLBnrpJoT_9tRtsS8,23322
6
- opencos/eda_base.py,sha256=oJ8ISAN6IykQfun8eVNuzjggQ5RvIKeWwQ85Wv4ww_4,107887
6
+ opencos/eda_base.py,sha256=jf4t11UPj39swL41z-EJiGTOnHvRScaQc-SAjpZ5XI4,109651
7
7
  opencos/eda_config.py,sha256=z3yQOPGBX7-yKp6BdQYfJ9eOJf-Jctl-mwCDj3vj2BI,12712
8
- opencos/eda_config_defaults.yml,sha256=u1kzmclo5nElsn2BHOIYHk9_djbL0rUts4CLWYJbuc8,16036
8
+ opencos/eda_config_defaults.yml,sha256=LF0yAncYeaPtZIoAfEeo2aiDXT4cjYa99soGks0WRzM,16063
9
9
  opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
10
10
  opencos/eda_config_reduced.yml,sha256=cQ9jY4J7EvAbeHTiP6bvpDSVJAYiitjLZPSxxLKIEbk,1440
11
11
  opencos/eda_deps_bash_completion.bash,sha256=jMkQKY82HBgOnQeMdA1hMrXguRFtB52SMBxUemKovL4,1958
@@ -14,11 +14,11 @@ opencos/eda_extract_targets.py,sha256=POlxZfqf2dNH2nc1CEw5B_53vSHAicSTkpU9_-2_6Z
14
14
  opencos/eda_tool_helper.py,sha256=_YgobDLEWW6Fzdr976LxaCDZ4DKRyuMs5CrYQHaTPrU,2558
15
15
  opencos/export_helper.py,sha256=5BnrkhiieJBgYKAryhXD7HSGtrgvXQpZ8B5ltdrhbRY,22649
16
16
  opencos/export_json_convert.py,sha256=tSIMbLFtc_Fo66EhFovMii1v_qJYyFZJrPNnoPdW7L0,4182
17
- opencos/files.py,sha256=aoq0O2KfISzZb-Vi_q_0TTGBER9xJc--FkVZf0ga7pA,1549
17
+ opencos/files.py,sha256=AQOnsrvoc0r76LiFrkoMbwOGdUO1FpBiFY_jyyI_ve8,1566
18
18
  opencos/names.py,sha256=Y2aJ5wgpbNIJ-_P5xUXnHMv_h-zMOX2Rt6iLuduqC1Q,1213
19
19
  opencos/peakrdl_cleanup.py,sha256=vHNGtalTrIVP335PhRjPt9RhoccgpK1HJAi-E4M8Kc8,736
20
20
  opencos/seed.py,sha256=IL9Yg-r9SLSRseMVWaEHmuw2_DNi_eyut11EafoNTsU,942
21
- opencos/util.py,sha256=KK5_lSKSf8O6Wp-CtQ9bTg-izBEPkuxGh_N64DJhE-0,40038
21
+ opencos/util.py,sha256=ffaSoDDOsL6mx_fYgAyJA8fawsxZ5YHKqs67b8iW5rw,41921
22
22
  opencos/commands/__init__.py,sha256=oOOQmn5_jHAMSOfA3swJJ7mdoyHsJA0lJwKPTudlTns,1125
23
23
  opencos/commands/build.py,sha256=mvJYxk5J15k0Cr8R7oIdIIdsEtWV3gE-LnPweVwtSDo,1487
24
24
  opencos/commands/deps_help.py,sha256=WDrU7H9sypzDAxe_CHqhW5B_scbQMzBEdf-v-Jcfd5Q,10682
@@ -40,22 +40,22 @@ opencos/commands/waves.py,sha256=nrp3ALwfJujZns44tgCgia_dEedQyKe0T3fuws8h39U,769
40
40
  opencos/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  opencos/deps/defaults.py,sha256=NXh3V4oInrBVlDw64B2OCI77wzdn1NtaD64srhBnmZU,1486
42
42
  opencos/deps/deps_commands.py,sha256=q4JfSfzRO2nM2zdNT4enCy33FokEytZYQJn1HJ6osJk,16606
43
- opencos/deps/deps_file.py,sha256=nVZWrq6PVhWig1yMNpy8w_7LQJ1rgb7Js0N1ngoqLio,16306
44
- opencos/deps/deps_processor.py,sha256=i8R6lNSVc_ybTnC16qmGevB3Y-pkcbxkZaT04HTLE8Y,37890
43
+ opencos/deps/deps_file.py,sha256=YQ5ftYvppRTqUto22r-XDH6-bcMO7cA-WiJ7QzPjljY,17103
44
+ opencos/deps/deps_processor.py,sha256=DBaMUEnpoIL4xaNPs2f2AFGcWLST5pP_Qgup9r-8D7M,41403
45
45
  opencos/hw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  opencos/hw/oc_cli.py,sha256=U1JGlshLZhtd0LgndZFBZVltAj_HemdhbjO_Zo8ZuVM,132252
47
47
  opencos/hw/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
48
48
  opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
50
- opencos/tests/helpers.py,sha256=9KHSZ1JMmo2nUbJYiRehmNqd1mNQ29XbzReu_tHkxjk,11234
50
+ opencos/tests/helpers.py,sha256=wkEzYRt78B4b7Qni2GoWcncrhWjZ3nLhDMHtPYvvW8w,11725
51
51
  opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
52
52
  opencos/tests/test_deps_helpers.py,sha256=uQZxleh6aKO-mZQhagHh5xLIBbpQ8dav7-5D0eemq_g,8164
53
53
  opencos/tests/test_deps_schema.py,sha256=T3P9KjaMyKsk8b7snNVvNSsom2hIJcg6Z9apYiXoH9Y,941
54
- opencos/tests/test_eda.py,sha256=fkJvBiwPczs3Yl3zwl_1Upx-siJsGDrjEVipzpiw8vc,37880
54
+ opencos/tests/test_eda.py,sha256=PhAFCqoZxUhjuSvNeHpTHdwt8UhHjRdVppesgcWBX64,37407
55
55
  opencos/tests/test_eda_elab.py,sha256=AjU4WMYtFoHpNe1Z4yWWpxDKy4V_hAjL5rl3jqphZrk,3179
56
56
  opencos/tests/test_eda_synth.py,sha256=BtBrNVJ9C-LJt3K0wNNS5ukEVrET16AbRXl2IzxudJ8,5744
57
57
  opencos/tests/test_oc_cli.py,sha256=w-F-LjSSWVql3D2WG8tcV4_C52i-hL_2WT3oDpKQn9s,734
58
- opencos/tests/test_tools.py,sha256=-WMpDZexAgko0FAcfiuASqSKNNL0Mr1ztag05808Upc,13735
58
+ opencos/tests/test_tools.py,sha256=JFjwq8YJPxaALbIEZUA-1VR8O_N-zmmM4ueboFEYA4Y,13589
59
59
  opencos/tests/deps_files/command_order/DEPS.yml,sha256=PSzBBJDSU8ccCy3Ls5j_ws_vepmUkTIgWjaMjBhNbSg,806
60
60
  opencos/tests/deps_files/error_msgs/DEPS.yml,sha256=fYvHouIscOlr8V28bqx9SoxRBpDBLX4AG-AkVXh8qbo,717
61
61
  opencos/tests/deps_files/iverilog_test/DEPS.yml,sha256=vDylEuLt642lhRSvOr3F5ziB5lhPSwkaUGN4_mWJw-c,40
@@ -88,10 +88,10 @@ opencos/utils/str_helpers.py,sha256=726ScK5-v7QkBi-zqESKZLsOl2_ya4vVJ5ZhxJqmBFo,
88
88
  opencos/utils/subprocess_helpers.py,sha256=xemAGPey6M0sWY_FElvr-Z0phCfdjaC-znP8FKihPaE,3535
89
89
  opencos/utils/vscode_helper.py,sha256=9nHyMUIL-gzfW-qLH06sgaCnVK-YTOtu6pusitNNhL8,1363
90
90
  opencos/utils/vsim_helper.py,sha256=1johPOGbjbMgnCDSTpgsQcSuAquiqq1Y2MBxS6WY6b4,1552
91
- opencos_eda-0.3.0.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
- opencos_eda-0.3.0.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
- opencos_eda-0.3.0.dist-info/METADATA,sha256=KFNlElAF1mTMOenvOMpVfD5MRlIj1shxNBUiFGTuTQM,666
94
- opencos_eda-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
- opencos_eda-0.3.0.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
- opencos_eda-0.3.0.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
- opencos_eda-0.3.0.dist-info/RECORD,,
91
+ opencos_eda-0.3.2.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
+ opencos_eda-0.3.2.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
+ opencos_eda-0.3.2.dist-info/METADATA,sha256=0PgS7CqBIdwYkBXPQ_6PaCFCNFKEsRz5G_owk48zkJI,666
94
+ opencos_eda-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
+ opencos_eda-0.3.2.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
+ opencos_eda-0.3.2.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
+ opencos_eda-0.3.2.dist-info/RECORD,,