opencos-eda 0.2.31__py3-none-any.whl → 0.2.33__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/commands/multi.py CHANGED
@@ -268,26 +268,23 @@ class CommandMulti(CommandParallel):
268
268
  if parsed.parallel < 1 or parsed.parallel > 256:
269
269
  self.error("Arg 'parallel' must be between 1 and 256")
270
270
 
271
- for value in unparsed:
272
- if value in self.config['command_handler'].keys():
273
- command = value
274
- unparsed.remove(value)
275
- break
271
+ command = self.get_command_from_unparsed_args(tokens=unparsed)
276
272
 
277
273
  # Need to know the tool for this command, either it was set correctly via --tool and/or
278
274
  # the command (class) will tell us.
279
275
  all_multi_tools = self.multi_which_tools(command)
280
276
 
277
+ single_cmd_unparsed = self.get_unparsed_args_on_single_command(
278
+ command=command, tokens=unparsed
279
+ )
280
+
281
281
  util.debug(f"Multi: {unparsed=}, looking for target_globs")
282
282
  for token in unparsed:
283
- if token.startswith("-") or token.startswith("+"):
284
- # save all --arg, -arg, or +plusarg for the job target:
285
- arg_tokens.append(token)
286
- else:
283
+ if token in single_cmd_unparsed:
287
284
  target_globs.append(token)
285
+ else:
286
+ arg_tokens.append(token)
288
287
 
289
- if command == "":
290
- self.error("Didn't get a command after 'multi'!")
291
288
 
292
289
  # now we need to expand the target list
293
290
  self.single_command = command
@@ -379,27 +376,23 @@ class CommandMulti(CommandParallel):
379
376
  # Do not use for CommandMulti, b/c we support list of tools.
380
377
  raise NotImplementedError
381
378
 
382
- def multi_which_tools(self, command):
379
+ def multi_which_tools(self, command) -> list:
383
380
  '''returns a list, or None, of the tool that was already determined to run the command
384
381
 
385
382
  CommandToolsMulti will override and return its own list'''
386
383
  return [eda_base.which_tool(command, config=self.config)]
387
384
 
388
- def _append_job_command_args(self, command_list: list) -> None:
389
- # Many args were consumed by eda before CommandMulti saw them
390
- # --config-yml, --tool, --eda-safe. Some are in self.config.
391
- # We need to apply those eda level args to each multi exec-command
392
- # TODO(drew): this is really clunky. It would be way eaiser if our
393
- # self.config['eda_original_args'] had a better way of passing allow-listed
394
- # args from multi --> exec-command.
395
- # Alternatively, we could track all the parsed args up to this point, that have
396
- # deviated from the default value(s), and apply those as needed.
397
- if any(a.startswith('--config-yml') for a in self.config['eda_original_args']):
398
- cfg_yml_fname = self.config.get('config-yml', None)
399
- if cfg_yml_fname:
400
- command_list.append(f'--config-yml={cfg_yml_fname}')
401
- if '--eda-safe' in self.config['eda_original_args']:
402
- command_list.append('--eda-safe')
385
+ def _append_job_command_args( # pylint: disable=R0913,R0917 # too-many-arguments
386
+ self, command_list: list, tool: str, all_multi_tools: list, short_target: str,
387
+ command: str
388
+ ) -> None:
389
+
390
+ super().update_args_list(args=command_list, tool=tool)
391
+ if self.args.get('export-jsonl', False):
392
+ # Special case for 'multi' --export-jsonl, run reach child with --export-json
393
+ command_list.append('--export-json')
394
+ if tool and len(all_multi_tools) > 1:
395
+ command_list.append(f'--sub-work-dir={short_target}.{command}.{tool}')
403
396
 
404
397
  def append_jobs_from_targets(self, args:list):
405
398
  '''Helper method in CommandMulti to apply 'args' (list) to all self.targets,
@@ -422,17 +415,11 @@ class CommandMulti(CommandParallel):
422
415
 
423
416
  _, short_target = os.path.split(target) # trim path info on left
424
417
 
425
- self._append_job_command_args(command_list=command_list)
418
+ self._append_job_command_args(
419
+ command_list=command_list, tool=tool, all_multi_tools=all_multi_tools,
420
+ short_target=short_target, command=command
421
+ )
426
422
 
427
- if tool:
428
- # tool can be None, we won't add it to the command (assumes default from config-yml)
429
- command_list.append('--tool=' + tool)
430
- if len(all_multi_tools) > 1:
431
- command_list += [f'--sub-work-dir={short_target}.{command}.{tool}']
432
-
433
- if self.args.get('export-jsonl', False):
434
- # Special case for 'multi' --export-jsonl, run reach child with --export-json
435
- command_list += [ '--export-json']
436
423
  # if self.args['parallel']: command_list += ['--quiet']
437
424
  command_list += args # put the args prior to the target.
438
425
  command_list += [target]
@@ -540,6 +527,8 @@ class CommandMulti(CommandParallel):
540
527
  output_json_path=output_json_path)
541
528
 
542
529
 
530
+
531
+
543
532
  class CommandToolsMulti(CommandMulti):
544
533
  '''eda.py command handler for: eda tools-multi <args,targets,target-globs,...>
545
534
 
opencos/commands/sweep.py CHANGED
@@ -7,7 +7,7 @@ import os
7
7
  import re
8
8
 
9
9
  from opencos import util
10
- from opencos.eda_base import CommandDesign, CommandParallel, get_eda_exec
10
+ from opencos.eda_base import CommandDesign, CommandParallel, get_eda_exec, which_tool
11
11
 
12
12
  class CommandSweep(CommandDesign, CommandParallel):
13
13
  '''Command handler for: eda sweep ...'''
@@ -19,6 +19,19 @@ class CommandSweep(CommandDesign, CommandParallel):
19
19
  CommandParallel.__init__(self, config=config)
20
20
  self.sweep_target = ''
21
21
  self.single_command = ''
22
+ self.args.update({
23
+ 'sweep': []
24
+ })
25
+ self.args_help.update({
26
+ "sweep": ("List append arg, where range or value expansion is peformed on the RHS:"
27
+ " --sweep='--arg0=(start,last,iter=1)' "
28
+ " --sweep='--arg1=[val0,val1,val2,...]' "
29
+ " --sweep='+define+NAME0=(start,last,iter=1)' "
30
+ " --sweep='+define+NAME1=[val0,val1,val2,...]' "
31
+ " --sweep='+define+[NAME0,NAME1,NAME2,...]' ."
32
+ " Note that range expansion of (1,4) will expand to values [1,2,3,4]."
33
+ ),
34
+ })
22
35
 
23
36
 
24
37
  def check_args(self) -> None:
@@ -27,22 +40,9 @@ class CommandSweep(CommandDesign, CommandParallel):
27
40
  self.error(f"Arg {self.args['parallel']=} must be between 1 and 256")
28
41
 
29
42
  def _append_sweep_args(self, arg_tokens: list) -> None:
30
- '''Modifies list arg_tokens, bit of a hack'''
31
-
32
- # TODO(drew): similar clunky behavior with self.config['eda_orignal_args'] that
33
- # CommandMulti has we need to pass global args to each sweep job, which we can
34
- # do via arg_tokens (list)
35
- # TODO(drew): fix this, for now it works but --color and other args do not work.
36
- if any(a.startswith('--config-yml') for a in self.config['eda_original_args']):
37
- cfg_yml_fname = self.config.get('config-yml', None)
38
- if cfg_yml_fname:
39
- arg_tokens.append(f'--config-yml={cfg_yml_fname}')
40
- if '--eda-safe' in self.config['eda_original_args']:
41
- arg_tokens.append('--eda-safe')
42
- if any(a.startswith('--tool') for a in self.config['eda_original_args']):
43
- tool = self.config.get('tool', None)
44
- if tool:
45
- arg_tokens.append('--tool=' + tool)
43
+ '''Modifies list arg_tokens, using known top-level args'''
44
+ tool = which_tool(command=self.single_command, config=self.config)
45
+ super().update_args_list(args=arg_tokens, tool=tool)
46
46
 
