siliconcompiler 0.30.0__py3-none-any.whl → 0.31.1__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.
Files changed (79) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/sc_install.py +26 -4
  3. siliconcompiler/apps/sc_remote.py +1 -3
  4. siliconcompiler/core.py +24 -9
  5. siliconcompiler/flowgraph.py +11 -23
  6. siliconcompiler/{package.py → package/__init__.py} +62 -176
  7. siliconcompiler/package/git.py +81 -0
  8. siliconcompiler/package/https.py +93 -0
  9. siliconcompiler/remote/schema.py +9 -8
  10. siliconcompiler/report/report.py +4 -3
  11. siliconcompiler/scheduler/__init__.py +127 -113
  12. siliconcompiler/scheduler/docker_runner.py +4 -4
  13. siliconcompiler/scheduler/run_node.py +3 -3
  14. siliconcompiler/scheduler/send_messages.py +1 -1
  15. siliconcompiler/schema/schema_cfg.py +367 -357
  16. siliconcompiler/schema/schema_obj.py +39 -29
  17. siliconcompiler/schema/utils.py +19 -0
  18. siliconcompiler/sphinx_ext/schemagen.py +3 -1
  19. siliconcompiler/templates/replay/replay.sh.j2 +92 -0
  20. siliconcompiler/templates/tcl/manifest.tcl.j2 +1 -1
  21. siliconcompiler/tools/_common/__init__.py +8 -2
  22. siliconcompiler/tools/_common/asic.py +1 -1
  23. siliconcompiler/tools/_common/tcl/sc_pin_constraints.tcl +3 -5
  24. siliconcompiler/tools/genfasm/genfasm.py +1 -1
  25. siliconcompiler/tools/klayout/export.py +5 -0
  26. siliconcompiler/tools/klayout/klayout.py +18 -1
  27. siliconcompiler/tools/klayout/klayout_export.py +4 -1
  28. siliconcompiler/tools/klayout/klayout_operations.py +5 -2
  29. siliconcompiler/tools/klayout/klayout_utils.py +23 -0
  30. siliconcompiler/tools/klayout/operations.py +5 -0
  31. siliconcompiler/tools/magic/magic.py +1 -1
  32. siliconcompiler/tools/openroad/_apr.py +14 -3
  33. siliconcompiler/tools/openroad/antenna_repair.py +2 -1
  34. siliconcompiler/tools/openroad/clock_tree_synthesis.py +2 -1
  35. siliconcompiler/tools/openroad/detailed_placement.py +2 -1
  36. siliconcompiler/tools/openroad/detailed_route.py +8 -0
  37. siliconcompiler/tools/openroad/fillercell_insertion.py +2 -1
  38. siliconcompiler/tools/openroad/global_placement.py +2 -1
  39. siliconcompiler/tools/openroad/pin_placement.py +2 -1
  40. siliconcompiler/tools/openroad/rdlroute.py +4 -0
  41. siliconcompiler/tools/openroad/repair_design.py +2 -1
  42. siliconcompiler/tools/openroad/repair_timing.py +2 -1
  43. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +6 -0
  44. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +1 -0
  45. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +8 -0
  46. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +1 -0
  47. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -0
  48. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -3
  49. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +5 -0
  50. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +1 -0
  51. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -0
  52. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +3 -0
  53. siliconcompiler/tools/openroad/scripts/common/procs.tcl +29 -12
  54. siliconcompiler/tools/openroad/scripts/common/reports.tcl +15 -0
  55. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -0
  56. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -13
  57. siliconcompiler/tools/vpr/vpr.py +86 -6
  58. siliconcompiler/tools/yosys/__init__.py +7 -0
  59. siliconcompiler/tools/yosys/sc_syn.tcl +33 -24
  60. siliconcompiler/tools/yosys/syn_asic.py +27 -0
  61. siliconcompiler/tools/yosys/syn_asic.tcl +27 -0
  62. siliconcompiler/toolscripts/_tools.json +15 -3
  63. siliconcompiler/toolscripts/rhel8/install-yosys-moosic.sh +17 -0
  64. siliconcompiler/toolscripts/rhel8/install-yosys-slang.sh +22 -0
  65. siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +17 -0
  66. siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +22 -0
  67. siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +17 -0
  68. siliconcompiler/toolscripts/ubuntu20/install-yosys-slang.sh +22 -0
  69. siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +17 -0
  70. siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +22 -0
  71. siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +17 -0
  72. siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +22 -0
  73. siliconcompiler/utils/__init__.py +33 -5
  74. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/METADATA +21 -23
  75. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/RECORD +79 -66
  76. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/WHEEL +1 -1
  77. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/entry_points.txt +4 -0
  78. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/LICENSE +0 -0
  79. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # Version number following semver standard.
