Skip to content

Commit f9182be

Browse files
committed
[virtual-vlnv] Add CLI option to add virtual providers
Add --add-vlnv to enable adding user-requested VLNVs that are not under the "system" or top core's explicit dependency tree. The intention of this argument is to allow users to specify cores that provide the implementations of virtual VLNVs in these "side" trees. The CLI option allows users to provide these without requiring someone to write a core file with the explicit deps, so targets may be reused across multiple different configurations, and users don't need to have duplicate core files that only differ by virtual providers. Signed-off-by: Alexander Williams <[email protected]>
1 parent b604f53 commit f9182be

File tree

5 files changed

+107
-22
lines changed

5 files changed

+107
-22
lines changed

fusesoc/coremanager.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ def _hash_flags_dict(self, flags):
110110
h ^= hash(pair)
111111
return h
112112

113-
def solve(self, top_core, flags):
114-
return self._solve(top_core, flags)
113+
def solve(self, top_core, flags, added_cores):
114+
return self._solve(top_core, flags, added_cores)
115115

116116
def _get_conflict_map(self):
117117
"""Return a map of cores to their conflicts
@@ -142,7 +142,7 @@ def _get_conflict_map(self):
142142
conflict_set.remove(real_pkg)
143143
return conflict_map
144144

145-
def _solve(self, top_core, flags={}, only_matching_vlnv=False):
145+
def _solve(self, top_core, flags={}, added_cores=(), only_matching_vlnv=False):
146146
def eq_vln(this, that):
147147
return (
148148
this.vendor == that.vendor
@@ -151,7 +151,12 @@ def eq_vln(this, that):
151151
)
152152

153153
# Try to return a cached result
154-
solver_cache_key = (top_core, self._hash_flags_dict(flags), only_matching_vlnv)
154+
requested_cores = frozenset(added_cores + (top_core,))
155+
solver_cache_key = (
156+
requested_cores,
157+
self._hash_flags_dict(flags),
158+
only_matching_vlnv,
159+
)
155160
cached_solution = self._solver_cache_lookup(solver_cache_key)
156161
if cached_solution:
157162
return cached_solution
@@ -206,12 +211,13 @@ def eq_vln(this, that):
206211
repo.add_package(package)
207212

208213
request = Request()
209-
_top_dep = "{} {} {}".format(
210-
self._package_name(top_core),
211-
top_core.relation,
212-
self._package_version(top_core),
213-
)
214-
request.install(Requirement._from_string(_top_dep))
214+
for requested_core in requested_cores:
215+
dep = "{} {} {}".format(
216+
self._package_name(requested_core),
217+
requested_core.relation,
218+
self._package_version(requested_core),
219+
)
220+
request.install(Requirement._from_string(dep))
215221

216222
installed_repository = Repository()
217223
pool = Pool([repo])
@@ -249,11 +255,12 @@ def eq_vln(this, that):
249255
]
250256
# Print a warning for all virtual selections that has no concrete requirement selection
251257
for virtual in virtual_selection.values():
252-
logger.warning(
253-
"Non-deterministic selection of virtual core {} selected {}".format(
254-
virtual[1], virtual[0]
258+
if virtual[0] not in requested_cores:
259+
logger.warning(
260+
"Non-deterministic selection of virtual core {} selected {}".format(
261+
virtual[1], virtual[0]
262+
)
255263
)
256-
)
257264

258265
result = [op.package.core for op in transaction.operations]
259266

@@ -402,13 +409,16 @@ def get_libraries(self):
402409
"""Get all registered libraries"""
403410
return self._lm.get_libraries()
404411

405-
def get_depends(self, core, flags):
412+
def get_depends(self, core, flags, added_cores):
406413
"""Get an ordered list of all dependencies of a core
407414
408415
All direct and indirect dependencies are resolved into a dependency
409416
tree, the tree is flattened, and an ordered list of dependencies is
410417
created.
411418
419+
The list is augmented by any added_cores required by the call, which can
420+
deterministically satisfy virtual VLNV dependencies.
421+
412422
The first element in the list is a leaf dependency, the last element
413423
is the core at the root of the dependency tree.
414424
"""
@@ -417,8 +427,9 @@ def get_depends(self, core, flags):
417427
core.relation, str(core), str(flags)
418428
)
419429
)
430+
logger.debug(" User added cores to request: " + ", ".join([str(c) for c in added_cores]))
420431
resolved_core = self.db.find(core)
421-
deps = self.db.solve(resolved_core.name, flags)
432+
deps = self.db.solve(resolved_core.name, flags, added_cores)
422433
logger.debug(" Resolved core to {}".format(str(resolved_core.name)))
423434
logger.debug(" with dependencies " + ", ".join([str(c.name) for c in deps]))
424435
return deps

fusesoc/edalizer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ def __init__(
4949
export_root=None,
5050
system_name=None,
5151
resolve_env_vars=False,
52+
added_core_reqs=(),
5253
):
5354
logger.debug("Building EDAM structure")
5455

5556
self.toplevel = toplevel
5657
self.flags = flags
5758
self.core_manager = core_manager
59+
self.added_core_reqs = added_core_reqs
5860
self.work_root = work_root
5961
self.export_root = export_root
6062
self.system_name = system_name
@@ -74,7 +76,11 @@ def cores(self):
7476
@property
7577
def resolved_cores(self):
7678
"""Get a list of all "used" cores after the dependency resolution"""
77-
return self.core_manager.get_depends(self.toplevel, self.flags)
79+
return self.core_manager.get_depends(
80+
self.toplevel,
81+
self.flags,
82+
self.added_core_reqs
83+
)
7884

7985
@property
8086
def discovered_cores(self):

fusesoc/fusesoc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def get_work_root(self, core, flags):
115115

116116
return work_root
117117

118-
def get_backend(self, core, flags, backendargs=[]):
118+
def get_backend(self, core, flags, added_core_reqs=(), backendargs=[]):
119119

120120
work_root = self.get_work_root(core, flags)
121121

@@ -156,6 +156,7 @@ def get_backend(self, core, flags, backendargs=[]):
156156
export_root=export_root,
157157
system_name=self.config.system_name,
158158
resolve_env_vars=self.config.resolve_env_vars_early,
159+
added_core_reqs=added_core_reqs,
159160
)
160161

161162
try:

fusesoc/main.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from fusesoc.coremanager import DependencyError
3030
from fusesoc.fusesoc import Fusesoc
3131
from fusesoc.librarymanager import Library
32+
from fusesoc.vlnv import Vlnv
3233

3334
logger = logging.getLogger(__name__)
3435

@@ -303,6 +304,11 @@ def run(fs, args):
303304
else:
304305
flags[flag] = True
305306

307+
added_core_reqs = []
308+
for vlnv in args.add_vlnv:
309+
added_core_reqs.append(Vlnv(vlnv))
310+
added_core_reqs = tuple(added_core_reqs)
311+
306312
core = _get_core(fs, args.system)
307313

308314
try:
@@ -326,7 +332,9 @@ def run(fs, args):
326332
# Frontend/backend separation
327333

328334
try:
329-
edam_file, backend = fs.get_backend(core, flags, args.backendargs)
335+
edam_file, backend = fs.get_backend(
336+
core, flags, added_core_reqs, args.backendargs
337+
)
330338

331339
except RuntimeError as e:
332340
logger.error(str(e))
@@ -592,6 +600,13 @@ def get_parser():
592600
parser_run.add_argument("--run", action="store_true", help="Execute run stage")
593601
parser_run.add_argument("--target", help="Override default target")
594602
parser_run.add_argument("--tool", help="Override default tool for target")
603+
parser_run.add_argument(
604+
"--add-vlnv",
605+
help="Add a VLNV to the build as a dependency of the 'system'. Intended for explicit virtual providers. Multiple uses allowed.",
606+
action="append",
607+
default=[],
608+
metavar="VLNV",
609+
)
595610
parser_run.add_argument(
596611
"--flag",
597612
help="Set custom use flags. Can be specified multiple times",

tests/test_coremanager.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_deptree(tmp_path):
8888
edam = edalizer.run()
8989

9090
# Check dependency tree (after running all generators)
91-
deps = cm.get_depends(root_core.name, {})
91+
deps = cm.get_depends(root_core.name, {}, ())
9292
deps_names = [str(c) for c in deps]
9393

9494
all_core_names = set()
@@ -274,7 +274,7 @@ def test_virtual():
274274
)
275275
edalizer.run()
276276

277-
deps = cm.get_depends(root_core.name, {})
277+
deps = cm.get_depends(root_core.name, {}, ())
278278
deps_names = [str(c) for c in deps]
279279

280280
assert deps_names == expected_deps
@@ -355,7 +355,7 @@ def test_virtual_non_deterministic_virtual(caplog):
355355
edalizer.run()
356356
assert "Non-deterministic selection of virtual core" in caplog.text
357357

358-
deps = cm.get_depends(root_core.name, {})
358+
deps = cm.get_depends(root_core.name, {}, ())
359359
deps_names = [str(c) for c in deps]
360360

361361
for dependency in deps_names:
@@ -365,3 +365,55 @@ def test_virtual_non_deterministic_virtual(caplog):
365365
"::user:0",
366366
"::top_non_deterministic:0",
367367
]
368+
369+
370+
def test_virtual_explicit_providers(caplog):
371+
"""
372+
Test virtual core selection when there are explicit selected implementations on the side.
373+
This shall NOT result in a warning that the virtual core selection is non-deteministic.
374+
"""
375+
import logging
376+
import os
377+
import tempfile
378+
379+
from fusesoc.config import Config
380+
from fusesoc.coremanager import CoreManager
381+
from fusesoc.edalizer import Edalizer
382+
from fusesoc.librarymanager import Library
383+
from fusesoc.vlnv import Vlnv
384+
385+
flags = {"tool": "icarus"}
386+
387+
build_root = tempfile.mkdtemp(prefix="export_")
388+
work_root = os.path.join(build_root, "work")
389+
390+
core_dir = os.path.join(os.path.dirname(__file__), "capi2_cores", "virtual")
391+
392+
cm = CoreManager(Config())
393+
cm.add_library(Library("virtual", core_dir), [])
394+
395+
root_core = cm.get_core(Vlnv("::top_non_deterministic"))
396+
397+
added_core_options = ((Vlnv("::impl1:0"),), (Vlnv("::impl2:0"),))
398+
399+
for added_cores in added_core_options:
400+
edalizer = Edalizer(
401+
toplevel=root_core.name,
402+
flags=flags,
403+
core_manager=cm,
404+
work_root=work_root,
405+
added_core_reqs=added_cores,
406+
)
407+
408+
with caplog.at_level(logging.WARNING):
409+
edalizer.run()
410+
assert "Non-deterministic selection of virtual core" not in caplog.text
411+
412+
deps = cm.get_depends(root_core.name, {}, added_cores)
413+
deps_names = [str(c) for c in deps]
414+
415+
for dependency in deps_names:
416+
assert dependency in [
417+
"::user:0",
418+
"::top_non_deterministic:0",
419+
] + [str(c) for c in added_cores]

0 commit comments

Comments
 (0)