47
47
 
48
48
  def process_tokens(
@@ -55,7 +55,7 @@ class CommandSweep(CommandDesign, CommandParallel):
55
55
  - builds sweep_axis_list to run multiple jobs for the target
56
56
  '''
57
57
 
58
- # multi is special in the way it handles tokens, due to most of them being processed by
58
+ # 'sweep' is special in the way it handles tokens, due to most of them being processed by
59
59
  # a sub instance
60
60
  sweep_axis_list = []
61
61
  arg_tokens = []
@@ -64,59 +64,49 @@ class CommandSweep(CommandDesign, CommandParallel):
64
64
  tokens=tokens,
65
65
  parser_arg_list=[
66
66
  'parallel',
67
+ 'sweep',
67
68
  ],
68
69
  apply_parsed_args=True
69
70
  )
70
71
 
71
72
  self.check_args()
72
73
 
73
- tokens = unparsed
74
-
75
- self.single_command = self.get_command_from_unparsed_args(tokens=tokens)
74
+ self.single_command = self.get_command_from_unparsed_args(tokens=unparsed)
76
75
 
77
76
  self._append_sweep_args(arg_tokens=arg_tokens)
78
77
 
79
- while tokens:
80
- token = tokens.pop(0)
81
-
82
- # command and --parallel already processed by argparse
83
-
84
- m = re.match(r'(\S+)\=\(([\d\.]+)\,([\d\.]+)(,([\d\.]+))?\)', token)
85
- if m:
86
- # Form --arg=CUST "CUST=(range-start,range-stop,range-step)"
87
- sweep_axis = { 'key' : m.group(1),
88
- 'values' : [ ] }
89
- for v in range(
90
- int(m.group(2)),
91
- int(m.group(3)) + 1,
92
- int(m.group(5)) if m.group(4) else 1
93
- ):
94
- sweep_axis['values'].append(v)
95
- util.debug(f"Sweep axis: {sweep_axis['key']} : {sweep_axis['values']}")
96
- sweep_axis_list.append(sweep_axis)
97
- continue
98
- m = re.match(r'(\S+)\=\[([^\]]+)\]', token)
99
- if m:
100
- # Form --arg=CUST "CUST=[val0,val1,val2,...]"
101
- sweep_axis = { 'key' : m.group(1), 'values' : [] }
102
- for v in m.group(2).split(','):
103
- v = v.replace(' ','')
104
- sweep_axis['values'].append(v)
105
- util.debug(f"Sweep axis: {sweep_axis['key']} : {sweep_axis['values']}")
106
- sweep_axis_list.append(sweep_axis)
107
- continue
108
- if token.startswith('--') or token.startswith('+'):
78
+ for sweep_arg_value in self.args['sweep']:
79
+ # Deal with --sweep= args we already parsed, but haven't expanded yet.
80
+ sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
81
+ sweep_axis_list_entry = self._process_sweep_arg(sweep_arg_value=sweep_arg_value)
82
+ if sweep_axis_list_entry:
83
+ sweep_axis_list.append(sweep_axis_list_entry)
84
+
85
+
86
+ # command, --parallel, and --sweep already processed by argparse,
87
+ # let's tentatively parse what the child jobs cannot consume.
88
+ # Whatever is leftover is either an eda target, or another unparsed token.
89
+ single_cmd_unparsed = self.get_unparsed_args_on_single_command(
90
+ command=self.single_command, tokens=unparsed
91
+ )
92
+
93
+ for token in unparsed:
94
+ if token in single_cmd_unparsed:
95
+
96
+ if self.resolve_target(token, no_recursion=True):
97
+ if self.sweep_target:
98
+ self.error("Sweep can only take one target, already got",
99
+ f"{self.sweep_target} now getting {token}")
100
+ self.sweep_target = token
101
+ else:
102
+ # If we don't know what to do with it, pass it to downstream
103
+ arg_tokens.append(token)
104
+
105
+ else:
106
+ # If it wasn't in single_cmd_unparsed, then it can definitely be
107
+ # consumed downstream
109
108
  arg_tokens.append(token)
110
- continue
111
- if self.resolve_target(token, no_recursion=True):
112
- if self.sweep_target != "":
113
- self.error(f"Sweep can only take one target, already got {self.sweep_target},"
114
- f"now getting {token}")
115
- self.sweep_target = token
116
- continue
117
- self.error(f"Sweep doesn't know what to do with arg '{token}'")
118
- if self.single_command == "":
119
- self.error("Didn't get a command after 'sweep'!")
109
+
120
110
 
121
111
  # now we need to expand the target list
122
112
  util.debug(f"Sweep: command: '{self.single_command}'")
@@ -128,7 +118,74 @@ class CommandSweep(CommandDesign, CommandParallel):
128
118
 
129
119
  self.expand_sweep_axis(arg_tokens=arg_tokens, sweep_axis_list=sweep_axis_list)
130
120
  self.run_jobs(command=self.single_command)
131
- return tokens
121
+ return [] # we used all of unparsed.
122
+
123
+
124
+ @staticmethod
125
+ def _process_sweep_arg(sweep_arg_value: str) -> dict:
126
+ '''If processed, returns a non-empty dict. Handles processing of --sweep=VALUE
127
+
128
+ Where VALUE could be one of:
129
+ --arg0=(1,4)
130
+ --arg1=[val0,val1]
131
+ +define+NAME0=[val0,val1]
132
+
133
+ Return value is {} or {'lhs': str, 'operator': str (+ or =), 'values': list}
134
+ '''
135
+
136
+ sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
137
+
138
+ util.debug(f'{sweep_arg_value=}')
139
+ # Try to match a sweep range expansion:
140
+ # --sweep='--arg0=(1,4)'
141
+ # --sweep='--arg0=(1,4,1)'
142
+ # --sweep='+define+NAME0=(1,4,1)'
143
+ m = re.match(
144
+ r'(.*)(\=) \( ([\d\.]+) \, ([\d\.]+) (\, ([\d\.]+) )? \)'.replace(' ',''),
145
+ sweep_arg_value
146
+ )
147
+ if m:
148
+ lhs = m.group(1)
149
+ operator = m.group(2)
150
+ sweep_axis_values = []
151
+ if m.group(5):
152
+ rhs_range_iter = int(m.group(6))
153
+ else:
154
+ rhs_range_iter = 1
155
+ for v in range(int(m.group(3)), int(m.group(4)) + 1, rhs_range_iter):
156
+ sweep_axis_values.append(v)
157
+ util.debug(f"Sweep axis: {lhs} {operator} {sweep_axis_values}")
158
+ return {
159
+ 'lhs': lhs,
160
+ 'operator': operator,
161
+ 'values': sweep_axis_values,
162
+ }
163
+
164
+ # Try to match a sweep list value expansion:
165
+ # --sweep='--arg0=[1,2]'
166
+ # --sweep='--arg1=[3,4,5,6]'
167
+ # --sweep='+define+NAME=[8,9]'
168
+ # --sweep='+define+[NAME0,NAME1,NAME2]'
169
+ # --sweep='+define+[NAME0=1,NAME1=22,NAME2=46]'
170
+ m = re.match(
171
+ r'(.*)([\=\+]) \[ ([^\]]+) \] '.replace(' ',''),
172
+ sweep_arg_value
173
+ )
174
+ if m:
175
+ sweep_axis_values = []
176
+ lhs = m.group(1)
177
+ operator = m.group(2)
178
+ for v in m.group(3).split(','):
179
+ sweep_axis_values.append(v)
180
+ util.debug(f"Sweep axis: {lhs} {operator} {sweep_axis_values}")
181
+ return {
182
+ 'lhs': lhs,
183
+ 'operator': operator,
184
+ 'values': sweep_axis_values,
185
+ }
186
+
187
+ util.warning(f'Ignored unprocessed expansion for --sweep={sweep_arg_value}')
188
+ return {}
132
189
 
133
190
 
134
191
  def expand_sweep_axis(
@@ -136,35 +193,38 @@ class CommandSweep(CommandDesign, CommandParallel):
136
193
  ) -> None:
137
194
  '''Returns None, appends jobs to self.jobs to be run by CommandParallel.run_jobs(..)'''
138
195
 
139
- command = self.single_command
140
- target = self.sweep_target
141
-
142
- util.debug(f"Entering expand_sweep_axis: command={command}, target={target},",
143
- f"arg_tokens={arg_tokens}, sweep_axis_list={sweep_axis_list}")
144
- if len(sweep_axis_list) == 0:
196
+ util.debug(f"Entering expand_sweep_axis: command={self.single_command},",
197
+ f"target={self.sweep_target}, arg_tokens={arg_tokens},",
198
+ f"sweep_axis_list={sweep_axis_list}")
199
+ if not sweep_axis_list:
145
200
  # we aren't sweeping anything, create one job
146
- snapshot_name = target.replace('../','').replace('/','_') + sweep_string
201
+ snapshot_name = self.sweep_target.replace('../','').replace('/','_') + sweep_string
147
202
  eda_path = get_eda_exec('sweep')
148
203
  self.jobs.append({
149
204
  'name' : snapshot_name,
150
205
  'index' : len(self.jobs),
151
206
  'command_list' : (
152
- [eda_path, command, target, '--job_name', snapshot_name] + arg_tokens
207
+ [eda_path, self.single_command, self.sweep_target,
208
+ '--job_name', snapshot_name] + arg_tokens
153
209
  )
154
210
  })
155
211
  return
156
- sweep_axis = sweep_axis_list[0]
212
+
213
+ sweep_axis = sweep_axis_list.pop(0)
214
+ lhs = sweep_axis['lhs']
215
+ operator = sweep_axis['operator']
216
+
217
+ lhs_trimmed = lhs.replace('-', '').replace('+', '').replace('=', '')
218
+
157
219
  for v in sweep_axis['values']:
158
- this_arg_tokens = []
159
- for a in arg_tokens:
160
- a_swept = re.sub(rf'\b{sweep_axis["key"]}\b', f"{v}", a)
161
- this_arg_tokens.append(a_swept)
162
- next_sweep_axis_list = []
163
- if len(sweep_axis_list)>1:
164
- next_sweep_axis_list = sweep_axis_list[1:]
220
+ this_arg_tokens = arg_tokens.copy()
221
+ this_arg_tokens.append(f'{lhs}{operator}{v}')
222
+
165
223
  v_string = f"{v}".replace('.','p')
224
+ this_sweep_string = sweep_string + f"_{lhs_trimmed}_{v_string}"
225
+
166
226
  self.expand_sweep_axis(
167
227
  arg_tokens=this_arg_tokens,
168
- sweep_axis_list=next_sweep_axis_list,
169
- sweep_string = sweep_string + f"_{sweep_axis['key']}_{v_string}"
228
+ sweep_axis_list=sweep_axis_list,
229
+ sweep_string=this_sweep_string
170
230
  )
opencos/deps_helpers.py CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  import fnmatch
3
3
  import os
4
+ from pathlib import Path
4
5
  import sys
5
6
  import re
6
7
  import shutil
@@ -1149,9 +1150,9 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
1149
1150
 
1150
1151
 
1151
1152
  shell_commands = [
1152
- [ 'peakrdl', 'regblock', '-o', 'peakrdl/'] + args_list,
1153
+ [ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
1153
1154
  # Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
1154
- [ 'python3', peakrdl_cleanup_py, f'peakrdl/{top}.sv', f'peakrdl/{top}.sv' ],
1155
+ [ 'python3', peakrdl_cleanup_py, str(Path(f'peakrdl/{top}.sv')), str(Path(f'peakrdl/{top}.sv')) ],
1155
1156
  ]
1156
1157
 
1157
1158
  ret_dict = {
opencos/deps_schema.py CHANGED
@@ -125,7 +125,7 @@ import sys
125
125
 
126
126
  from schema import Schema, Or, Optional, SchemaError
127
127
 
128
- from opencos.deps_helpers import deps_markup_safe_load, get_deps_markup_file
128
+ from opencos import util, deps_helpers
129
129
 
130
130
  # Because we deal with YAML, where a Table Key with dangling/empty value is allowed
131
131
  # and we have things like SystemVerilog defines where there's a Table key with no Value,
@@ -299,8 +299,10 @@ FILE_SIMPLIFIED = Schema(
299
299
 
300
300
 
301
301
 
302
- def check(data: dict, schema_obj=FILE):
302
+ def check(data: dict, schema_obj=FILE) -> (bool, str):
303
303
  '''Returns (bool, str) for checking dict against FILE schema'''
304
+
305
+
304
306
  try:
305
307
  schema_obj.validate(data)
306
308
  return True, None
@@ -310,6 +312,37 @@ def check(data: dict, schema_obj=FILE):
310
312
  return False, str(e)
311
313
 
312
314
 
315
+ def deps_markup_safe_load(deps_filepath: str) -> (bool, dict):
316
+ '''Returns tuple (bool False if took errors, dict of markp data)'''
317
+ current_errors = util.args['errors']
318
+ data = deps_helpers.deps_markup_safe_load(deps_filepath)
319
+ if util.args['errors'] > current_errors:
320
+ return False, data
321
+ return True, data
322
+
323
+
324
+ def check_file(filepath: str, schema_obj=FILE) -> (bool, str, str):
325
+ '''Returns tuple (bool pass/fail, str error retdata, str deps_filepath)'''
326
+
327
+ deps_filepath = filepath
328
+ if os.path.isdir(filepath):
329
+ deps_filepath = deps_helpers.get_deps_markup_file(base_path=filepath)
330
+
331
+ # get deps file
332
+ if not os.path.exists(deps_filepath):
333
+ print(f'ERROR: internal error(s) no DEPS.[yml|..] found in {filepath=}')
334
+ return False, '', deps_filepath
335
+
336
+ passes, data = deps_markup_safe_load(deps_filepath)
337
+ if not passes:
338
+ print(f'ERROR: internal error(s) from deps_markup_safe_load({deps_filepath=})')
339
+ return False, '', deps_filepath
340
+
341
+ passes, retdata = check(data, schema_obj)
342
+ return passes, retdata, deps_filepath
343
+
344
+
345
+
313
346
  def check_files(files, schema_obj=FILE) -> bool:
314
347
  '''Returns True if files lint cleanly in the FILE schema.'''
315
348
 
@@ -319,20 +352,16 @@ def check_files(files, schema_obj=FILE) -> bool:
319
352
  passes_list = []
320
353
  error_files = []
321
354
  for filepath in files:
322
- deps_filepath = filepath
323
- if os.path.isdir(filepath):
324
- # get deps file
325
- deps_filepath = get_deps_markup_file(base_path=filepath)
326
- assert os.path.exists(deps_filepath)
327
- data = deps_markup_safe_load(deps_filepath)
328
- passes, retdata = check(data, schema_obj)
355
+
356
+ passes, retdata, deps_filepath = check_file(filepath, schema_obj)
329
357
  passes_list.append(passes)
330
358
  if passes:
331
359
  print(f'{deps_filepath}: [PASS]')
332
360
  if not passes:
333
361
  print(f'ERROR: {deps_filepath}:')
334
- print('-- retdata --')
335
- print(retdata)
362
+ if retdata:
363
+ print('-- retdata --')
364
+ print(retdata)
336
365
  print(f' previous error on: {deps_filepath}\n')
337
366
  error_files.append(deps_filepath)
338
367
 
opencos/eda_base.py CHANGED
@@ -398,12 +398,6 @@ class Command:
398
398
  else:
399
399
  assert False, f'{key=} {value=} how do we do argparse for this type of value?'
400
400
 
401
- # TODO(drew): it might be nice to support positional args here as a list
402
- # self.target_args (files/targets/patterns), something like:
403
- # parser.add_argument(
404
- # 'targets', nargs='+', help='positional arg for targets/files/pattern'
405
- # )
406
-
407
401
  return parser
408
402
 
409
403
 
@@ -425,7 +419,7 @@ class Command:
425
419
  parsed, unparsed = parser.parse_known_args(tokens + [''])
426
420
  unparsed = list(filter(None, unparsed))
427
421
  except argparse.ArgumentError:
428
- self.error(f'problem attempting to parse_known_args for {tokens=}')
422
+ self.error(f'problem {command_name=} attempting to parse_known_args for {tokens=}')
429
423
 
430
424
  parsed_as_dict = vars(parsed)
431
425
 
@@ -506,7 +500,8 @@ class Command:
506
500
  break
507
501
 
508
502
  if not ret and error_if_no_command:
509
- self.error(f"Looking for a valid eda <command> but didn't find one in {tokens=}")
503
+ self.error(f"Looking for a valid eda {self.command_name} <command>",
504
+ f"but didn't find one in {tokens=}")
510
505
  return ret
511
506
 
512
507
 
@@ -696,8 +691,12 @@ class CommandDesign(Command):
696
691
  util.info(f'run_dep_shell_commands {iter=}: {d=}')
697
692
  clist = util.ShellCommandList(d['exec_list'])
698
693
  # NOTE(drew): shell=True subprocess call, can disable with self.config
699
- self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
700
- shell=self.config.get('deps_subprocess_shell', False))
694
+ # However, in Windows, we seem to have to run these with shell=False
695
+ if sys.platform.startswith('win'):
696
+ self.exec(self.args['work-dir'], clist, shell=False)
697
+ else:
698
+ self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
699
+ shell=self.config.get('deps_subprocess_shell', False))
701
700
 
702
701
  def update_file_lists_for_work_dir(self):
703
702
  if len(self.dep_work_dir_add_srcs) == 0:
@@ -748,7 +747,7 @@ class CommandDesign(Command):
748
747
  to unprocessed-plusargs.
749
748
  '''
750
749
 
751
- # Since this may be coming from a raw CLI/bash argparser, we may have
750
+ # Since this may be coming from a raw CLI/bash/powershell argparser, we may have
752
751
  # args that come from shlex.quote(token), such as:
753
752
  # token = '\'+define+OC_ROOT="/foo/bar/opencos"\''
754
753
  # So we strip all outer ' or " on the plusarg:
@@ -1514,3 +1513,57 @@ class CommandParallel(Command):
1514
1513
  if self.status == 0:
1515
1514
  self.status = 0 if len(self.jobs_status) == 0 else max(self.jobs_status)
1516
1515
  util.fancy_stop()
1516
+
1517
+
1518
+ def update_args_list(self, args: list, tool: str) -> None:
1519
+ '''Modfies list args, using allow-listed known top-level args:
1520
+
1521
+ --config-yml
1522
+ --eda-safe
1523
+ --tool
1524
+
1525
+ Many args were consumed by eda before CommandParallel saw them
1526
+ (for commands like 'multi' or 'sweep'). Some are in self.config.
1527
+ We need to apply those eda level args to each single exec-command
1528
+ '''
1529
+ if any(a.startswith('--config-yml') for a in self.config['eda_original_args']):
1530
+ cfg_yml_fname = self.config.get('config-yml', None)
1531
+ if cfg_yml_fname:
1532
+ args.append(f'--config-yml={cfg_yml_fname}')
1533
+ if '--eda-safe' in self.config['eda_original_args']:
1534
+ args.append('--eda-safe')
1535
+ if tool:
1536
+ # tool can be None, if so we won't add it to the command (assumes default from
1537
+ # config-yml auto load order)
1538
+ args.append('--tool=' + tool)
1539
+
1540
+
1541
+ def get_unparsed_args_on_single_command(self, command: str, tokens: list) -> list:
1542
+ '''Returns a list of args that the single (non-multi) command cannot parse
1543
+
1544
+ This will error on bad --args or -arg, such as:
1545
+ eda multi sim --seeeed=1
1546
+ is not a valid arg in CommandSim
1547
+
1548
+ +arg=value, +arg+value will not be included in the return list, those are
1549
+ intended to be consumed by the single/job command downstream (anything starting
1550
+ with '+')
1551
+
1552
+ Used by CommandMulti and CommandSweep.
1553
+ '''
1554
+ single_cmd_handler = self.config['command_handler'][command](config=self.config)
1555
+ single_cmd_parsed, single_cmd_unparsed = single_cmd_handler.run_argparser_on_list(
1556
+ tokens=tokens.copy(),
1557
+ apply_parsed_args=False,
1558
+ )
1559
+ util.debug(f'{self.command_name}: {single_cmd_unparsed=}')
1560
+
1561
+ # There should not be any single_cmd_unparsed args starting with '-'
1562
+ bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
1563
+ if bad_remaining_args:
1564
+ self.error(f'for {self.command_name} {command=} the following args are unknown',
1565
+ f'{bad_remaining_args}')
1566
+
1567
+ # Remove unparsed args starting with '+', since those are commonly sent downstream to
1568
+ # single job (example, CommandSim plusargs).
1569
+ return [x for x in single_cmd_unparsed if not x.startswith('+')]
@@ -2,7 +2,6 @@
2
2
 
3
3
  import sys
4
4
  import os
5
- import subprocess
6
5
 
7
6
  def run(file_in, file_out):
8
7
  with open(file_in) as f:
opencos/tests/helpers.py CHANGED
@@ -11,6 +11,19 @@ from contextlib import redirect_stdout, redirect_stderr
11
11
  from opencos import eda, util
12
12
  from opencos import deps_schema
13
13
 
14
+ def can_run_eda_command(*commands, config: dict) -> bool:
15
+ '''Returns True if we have any installed tool that can run: eda <command>'''
16
+ runnable = []
17
+ for command in list(commands):
18
+ handler = config['command_handler'].get(command, None)
19
+ if not handler:
20
+ return False
21
+ if handler and getattr(handler, 'CHECK_REQUIRES', []):
22
+ if not all(isinstance(handler, x) for x in getattr(handler, 'CHECK_REQUIRES', [])):
23
+ return False
24
+ runnable.append(True)
25
+ return runnable and all(runnable)
26
+
14
27
  def chdir_remove_work_dir(startpath, relpath):
15
28
  '''Changes dir to startpath/relpath, removes the work directories (eda.work, eda.export*)'''
16
29
  os.chdir(os.path.join(startpath, relpath))
@@ -139,41 +152,52 @@ class Helpers:
139
152
  print(f'Wrote: {os.path.abspath(logfile)=}')
140
153
  return rc
141
154
 
142
- def is_in_log(self, *want_str, logfile=None):
155
+ def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
143
156
  '''Check if any of want_str args are in the logfile, or self.DEFAULT_LOG'''
144
157
  if logfile is None:
145
158
  logfile = self.DEFAULT_LOG
146
- want_str = ' '.join(list(want_str))
159
+ want_str0 = ' '.join(list(want_str))
160
+ want_str1 = want_str0.replace('/', '\\')
147
161
  with open(logfile, encoding='utf-8') as f:
148
162
  for line in f.readlines():
149
- if want_str in line:
163
+ if want_str0 in line or \
164
+ (windows_path_support and want_str1 in line):
150
165
  return True
151
166
  return False
152
167
 
153
- def get_log_lines_with(self, *want_str, logfile=None):
168
+ def get_log_lines_with(self, *want_str, logfile=None, windows_path_support=False):
154
169
  '''gets all log lines with any of want_str args are in the logfile, or self.DEFAULT_LOG'''
155
170
  if logfile is None:
156
171
  logfile = self.DEFAULT_LOG
157
172
  ret_list = []
158
- want_str = ' '.join(list(want_str))
173
+ want_str0 = ' '.join(list(want_str))
174
+ want_str1 = want_str0.replace('/', '\\')
159
175
  with open(logfile, encoding='utf-8') as f:
160
176
  for line in f.readlines():
161
- if want_str in line:
177
+ if want_str0 in line:
178
+ ret_list.append(line)
179
+ elif windows_path_support and want_str1 in line:
162
180
  ret_list.append(line)
163
181
  return ret_list
164
182
 
165
- def get_log_words_with(self, *want_str, logfile=None):
183
+ def get_log_words_with(self, *want_str, logfile=None, windows_path_support=False):
166
184
  '''gets all log lines with any of *want_str within a single word
167
185
  in the logfile or self.DEFAULT_LOG
168
186
  '''
169
187
  if logfile is None:
170
188
  logfile = self.DEFAULT_LOG
171
189
  ret_list = []
172
- want_str = ' '.join(list(want_str))
190
+ want_str0 = ' '.join(list(want_str))
191
+ want_str1 = want_str0.replace('/', '\\')
173
192
  with open(logfile, encoding='utf-8') as f:
174
193
  for line in f.readlines():
175
- if want_str in line:
194
+ if want_str0 in line:
176
195
  for word in line.split():
177
- if want_str in word:
196
+ if want_str0 in word:
178
197
  ret_list.append(word)
198
+ elif windows_path_support and want_str1 in line:
199
+ for word in line.split():
200
+ if want_str1 in word:
201
+ ret_list.append(word)
202
+
179
203
  return ret_list
@@ -6,6 +6,7 @@
6
6
  # TODO(drew): for now, ignore long lines and docstrings
7
7
  # pylint: disable=line-too-long,missing-function-docstring
8
8
 
9
+ from pathlib import Path
9
10
  import os
10
11
  import pytest
11
12
 
@@ -22,15 +23,15 @@ def test_get_all_targets():
22
23
 
23
24
  targets = deps_helpers.get_all_targets(
24
25
  dirs=[
25
- '../../lib/tests',
26
- '../../lib/rams/tests',
26
+ str(Path('../../lib/tests')),
27
+ str(Path('../../lib/rams/tests')),
27
28
  ],
28
- base_path=THISPATH,
29
+ base_path=str(Path(THISPATH)),
29
30
  filter_str='*test',
30
31
  )
31
32
  print(f'{targets=}')
32
- assert '../../lib/rams/tests/oclib_ram2rw_test' in targets
33
- assert '../../lib/tests/oclib_fifo_test' in targets
33
+ assert str(Path('../../lib/rams/tests/oclib_ram2rw_test')) in targets
34
+ assert str(Path('../../lib/tests/oclib_fifo_test')) in targets
34
35
  for t in targets:
35
36
  assert t.endswith('test'), f'target {t} filter did not work *test'
36
37
 
opencos/tests/test_eda.py CHANGED
@@ -24,15 +24,22 @@ from opencos.tests.helpers import eda_wrap, eda_sim_wrap, eda_elab_wrap, \
24
24
  Helpers
25
25
 
26
26
 
27
- thispath = os.path.dirname(__file__)
27
+ THISPATH = os.path.dirname(__file__)
28
28
 
29
29
  def chdir_remove_work_dir(relpath):
30
30
  '''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
31
- return helpers.chdir_remove_work_dir(thispath, relpath)
31
+ return helpers.chdir_remove_work_dir(THISPATH, relpath)
32
32
 
33
33
  # Figure out what tools the system has available, without calling eda.main(..)
34
34
  config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
35
35
 
36
+ def can_run_eda_sim() -> bool:
37
+ '''Returns True if we have any installed tool that can run: eda sim'''
38
+ return helpers.can_run_eda_command('sim', config=config)
39
+
40
+ def can_run_eda_elab() -> bool:
41
+ '''Returns True if we have any installed tool that can run: eda elab'''
42
+ return helpers.can_run_eda_command('elab', config=config)
36
43
 
37
44
  @pytest.mark.skipif(
38
45
  'verilator' not in tools_loaded and 'vivado' not in tools_loaded,
@@ -112,7 +119,7 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
112
119
  print(f'{rc=}')
113
120
  assert rc == 0
114
121
 
115
- os.chdir(os.path.join(thispath, '../../lib/tests/eda.work/oclib_fifo_test.sim'))
122
+ os.chdir(os.path.join(THISPATH, '../../lib/tests/eda.work/oclib_fifo_test.sim'))
116
123
  res = subprocess.run(
117
124
  [ './lint_only.sh' ], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
118
125
  check=True
@@ -391,18 +398,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
391
398
 
392
399
 
393
400
  class TestMissingDepsFileErrorMessages(Helpers):
394
- '''Test for missing DEPS.yml file'''
395
- DEFAULT_DIR = os.path.join(thispath, 'deps_files', 'no_deps_here', 'empty')
401
+ '''Test for missing DEPS.yml file, using 'eda export' to avoid tools.'''
402
+ DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'no_deps_here', 'empty')
396
403
  DEFAULT_LOG = 'eda.log'
397
404
 
398
405
  def test_bad0(self):
399
406
  '''Looks for target_bad0, but there is no DEPS file in .'''
400
407
  self.chdir()
401
- rc = self.log_it(command_str='elab target_bad0')
408
+ rc = self.log_it(command_str='export target_bad0')
402
409
  assert rc != 0
403
410
  assert self.is_in_log(
404
411
  'Trying to resolve command-line target=./target_bad0: was not found',
405
- 'in deps_file=None, possible targets in deps file = []'
412
+ 'in deps_file=None, possible targets in deps file = []',
413
+ windows_path_support=True
406
414
  )
407
415
 
408
416
 
@@ -413,35 +421,35 @@ class TestDepsResolveErrorMessages(Helpers):
413
421
  or other less helpful information to the user. This confirms that file/target/
414
422
  linenumber information is printed when available.'''
415
423
 
416
- DEFAULT_DIR = os.path.join(thispath, 'deps_files', 'error_msgs')
424
+ DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'error_msgs')
417
425
  DEFAULT_LOG = 'eda.log'