2
- version = '0.30.0'
2
+ version = '0.31.1'
3
3
 
4
4
  # Default server address for remote runs, if unspecified.
5
5
  default_server = 'https://server.siliconcompiler.com'
@@ -17,7 +17,7 @@ class ChoiceOptional(Container):
17
17
  def __init__(self, choices):
18
18
  super().__init__()
19
19
 
20
- self.__choices = set(choices)
20
+ self.__choices = sorted(set(choices))
21
21
 
22
22
  def __contains__(self, item):
23
23
  if not item:
@@ -30,7 +30,7 @@ class ChoiceOptional(Container):
30
30
 
31
31
  def get_items(self, choices):
32
32
  items = set(choices)
33
- return sorted(list(items))
33
+ return sorted(items)
34
34
 
35
35
 
36
36
  def install_tool(tool, script, build_dir, prefix):
@@ -101,6 +101,19 @@ def print_machine_info():
101
101
  print("Scripts: ", _get_tool_script_dir())
102
102
 
103
103
 
104
+ def __print_summary(successful, failed):
105
+ max_len = 64
106
+ print("#"*max_len)
107
+ if successful:
108
+ msg = f"Installed: {', '.join(sorted(successful))}"
109
+ print(f"# {msg}")
110
+
111
+ if failed:
112
+ msg = f"Failed to install: {failed}"
113
+ print(f"# {msg}")
114
+ print("#"*max_len)
115
+
116
+
104
117
  def _get_tool_script_dir():
105
118
  return Path(siliconcompiler.__file__).parent / "toolscripts"
106
119
 
@@ -143,6 +156,10 @@ def _recommended_tool_groups(tools):
143
156
  return filter_groups
144
157
 
145
158
 
159
+ class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
160
+ pass
161
+
162
+
146
163
  def main():
147
164
  progname = "sc-install"
