Skip to content

Commit e17fffb

Browse files
authored
Merge pull request #728 from blueluna/lockfile
Adding Lockfile
2 parents 663b348 + 4be367b commit e17fffb

File tree

15 files changed

+491
-1
lines changed

15 files changed

+491
-1
lines changed

fusesoc/coremanager.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import logging
66
import os
7+
import pathlib
78

89
from okonomiyaki.versions import EnpkgVersion
910
from simplesat.constraints import PrettyPackageStringParser, Requirement
@@ -16,6 +17,8 @@
1617
from fusesoc.capi2.coreparser import Core2Parser
1718
from fusesoc.core import Core
1819
from fusesoc.librarymanager import LibraryManager
20+
from fusesoc.lockfile import load_lockfile
21+
from fusesoc.vlnv import Vlnv, compare_relation
1922

2023
logger = logging.getLogger(__name__)
2124

@@ -33,6 +36,7 @@ class CoreDB:
3336
def __init__(self):
3437
self._cores = {}
3538
self._solver_cache = {}
39+
self._lockfile = None
3640

3741
# simplesat doesn't allow ':', '-' or leading '_'
3842
def _package_name(self, vlnv):
@@ -45,6 +49,7 @@ def _package_version(self, vlnv):
4549
def _parse_depend(self, depends):
4650
# FIXME: Handle conflicts
4751
deps = []
52+
4853
_s = "{} {} {}"
4954
for d in depends:
5055
for simple in d.simpleVLNVs():
@@ -83,6 +88,9 @@ def find(self, vlnv=None):
8388
found = list([core["core"] for core in self._cores.values()])
8489
return found
8590

91+
def load_lockfile(self, filepath: pathlib.Path):
92+
self._lockfile = load_lockfile(filepath)
93+
8694
def _solver_cache_lookup(self, key):
8795
if key in self._solver_cache:
8896
return self._solver_cache[key]
@@ -110,6 +118,27 @@ def _hash_flags_dict(self, flags):
110118
h ^= hash(pair)
111119
return h
112120

121+
def _lockfile_replace(self, core: Vlnv):
122+
"""Try to pin the core version from cores defined in the lock file"""
123+
if self._lockfile:
124+
for locked_core in self._lockfile["cores"]:
125+
if locked_core.vln_str() == core.vln_str():
126+
valid_version = compare_relation(locked_core, core.relation, core)
127+
if valid_version:
128+
core.version = locked_core.version
129+
core.revision = locked_core.revision
130+
core.relation = "=="
131+
else:
132+
# Invalid version in lockfile
133+
logger.warning(
134+
"Failed to pin core {} outside of dependency version {} {} {}".format(
135+
str(locked_core),
136+
core.vln_str(),
137+
core.relation,
138+
core.version,
139+
)
140+
)
141+
113142
def solve(self, top_core, flags):
114143
return self._solve(top_core, flags)
115144

@@ -195,8 +224,12 @@ def eq_vln(this, that):
195224
_flags["is_toplevel"] = core.name == top_core
196225
_depends = core.get_depends(_flags)
197226
if _depends:
227+
for depend in _depends:
228+
self._lockfile_replace(depend)
198229
_s = "; depends ( {} )"
199230
package_str += _s.format(self._parse_depend(_depends))
231+
else:
232+
self._lockfile_replace(top_core)
200233

201234
parser = PrettyPackageStringParser(EnpkgVersion.from_string)
202235

@@ -226,6 +259,7 @@ def eq_vln(this, that):
226259
raise DependencyError(top_core.name)
227260