418
426
 
419
427
  # files foo.sv, foo2.sv, target_bad0.sv, and target_bad1.sv exist.
420
428
  # files missing*.sv and targets missing* do not exist.
421
- # These all run elab targets.
429
+ # These all "export" targets, to avoid requiring an installed tool (for example, to elab)
422
430
 
423
431
  def test_good0(self):
424
432
  '''Simple test with good target (foo)'''
425
433
  self.chdir()
426
- rc = self.log_it('elab foo')
434
+ rc = self.log_it('export foo')
427
435
  assert rc == 0
428
436
 
429
437
  def test_good1(self):
430
438
  '''Simple test with good target (foo2)'''
431
439
  self.chdir()
432
- rc = self.log_it('elab foo2')
440
+ rc = self.log_it('export foo2')
433
441
  assert rc == 0
434
442
 
435
443
  def test_good2(self):
436
444
  '''Simple test with good target (foo + top=foo using deps str)'''
437
445
  self.chdir()
438
- rc = self.log_it('elab target_good2')
446
+ rc = self.log_it('export target_good2')
439
447
  assert rc == 0
440
448
 
441
449
  def test_good3(self):
442
450
  '''Simple test with good target (foo2 + top=foo2 using deps list)'''
443
451
  self.chdir()
444
- rc = self.log_it('elab target_good3')
452
+ rc = self.log_it('export target_good3')
445
453
  assert rc == 0