148
165
  description = """
@@ -174,7 +191,7 @@ To system debugging information (this should only be used to debug):
174
191
  parser = argparse.ArgumentParser(
175
192
  prog=progname,
176
193
  description=description,
177
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
194
+ formatter_class=HelpFormatter)
178
195
 
179
196
  tools = _get_tools_list()
180
197
 
@@ -238,6 +255,7 @@ To system debugging information (this should only be used to debug):
238
255
  args.tool.extend(tool_groups[group])
239
256
 
240
257
  tools_handled = set()
258
+ tools_completed = set()
241
259
  for tool in args.tool:
242
260
  if tool in tools_handled:
243
261
  continue
@@ -246,9 +264,14 @@ To system debugging information (this should only be used to debug):
246
264
  show_tool(tool, tools[tool])
247
265
  else:
248
266
  if not install_tool(tool, tools[tool], args.build_dir, args.prefix):
267
+ __print_summary(tools_completed, tool)
249
268
  return 1
269
+ else:
270
+ tools_completed.add(tool)
250
271
 
251
272
  if not args.show:
273
+ __print_summary(tools_completed, None)
274
+
252
275
  msgs = []
253
276
  for env, path in (
254
277
  ("PATH", "bin"),
@@ -258,7 +281,6 @@ To system debugging information (this should only be used to debug):
258
281
  os.path.expandvars(os.path.expanduser(p))
259
282
  for p in os.getenv(env, "").split(":")
260
283
  ]
261
- print(envs)
262
284
  if check_path not in envs:
263
285
  msgs.extend([
264
286
  "",
@@ -1,5 +1,4 @@
1
1
  # Copyright 2023 Silicon Compiler Authors. All Rights Reserved.
2
- import copy
3
2
  import os
4
3
  import sys
5
4
 
@@ -157,7 +156,6 @@ To delete a job, use:
157
156
  # in its "check_progress/ until job is done" loop.
158
157
  elif args['reconnect']:
159
158
  # Start from successors of entry nodes, so entry nodes are not fetched from remote.
160
- environment = copy.deepcopy(os.environ)
161
159
  flow = chip.get('option', 'flow')
162
160
  entry_nodes = _get_flowgraph_entry_nodes(chip, flow)
163
161
  for entry_node in entry_nodes:
@@ -177,7 +175,7 @@ To delete a job, use:
177
175
  f'{chip.design}.pkg.json')
178
176
  if os.path.exists(manifest):
179
177
  chip.schema.read_journal(manifest)
180
- _finalize_run(chip, environment)
178
+ _finalize_run(chip)
181
179
 
182
180
  # Summarize the run.
183
181
  chip.summary()
siliconcompiler/core.py CHANGED
@@ -392,7 +392,7 @@ class Chip:
392
392
  for vals, step, index in self.schema._getvals(*key):
393
393
  if not vals:
394
394
  continue
395
- if self.get(*key, field='pernode') != 'never':
395
+ if not self.get(*key, field='pernode').is_never():
396
396
  if step is None:
397
397
  step = Schema.GLOBAL_KEY
398
398
  if index is None:
@@ -852,7 +852,7 @@ class Chip:
852
852
  strict = self.schema.get('option', 'strict')
853
853
  if field == 'value' and strict:
854
854
  pernode = self.schema.get(*keypath, field='pernode')
855
- if pernode == 'optional' and \
855
+ if pernode == schema_utils.PerNode.OPTIONAL and \
856
856
  (step is None or index is None) and \
857
857
  (Schema.GLOBAL_KEY not in (step, index)): # allow explicit access to global
858
858
  self.error(
@@ -1111,7 +1111,7 @@ class Chip:
1111
1111
  package_dir = os.path.dirname(os.path.abspath(filename))
1112
1112
 
1113
1113
  def __make_path(rel, path):
1114
- path = utils._resolve_env_vars(self, path)
1114
+ path = utils._resolve_env_vars(self, path, None, None)
1115
1115
  if os.path.isabs(path):
1116
1116
  if path.startswith(rel):
1117
1117
  return os.path.relpath(path, rel), package_name
@@ -1282,7 +1282,7 @@ class Chip:
1282
1282
  """
1283
1283
  strict = self.get('option', 'strict')
1284
1284
  pernode = self.get(*keypath, field='pernode')
1285
- if strict and pernode == 'optional' and (step is None or index is None):
1285
+ if strict and pernode == schema_utils.PerNode.OPTIONAL and (step is None or index is None):
1286
1286
  self.error(
1287
1287
  f"Invalid args to find_files() of keypath {keypath}: step and "
1288
1288
  "index are required for reading from this parameter while "
@@ -1394,7 +1394,9 @@ class Chip:
1394
1394
  result.append(utils.find_sc_file(self,
1395
1395
  path,
1396
1396
  missing_ok=missing_ok,
1397
- search_paths=search_paths))
1397
+ search_paths=search_paths,
1398
+ step=step,
1399
+ index=index))
1398
1400
 
1399
1401
  if self._relative_path and not abs_path_only:
1400
1402
  rel_result = []
@@ -2828,10 +2830,10 @@ class Chip:
2828
2830
  set_step = None
2829
2831
  set_index = None
2830
2832
  pernode = self.get(*keypath, field='pernode')
2831
- if pernode == 'required':
2833
+ if pernode == schema_utils.PerNode.REQUIRED:
2832
2834
  set_step = step
2833
2835
  set_index = index
2834
- elif pernode == 'optional':
2836
+ elif pernode == schema_utils.PerNode.OPTIONAL:
2835
2837
  for vals, key_step, key_index in self.schema._getvals(*keypath):
2836
2838
  if key_step == step and key_index == index and vals:
2837
2839
  set_step = step
@@ -2887,7 +2889,7 @@ class Chip:
2887
2889
  return self._dash
2888
2890
 
2889
2891
  ###########################################################################
