diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py index 60e71f0d0fd..aa1fb298c2e 100644 --- a/conan/cli/commands/graph.py +++ b/conan/cli/commands/graph.py @@ -15,7 +15,7 @@ from conan.errors import ConanException from conan.internal.deploy import do_deploys from conans.client.graph.graph import BINARY_MISSING -from conans.client.graph.install_graph import InstallGraph +from conans.client.graph.install_graph import InstallGraph, ProfileArgs from conan.internal.errors import NotFoundException from conans.model.recipe_ref import ref_matches, RecipeReference @@ -117,7 +117,8 @@ def graph_build_order(conan_api, parser, subparser, *args): out = ConanOutput() out.title("Computing the build order") - install_graph = InstallGraph(deps_graph, order_by=args.order_by) + install_graph = InstallGraph(deps_graph, order_by=args.order_by, + profile_args=ProfileArgs.from_args(args)) if args.reduce: if args.order_by is None: raise ConanException("--reduce needs --order-by argument defined") diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py index cc7db6587c2..9bd432b7c95 100644 --- a/conans/client/graph/install_graph.py +++ b/conans/client/graph/install_graph.py @@ -334,17 +334,41 @@ def merge(self, other): self.filenames.append(d) +class ProfileArgs: + def __init__(self, args): + self._args = args + + @staticmethod + def from_args(args): + pr_args = [] + for context in "host", "build": + for f in "profile", "settings", "options", "conf": + s = "pr" if f == "profile" else f[0] + pr_args += [f'-{s}:{context[0]}="{v}"' for v in + getattr(args, f"{f}_{context}") or []] + return ProfileArgs(" ".join(pr_args)) + + @staticmethod + def deserialize(data): + return ProfileArgs(data.get("args")) + + def serialize(self): + return {"args": self._args} + + class InstallGraph: """ A graph containing the package references in order to be built/downloaded """ - def __init__(self, deps_graph, order_by=None): + def __init__(self, deps_graph, order_by=None, profile_args=None): self._nodes = {} # ref with rev: _InstallGraphNode order_by = order_by or "recipe" self._order = order_by self._node_cls = _InstallRecipeReference if order_by == "recipe" else _InstallConfiguration self._is_test_package = False self.reduced = False + self._profiles = {"self": profile_args} if profile_args is not None else {} + self._filename = None if deps_graph is not None: self._initialize_deps_graph(deps_graph) self._is_test_package = deps_graph.root.conanfile.tested_reference_str is not None @@ -371,15 +395,24 @@ def merge(self, other): self._nodes[ref] = install_node else: existing.merge(install_node) + # Make sure that self is also updated + current = self._profiles.pop("self", None) + if current is not None: + self._profiles[self._filename] = current + new = other._profiles.get("self") + if new is not None: + self._profiles[other._filename] = new @staticmethod def deserialize(data, filename): legacy = isinstance(data, list) - order, data, reduced = ("recipe", data, False) if legacy else \ - (data["order_by"], data["order"], data["reduced"]) + order, data, reduced, profiles = ("recipe", data, False, {}) if legacy else \ + (data["order_by"], data["order"], data["reduced"], data.get("profiles", {})) result = InstallGraph(None, order_by=order) result.reduced = reduced result.legacy = legacy + result._filename = filename + result._profiles = {k: ProfileArgs.deserialize(v) for k, v in profiles.items()} for level in data: for item in level: elem = result._node_cls.deserialize(item, filename) @@ -464,17 +497,19 @@ def install_build_order(self): install_order = self.install_order() result = {"order_by": self._order, "reduced": self.reduced, - "order": [[n.serialize() for n in level] for level in install_order]} + "order": [[n.serialize() for n in level] for level in install_order], + "profiles": {k: v.serialize() for k, v in self._profiles.items()} + } return result def _get_missing_invalid_packages(self): missing, invalid = [], [] - def analyze_package(package): - if package.binary == BINARY_MISSING: - missing.append(package) - elif package.binary == BINARY_INVALID: - invalid.append(package) + def analyze_package(pkg): + if pkg.binary == BINARY_MISSING: + missing.append(pkg) + elif pkg.binary == BINARY_INVALID: + invalid.append(pkg) for _, install_node in self._nodes.items(): if self._order == "recipe": for package in install_node.packages.values(): @@ -508,7 +543,8 @@ def get_errors(self): return "\n".join(errors) return None - def _raise_invalid(self, invalid): + @staticmethod + def _raise_invalid(invalid): msg = ["There are invalid packages:"] for package in invalid: node = package.nodes[0] diff --git a/test/integration/command_v2/test_info_build_order.py b/test/integration/command_v2/test_info_build_order.py index bb9c688c7a0..ef7ec1346f6 100644 --- a/test/integration/command_v2/test_info_build_order.py +++ b/test/integration/command_v2/test_info_build_order.py @@ -766,6 +766,26 @@ def validate(self): assert "dep/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Invalid configuration" in tc.out assert "IndexError: list index out of range" not in tc.out + +def test_multi_configuration_profile_args(): + c = TestClient() + c.save({"pkg/conanfile.py": GenConanfile().with_settings("os"), + "consumer/conanfile.txt": "[requires]\npkg/0.1", + "mypr": ""}) + c.run("export pkg --name=pkg --version=0.1") + args = "-pr=mypr -s:b os=Linux -o:h *:shared=True -c:h user.my:conf=1" + c.run(f"graph build-order consumer --format=json --build=missing -s os=Windows {args} " + "--order-by=recipe", redirect_stdout="bo_win.json") + c.run(f"graph build-order consumer --format=json --build=missing -s os=Linux {args} " + "--order-by=recipe", redirect_stdout="bo_nix.json") + c.run("graph build-order-merge --file=bo_win.json --file=bo_nix.json --format=json", + redirect_stdout="bo3.json") + bo_json = json.loads(c.load("bo3.json")) + win = '-pr:h="mypr" -s:h="os=Windows" -o:h="*:shared=True" -c:h="user.my:conf=1" -s:b="os=Linux"' + nix = '-pr:h="mypr" -s:h="os=Linux" -o:h="*:shared=True" -c:h="user.my:conf=1" -s:b="os=Linux"' + assert bo_json["profiles"] == {"bo_win": {"args": win}, "bo_nix": {"args": nix}} + + def test_build_order_space_in_options(): tc = TestClient(light=True) tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0") @@ -784,3 +804,4 @@ def test_build_order_space_in_options(): tc.run("graph build-order . --order-by=configuration --build=dep/1.0 -f=json", redirect_stdout="order.json") order = json.loads(tc.load("order.json")) assert order["order"][0][0]["build_args"] == '''--requires=dep/1.0 --build=dep/1.0 -o="dep/*:extras=cxx="yes" gnuext='no'" -o="dep/*:flags=define=FOO define=BAR define=BAZ"''' +