446
454
 
447
455
  # Bit of a change-detector-test here, but I want to make sure the
@@ -449,133 +457,146 @@ class TestDepsResolveErrorMessages(Helpers):
449
457
  def test_bad0(self):
450
458
  '''Tests missing file in DEPS target using implicit deps str style'''
451
459
  self.chdir()
452
- rc = self.log_it(command_str='elab target_bad0')
460
+ rc = self.log_it(command_str='export target_bad0')
453
461
  assert rc != 0
454
462
  assert self.is_in_log(
455
463
  "target=./missing0.sv (file?): called from ./DEPS.yml::target_bad0::line=20,",
456
- "File=missing0.sv not found in directory=."
464
+ "File=missing0.sv not found in directory=.",
465
+ windows_path_support=True
457
466
  )
458
467
 
459
468
  def test_bad1(self):
460
469
  '''Tests missing file in DEPS target using implicit deps list style'''
461
470
  self.chdir()
462
- rc = self.log_it(command_str='elab target_bad1')
471
+ rc = self.log_it(command_str='export target_bad1')
463
472
  assert rc != 0
464
473
  assert self.is_in_log(
465
474
  "target=./missing1.sv (file?): called from ./DEPS.yml::target_bad1::line=24,",
466
- "File=missing1.sv not found in directory=."
475
+ "File=missing1.sv not found in directory=.",
476
+ windows_path_support=True
467
477
  )
