diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py index f6f1ba5cb53..56463b2b5bd 100644 --- a/conans/client/graph/graph.py +++ b/conans/client/graph/graph.py @@ -97,6 +97,11 @@ def propagate_downstream(self, require, node, src_node=None): # print(" +++++Runtime conflict!", require, "with", node.ref) return True require.aggregate(existing.require) + # An override can be overriden by a downstream force/override + if existing.require.override and existing.require.ref != require.ref: + # If it is an override, but other value, it has been overriden too + existing.require.overriden_ref = existing.require.ref + existing.require.override_ref = require.ref assert not require.version_range # No ranges slip into transitive_deps definitions # TODO: Might need to move to an update() for performance @@ -118,6 +123,7 @@ def propagate_downstream(self, require, node, src_node=None): if down_require is None: return + down_require.defining_require = require.defining_require return d.src.propagate_downstream(down_require, node) def check_downstream_exists(self, require): @@ -161,6 +167,7 @@ def check_downstream_exists(self, require): # print(" No need to check downstream more") return result + down_require.defining_require = require.defining_require source_node = dependant.src return source_node.check_downstream_exists(down_require) or result @@ -275,9 +282,9 @@ def create(nodes): overrides = {} for n in nodes: for r in n.conanfile.requires.values(): - if r.override: + if r.override and not r.overriden_ref: # overrides are not real graph edges continue - if r.overriden_ref and not r.force: + if r.overriden_ref: overrides.setdefault(r.overriden_ref, set()).add(r.override_ref) else: overrides.setdefault(r.ref, set()).add(None) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 937d32f76a2..64c9a4bad4e 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -81,9 +81,13 @@ def _expand_require(self, require, node, graph, profile_host, profile_build, gra prev_ref = prev_node.ref if prev_node else prev_require.ref if prev_require.force or prev_require.override: # override - require.overriden_ref = require.ref # Store that the require has been overriden - require.override_ref = prev_ref - require.ref = prev_ref + if prev_require.defining_require is not require: + require.overriden_ref = require.overriden_ref or require.ref.copy() # Old one + # require.override_ref can be !=None if lockfile-overrides defined + require.override_ref = (require.override_ref or prev_require.override_ref + or prev_require.ref.copy()) # New one + require.defining_require = prev_require.defining_require # The overrider + require.ref = prev_ref # New one, maybe resolved with revision else: self._conflicting_version(require, node, prev_require, prev_node, prev_ref, base_previous, self._resolve_prereleases) @@ -195,6 +199,8 @@ def _initialize_requires(self, node, graph, graph_lock, profile_build, profile_h if not resolved: self._resolve_alias(node, require, alias, graph) self._resolve_replace_requires(node, require, profile_build, profile_host, graph) + if graph_lock: + graph_lock.resolve_overrides(require) node.transitive_deps[require] = TransitiveRequirement(require, node=None) def _resolve_alias(self, node, require, alias, graph): diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py index 2471e1f62a5..51cec4fcb6e 100644 --- a/conans/client/graph/install_graph.py +++ b/conans/client/graph/install_graph.py @@ -160,7 +160,7 @@ def add(self, node): if dep.dst.binary != BINARY_SKIP: if dep.dst.ref == node.ref: # If the node is itself, then it is internal dep install_pkg_ref.depends.append(dep.dst.pref.package_id) - else: + elif dep.dst.ref not in self.depends: self.depends.append(dep.dst.ref) def _install_order(self): diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index 2ac43ac58c3..29e87de77ba 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -261,7 +261,6 @@ def resolve_locked(self, node, require, resolve_prereleases): locked_refs = self._conf_requires.refs() else: locked_refs = self._requires.refs() - self._resolve_overrides(require) try: self._resolve(require, locked_refs, resolve_prereleases) except ConanException: @@ -273,13 +272,19 @@ def resolve_locked(self, node, require, resolve_prereleases): ConanOutput().error(msg, error_type="exception") raise - def _resolve_overrides(self, require): - existing = self._overrides.get(require.ref) - if existing is not None and len(existing) == 1: - require.overriden_ref = require.ref # Store that the require has been overriden - ref = next(iter(existing)) - require.ref = ref - require.override_ref = ref + def resolve_overrides(self, require): + """ The lockfile contains the overrides to be able to inject them when the lockfile is + applied to upstream dependencies, that have the overrides downstream + """ + if not self._overrides: + return + + overriden = self._overrides.get(require.ref) + if overriden and len(overriden) == 1: + override_ref = next(iter(overriden)) + require.overriden_ref = require.overriden_ref or require.ref.copy() + require.override_ref = override_ref + require.ref = override_ref def resolve_prev(self, node): if node.context == CONTEXT_BUILD: diff --git a/conans/model/recipe_ref.py b/conans/model/recipe_ref.py index ebbe5c18957..01168f09f85 100644 --- a/conans/model/recipe_ref.py +++ b/conans/model/recipe_ref.py @@ -26,6 +26,11 @@ def __init__(self, name=None, version=None, user=None, channel=None, revision=No self.revision = revision self.timestamp = timestamp + def copy(self): + # Used for creating copy in lockfile-overrides mechanism + return RecipeReference(self.name, self.version, self.user, self.channel, self.revision, + self.timestamp) + def __repr__(self): """ long repr like pkg/0.1@user/channel#rrev%timestamp """ result = self.repr_notime() diff --git a/conans/model/requires.py b/conans/model/requires.py index 428bb596863..23987d0dc81 100644 --- a/conans/model/requires.py +++ b/conans/model/requires.py @@ -29,6 +29,9 @@ def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visib self._direct = direct self.options = options # Meta and auxiliary information + # The "defining_require" is the require that defines the current value. If this require is + # overriden/forced, this attribute will point to the overriding/forcing requirement. + self.defining_require = self # if not overriden, it points to itself self.overriden_ref = None # to store if the requirement has been overriden (store old ref) self.override_ref = None # to store if the requirement has been overriden (store new ref) self.is_test = test # to store that it was a test, even if used as regular requires too @@ -518,16 +521,6 @@ def build_require(self, ref, raise_if_duplicated=True, package_id_mode=None, vis raise ConanException("Duplicated requirement: {}".format(ref)) self._requires[req] = req - def override(self, ref): - req = Requirement(ref) - old_requirement = self._requires.get(req) - if old_requirement is not None: - req.force = True - self._requires[req] = req - else: - req.override = True - self._requires[req] = req - def test_require(self, ref, run=None, options=None, force=None): """ Represent a testing framework like gtest diff --git a/conans/test/integration/graph/conflict_diamond_test.py b/conans/test/integration/graph/conflict_diamond_test.py index 462bfecddcf..86d4320e730 100644 --- a/conans/test/integration/graph/conflict_diamond_test.py +++ b/conans/test/integration/graph/conflict_diamond_test.py @@ -54,7 +54,7 @@ def _game_conanfile(version, reverse=False): c.save({"game/conanfile.py": _game_conanfile(v)}) c.run("install game") c.assert_overrides({"math/1.0": [f"math/{v}"], - "math/1.0.1": [f"math/{v}#8e1a7a5ce869d8c54ae3d33468fd657c"]}) + "math/1.0.1": [f"math/{v}"]}) c.assert_listed_require({f"math/{v}": "Cache"}) # Check that order of requirements doesn't affect @@ -62,7 +62,7 @@ def _game_conanfile(version, reverse=False): c.save({"game/conanfile.py": _game_conanfile(v, reverse=True)}) c.run("install game") c.assert_overrides({"math/1.0": [f"math/{v}"], - "math/1.0.1": [f"math/{v}#8e1a7a5ce869d8c54ae3d33468fd657c"]}) + "math/1.0.1": [f"math/{v}"]}) c.assert_listed_require({f"math/{v}": "Cache"}) c.run("install --requires=engine/1.0 --requires=ai/1.0", assert_error=True) diff --git a/conans/test/integration/graph/test_test_requires.py b/conans/test/integration/graph/test_test_requires.py index c5531422f5a..18ce5bd32e5 100644 --- a/conans/test/integration/graph/test_test_requires.py +++ b/conans/test/integration/graph/test_test_requires.py @@ -106,7 +106,7 @@ def requirements(self): c.run("create rapidcheck") c.run("install game") c.assert_listed_require({"gtest/1.0": "Cache"}, test=True) - c.assert_overrides({"gtest/1.1": ["gtest/1.0#08dd14dd79315dd95c94b85d13bd1388"]}) + c.assert_overrides({"gtest/1.1": ["gtest/1.0"]}) def test_require_options(): diff --git a/conans/test/integration/graph/version_ranges/version_range_override_test.py b/conans/test/integration/graph/version_ranges/version_range_override_test.py index 52587d45aac..30bbf2d7bb2 100644 --- a/conans/test/integration/graph/version_ranges/version_range_override_test.py +++ b/conans/test/integration/graph/version_ranges/version_range_override_test.py @@ -104,7 +104,7 @@ def test_override(self): "ros_core/pr-53@3rdparty/snapshot" ], "ros_core/1.1.4@3rdparty/unstable": [ - "ros_core/pr-53@3rdparty/snapshot#4d670581ccb765839f2239cc8dff8fbd" + "ros_core/pr-53@3rdparty/snapshot" ] } assert info['graph']["overrides"] == expected_overrides diff --git a/conans/test/integration/lockfile/test_ci_overrides.py b/conans/test/integration/lockfile/test_ci_overrides.py new file mode 100644 index 00000000000..e587988df7c --- /dev/null +++ b/conans/test/integration/lockfile/test_ci_overrides.py @@ -0,0 +1,389 @@ +import json + +import pytest + +from conans.model.recipe_ref import RecipeReference +from conans.test.assets.genconanfile import GenConanfile +from conans.test.utils.tools import TestClient + + +def test_graph_build_order_override_error(): + """ + libc -> libb -> liba -> zlib/1.2 + |--------------/ + |-----override------> zlib/1.3 + """ + c = TestClient() + c.save({"zlib/conanfile.py": GenConanfile("zlib"), + "liba/conanfile.py": GenConanfile("liba", "0.1").with_requires("zlib/1.0"), + "libb/conanfile.py": GenConanfile("libb", "0.1").with_requires("liba/0.1", "zlib/2.0"), + "libc/conanfile.py": GenConanfile("libc", "0.1").with_requirement("libb/0.1") + .with_requirement("zlib/3.0", + override=True) + }) + c.run("export zlib --version=2.0") + c.run("export zlib --version=3.0") + c.run("export liba") + c.run("export libb") + c.run("export libc") + c.run("graph info --requires=libc/0.1 --lockfile-out=output.lock") + + c.run("graph build-order --requires=libc/0.1 --lockfile=output.lock --order-by=configuration " + "--build=missing --format=json") + + to_build = json.loads(c.stdout) + for level in to_build["order"]: + for package in level: + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=output.lock") + ref = RecipeReference.loads(package["ref"]) + assert f"{ref}: Building from source" + + c.run("install --requires=libc/0.1 --lockfile=output.lock") + # All works, all binaries exist now + assert "zlib/3.0: Already installed!" in c.out + assert "liba/0.1: Already installed!" in c.out + assert "libb/0.1: Already installed!" in c.out + assert "libc/0.1: Already installed!" in c.out + + +@pytest.mark.parametrize("replace_pattern", ["*", "3.0"]) +def test_graph_build_order_override_replace_requires(replace_pattern): + """ + libc -> libb -> liba -> zlib/1.2 + |--------------/ + |-----override------> zlib/1.3 + + replace_requires zlib -> zlib/system + """ + c = TestClient() + c.save({"zlib/conanfile.py": GenConanfile("zlib"), + "liba/conanfile.py": GenConanfile("liba", "0.1").with_requires("zlib/1.0"), + "libb/conanfile.py": GenConanfile("libb", "0.1").with_requires("liba/0.1", "zlib/2.0"), + "libc/conanfile.py": GenConanfile("libc", "0.1").with_requirement("libb/0.1") + .with_requirement("zlib/3.0", + override=True), + "profile": f"[replace_requires]\nzlib/{replace_pattern}: zlib/system" + }) + c.run("export zlib --version=2.0") + c.run("export zlib --version=3.0") + c.run("export zlib --version=system") + c.run("export liba") + c.run("export libb") + c.run("export libc") + c.run("lock create --requires=libc/0.1 --lockfile-out=output.lock -pr=profile") + + c.run("graph build-order --requires=libc/0.1 --lockfile=output.lock --order-by=configuration " + "--build=missing -pr=profile --format=json") + + to_build = json.loads(c.stdout) + for level in to_build["order"]: + for package in level: + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=output.lock -pr=profile") + ref = RecipeReference.loads(package["ref"]) + assert f"{ref}: Building from source" + + c.run("install --requires=libc/0.1 --lockfile=output.lock -pr=profile") + # All works, all binaries exist now + assert "zlib/system: Already installed!" in c.out + assert "liba/0.1: Already installed!" in c.out + assert "libb/0.1: Already installed!" in c.out + assert "libc/0.1: Already installed!" in c.out + + +def test_single_config_decentralized_overrides(): + r""" same scenario as "test_single_config_centralized()", but distributing the build in + different build servers, using the "build-order" + Now with overrides + + pkga -> toola/1.0 -> toolb/1.0 -> toolc/1.0 + \------override-----> toolc/2.0 + pkgb -> toola/2.0 -> toolb/1.0 -> toolc/1.0 + \------override-----> toolc/3.0 + pkgc -> toola/3.0 -> toolb/1.0 -> toolc/1.0 + """ + c = TestClient() + c.save({"toolc/conanfile.py": GenConanfile("toolc"), + "toolb/conanfile.py": GenConanfile("toolb").with_requires("toolc/1.0"), + "toola/conanfile.py": GenConanfile("toola", "1.0").with_requirement("toolb/1.0") + .with_requirement("toolc/2.0", + override=True), + "toola2/conanfile.py": GenConanfile("toola", "2.0").with_requirement("toolb/1.0") + .with_requirement("toolc/3.0", + override=True), + "toola3/conanfile.py": GenConanfile("toola", "3.0").with_requirement("toolb/1.0"), + "pkga/conanfile.py": GenConanfile("pkga", "1.0").with_tool_requires("toola/1.0"), + "pkgb/conanfile.py": GenConanfile("pkgb", "1.0").with_requires("pkga/1.0") + .with_tool_requires("toola/2.0"), + "pkgc/conanfile.py": GenConanfile("pkgc", "1.0").with_requires("pkgb/1.0") + .with_tool_requires("toola/3.0"), + }) + c.run("export toolc --version=1.0") + c.run("export toolc --version=2.0") + c.run("export toolc --version=3.0") + + c.run("export toolb --version=1.0") + + c.run("export toola") + c.run("export toola2") + c.run("export toola3") + + c.run("export pkga") + c.run("export pkgb") + c.run("lock create pkgc") + lock = json.loads(c.load("pkgc/conan.lock")) + requires = "\n".join(lock["build_requires"]) + assert "toolc/3.0" in requires + assert "toolc/2.0" in requires + assert "toolc/1.0" in requires + assert len(lock["overrides"]) == 1 + assert set(lock["overrides"]["toolc/1.0"]) == {"toolc/3.0", "toolc/2.0", None} + + c.run("graph build-order pkgc --lockfile=pkgc/conan.lock --format=json --build=missing") + to_build = json.loads(c.stdout) + for level in to_build: + for elem in level: + for package in elem["packages"][0]: # assumes no dependencies between packages + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=pkgc/conan.lock") + + c.run("install pkgc --lockfile=pkgc/conan.lock") + # All works, all binaries exist now + assert "pkga/1.0: Already installed!" in c.out + assert "pkgb/1.0: Already installed!" in c.out + + +def test_single_config_decentralized_overrides_nested(): + r""" same scenario as "test_single_config_centralized()", but distributing the build in + different build servers, using the "build-order" + Now with overrides + + pkga -> toola/1.0 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + \ \-----------override---------> libf/2.0 + \--------------------override--------------------------> libf/3.0 + """ + c = TestClient() + c.save({"libf/conanfile.py": GenConanfile("libf"), + "libe/conanfile.py": GenConanfile("libe", "1.0").with_requires("libf/1.0"), + "libd/conanfile.py": GenConanfile("libd", "1.0").with_requires("libe/1.0"), + "libc/conanfile.py": GenConanfile("libc", "1.0").with_requirement("libd/1.0") + .with_requirement("libf/2.0", + override=True), + "libb/conanfile.py": GenConanfile("libb", "1.0").with_requires("libc/1.0"), + "toola/conanfile.py": GenConanfile("toola", "1.0").with_requirement("libb/1.0") + .with_requirement("libf/3.0", + override=True), + "pkga/conanfile.py": GenConanfile("pkga", "1.0").with_tool_requires("toola/1.0"), + }) + + c.run("export libf --version=3.0") + c.run("export libe") + c.run("export libd") + c.run("export libc") + c.run("export libb") + c.run("export toola") + + c.run("lock create pkga") + lock = json.loads(c.load("pkga/conan.lock")) + assert lock["overrides"] == {"libf/1.0": ["libf/3.0"], + "libf/2.0": ["libf/3.0"]} + + c.run("graph build-order pkga --lockfile=pkga/conan.lock --format=json --build=missing") + to_build = json.loads(c.stdout) + for level in to_build: + for elem in level: + ref = elem["ref"] + if "libc" in ref: + pass + for package in elem["packages"][0]: # assumes no dependencies between packages + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=pkga/conan.lock") + + c.run("install pkga --lockfile=pkga/conan.lock") + # All works, all binaries exist now + assert "Install finished successfully" in c.out + + +@pytest.mark.parametrize("forced", [False, True]) +def test_single_config_decentralized_overrides_multi(forced): + r""" same scenario as "test_single_config_centralized()", but distributing the build in + different build servers, using the "build-order" + Now with overrides + + pkga -> toola/1.0 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + | \ \-----------override--------> libf/2.0 + | \--------------------override-------------------------> libf/3.0 + pkgb -> toola/1.1 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + | \ \-----------override--------> libf/2.0 + | \--------------------override-------------------------> libf/4.0 + pkgc -> toola/1.2 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + \-----------override--------> libf/2.0 + """ + override, force = (True, False) if not forced else (False, True) + c = TestClient() + c.save({"libf/conanfile.py": GenConanfile("libf"), + "libe/conanfile.py": GenConanfile("libe", "1.0").with_requires("libf/1.0"), + "libd/conanfile.py": GenConanfile("libd", "1.0").with_requires("libe/1.0"), + "libc/conanfile.py": GenConanfile("libc", "1.0").with_requirement("libd/1.0") + .with_requirement("libf/2.0", + override=override, + force=force), + "libb/conanfile.py": GenConanfile("libb", "1.0").with_requires("libc/1.0"), + "toola/conanfile.py": GenConanfile("toola", "1.0").with_requirement("libb/1.0") + .with_requirement("libf/3.0", + override=override, + force=force), + "toola1/conanfile.py": GenConanfile("toola", "1.1").with_requirement("libb/1.0") + .with_requirement("libf/4.0", + override=override, + force=force), + "toola2/conanfile.py": GenConanfile("toola", "1.2").with_requirement("libb/1.0"), + "pkga/conanfile.py": GenConanfile("pkga", "1.0").with_tool_requires("toola/1.0"), + "pkgb/conanfile.py": GenConanfile("pkgb", "1.0").with_requires("pkga/1.0") + .with_tool_requires("toola/1.1"), + "pkgc/conanfile.py": GenConanfile("pkgc", "1.0").with_requires("pkgb/1.0") + .with_tool_requires("toola/1.2"), + }) + + c.run("export libf --version=2.0") + c.run("export libf --version=3.0") + c.run("export libf --version=4.0") + c.run("export libe") + c.run("export libd") + c.run("export libc") + c.run("export libb") + + c.run("export toola") + c.run("export toola1") + c.run("export toola2") + + c.run("export pkga") + c.run("export pkgb") + c.run("lock create pkgc") + lock = json.loads(c.load("pkgc/conan.lock")) + assert len(lock["overrides"]) == 2 + assert set(lock["overrides"]["libf/1.0"]) == {"libf/4.0", "libf/2.0", "libf/3.0"} + + if forced: # When forced, there is one libf/2.0 that is not overriden + assert set(lock["overrides"]["libf/2.0"]) == {"libf/4.0", "libf/3.0", None} + else: + assert set(lock["overrides"]["libf/2.0"]) == {"libf/4.0", "libf/3.0"} + + c.run("graph build-order pkgc --lockfile=pkgc/conan.lock --format=json --build=missing") + to_build = json.loads(c.stdout) + for level in to_build: + for elem in level: + ref = elem["ref"] + if "libc" in ref: + pass + for package in elem["packages"][0]: # assumes no dependencies between packages + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=pkgc/conan.lock") + + c.run("install pkgc --lockfile=pkgc/conan.lock") + # All works, all binaries exist now + assert "pkga/1.0: Already installed!" in c.out + assert "pkgb/1.0: Already installed!" in c.out + + +@pytest.mark.parametrize("replace_pattern", ["*", "1.0", "2.0", "3.0", "4.0"]) +@pytest.mark.parametrize("forced", [False, True]) +def test_single_config_decentralized_overrides_multi_replace_requires(replace_pattern, forced): + r""" same scenario as "test_single_config_centralized()", but distributing the build in + different build servers, using the "build-order" + Now with overrides + + pkga -> toola/1.0 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + | \ \-----------override--------> libf/2.0 + | \--------------------override-------------------------> libf/3.0 + pkgb -> toola/1.1 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + | \ \-----------override--------> libf/2.0 + | \--------------------override-------------------------> libf/4.0 + pkgc -> toola/1.2 -> libb/1.0 -> libc/1.0 -> libd/1.0 -> libe/1.0 -> libf/1.0 + \-----------override--------> libf/2.0 + """ + override, force = (True, False) if not forced else (False, True) + c = TestClient() + c.save({"libf/conanfile.py": GenConanfile("libf"), + "libe/conanfile.py": GenConanfile("libe", "1.0").with_requires("libf/1.0"), + "libd/conanfile.py": GenConanfile("libd", "1.0").with_requires("libe/1.0"), + "libc/conanfile.py": GenConanfile("libc", "1.0").with_requirement("libd/1.0") + .with_requirement("libf/2.0", + override=override, + force=force), + "libb/conanfile.py": GenConanfile("libb", "1.0").with_requires("libc/1.0"), + "toola/conanfile.py": GenConanfile("toola", "1.0").with_requirement("libb/1.0") + .with_requirement("libf/3.0", + override=override, + force=force), + "toola1/conanfile.py": GenConanfile("toola", "1.1").with_requirement("libb/1.0") + .with_requirement("libf/4.0", + override=override, + force=force), + "toola2/conanfile.py": GenConanfile("toola", "1.2").with_requirement("libb/1.0"), + "pkga/conanfile.py": GenConanfile("pkga", "1.0").with_tool_requires("toola/1.0"), + "pkgb/conanfile.py": GenConanfile("pkgb", "1.0").with_requires("pkga/1.0") + .with_tool_requires("toola/1.1"), + "pkgc/conanfile.py": GenConanfile("pkgc", "1.0").with_requires("pkgb/1.0") + .with_tool_requires("toola/1.2"), + "profile": f"include(default)\n[replace_requires]\nlibf/{replace_pattern}: libf/system" + }) + + c.run("export libf --version=2.0") + c.run("export libf --version=3.0") + c.run("export libf --version=4.0") + c.run("export libf --version=system") + c.run("export libe") + c.run("export libd") + c.run("export libc") + c.run("export libb") + + c.run("export toola") + c.run("export toola1") + c.run("export toola2") + + c.run("export pkga") + c.run("export pkgb") + c.run("lock create pkgc -pr:b=profile") + + # overrides will be different everytime, just checking that things can be built + c.run("graph build-order pkgc --lockfile=pkgc/conan.lock --format=json -pr:b=profile " + "--build=missing") + + to_build = json.loads(c.stdout) + for level in to_build: + for elem in level: + ref = elem["ref"] + if "libc" in ref: + pass + for package in elem["packages"][0]: # assumes no dependencies between packages + binary = package["binary"] + if binary != "Build": + continue + build_args = package["build_args"] + c.run(f"install {build_args} --lockfile=pkgc/conan.lock -pr:b=profile") + + c.run("install pkgc --lockfile=pkgc/conan.lock -pr:b=profile") + # All works, all binaries exist now + assert "pkga/1.0: Already installed!" in c.out + assert "pkgb/1.0: Already installed!" in c.out + if replace_pattern == "1.0": # These will overriden by downstream + assert "libf/system#7fb6d926dabeb955bcea1cafedf953c8 - Cache" not in c.out + else: + assert "libf/system#7fb6d926dabeb955bcea1cafedf953c8 - Cache" in c.out diff --git a/conans/test/integration/lockfile/test_graph_overrides.py b/conans/test/integration/lockfile/test_graph_overrides.py index 6c64ecb1a88..4c42a9c67ea 100644 --- a/conans/test/integration/lockfile/test_graph_overrides.py +++ b/conans/test/integration/lockfile/test_graph_overrides.py @@ -134,7 +134,7 @@ def test_overrides_diamond(override, force): overrides = json_graph['graph']["overrides"] assert len(overrides) == 2 assert overrides['pkga/0.1'] == ['pkga/0.3'] - assert overrides['pkga/0.2'] == ['pkga/0.3#e3afb975277bc245212d6e8de88f4f8f'] + assert overrides['pkga/0.2'] == ['pkga/0.3'] # apply the lockfile to pkgb, should it lock to pkga/0.3 c.run("graph info pkgb --lockfile=pkgd/conan.lock --format=json")