2890
- def summary(self, show_all_indices=False, generate_image=True, generate_html=True):
2892
+ def summary(self, show_all_indices=False, generate_image=True, generate_html=False):
2891
2893
  '''
2892
2894
  Prints a summary of the compilation manifest.
2893
2895
 
@@ -2927,6 +2929,10 @@ class Chip:
2927
2929
  results_img = os.path.join(work_dir, f'{self.design}.png')
2928
2930
  results_html = os.path.join(work_dir, 'report.html')
2929
2931
 
2932
+ for path in (results_img, results_html):
2933
+ if os.path.exists(path):
2934
+ os.remove(path)
2935
+
2930
2936
  if generate_image:
2931
2937
  _generate_summary_image(self, results_img)
2932
2938
 
@@ -2944,7 +2950,7 @@ class Chip:
2944
2950
  elif os.path.isfile(results_html):
2945
2951
  _open_html_report(self, results_html)
2946
2952
  else:
2947
- self._dashboard(wait=False)
2953
+ self.dashboard(wait=False)
2948
2954
 
2949
2955
  ###########################################################################
2950
2956
  def clock(self, pin, period, jitter=0, mode='global'):
@@ -3092,6 +3098,13 @@ class Chip:
3092
3098
  step (str): Step name
3093
3099
  index (int): Step index
3094
3100
  '''
3101
+
3102
+ if flow not in self.getkeys('flowgraph'):
3103
+ raise ValueError(f'{flow} is not in the manifest')
3104
+
3105
+ if step not in self.getkeys('flowgraph', flow):
3106
+ raise ValueError(f'{step} is not a valid step in {flow}')
3107
+
3095
3108
  if index is None:
3096
3109
  # Iterate over all indexes
3097
3110
  for index in self.getkeys('flowgraph', flow, step):
@@ -3099,6 +3112,8 @@ class Chip:
3099
3112
  return
3100
3113
 
3101
3114
  index = str(index)
3115
+ if index not in self.getkeys('flowgraph', flow, step):
3116
+ raise ValueError(f'{index} is not a valid index for {step} in {flow}')
3102
3117
 
3103
3118
  # Save input edges
3104
3119
  node = (step, index)
@@ -4,23 +4,6 @@ from siliconcompiler import SiliconCompilerError, NodeStatus
4
4
  from siliconcompiler.tools._common import input_file_node_name, get_tool_task
5
5
 
6
6
 
7
- def _check_execution_nodes_inputs(chip, flow):
8
- for node in nodes_to_execute(chip, flow):
9
- if node in _get_execution_entry_nodes(chip, flow):
10
- continue
11
- pruned_node_inputs = set(_get_pruned_node_inputs(chip, flow, node))
12
- node_inputs = set(_get_flowgraph_node_inputs(chip, flow, node))
13
- tool, task = get_tool_task(chip, node[0], node[1], flow=flow)
14
- if tool == 'builtin' and not pruned_node_inputs or \
15
- tool != 'builtin' and pruned_node_inputs != node_inputs:
16
- chip.logger.warning(
17
- f'Flowgraph connection from {node_inputs.difference(pruned_node_inputs)} '
18
- f'to {node} is missing. '
19
- f'Double check your flowgraph and from/to/prune options.')
20
- return False
21
- return True
22
-
23
-
24
7
  def _nodes_to_execute(chip, flow, from_nodes, to_nodes, prune_nodes):