468
478
 
469
479
  def test_bad2(self):
470
480
  '''Tests missing file in DEPS target using deps as str style'''
471
481
  self.chdir()
472
- rc = self.log_it(command_str='elab target_bad2')
482
+ rc = self.log_it(command_str='export target_bad2')
473
483
  assert rc != 0
474
484
  assert self.is_in_log(
475
485
  "target=./missing2.sv (file?): called from ./DEPS.yml::target_bad2::line=28,",
476
- "File=missing2.sv not found in directory=."
486
+ "File=missing2.sv not found in directory=.",
487
+ windows_path_support=True
477
488
  )
478
489
 
479
490
  def test_bad3(self):
480
491
  '''Tests missing file in DEPS target using deps as list style'''
481
492
  self.chdir()
482
- rc = self.log_it(command_str='elab target_bad3')
493
+ rc = self.log_it(command_str='export target_bad3')
483
494
  assert rc != 0
484
495
  assert self.is_in_log(
485
496
  "target=./missing3.sv (file?): called from ./DEPS.yml::target_bad3::line=33,",
486
- "File=missing3.sv not found in directory=."
497
+ "File=missing3.sv not found in directory=.",
498
+ windows_path_support=True
487
499
  )
488
500
 
489
501
  def test_bad4(self):
490
502
  '''EDA on a bad target (bad target within deps of 'target_bad4'), explicit deps str'''
