siliconcompiler 0.33.1__py3-none-any.whl → 0.34.0__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.
- siliconcompiler/__init__.py +2 -0
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_issue.py +5 -3
- siliconcompiler/apps/sc_remote.py +0 -17
- siliconcompiler/apps/utils/replay.py +5 -5
- siliconcompiler/checklist.py +1 -1
- siliconcompiler/core.py +39 -48
- siliconcompiler/data/templates/replay/replay.sh.j2 +18 -1
- siliconcompiler/dependencyschema.py +392 -0
- siliconcompiler/design.py +664 -0
- siliconcompiler/flowgraph.py +32 -1
- siliconcompiler/metric.py +19 -0
- siliconcompiler/package/__init__.py +383 -223
- siliconcompiler/package/git.py +75 -77
- siliconcompiler/package/github.py +70 -97
- siliconcompiler/package/https.py +77 -93
- siliconcompiler/packageschema.py +260 -0
- siliconcompiler/pdk.py +2 -2
- siliconcompiler/record.py +57 -5
- siliconcompiler/remote/client.py +61 -13
- siliconcompiler/remote/server.py +109 -64
- siliconcompiler/report/dashboard/cli/board.py +1 -2
- siliconcompiler/scheduler/__init__.py +3 -1375
- siliconcompiler/scheduler/docker.py +268 -0
- siliconcompiler/scheduler/run_node.py +20 -19
- siliconcompiler/scheduler/scheduler.py +308 -0
- siliconcompiler/scheduler/schedulernode.py +934 -0
- siliconcompiler/scheduler/slurm.py +147 -163
- siliconcompiler/scheduler/taskscheduler.py +39 -52
- siliconcompiler/schema/__init__.py +3 -3
- siliconcompiler/schema/baseschema.py +256 -11
- siliconcompiler/schema/editableschema.py +4 -0
- siliconcompiler/schema/journal.py +210 -0
- siliconcompiler/schema/namedschema.py +31 -2
- siliconcompiler/schema/parameter.py +14 -1
- siliconcompiler/schema/parametervalue.py +1 -34
- siliconcompiler/schema/schema_cfg.py +211 -350
- siliconcompiler/tool.py +139 -37
- siliconcompiler/tools/_common/__init__.py +14 -11
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/verify.py +1 -2
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +27 -25
- siliconcompiler/tools/slang/__init__.py +3 -2
- siliconcompiler/tools/vpr/route.py +69 -0
- siliconcompiler/tools/yosys/sc_synth_asic.tcl +0 -4
- siliconcompiler/toolscripts/_tools.json +13 -8
- siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +4 -0
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +4 -0
- siliconcompiler/utils/__init__.py +2 -23
- siliconcompiler/utils/flowgraph.py +5 -5
- siliconcompiler/utils/logging.py +2 -1
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/METADATA +8 -6
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/RECORD +57 -52
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/WHEEL +1 -1
- siliconcompiler/scheduler/docker_runner.py +0 -254
- siliconcompiler/schema/journalingschema.py +0 -238
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/top_level.txt +0 -0
siliconcompiler/metric.py
CHANGED
|
@@ -44,6 +44,25 @@ class MetricSchema(BaseSchema):
|
|
|
44
44
|
|
|
45
45
|
return self.set(metric, value, step=step, index=str(index))
|
|
46
46
|
|
|
47
|
+
def record_tasktime(self, step, index, record):
|
|
48
|
+
"""
|
|
49
|
+
Record the task time for this node
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
step (str): step to record
|
|
53
|
+
index (str/int): index to record
|
|
54
|
+
record (:class:`RecordSchema`): record to lookup data in
|
|
55
|
+
"""
|
|
56
|
+
start_time, end_time = [
|
|
57
|
+
record.get_recorded_time(step, index, RecordTime.START),
|
|
58
|
+
record.get_recorded_time(step, index, RecordTime.END)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
if start_time is None or end_time is None:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
return self.record(step, index, "tasktime", end_time-start_time, unit="s")
|
|
65
|
+
|
|
47
66
|
def record_totaltime(self, step, index, flow, record):
|
|
48
67
|
"""
|
|
49
68
|
Record the total time for this node
|
|
@@ -1,279 +1,439 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import contextlib
|
|
2
|
+
import functools
|
|
3
3
|
import importlib
|
|
4
|
-
import re
|
|
5
|
-
from siliconcompiler import SiliconCompilerError
|
|
6
|
-
from siliconcompiler.utils import default_cache_dir
|
|
7
4
|
import json
|
|
8
|
-
|
|
9
|
-
import
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
10
8
|
import time
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from siliconcompiler.utils import get_plugins, get_env_vars
|
|
14
|
-
from siliconcompiler.schema.parametervalue import PathNodeValue
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_cache_path(chip):
|
|
18
|
-
cache_path = chip.get('option', 'cachedir')
|
|
19
|
-
if cache_path:
|
|
20
|
-
cache_path = chip.find_files('option', 'cachedir', missing_ok=True)
|
|
21
|
-
if not cache_path:
|
|
22
|
-
cache_path = os.path.join(chip.cwd, chip.get('option', 'cachedir'))
|
|
23
|
-
if not cache_path:
|
|
24
|
-
cache_path = default_cache_dir()
|
|
25
|
-
|
|
26
|
-
return cache_path
|
|
27
|
-
|
|
9
|
+
import threading
|
|
28
10
|
|
|
29
|
-
|
|
30
|
-
cache_path = get_cache_path(chip)
|
|
31
|
-
if not os.path.exists(cache_path):
|
|
32
|
-
os.makedirs(cache_path, exist_ok=True)
|
|
11
|
+
import os.path
|
|
33
12
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
os.path.join(cache_path, f'{package}-{ref}'), \
|
|
39
|
-
os.path.join(cache_path, f'{package}-{ref}.lock')
|
|
13
|
+
from fasteners import InterProcessLock
|
|
14
|
+
from importlib.metadata import distributions, distribution
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from urllib import parse as url_parse
|
|
40
17
|
|
|
18
|
+
from siliconcompiler.utils import get_plugins
|
|
41
19
|
|
|
42
|
-
def _file_path_resolver(chip, package, path, ref, url, fetch):
|
|
43
|
-
return os.path.abspath(path.replace('file://', ''))
|
|
44
20
|
|
|
21
|
+
def path(chip, package):
|
|
22
|
+
import warnings
|
|
23
|
+
warnings.warn("The 'path' method has been deprecated",
|
|
24
|
+
DeprecationWarning)
|
|
25
|
+
return chip.get("package", field="schema").get_resolver(package).get_path()
|
|
45
26
|
|
|
46
|
-
def _python_path_resolver(chip, package, path, ref, url, fetch):
|
|
47
|
-
return path_from_python(chip, url.netloc)
|
|
48
27
|
|
|
28
|
+
def register_python_data_source(chip,
|
|
29
|
+
package_name,
|
|
30
|
+
python_module,
|
|
31
|
+
alternative_path,
|
|
32
|
+
alternative_ref=None,
|
|
33
|
+
python_module_path_append=None):
|
|
34
|
+
'''
|
|
35
|
+
Helper function to register a python module as data source with an alternative in case
|
|
36
|
+
the module is not installed in an editable state
|
|
37
|
+
'''
|
|
38
|
+
import warnings
|
|
39
|
+
warnings.warn("The 'register_python_data_source' method was renamed "
|
|
40
|
+
"PythonPathResolver.register_source",
|
|
41
|
+
DeprecationWarning)
|
|
42
|
+
|
|
43
|
+
PythonPathResolver.register_source(
|
|
44
|
+
chip, package_name, python_module, alternative_path,
|
|
45
|
+
alternative_ref=alternative_ref,
|
|
46
|
+
python_module_path_append=python_module_path_append)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Resolver:
|
|
50
|
+
_RESOLVERS_LOCK = threading.Lock()
|
|
51
|
+
_RESOLVERS = {}
|
|
52
|
+
|
|
53
|
+
def __init__(self, name, root, source, reference=None):
|
|
54
|
+
self.__name = name
|
|
55
|
+
self.__root = root
|
|
56
|
+
self.__source = source
|
|
57
|
+
self.__reference = reference
|
|
58
|
+
self.__changed = False
|
|
59
|
+
self.__cache = {}
|
|
60
|
+
|
|
61
|
+
if self.__root and hasattr(self.__root, "logger"):
|
|
62
|
+
self.__logger = self.__root.logger.getChild(f"resolver-{self.name}")
|
|
63
|
+
else:
|
|
64
|
+
self.__logger = logging.getLogger(f"resolver-{self.name}")
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
else:
|
|
55
|
-
paths = chip.find_files(*key, step=chip.get('arg', 'step'), index=chip.get('arg', 'index'))
|
|
66
|
+
@staticmethod
|
|
67
|
+
def populate_resolvers():
|
|
68
|
+
with Resolver._RESOLVERS_LOCK:
|
|
69
|
+
Resolver._RESOLVERS.clear()
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
Resolver._RESOLVERS.update({
|
|
72
|
+
"": FileResolver,
|
|
73
|
+
"file": FileResolver,
|
|
74
|
+
"key": KeyPathResolver,
|
|
75
|
+
"python": PythonPathResolver
|
|
76
|
+
})
|
|
60
77
|
|
|
78
|
+
for resolver in get_plugins("path_resolver"):
|
|
79
|
+
Resolver._RESOLVERS.update(resolver())
|
|
61
80
|
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
@staticmethod
|
|
82
|
+
def find_resolver(source):
|
|
83
|
+
if os.path.isabs(source):
|
|
84
|
+
return FileResolver
|
|
64
85
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if func:
|
|
68
|
-
return func, url
|
|
86
|
+
if not Resolver._RESOLVERS:
|
|
87
|
+
Resolver.populate_resolvers()
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
url = url_parse.urlparse(source)
|
|
90
|
+
with Resolver._RESOLVERS_LOCK:
|
|
91
|
+
if url.scheme in Resolver._RESOLVERS:
|
|
92
|
+
return Resolver._RESOLVERS[url.scheme]
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
return _file_path_resolver, url
|
|
94
|
+
raise ValueError(f"{source} is not supported")
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
@property
|
|
97
|
+
def name(self) -> str:
|
|
98
|
+
return self.__name
|
|
78
99
|
|
|
79
|
-
|
|
100
|
+
@property
|
|
101
|
+
def root(self):
|
|
102
|
+
return self.__root
|
|
80
103
|
|
|
104
|
+
@property
|
|
105
|
+
def logger(self) -> logging.Logger:
|
|
106
|
+
return self.__logger
|
|
81
107
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
data['path'] = chip.get('package', 'source', package, 'path')
|
|
86
|
-
data['ref'] = chip.get('package', 'source', package, 'ref')
|
|
87
|
-
if not data['path']:
|
|
88
|
-
if package.startswith("key://"):
|
|
89
|
-
data['path'] = package
|
|
90
|
-
else:
|
|
91
|
-
raise SiliconCompilerError(
|
|
92
|
-
f'Could not find package source for {package} in schema. '
|
|
93
|
-
'You can use register_source() to add it.', chip=chip)
|
|
108
|
+
@property
|
|
109
|
+
def source(self) -> str:
|
|
110
|
+
return self.__source
|
|
94
111
|
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
@property
|
|
113
|
+
def reference(self) -> str:
|
|
114
|
+
return self.__reference
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
116
|
+
@property
|
|
117
|
+
def urlparse(self) -> url_parse.ParseResult:
|
|
118
|
+
return url_parse.urlparse(self.__resolve_env(self.source))
|
|
101
119
|
|
|
102
|
-
|
|
120
|
+
@property
|
|
121
|
+
def urlscheme(self) -> str:
|
|
122
|
+
return self.urlparse.scheme
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
@property
|
|
125
|
+
def urlpath(self) -> str:
|
|
126
|
+
return self.urlparse.netloc
|
|
105
127
|
|
|
128
|
+
@property
|
|
129
|
+
def changed(self):
|
|
130
|
+
change = self.__changed
|
|
131
|
+
self.__changed = False
|
|
132
|
+
return change
|
|
106
133
|
|
|
107
|
-
def
|
|
108
|
-
|
|
109
|
-
Compute data source data path
|
|
110
|
-
Additionally cache data source data if possible
|
|
111
|
-
Parameters:
|
|
112
|
-
package (str): Name of the data source
|
|
113
|
-
fetch (bool): Flag to indicate that the path should be fetched
|
|
114
|
-
Returns:
|
|
115
|
-
path: Location of data source on the local system
|
|
116
|
-
"""
|
|
134
|
+
def set_changed(self):
|
|
135
|
+
self.__changed = True
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
data_path = _path(chip, package, fetch)
|
|
137
|
+
def set_cache(self, cache):
|
|
138
|
+
self.__cache = cache
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
def resolve(self):
|
|
141
|
+
raise NotImplementedError("child class must implement this")
|
|
124
142
|
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
def get_path(self):
|
|
144
|
+
if self.name in self.__cache:
|
|
145
|
+
return self.__cache[self.name]
|
|
127
146
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else:
|
|
132
|
-
chip.logger.info(f'Found {package} data at {data_path}')
|
|
147
|
+
path = self.resolve()
|
|
148
|
+
if not os.path.exists(path):
|
|
149
|
+
raise FileNotFoundError(f"Unable to locate {self.name} at {path}")
|
|
133
150
|
|
|
134
|
-
|
|
151
|
+
if self.changed and self.name not in self.__cache:
|
|
152
|
+
self.logger.info(f'Saved {self.name} data to {path}')
|
|
135
153
|
else:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
self.logger.info(f'Found {self.name} data at {path}')
|
|
155
|
+
self.__cache[self.name] = path
|
|
156
|
+
return self.__cache[self.name]
|
|
157
|
+
|
|
158
|
+
def __resolve_env(self, path):
|
|
159
|
+
env_save = os.environ.copy()
|
|
160
|
+
|
|
161
|
+
if self.root:
|
|
162
|
+
schema_env = {}
|
|
163
|
+
if self.root.valid("option", "env"):
|
|
164
|
+
for env in self.root.getkeys('option', 'env'):
|
|
165
|
+
schema_env[env] = self.root.get('option', 'env', env)
|
|
166
|
+
os.environ.update(schema_env)
|
|
167
|
+
|
|
168
|
+
path = os.path.expandvars(path)
|
|
169
|
+
path = os.path.expanduser(path)
|
|
170
|
+
os.environ.clear()
|
|
171
|
+
os.environ.update(env_save)
|
|
172
|
+
return path
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class RemoteResolver(Resolver):
|
|
176
|
+
_CACHE_LOCKS = {}
|
|
177
|
+
_CACHE_LOCK = threading.Lock()
|
|
178
|
+
|
|
179
|
+
def __init__(self, name, root, source, reference=None):
|
|
180
|
+
if reference is None:
|
|
181
|
+
raise ValueError(f'Reference is required for cached data: {name}')
|
|
182
|
+
|
|
183
|
+
super().__init__(name, root, source, reference)
|
|
184
|
+
|
|
185
|
+
# Wait a maximum of 10 minutes for other processes to finish
|
|
186
|
+
self.__max_lock_wait = 60 * 10
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def timeout(self):
|
|
190
|
+
return self.__max_lock_wait
|
|
191
|
+
|
|
192
|
+
def set_timeout(self, value):
|
|
193
|
+
self.__max_lock_wait = value
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def determine_cache_dir(root) -> Path:
|
|
197
|
+
default_path = os.path.join(Path.home(), '.sc', 'cache')
|
|
198
|
+
if not root:
|
|
199
|
+
return Path(default_path)
|
|
200
|
+
|
|
201
|
+
if root.valid('option', 'cachedir'):
|
|
202
|
+
path = root.get('option', 'cachedir')
|
|
203
|
+
if path:
|
|
204
|
+
path = root.find_files('option', 'cachedir', missing_ok=True)
|
|
205
|
+
if not path:
|
|
206
|
+
path = os.path.join(getattr(root, "cwd", os.getcwd()),
|
|
207
|
+
root.get('option', 'cachedir'))
|
|
208
|
+
if not path:
|
|
209
|
+
path = default_path
|
|
210
|
+
|
|
211
|
+
return Path(path)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def cache_dir(self) -> Path:
|
|
215
|
+
return RemoteResolver.determine_cache_dir(self.root)
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def cache_name(self) -> str:
|
|
219
|
+
return f"{self.name}-{self.reference}"
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def cache_path(self) -> Path:
|
|
223
|
+
cache_dir = self.cache_dir
|
|
224
|
+
if not os.path.exists(cache_dir):
|
|
225
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
226
|
+
|
|
227
|
+
return self.cache_dir / self.cache_name
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def lock_file(self) -> Path:
|
|
231
|
+
cache_dir = self.cache_dir
|
|
232
|
+
if not os.path.exists(cache_dir):
|
|
233
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
234
|
+
|
|
235
|
+
return self.cache_dir / f"{self.cache_name}.lock"
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def sc_lock_file(self) -> Path:
|
|
239
|
+
cache_dir = self.cache_dir
|
|
240
|
+
if not os.path.exists(cache_dir):
|
|
241
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
242
|
+
|
|
243
|
+
return self.cache_dir / f"{self.cache_name}.sc_lock"
|
|
244
|
+
|
|
245
|
+
def thread_lock(self):
|
|
246
|
+
with RemoteResolver._CACHE_LOCK:
|
|
247
|
+
if self.name not in RemoteResolver._CACHE_LOCKS:
|
|
248
|
+
RemoteResolver._CACHE_LOCKS[self.name] = threading.Lock()
|
|
249
|
+
return RemoteResolver._CACHE_LOCKS[self.name]
|
|
250
|
+
|
|
251
|
+
@contextlib.contextmanager
|
|
252
|
+
def lock(self):
|
|
253
|
+
lock = self.thread_lock()
|
|
254
|
+
lock_acquired = False
|
|
255
|
+
try:
|
|
256
|
+
if lock.acquire_lock(timeout=self.timeout):
|
|
257
|
+
data_path_lock = InterProcessLock(self.lock_file)
|
|
258
|
+
sc_data_path_lock = None
|
|
259
|
+
try:
|
|
260
|
+
lock_acquired = data_path_lock.acquire(timeout=self.timeout)
|
|
261
|
+
except (OSError, RuntimeError):
|
|
262
|
+
if not lock_acquired:
|
|
263
|
+
sc_data_path_lock = Path(self.sc_lock_file)
|
|
264
|
+
max_seconds = self.timeout
|
|
265
|
+
while sc_data_path_lock.exists():
|
|
266
|
+
if max_seconds == 0:
|
|
267
|
+
raise RuntimeError(f'Failed to access {self.cache_path}. '
|
|
268
|
+
f'Lock {sc_data_path_lock} still exists.')
|
|
269
|
+
time.sleep(1)
|
|
270
|
+
max_seconds -= 1
|
|
271
|
+
sc_data_path_lock.touch()
|
|
272
|
+
lock_acquired = True
|
|
273
|
+
if lock_acquired:
|
|
274
|
+
yield
|
|
275
|
+
finally:
|
|
276
|
+
if lock.locked():
|
|
277
|
+
lock.release()
|
|
278
|
+
if lock_acquired:
|
|
279
|
+
if data_path_lock.acquired:
|
|
280
|
+
data_path_lock.release()
|
|
281
|
+
if sc_data_path_lock:
|
|
282
|
+
sc_data_path_lock.unlink(missing_ok=True)
|
|
283
|
+
|
|
284
|
+
if not lock_acquired:
|
|
285
|
+
raise RuntimeError(f'Failed to access {self.cache_path}. '
|
|
286
|
+
f'{self.lock_file} is still locked, if this is a mistake, '
|
|
287
|
+
'please delete it.')
|
|
141
288
|
|
|
142
|
-
def
|
|
143
|
-
|
|
144
|
-
return Path(f'{base}.sc_lock')
|
|
289
|
+
def resolve_remote(self):
|
|
290
|
+
raise NotImplementedError("child class must implement this")
|
|
145
291
|
|
|
292
|
+
def check_cache(self):
|
|
293
|
+
raise NotImplementedError("child class must implement this")
|
|
146
294
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# Fall back to file based locking method
|
|
155
|
-
lock_file = __get_filebased_lock(data_lock)
|
|
156
|
-
while (lock_file.exists()):
|
|
157
|
-
if max_seconds == 0:
|
|
158
|
-
raise SiliconCompilerError(f'Failed to access {data_path}.'
|
|
159
|
-
f'Lock {lock_file} still exists.')
|
|
160
|
-
time.sleep(1)
|
|
161
|
-
max_seconds -= 1
|
|
295
|
+
def resolve(self) -> Path:
|
|
296
|
+
cache_dir = self.cache_dir
|
|
297
|
+
if not os.path.exists(cache_dir):
|
|
298
|
+
try:
|
|
299
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
300
|
+
except OSError:
|
|
301
|
+
return self.cache_path
|
|
162
302
|
|
|
163
|
-
|
|
303
|
+
if not os.access(self.cache_dir, os.W_OK):
|
|
304
|
+
return self.cache_path
|
|
164
305
|
|
|
165
|
-
|
|
306
|
+
with self.lock():
|
|
307
|
+
if self.check_cache():
|
|
308
|
+
return self.cache_path
|
|
166
309
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
310
|
+
self.resolve_remote()
|
|
311
|
+
self.set_changed()
|
|
312
|
+
return self.cache_path
|
|
170
313
|
|
|
171
314
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
315
|
+
###############
|
|
316
|
+
class FileResolver(Resolver):
|
|
317
|
+
def __init__(self, name, root, source, reference=None):
|
|
318
|
+
if source.startswith("file://"):
|
|
319
|
+
source = source[7:]
|
|
320
|
+
if not os.path.isabs(source):
|
|
321
|
+
source = os.path.join(getattr(root, "cwd", os.getcwd()), source)
|
|
178
322
|
|
|
179
|
-
|
|
323
|
+
super().__init__(name, root, f"file://{source}", None)
|
|
180
324
|
|
|
325
|
+
@property
|
|
326
|
+
def urlpath(self):
|
|
327
|
+
parse = self.urlparse
|
|
328
|
+
if parse.netloc:
|
|
329
|
+
return parse.netloc
|
|
330
|
+
else:
|
|
331
|
+
return parse.path
|
|
181
332
|
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
module = importlib.import_module(python_package)
|
|
185
|
-
except: # noqa E722
|
|
186
|
-
raise SiliconCompilerError(f'Failed to import {python_package}.', chip=chip)
|
|
333
|
+
def resolve(self):
|
|
334
|
+
return os.path.abspath(self.urlpath)
|
|
187
335
|
|
|
188
|
-
python_path = os.path.dirname(module.__file__)
|
|
189
|
-
if append_path:
|
|
190
|
-
if isinstance(append_path, str):
|
|
191
|
-
append_path = [append_path]
|
|
192
|
-
python_path = os.path.join(python_path, *append_path)
|
|
193
336
|
|
|
194
|
-
|
|
337
|
+
class PythonPathResolver(Resolver):
|
|
338
|
+
def __init__(self, name, root, source, reference=None):
|
|
339
|
+
super().__init__(name, root, source, None)
|
|
195
340
|
|
|
196
|
-
|
|
341
|
+
@staticmethod
|
|
342
|
+
@functools.lru_cache(maxsize=1)
|
|
343
|
+
def get_python_module_mapping():
|
|
344
|
+
mapping = {}
|
|
197
345
|
|
|
346
|
+
for dist in distributions():
|
|
347
|
+
dist_name = None
|
|
348
|
+
if hasattr(dist, 'name'):
|
|
349
|
+
dist_name = dist.name
|
|
350
|
+
else:
|
|
351
|
+
metadata = dist.read_text('METADATA')
|
|
352
|
+
if metadata:
|
|
353
|
+
find_name = re.compile(r'Name: (.*)')
|
|
354
|
+
for data in metadata.splitlines():
|
|
355
|
+
group = find_name.findall(data)
|
|
356
|
+
if group:
|
|
357
|
+
dist_name = group[0]
|
|
358
|
+
break
|
|
359
|
+
|
|
360
|
+
if not dist_name:
|
|
361
|
+
continue
|
|
362
|
+
|
|
363
|
+
provides = dist.read_text('top_level.txt')
|
|
364
|
+
if provides:
|
|
365
|
+
for module in dist.read_text('top_level.txt').split():
|
|
366
|
+
mapping.setdefault(module, []).append(dist_name)
|
|
367
|
+
|
|
368
|
+
return mapping
|
|
369
|
+
|
|
370
|
+
@staticmethod
|
|
371
|
+
def is_python_module_editable(module_name):
|
|
372
|
+
dist_map = PythonPathResolver.get_python_module_mapping()
|
|
373
|
+
dist = dist_map[module_name][0]
|
|
374
|
+
|
|
375
|
+
is_editable = False
|
|
376
|
+
for f in distribution(dist).files:
|
|
377
|
+
if f.name == 'direct_url.json':
|
|
378
|
+
info = None
|
|
379
|
+
with open(f.locate(), 'r') as f:
|
|
380
|
+
info = json.load(f)
|
|
381
|
+
|
|
382
|
+
if "dir_info" in info:
|
|
383
|
+
is_editable = info["dir_info"].get("editable", False)
|
|
384
|
+
|
|
385
|
+
return is_editable
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def register_source(root,
|
|
389
|
+
package_name,
|
|
390
|
+
python_module,
|
|
391
|
+
alternative_path,
|
|
392
|
+
alternative_ref=None,
|
|
393
|
+
python_module_path_append=None):
|
|
394
|
+
'''
|
|
395
|
+
Helper function to register a python module as data source with an alternative in case
|
|
396
|
+
the module is not installed in an editable state
|
|
397
|
+
'''
|
|
398
|
+
# check if installed in an editable state
|
|
399
|
+
if PythonPathResolver.is_python_module_editable(python_module):
|
|
400
|
+
if python_module_path_append:
|
|
401
|
+
path = PythonPathResolver(
|
|
402
|
+
python_module, root, f"python://{python_module}").resolve()
|
|
403
|
+
path = os.path.abspath(os.path.join(path, python_module_path_append))
|
|
404
|
+
else:
|
|
405
|
+
path = f"python://{python_module}"
|
|
406
|
+
ref = None
|
|
407
|
+
else:
|
|
408
|
+
path = alternative_path
|
|
409
|
+
ref = alternative_ref
|
|
198
410
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
411
|
+
root.register_source(name=package_name,
|
|
412
|
+
path=path,
|
|
413
|
+
ref=ref)
|
|
202
414
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
with open(f.locate(), 'r') as f:
|
|
208
|
-
info = json.load(f)
|
|
415
|
+
def resolve(self):
|
|
416
|
+
module = importlib.import_module(self.urlpath)
|
|
417
|
+
python_path = os.path.dirname(module.__file__)
|
|
418
|
+
return os.path.abspath(python_path)
|
|
209
419
|
|
|
210
|
-
if "dir_info" in info:
|
|
211
|
-
is_editable = info["dir_info"].get("editable", False)
|
|
212
420
|
|
|
213
|
-
|
|
421
|
+
class KeyPathResolver(Resolver):
|
|
422
|
+
def __init__(self, name, root, source, reference=None):
|
|
423
|
+
super().__init__(name, root, source, None)
|
|
214
424
|
|
|
425
|
+
def resolve(self):
|
|
426
|
+
if not self.root:
|
|
427
|
+
raise RuntimeError(f"Root schema has not be defined for {self.name}")
|
|
215
428
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
alternative_path,
|
|
220
|
-
alternative_ref=None,
|
|
221
|
-
python_module_path_append=None):
|
|
222
|
-
'''
|
|
223
|
-
Helper function to register a python module as data source with an alternative in case
|
|
224
|
-
the module is not installed in an editable state
|
|
225
|
-
'''
|
|
226
|
-
# check if installed in an editable state
|
|
227
|
-
if is_python_module_editable(python_module):
|
|
228
|
-
if python_module_path_append:
|
|
229
|
-
path = path_from_python(chip, python_module, append_path=python_module_path_append)
|
|
429
|
+
key = self.urlpath.split(",")
|
|
430
|
+
if self.root.get(*key, field='pernode').is_never():
|
|
431
|
+
paths = self.root.find_files(*key)
|
|
230
432
|
else:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
path = alternative_path
|
|
235
|
-
ref = alternative_ref
|
|
236
|
-
|
|
237
|
-
chip.register_source(name=package_name,
|
|
238
|
-
path=path,
|
|
239
|
-
ref=ref)
|
|
433
|
+
paths = self.root.find_files(*key,
|
|
434
|
+
step=self.root.get('arg', 'step'),
|
|
435
|
+
index=self.root.get('arg', 'index'))
|
|
240
436
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
mapping = {}
|
|
245
|
-
|
|
246
|
-
for dist in distributions():
|
|
247
|
-
dist_name = None
|
|
248
|
-
if hasattr(dist, 'name'):
|
|
249
|
-
dist_name = dist.name
|
|
250
|
-
else:
|
|
251
|
-
metadata = dist.read_text('METADATA')
|
|
252
|
-
if metadata:
|
|
253
|
-
find_name = re.compile(r'Name: (.*)')
|
|
254
|
-
for data in metadata.splitlines():
|
|
255
|
-
group = find_name.findall(data)
|
|
256
|
-
if group:
|
|
257
|
-
dist_name = group[0]
|
|
258
|
-
break
|
|
259
|
-
|
|
260
|
-
if not dist_name:
|
|
261
|
-
continue
|
|
262
|
-
|
|
263
|
-
provides = dist.read_text('top_level.txt')
|
|
264
|
-
if provides:
|
|
265
|
-
for module in dist.read_text('top_level.txt').split():
|
|
266
|
-
mapping.setdefault(module, []).append(dist_name)
|
|
267
|
-
|
|
268
|
-
return mapping
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def register_private_github_data_source(chip,
|
|
272
|
-
package_name,
|
|
273
|
-
repository,
|
|
274
|
-
release,
|
|
275
|
-
artifact):
|
|
276
|
-
chip.register_source(
|
|
277
|
-
package_name,
|
|
278
|
-
path=f"github+private://{repository}/{release}/{artifact}",
|
|
279
|
-
ref=release)
|
|
437
|
+
if isinstance(paths, list):
|
|
438
|
+
return paths[0]
|
|
439
|
+
return paths
|