25
8
  '''
26
9
  Assumes a flowgraph with valid edges for the inputs
@@ -283,12 +266,12 @@ def nodes_to_execute(chip, flow=None):
283
266
  if flow is None:
284
267
  flow = chip.get('option', 'flow')
285
268
 
286
- from_nodes = _get_execution_entry_nodes(chip, flow)
287
- to_nodes = _get_execution_exit_nodes(chip, flow)
288
- prune_nodes = chip.get('option', 'prune')
269
+ from_nodes = set(_get_execution_entry_nodes(chip, flow))
270
+ to_nodes = set(_get_execution_exit_nodes(chip, flow))
271
+ prune_nodes = set(chip.get('option', 'prune'))
289
272
  if from_nodes == to_nodes:
290
273
  return list(filter(lambda node: node not in prune_nodes, from_nodes))
291
- return _nodes_to_execute(chip, flow, set(from_nodes), set(to_nodes), set(prune_nodes))
274
+ return _nodes_to_execute(chip, flow, from_nodes, to_nodes, prune_nodes)
292
275
 
293
276
 
294
277
  ###########################################################################
@@ -345,8 +328,13 @@ def _check_flowgraph(chip, flow=None):
345
328
  chip.logger.error(f'{step} is not defined in the {flow} flowgraph')
346
329
  error = True
347
330
 
348
- if not _check_execution_nodes_inputs(chip, flow):
349
- error = True
331
+ for step, index in chip.get('option', 'prune'):
332
+ if step not in chip.getkeys('flowgraph', flow):
333
+ chip.logger.error(f'{step} is not defined in the {flow} flowgraph')
334
+ error = True
335
+ elif str(index) not in chip.getkeys('flowgraph', flow, step):
336
+ chip.logger.error(f'{step}{index} is not defined in the {flow} flowgraph')
337
+ error = True
350
338
 
351
339
  unreachable_steps = _unreachable_steps_to_execute(chip, flow)
352
340
  if unreachable_steps:
@@ -1,25 +1,20 @@
1
1
  import os
2
- import requests
3
- import tarfile
4
- import zipfile
5
- from git import Repo, GitCommandError
6
2
  from urllib.parse import urlparse
7
3
  import importlib
8
- import shutil
9
4
  import re
10
5
  from siliconcompiler import SiliconCompilerError
11
6
  from siliconcompiler.utils import default_cache_dir, _resolve_env_vars
12
7
  import json
13
8
  from importlib.metadata import distributions, distribution
14
9
  import functools
15
- import fasteners
16
10
  import time
17
11
  from pathlib import Path
18
- from io import BytesIO
19
12
 
20
13
  from github import Github
21
14
  import github.Auth
22
15
 
16
+ from siliconcompiler.utils import get_plugins
17
+
23
18
 
24
19
  def get_cache_path(chip):
25
20
  cache_path = chip.get('option', 'cachedir')
@@ -33,10 +28,45 @@ def get_cache_path(chip):
33
28
  return cache_path
34
29
 
35
30
 
36
- def _path(chip, package, download_handler):
37
- if package in chip._packages:
38
- return chip._packages[package]
31
+ def get_download_cache_path(chip, package, ref):
32
+ cache_path = get_cache_path(chip)
33
+ if not os.path.exists(cache_path):
34
+ os.makedirs(cache_path, exist_ok=True)
35
+
36
+ if ref is None:
37
+ raise SiliconCompilerError(f'Reference is required for cached data: {package}', chip=chip)
38
+
39
+ return \
40
+ os.path.join(cache_path, f'{package}-{ref}'), \
41
+ os.path.join(cache_path, f'{package}-{ref}.lock')
42
+
43
+
44
+ def _file_path_resolver(chip, package, path, ref, url):
45
+ return os.path.abspath(path.replace('file://', ''))
46
+
47
+
48
+ def _python_path_resolver(chip, package, path, ref, url):
49
+ return path_from_python(chip, url.netloc)
39
50
 
51
+
52
+ def _get_path_resolver(path):
53
+ url = urlparse(path)
54
+
55
+ for resolver in get_plugins("path_resolver"):
56
+ func = resolver(url)
57
+ if func:
58
+ return func, url
59
+
60
+ if url.scheme == "file":
61
+ return _file_path_resolver, url
62
+
63
+ if url.scheme == "python":
64
+ return _python_path_resolver, url
65
+
66
+ raise ValueError(f"{path} is not supported")
67
+
68
+
69
+ def _path(chip, package):
40
70
  # Initially try retrieving data source from schema
41
71
  data = {}
42
72
  data['path'] = chip.get('package', 'source', package, 'path')
@@ -46,50 +76,15 @@ def _path(chip, package, download_handler):
46
76
  f'Could not find package source for {package} in schema. '
47
77
  'You can use register_source() to add it.', chip=chip)
48
78
 
49
- data['path'] = _resolve_env_vars(chip, data['path'])
79
+ data['path'] = _resolve_env_vars(chip, data['path'], None, None)
50
80
 
51
- url = urlparse(data['path'])
81
+ if os.path.exists(data['path']):
82
+ # Path is already a path
83
+ return os.path.abspath(data['path'])
52
84
 
53
- # check network drive for package data source
54
- if data['path'].startswith('file://') or os.path.exists(data['path']):
55
- path = os.path.abspath(data['path'].replace('file://', ''))
56
- chip.logger.info(f'Found {package} data at {path}')
57
- chip._packages[package] = path
58
- return path
59
- elif data['path'].startswith('python://'):
60
- path = path_from_python(chip, url.netloc)
61
- chip.logger.info(f'Found {package} data at {path}')
62
- chip._packages[package] = path
63
- return path
64
-
65
- # location of the python package
66
- cache_path = get_cache_path(chip)
67
- if not os.path.exists(cache_path):
68
- os.makedirs(cache_path, exist_ok=True)
69
- project_id = f'{package}-{data.get("ref")}'
70
- if url.scheme not in ['git', 'git+https', 'https', 'git+ssh', 'ssh'] or not project_id:
71
- raise SiliconCompilerError(
72
- f'Could not find data path in package {package}: {data["path"]}',
73
- chip=chip)
74
-
75
- data_path = os.path.join(cache_path, project_id)
76
-
77
- if download_handler:
78
- download_handler(chip,
79
- package,
80
- data,
81
- url,
82
- data_path,
83
- os.path.join(cache_path, f'{project_id}.lock'))
84
-
85
- if os.path.exists(data_path):
86
- if package not in chip._packages:
87
- chip.logger.info(f'Saved {package} data to {data_path}')
88
- chip._packages[package] = data_path
89
- return data_path
85
+ path_resolver, url = _get_path_resolver(data['path'])
90
86
 
91
- raise SiliconCompilerError(f'Extracting {package} data to {data_path} failed',
92
- chip=chip)
87
+ return path_resolver(chip, package, data['path'], data['ref'], url)
93
88
 
94
89
 
95
90
  def path(chip, package):
@@ -102,40 +97,25 @@ def path(chip, package):
102
97
  path: Location of data source on the local system
103
98
  """
104
99
 
105
- return _path(chip, package, __download_data)
106
-
100
+ if package not in chip._packages:
101
+ changed = False
102
+ data_path = _path(chip, package)
107
103
 
108
- def __download_data(chip, package, data, url, data_path, data_path_lock):
109
- data_lock = fasteners.InterProcessLock(data_path_lock)
104
+ if isinstance(data_path, tuple) and len(data_path) == 2:
105
+ data_path, changed = data_path
110
106
 
111
- _aquire_data_lock(data_path, data_lock)
107
+ if os.path.exists(data_path):
108
+ if package not in chip._packages and changed:
109
+ chip.logger.info(f'Saved {package} data to {data_path}')
110
+ else:
111
+ chip.logger.info(f'Found {package} data at {data_path}')
112
112
 
113
- # check cached package data source
114
- if os.path.exists(data_path):
115
- chip.logger.info(f'Found cached {package} data at {data_path}')
116
- if url.scheme in ['git', 'git+https', 'ssh', 'git+ssh']:
117
- try:
118
- repo = Repo(data_path)
119
- if repo.untracked_files or repo.index.diff("HEAD"):
120
- chip.logger.warning('The repo of the cached data is dirty.')
121
- _release_data_lock(data_lock)
122
- chip._packages[package] = data_path
123
- return
124
- except GitCommandError:
125
- chip.logger.warning('Deleting corrupted cache data.')
126
- shutil.rmtree(path)
127
- else:
128
- _release_data_lock(data_lock)
129
113
  chip._packages[package] = data_path
130
- return
131
-
132
- # download package data source
133
- if url.scheme in ['git', 'git+https', 'ssh', 'git+ssh']:
134
- clone_synchronized(chip, package, data, data_path)
135
- elif url.scheme == 'https':
136
- extract_from_url(chip, package, data, data_path)
114
+ else:
115
+ raise SiliconCompilerError(f'Unable to locate {package} data in {data_path}',
116
+ chip=chip)
137
117
 
138
- _release_data_lock(data_lock)
118
+ return chip._packages[package]
139
119
 
140
120
 
141
121
  def __get_filebased_lock(data_lock):
@@ -143,7 +123,7 @@ def __get_filebased_lock(data_lock):
143
123
  return Path(f'{base}.sc_lock')
144
124
 
145
125
 
146
- def _aquire_data_lock(data_path, data_lock):
126
+ def aquire_data_lock(data_path, data_lock):
147
127
  # Wait a maximum of 10 minutes for other processes to finish
148
128
  max_seconds = 10 * 60
149
129
  try:
@@ -168,7 +148,7 @@ def _aquire_data_lock(data_path, data_lock):
168
148
  'please delete it.')
169
149
 
170
150
 
171
- def _release_data_lock(data_lock):
151
+ def release_data_lock(data_lock):
172
152
  # Check if file based locking method was used
173
153
  lock_file = __get_filebased_lock(data_lock)
174
154
  if lock_file.exists():
@@ -178,100 +158,6 @@ def _release_data_lock(data_lock):
178
158
  data_lock.release()
179
159
 
180
160
 
181
- def clone_synchronized(chip, package, data, data_path):
182
- url = urlparse(data['path'])
183
- try:
184
- clone_from_git(chip, package, data, data_path)
185
- except GitCommandError as e:
186
- if 'Permission denied' in repr(e):
187
- if url.scheme in ['ssh', 'git+ssh']:
188
- chip.logger.error('Failed to authenticate. Please setup your git ssh.')
189
- elif url.scheme in ['git', 'git+https']:
190
- chip.logger.error('Failed to authenticate. Please use a token or ssh.')
191
- else:
192
- chip.logger.error(str(e))
193
-
194
-
195
- def clone_from_git(chip, package, data, repo_path):
196
- url = urlparse(data['path'])
197
- if url.scheme in ['git', 'git+https'] and url.username:
198
- chip.logger.warning('Your token is in the data source path and will be stored in the '
199
- 'schema. If you do not want this set the env variable GIT_TOKEN '
200
- 'or use ssh for authentication.')
201
- if url.scheme in ['git+ssh', 'ssh']:
202
- chip.logger.info(f'Cloning {package} data from {url.netloc}:{url.path[1:]}')
203
- # Git requires the format git@github.com:org/repo instead of git@github.com/org/repo
204
- repo = Repo.clone_from(f'{url.netloc}:{url.path[1:]}',
205
- repo_path,
206
- recurse_submodules=True)
207
- else:
208
- if os.environ.get('GIT_TOKEN') and not url.username:
209
- url = url._replace(netloc=f'{os.environ.get("GIT_TOKEN")}@{url.hostname}')
210
- url = url._replace(scheme='https')
211
- chip.logger.info(f'Cloning {package} data from {url.geturl()}')
212
- repo = Repo.clone_from(url.geturl(), repo_path, recurse_submodules=True)
213
- chip.logger.info(f'Checking out {data["ref"]}')
214
- repo.git.checkout(data["ref"])
215
- for submodule in repo.submodules:
216
- submodule.update(init=True)
217
-
218
-
219
- def extract_from_url(chip, package, data, data_path):
220
- url = urlparse(data['path'])
221
- data_url = data.get('path')
222
- headers = {}
223
- if os.environ.get('GIT_TOKEN') or url.username:
224
- headers['Authorization'] = f'token {os.environ.get("GIT_TOKEN") or url.username}'
225
- if "github" in data_url:
226
- headers['Accept'] = 'application/octet-stream'
227
- data_url = data['path']
228
- if data_url.endswith('/'):
229
- data_url = f"{data_url}{data['ref']}.tar.gz"
230
- chip.logger.info(f'Downloading {package} data from {data_url}')
231
- response = requests.get(data_url, stream=True, headers=headers)
232
- if not response.ok:
233
- raise SiliconCompilerError(f'Failed to download {package} data source.', chip=chip)
234
-
235
- fileobj = BytesIO(response.content)
236
- try:
237
- with tarfile.open(fileobj=fileobj, mode='r|gz') as tar_ref:
238
- tar_ref.extractall(path=data_path)
239
- except tarfile.ReadError:
240
- fileobj.seek(0)
241
- # Try as zip
242
- with zipfile.ZipFile(fileobj) as zip_ref:
243
- zip_ref.extractall(path=data_path)
244
-
245
- if 'github' in url.netloc and len(os.listdir(data_path)) == 1:
246
- # Github inserts one folder at the highest level of the tar file
247
- # this compensates for this behavior
248
- gh_url = urlparse(data_url)
249
-
250
- repo = gh_url.path.split('/')[2]
251
-
252
- ref = gh_url.path.split('/')[-1]
253
- if repo.endswith('.git'):
254
- ref = data['ref']
255
- elif ref.endswith('.tar.gz'):
256
- ref = ref[0:-7]
257
- elif ref.endswith('.tgz'):
258
- ref = ref[0:-4]
259
- else:
260
- ref = ref.split('.')[0]
261
-
262
- if ref.startswith('v'):
263
- ref = ref[1:]
264
-
265
- github_folder = f"{repo}-{ref}"
266
-
267
- if github_folder in os.listdir(data_path):
268
- # This moves all files one level up
269
- git_path = os.path.join(data_path, github_folder)
270
- for data_file in os.listdir(git_path):
271
- shutil.move(os.path.join(git_path, data_file), data_path)
272
- os.removedirs(git_path)
273
-
274
-
275
161
  def path_from_python(chip, python_package, append_path=None):
276
162
  try:
277
163
  module = importlib.import_module(python_package)
@@ -0,0 +1,81 @@
1
+ import shutil
2
+
3
+ import os.path
4
+
5
+ from git import Repo, GitCommandError
6
+ from fasteners import InterProcessLock
7
+
8
+ from siliconcompiler.package import get_download_cache_path
9
+ from siliconcompiler.package import aquire_data_lock, release_data_lock
10
+
11
+
12
+ def get_resolver(url):
13
+ if url.scheme in ("git", "git+https", "git+ssh", "ssh"):
14
+ return git_resolver
15
+ return None
16
+
17
+
18
+ def git_resolver(chip, package, path, ref, url):
19
+ data_path, data_path_lock = get_download_cache_path(chip, package, ref)
20
+
21
+ # Acquire lock
22
+ data_lock = InterProcessLock(data_path_lock)
23
+ aquire_data_lock(data_path, data_lock)
24
+
25
+ if os.path.exists(data_path):
26
+ try:
27
+ repo = Repo(data_path)
28
+ if repo.untracked_files or repo.index.diff("HEAD"):
29
+ chip.logger.warning('The repo of the cached data is dirty.')
30
+ release_data_lock(data_lock)
31
+ return data_path, False
32
+ except GitCommandError:
33
+ chip.logger.warning('Deleting corrupted cache data.')
34
+ shutil.rmtree(data_path)
35
+
36
+ clone_synchronized(chip, package, path, ref, url, data_path)
37
+
38
+ release_data_lock(data_lock)
39
+
40
+ return data_path, True
41
+
42
+
43
+ def clone_synchronized(chip, package, path, ref, url, data_path):
44
+ try:
45
+ clone_from_git(chip, package, path, ref, url, data_path)
46
+ except GitCommandError as e:
47
+ if 'Permission denied' in repr(e):
48
+ if url.scheme in ['ssh', 'git+ssh']:
49
+ chip.logger.error('Failed to authenticate. Please setup your git ssh.')
50
+ elif url.scheme in ['git', 'git+https']:
51
+ chip.logger.error('Failed to authenticate. Please use a token or ssh.')
52
+ else:
53
+ chip.logger.error(str(e))
54
+
55
+
56
+ def clone_from_git(chip, package, path, ref, url, data_path):
57
+ if url.scheme in ['git', 'git+https'] and url.username:
58
+ chip.logger.warning('Your token is in the data source path and will be stored in the '
59
+ 'schema. If you do not want this set the env variable GIT_TOKEN '
60
+ 'or use ssh for authentication.')
61
+ if url.scheme in ['git+ssh']:
62
+ chip.logger.info(f'Cloning {package} data from {url.netloc}:{url.path[1:]}')
63
+ # Git requires the format git@github.com:org/repo instead of git@github.com/org/repo
64
+ repo = Repo.clone_from(f'{url.netloc}/{url.path[1:]}',
65
+ data_path,
66
+ recurse_submodules=True)
67
+ elif url.scheme in ['ssh']:
68
+ chip.logger.info(f'Cloning {package} data from {path}')
69
+ repo = Repo.clone_from(path,
70
+ data_path,
71
+ recurse_submodules=True)
72
+ else:
73
+ if os.environ.get('GIT_TOKEN') and not url.username:
74
+ url = url._replace(netloc=f'{os.environ.get("GIT_TOKEN")}@{url.hostname}')
75
+ url = url._replace(scheme='https')
76
+ chip.logger.info(f'Cloning {package} data from {url.geturl()}')
77
+ repo = Repo.clone_from(url.geturl(), data_path, recurse_submodules=True)
78
+ chip.logger.info(f'Checking out {ref}')
79
+ repo.git.checkout(ref)
80
+ for submodule in repo.submodules:
81
+ submodule.update(init=True)