491
503
  self.chdir()
492
- rc = self.log_it(command_str='elab target_bad4')
504
+ rc = self.log_it(command_str='export target_bad4')
493
505
  assert rc != 0
494
506
  assert self.is_in_log(
495
507
  "target=./missing_target4: called from ./DEPS.yml::target_bad4::line=39,",
496
- "Target not found in deps_file=./DEPS.yml"
508
+ "Target not found in deps_file=./DEPS.yml",
509
+ windows_path_support=True
497
510
  )
498
511
 
499
512
  def test_bad5(self):
500
513
  '''EDA on a bad target (bad target within deps of 'target_bad4'), explicit deps list'''
501
514
  self.chdir()
502
- rc = self.log_it(command_str='elab target_bad5')
515
+ rc = self.log_it(command_str='export target_bad5')
503
516
  assert rc != 0
504
517
  assert self.is_in_log(
505
518
  "target=./missing_target5: called from ./DEPS.yml::target_bad5::line=43,",
506
- "Target not found in deps_file=./DEPS.yml"
519
+ "Target not found in deps_file=./DEPS.yml",
520
+ windows_path_support=True
507
521
  )
508
522
 
509
523
  def test_bad6(self):
510
524
  '''EDA on a bad target (bad target within deps of 'target_bad4'), deps str'''
511
525
  self.chdir()
512
- rc = self.log_it(command_str='elab target_bad6')
526
+ rc = self.log_it(command_str='export target_bad6')
513
527
  assert rc != 0
514
528
  assert self.is_in_log(
515
529
  "target=./missing_target6: called from ./DEPS.yml::target_bad6::line=47,",
516
- "Target not found in deps_file=./DEPS.yml"
530
+ "Target not found in deps_file=./DEPS.yml",
531
+ windows_path_support=True
532
+
517
533
  )
518
534
 
519
535
  def test_bad7(self):
520
536
  '''EDA on a bad target (bad target within deps of 'target_bad4'), deps list'''
521
537
  self.chdir()
522
- rc = self.log_it(command_str='elab target_bad7')
538
+ rc = self.log_it(command_str='export target_bad7')
523
539
  assert rc != 0
524
540
  assert self.is_in_log(
525
541
  "target=./missing_target7: called from ./DEPS.yml::target_bad7::line=52,",
526
- "Target not found in deps_file=./DEPS.yml"
542
+ "Target not found in deps_file=./DEPS.yml",
543
+ windows_path_support=True
527
544
  )
528
545
 
529
546
  def test_cmd_line_good0(self):
530
547
  '''EDA w/ out DEPS, on file'''
531
548
  self.chdir()
532
- rc = self.log_it(command_str='elab foo.sv')
549
+ rc = self.log_it(command_str='export foo.sv')
533
550
  assert rc == 0
534
551
 
535
552
  def test_cmd_line_good1(self):
536
553
  '''EDA w/ out DEPS, on two files'''
537
554
  self.chdir()
538
- rc = self.log_it(command_str='elab foo.sv foo2.sv')
555
+ rc = self.log_it(command_str='export foo.sv foo2.sv')
539
556
  assert rc == 0
540
557
 
541
558
  def test_cmd_line_bad0(self):
542
559
  '''EDA calling a non-existent target in DEPS file'''
543
560
  self.chdir()
544
- rc = self.log_it(command_str='elab nope_target0')
561
+ rc = self.log_it(command_str='export nope_target0')
545
562
  assert rc != 0
546
563
  assert self.is_in_log(
547
564
  "Trying to resolve command-line target=./nope_target0: was not",
548
- "found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'"
565
+ "found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'",
566
+ windows_path_support=True
549
567
  )
550
568
 
551
569
  def test_cmd_line_bad1(self):
552
570
  '''EDA calling a non-existent target in DEPS file, with file that exists.'''
553
571
  self.chdir()
554
- rc = self.log_it(command_str='elab foo.sv nope_target1')
572
+ rc = self.log_it(command_str='export foo.sv nope_target1')
555
573
  assert rc != 0
556
574
  assert self.is_in_log(
557
575
  "Trying to resolve command-line target=./nope_target1: was not",
558
- "found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'"
576
+ "found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'",
577
+ windows_path_support=True
559
578
  )
560
579
 
561
580
  def test_cmd_line_bad2(self):
562
581
  '''EDA calling a non-existent file w/out DEPS'''
563
582
  self.chdir()
564
- rc = self.log_it(command_str='elab nope_file0.sv')
583
+ rc = self.log_it(command_str='export nope_file0.sv')
565
584
  assert rc != 0