228261
virtual_selection = {}
262+
partial_lockfile = False
229263
objdict = {}
230264
if len(transaction.operations) > 1:
231265
for op in transaction.operations:
@@ -244,6 +278,11 @@ def eq_vln(this, that):
244278
if p[0] in virtual_selection:
245279
# If package that implements a virtual core is required, remove from the dictionary
246280
del virtual_selection[p[0]]
281+
if (
282+
self._lockfile
283+
and op.package.core.name not in self._lockfile["cores"]
284+
):
285+
partial_lockfile = True
247286
op.package.core.direct_deps = [
248287
objdict[n[0]] for n in op.package.install_requires
249288
]
@@ -254,6 +293,8 @@ def eq_vln(this, that):
254293
virtual[1], virtual[0]
255294
)
256295
)
296+
if partial_lockfile:
297+
logger.warning("Using lock file with partial list of cores")
257298

258299
result = [op.package.core for op in transaction.operations]
259300

fusesoc/lockfile.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import json
2+
import logging
3+
import os
4+
import pathlib
5+
6+
import fastjsonschema
7+
8+
import fusesoc.utils
9+
from fusesoc.version import version
10+
from fusesoc.vlnv import Vlnv
11+
12+
logger = logging.getLogger(__name__)
13+
14+
lockfile_schema = """
15+
{
16+
"$schema": "https://json-schema.org/draft/2020-12/schema",
17+
"title": "FuseSoC Lockfile",
18+
"description": "FuseSoC Lockfile",
19+
"type": "object",
20+
"properties": {
21+
"cores": {
22+
"description": "Cores used in the build",
23+
"type": "array",
24+
"items": {
25+
"type": "object",
26+
"properties": {
27+
"name": {
28+
"description": "Core VLVN",
29+
"type": "string"
30+
}
31+
}
32+
}
33+
},
34+
"fusesoc_version": {
35+
"description": "FuseSoC version which generated the lockfile",
36+
"type": "string"
37+
},
38+
"lockfile_version": {
39+
"description": "Lockfile version",
40+
"type": "integer"
41+
}
42+
}
43+
}
44+
"""
45+
46+
47+
def load_lockfile(filepath: pathlib.Path):
48+
try:
49+
lockfile_data = fusesoc.utils.yaml_fread(filepath)
50+
try:
51+
validator = fastjsonschema.compile(
52+
json.loads(lockfile_schema), detailed_exceptions=False
53+
)
54+
validator(lockfile_data)
55+
except fastjsonschema.JsonSchemaDefinitionException as e:
56+
raise SyntaxError(f"Error parsing JSON Schema: {e}")
57+
except fastjsonschema.JsonSchemaException as e:
58+
raise SyntaxError(f"Error validating {e}")
59+
except FileNotFoundError:
60+
logger.warning(f"Lockfile {filepath} not found")
61+
return None
62+
63+
cores = {}
64+
for core in lockfile_data.setdefault("cores", []):
65+
if "name" in core:
66+
vlnv = Vlnv(core["name"])
67+
vln = vlnv.vln_str()
68+
if vln in map(Vlnv.vln_str, cores.keys()):
69+
raise SyntaxError(f"Core {vln} defined multiple times in lock file")
70+
cores[vlnv] = {"name": vlnv}
71+
else:
72+
raise SyntaxError(f"Core definition without a name")
73+
lockfile = {
74+
"cores": cores,
75+
}
76+
return lockfile

fusesoc/main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import argparse
88
import os
9+
import pathlib
910
import shutil
1011
import signal
1112
import sys
@@ -306,6 +307,13 @@ def run(fs, args):
306307
else:
307308
flags[flag] = True
308309

310+
if args.lockfile is not None:
311+
try:
312+
fs.cm.db.load_lockfile(args.lockfile)
313+
except SyntaxError as e:
314+
logger.error(f"Failed to load lock file, {str(e)}")
315+
exit(1)
316+
309317
core = _get_core(fs, args.system)
310318

311319
try:
@@ -666,6 +674,11 @@ def get_parser():
666674
parser_run.add_argument(
667675
"backendargs", nargs=argparse.REMAINDER, help="arguments to be sent to backend"
668676
)
677+
parser_run.add_argument(
678+
"--lockfile",
679+
help="Lockfile file path",
680+
type=pathlib.Path,
681+
)
669682
parser_run.set_defaults(func=run)
670683