566
585
  assert self.is_in_log(
567
586
  "Trying to resolve command-line target=./nope_file0.sv",
568
- "(file?): File=nope_file0.sv not found in directory=."
587
+ "(file?): File=nope_file0.sv not found in directory=.",
588
+ windows_path_support=True
569
589
  )
570
590
 
571
591
  def test_cmd_line_bad3(self):
572
592
  '''EDA calling a non-existent file w/out DEPS, and a file that does exist.'''
573
593
  self.chdir()
574
- rc = self.log_it(command_str='elab foo2.sv nope_file1.sv')
594
+ rc = self.log_it(command_str='export foo2.sv nope_file1.sv')
575
595
  assert rc != 0
576
596
  assert self.is_in_log(
577
597
  "Trying to resolve command-line target=./nope_file1.sv",
578
- "(file?): File=nope_file1.sv not found in directory=."
598
+ "(file?): File=nope_file1.sv not found in directory=.",
599
+ windows_path_support=True
579
600
  )
580
601
 
581
602
 
@@ -599,6 +620,8 @@ class TestsRequiresIVerilog(Helpers):
599
620
  print(f'{rc=}')
600
621
  assert rc == 0
601
622
 
623
+
624
+ @pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
602
625
  class TestDepsReqs:
603
626
  '''Tests for 'reqs' in the DEPS files. 'reqs' are requirements, like a .pcap or file
604
627
  used by SV $readmemh. They do not fit into normal compilable files (.sv, .v, .vhd[l])
@@ -647,6 +670,7 @@ class TestDepsReqs:
647
670
  assert rc != 0
648
671
 
649
672
 
673
+ @pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
650
674
  def test_deps_command_order():
651
675
  '''Test for various "commands" within a DEPS target. This test checks that command
652
676
  order is preserved in the top-to-bottom deps order, meaning that eda.py has to collect
@@ -684,7 +708,7 @@ def test_deps_command_order():
684
708
  # Added check, we redirected to create eda.log earlier to confirm the targets worked,
685
709
  # but as a general eda.py check, all shell commands should create their own
686
710
  # {target}__shell_0.log file:
687
- work_dir = os.path.join(thispath, 'deps_files/command_order', 'eda.work', 'target_test.sim')
711
+ work_dir = os.path.join(THISPATH, 'deps_files/command_order', 'eda.work', 'target_test.sim')
688
712
  with open(os.path.join(work_dir, 'target_echo_hi__shell_0.log'), encoding='utf-8') as f:
689
713
  text = ''.join(f.readlines()).strip()
690
714
  assert text == 'hi'
@@ -776,7 +800,7 @@ class TestDepsNoFilesTargets(Helpers):
776
800
 
777
801
  class TestDepsTags(Helpers):
778
802
  '''Series of tests for DEPS - target - tags, in ./deps_files/tags_with_tools'''
779
- DEFAULT_DIR = os.path.join(thispath, 'deps_files', 'tags_with_tools')
803
+ DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'tags_with_tools')
780
804
  DEFAULT_LOG = 'eda.log'
781
805
 
782
806
  @pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
@@ -3,6 +3,7 @@
3
3
  # pylint: disable=R0801 # (similar lines in 2+ files)
4
4
 
5
5
  import os
6
+ import sys
6
7
  import pytest
7
8
 
8
9
  from opencos import eda, util, eda_tool_helper, eda_base
@@ -29,7 +30,15 @@ def test_tools_loaded():
29
30
  '''
30
31
  assert config
31
32
  assert len(config.keys()) > 0
32
- assert len(tools_loaded) > 0
33
+
34
+ # It's possible we're running in some container or install that has no tools, for example,
35
+ # Windows.
36
+ if sys.platform.startswith('win') and \
37
+ not helpers.can_run_eda_command('elab', 'sim', config=config):
38
+ # Windows, not handlers for elab or sim:
39
+ pass
40
+ else:
41
+ assert len(tools_loaded) > 0
33
42
 
34
43
  def version_checker(
35
44
  obj: eda_base.Tool, chk_str: str
@@ -95,7 +104,7 @@ def test_err_fatal(command, tool, target, sim_expect_pass):
95
104
 
96
105
 
97
106
  @pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
98
- def test_vivado_xilinx_arg():
107
+ def test_vivado_tool_defines():
99
108
  '''This test attempts to confirm that the following class inheritance works:
100
109
 
101
110
  Command <- CommandDesign <- CommandSim <- CommandSimVivado <- CommandElabVivado
opencos/util.py CHANGED
@@ -582,7 +582,8 @@ def write_shell_command_file(dirpath : str, filename : str, command_lists : list
582
582
  assert type(command_lists) is list, f'{command_lists=}'
583
583
  fullpath = os.path.join(dirpath, filename)
584
584
  with open(fullpath, 'w') as f:
585
- if not bash_path: bash_path = "/bin/bash" # we may not get far, but we'll try
585
+ if not bash_path:
586
+ bash_path = "/bin/bash" # we may not get far, but we'll try
586
587
  f.write('#!' + bash_path + '\n\n')
587
588
  for obj in command_lists:
588
589
  assert isinstance(obj, list), f'{obj=} (obj must be list/ShellCommandList) {command_lists=}'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.31
3
+ Version: 0.2.33
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
@@ -1,10 +1,10 @@
1
1
  opencos/__init__.py,sha256=ZQ7aOCbP0jkIMYGdVrb-mkZX3rnvaK8epBkmp506tXU,85
2
2
  opencos/_version.py,sha256=qN7iBoOv-v4tEZz-Pu9sVUJwefshJOsgdaddn8HcHio,510
3
3
  opencos/_waves_pkg.sv,sha256=1lbhQOVGc3t_R8czYjP40hssP0I3FlZOpHTkI7yKFbI,1251
4
- opencos/deps_helpers.py,sha256=5Ewgt5Eo79CldIx5NqAGf6mmAiLXjyhkJjpqTQo5Lg4,56653
5
- opencos/deps_schema.py,sha256=WXgYpFSKZgpq6OAtCKP3006O_isa5qXOfJYlqARhIcQ,13785
4
+ opencos/deps_helpers.py,sha256=zM9n6k61jaW9SE7VTtfMNaF8oA9UFloEcI685NLOpIQ,56711
5
+ opencos/deps_schema.py,sha256=MhytzXwp071F14RwxqHt78ak8Qruoe4FeK5XSzkO2f0,14658
6
6
  opencos/eda.py,sha256=NCsSAnWANumzP2EzlsT-TvGUEuY8ONpHo-cvTcWC2wM,21261
7
- opencos/eda_base.py,sha256=O8mYRvnP2tPY3Rbm816Ce0cNTYRjIdJXnqJxk7Sixds,75050
7
+ opencos/eda_base.py,sha256=Fi21IIKvYIzyj_vQtaqPwrUXDaszfIixoB80-TQMM2k,77455
8
8
  opencos/eda_config.py,sha256=CXfFVW4QOtP1gpMcX8JhCe93_aibcdbGoS7RAiYQnOs,8561
9
9
  opencos/eda_config_defaults.yml,sha256=SqQEOA8cn4ASM2oHnBiEtb7s-RNMvtDYuJAoqB1jE2w,9326
10
10
  opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
@@ -18,33 +18,33 @@ opencos/files.py,sha256=IB-hTRrVQx884GNqdJX1V1ikSRUS1Vwu-B3yX0AN6FI,1495
18
18
  opencos/names.py,sha256=LMhAE5UH5LEy4zqHp3rOe6tP26i_Dka6JOJzKMo_bkg,1201
19
19
  opencos/oc_cli.py,sha256=kj2OXvgxli2WPj4MQ4zTBb36eFtsP2gsqDdeNeWGL4E,132108
20
20
  opencos/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
21
- opencos/peakrdl_cleanup.py,sha256=hSEmIl_Mud9ZnjJmQg-pdMtzsI5RkWL8ASjKHFhiWtI,504
21
+ opencos/peakrdl_cleanup.py,sha256=vLhSOVs6cEzsi_PwAP4pSXu5_ZMZjDvfK_WmHDLbDac,486
22
22
  opencos/seed.py,sha256=8TA2uXhBuT_lOaQdAKqdReYvfBWi_KuyQCFJzA2rOVM,549
23
- opencos/util.py,sha256=0ddstrUlMv377Uif-jrfkTaWqmontiKbv-Dcy4Ub2E0,27849
23
+ opencos/util.py,sha256=3rADW0tUzq69wdHsa2GB060N1jCAK2Dk0NLx6N6BMcc,27861
24
24
  opencos/commands/__init__.py,sha256=cHermtb27ZOUurW1UIGarQIJihH1oJvpFWwUbk6q2i0,858
25
25
  opencos/commands/build.py,sha256=jI5ul53qfwn6X-yfSdSQIcLBhGtzZUk7r_wKBBmKJI0,1425
26
26
  opencos/commands/elab.py,sha256=m6Gk03wSzX8UkcmReooK7turF7LpqO0IcdOZwJ8XiyI,1596
27
27
  opencos/commands/export.py,sha256=uGDI5_lUhyNB54gFjm9cWv9g_3waEOJN4wOC01oFlCY,3535
28
28
  opencos/commands/flist.py,sha256=rylcisH3X6UZGQY8WkSGHHIYWKi6o-JwtITz7gflsbY,8460
29
- opencos/commands/multi.py,sha256=I0Bj1WkbR27AWfZpKvphOmLMBIin_8wrlWqOEA85bEA,27220
29
+ opencos/commands/multi.py,sha256=__UF5If4WIY_9044gcGEQiU8MTGPro-q3kzQ4v0PMdI,26283
30
30
  opencos/commands/open.py,sha256=unrpGckzg0FE5W3oARq8x0jX7hhV_uM9Oh5FgISHFAg,724
31
31
  opencos/commands/proj.py,sha256=MdHTOtQYG93_gT97dWuSyAgUxX2vi9FRhL0dtc-rM98,1096
32
32
  opencos/commands/sim.py,sha256=phATz_wR0BAH6B_IJ4zyzg_Hptp5hSEOe_-9NLaL_bY,14058
33
- opencos/commands/sweep.py,sha256=VnkViqU452kbeUSFJOki6Cr89nisb5ER7efLhEQZfDc,6869
33
+ opencos/commands/sweep.py,sha256=L4AYF3vQHR-fi0871f1CwrD0y4tydrsNpWSDjVf1xIA,8719
34
34
  opencos/commands/synth.py,sha256=quB-HWS4LKYTiFBHiYarQi4pMnRmt12wQTZpi14VvlE,4355
35
35
  opencos/commands/upload.py,sha256=nlb4nlxrDCQPcabEmH3nP19g4PFILDqFDab4LwJ95Z4,796
36
36
  opencos/commands/waves.py,sha256=SRfjfsqhuszXHylQrgqYiUT3a5CQs9doxJQzuV4Ae0w,7055
37
37
  opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
39
- opencos/tests/helpers.py,sha256=-26Chc38kNjXBRJ1ex1YVicnzlsb1Gr9EGHSt3WY-Y4,7028
39
+ opencos/tests/helpers.py,sha256=M_XwM1vQmcrVEgfvpL7C23VPjiFhPpsCqhAH5ZZqAT4,8211
40
40
  opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
41
- opencos/tests/test_deps_helpers.py,sha256=3WUrS7TnhJfeSFBqLQJ_YZPxB17yrVKxQeAQX78sKpw,5848
41
+ opencos/tests/test_deps_helpers.py,sha256=_nJSgLN6WVlMKqu6sCr29gjQyN3Jj-dVk8Ac64ygpJs,5928
42
42
  opencos/tests/test_deps_schema.py,sha256=mWTWI4wriGXC8UAnaeq_MIvWJOvf08-fPUqUgELptQ4,896
43
- opencos/tests/test_eda.py,sha256=PBvB3VKOWwgWDDn37wa_b2qMWaAl8buIvvgxHjJmYVU,36625
43
+ opencos/tests/test_eda.py,sha256=HCUGCg9yVILIIyyey2XArllgtBV5XHeVVnL5AGglO7I,37786
44
44
  opencos/tests/test_eda_elab.py,sha256=75bJpOaoO8rn1FXFxiE4KSu5FdjZP1IbW6SyTCjM_ao,2553
45
45
  opencos/tests/test_eda_synth.py,sha256=C_1LzblTuK_lHFv_Hh8v3DKUN4hGfxoChYR77GricX4,2871
46
46
  opencos/tests/test_oc_cli.py,sha256=-ZmwVX_CPBXCGT9hXIBEr_XUSIGG2eky89YpSJIbRAg,731
47
- opencos/tests/test_tools.py,sha256=5enJM_-E7KnKjV9-CIzTV7Al07sx2eisKsGS_8fKalg,4907
47
+ opencos/tests/test_tools.py,sha256=LwOEaFDZpo5-a4Vs4kQUYL6dbeE_IrKvEcyY_p-o5bg,5221
48
48
  opencos/tests/deps_files/command_order/DEPS.yml,sha256=vloOzWZ5qU3yGNFaDlrAJdEzYxK6qf8gfac3zqF-0FI,438
49
49
  opencos/tests/deps_files/error_msgs/DEPS.yml,sha256=fYvHouIscOlr8V28bqx9SoxRBpDBLX4AG-AkVXh8qbo,717
50
50
  opencos/tests/deps_files/iverilog_test/DEPS.yml,sha256=vDylEuLt642lhRSvOr3F5ziB5lhPSwkaUGN4_mWJw-c,40
@@ -66,10 +66,10 @@ opencos/tools/tabbycad_yosys.py,sha256=h9kkAi479cZzYfb4R9WBNY_JmR6BgVFj4s3VShnGp
66
66
  opencos/tools/verilator.py,sha256=NNn9xF-YWFE6B8a9BNeuK-10xNCXPzACqyfKdwuqKTY,17996
67
67
  opencos/tools/vivado.py,sha256=cL5IZdudqquddvZqHQGDVmaHSxgBZsMUmAq18q8AgoM,39134
68
68
  opencos/tools/yosys.py,sha256=Nov3zKyliZ6rHwMbH4f8XEkoo9H66T2-MN-KNQTjWFk,3530
69
- opencos_eda-0.2.31.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
70
- opencos_eda-0.2.31.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
71
- opencos_eda-0.2.31.dist-info/METADATA,sha256=pUGvgUFre5nRTgzKjpbHFbPBbmFcpjti8Rs3XOQEj5g,604
72
- opencos_eda-0.2.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- opencos_eda-0.2.31.dist-info/entry_points.txt,sha256=777csZnOi0t9uC8lrdTchl9_Lu11t0UTo9xIKLNabNE,176
74
- opencos_eda-0.2.31.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
75
- opencos_eda-0.2.31.dist-info/RECORD,,
69
+ opencos_eda-0.2.33.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
70
+ opencos_eda-0.2.33.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
71
+ opencos_eda-0.2.33.dist-info/METADATA,sha256=d20V-XOWBX4u8Rqmmr5b5TgTZOClrFYDBVB8_b_TjmA,604
72
+ opencos_eda-0.2.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ opencos_eda-0.2.33.dist-info/entry_points.txt,sha256=777csZnOi0t9uC8lrdTchl9_Lu11t0UTo9xIKLNabNE,176
74
+ opencos_eda-0.2.33.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
75
+ opencos_eda-0.2.33.dist-info/RECORD,,