671684
# config subparser

fusesoc/vlnv.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,47 @@ def __lt__(self, other):
161161
other.name,
162162
other.version,
163163
)
164+
165+
def vln_str(self):
166+
"""Returns a string with <vendor>:<library>:<name>"""
167+
return f"{self.vendor}:{self.library}:{self.name}"
168+
169+
170+
def compare_relation(vlvn_a: Vlnv, relation: str, vlvn_b: Vlnv):
171+
"""Compare two VLVNs with the provided relation. Returns boolan."""
172+
from okonomiyaki.versions import EnpkgVersion
173+
174+
valid_version = False
175+
version_str = lambda v: f"{v.version}-{v.revision}"
176+
if vlvn_a.vln_str() == vlvn_b.vln_str():
177+
ver_a = EnpkgVersion.from_string(version_str(vlvn_a))
178+
ver_b = EnpkgVersion.from_string(version_str(vlvn_b))
179+
if relation == "==":
180+
valid_version = ver_a == ver_b
181+
elif relation == ">":
182+
valid_version = ver_a > ver_b
183+
elif relation == "<":
184+
valid_version = ver_a < ver_b
185+
elif relation == ">=":
186+
valid_version = ver_a >= ver_b
187+
elif relation == "<=":
188+
valid_version = ver_a <= ver_b
189+
elif relation == "^":
190+
nextversion = list(map(int, vlvn_a.version.split(".")))
191+
for pos in range(len(nextversion)):
192+
if pos == 0:
193+
nextversion[pos] += 1
194+
else:
195+
nextversion[pos] = 0
196+
nextversion = EnpkgVersion.from_string(".".join(map(str, nextversion)))
197+
valid_version = ver_a <= ver_b and ver_b < nextversion
198+
elif relation == "~":
199+
nextversion = list(map(int, vlvn_a.version.split(".")))
200+
for pos in range(len(nextversion)):
201+
if pos == 1:
202+
nextversion[pos] += 1
203+
elif pos > 1:
204+
nextversion[pos] = 0
205+
nextversion = EnpkgVersion.from_string(".".join(map(str, nextversion)))
206+
valid_version = ver_a <= ver_b and ver_b < nextversion
207+
return valid_version
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CAPI=2:
2+
# Copyright FuseSoC contributors
3+
# Licensed under the 2-Clause BSD License, see LICENSE for details.
4+
# SPDX-License-Identifier: BSD-2-Clause
5+
6+
name: ::dependencies-top
7+
8+
filesets:
9+
fs1:
10+
depend:
11+
- '>::used:1.0'
12+
13+
targets:
14+
default:
15+
filesets:
16+
- fs1
17+
toplevel:
18+
- top
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CAPI=2:
2+
# Copyright FuseSoC contributors
3+
# Licensed under the 2-Clause BSD License, see LICENSE for details.
4+
# SPDX-License-Identifier: BSD-2-Clause
5+
6+
name: ::used:1.0
7+
filesets:
8+
rtl:
9+
files:
10+
- used-1.0.sv
11+
file_type: systemVerilogSource
12+
13+
14+
targets:
15+
default:
16+
filesets:
17+
- rtl
18+
toplevel: used_1_0
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CAPI=2:
2+
# Copyright FuseSoC contributors
3+
# Licensed under the 2-Clause BSD License, see LICENSE for details.
4+
# SPDX-License-Identifier: BSD-2-Clause
5+
6+
name: ::used:1.1
7+
filesets:
8+
rtl:
9+
files:
10+
- used-1.1.sv
11+
file_type: systemVerilogSource
12+
13+
14+
targets:
15+
default:
16+
filesets:
17+
- rtl
18+
toplevel: used_1_1
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cores:
2+
- name: "::used:1.0"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cores:
2+
- name: "::used:1.1"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cores:
2+
- name: "::used:1.1"
3+
- name: "::dependencies-top:0"

0 commit comments

Comments
 (0)