From 27944170a3e451dbaca219921896363c990c6cc9 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 24 Sep 2024 15:20:47 +0200 Subject: [PATCH] fix requires(..., package_id_mode=) with diamonds (#16987) --- conans/model/info.py | 3 +- conans/model/requires.py | 5 +- .../package_id_requires_modes_test.py | 85 ++++++++++++++++++- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/conans/model/info.py b/conans/model/info.py index d6ede116663..ad1a7b2498d 100644 --- a/conans/model/info.py +++ b/conans/model/info.py @@ -72,7 +72,8 @@ def __init__(self, ref, package_id, default_package_id_mode): try: func_package_id_mode = getattr(self, default_package_id_mode) except AttributeError: - raise ConanException("'%s' is not a known package_id_mode" % default_package_id_mode) + raise ConanException(f"require {self._ref} package_id_mode='{default_package_id_mode}' " + "is not a known package_id_mode") else: func_package_id_mode() diff --git a/conans/model/requires.py b/conans/model/requires.py index b1974a7dc6f..7b312b40ee0 100644 --- a/conans/model/requires.py +++ b/conans/model/requires.py @@ -254,7 +254,10 @@ def aggregate(self, other): self.transitive_libs = self.transitive_libs or other.transitive_libs if not other.test: self.test = False # it it was previously a test, but also required by non-test - # TODO: self.package_id_mode => Choose more restrictive? + # package_id_mode is not being propagated downstream. So it is enough to check if the + # current require already defined it or not + if self.package_id_mode is None: + self.package_id_mode = other.package_id_mode def transform_downstream(self, pkg_type, require, dep_pkg_type): """ diff --git a/test/integration/package_id/package_id_requires_modes_test.py b/test/integration/package_id/package_id_requires_modes_test.py index 30628834e64..a3a067fad67 100644 --- a/test/integration/package_id/package_id_requires_modes_test.py +++ b/test/integration/package_id/package_id_requires_modes_test.py @@ -12,7 +12,6 @@ class TestPackageIDRequirementsModes: [("unrelated_mode", "2.0", "", ""), ("patch_mode", "1.0.0.1", "1.0.1", "1.0.1"), ("minor_mode", "1.0.1", "1.2", "1.2.Z"), - ("minor_mode", "1.0.1", "1.2", "1.2.Z"), ("major_mode", "1.5", "2.0", "2.Y.Z"), ("semver_mode", "1.5", "2.0", "2.Y.Z"), ("full_mode", "1.0", "1.0.0.1", "1.0.0.1")]) @@ -110,3 +109,87 @@ def package_id(self): self.assertIn("dep2/1.0@user/testing: PkgNames: ['dep1']", client.out) self.assertIn("consumer/1.0@user/testing: PKGNAMES: ['dep1', 'dep2']", client.out) self.assertIn("consumer/1.0@user/testing: Created", client.out) + + +class TestRequirementPackageId: + """ defining requires(..., package_id_mode) + """ + + @pytest.mark.parametrize("mode, pattern", + [("patch_mode", "1.2.3"), + ("minor_mode", "1.2.Z"), + ("major_mode", "1.Y.Z")]) + def test(self, mode, pattern): + c = TestClient(light=True) + pkg = GenConanfile("pkg", "0.1").with_requirement("dep/1.2.3", package_id_mode=mode) + c.save({"dep/conanfile.py": GenConanfile("dep", "1.2.3"), + "pkg/conanfile.py": pkg}) + c.run("create dep") + c.run("create pkg") + c.run("list pkg:*") + assert f"dep/{pattern}" in c.out + + def test_wrong_mode(self): + c = TestClient(light=True) + pkg = GenConanfile("pkg", "0.1").with_requirement("dep/1.2.3", package_id_mode="nothing") + c.save({"dep/conanfile.py": GenConanfile("dep", "1.2.3"), + "pkg/conanfile.py": pkg}) + c.run("create dep") + c.run("create pkg", assert_error=True) + assert "ERROR: require dep/1.2.3 package_id_mode='nothing' is not a known package_id_mode" \ + in c.out + + @pytest.mark.parametrize("mode, pattern", + [("patch_mode", "1.2.3"), + ("minor_mode", "1.2.Z"), + ("major_mode", "1.Y.Z")]) + def test_half_diamond(self, mode, pattern): + # pkg -> libb -> liba + # \----(mode)----/ + c = TestClient(light=True) + pkg = GenConanfile("pkg", "0.1").with_requirement("liba/1.2.3", package_id_mode=mode)\ + .with_requirement("libb/1.2.3") + c.save({"liba/conanfile.py": GenConanfile("liba", "1.2.3"), + "libb/conanfile.py": GenConanfile("libb", "1.2.3").with_requires("liba/1.2.3"), + "pkg/conanfile.py": pkg}) + c.run("create liba") + c.run("create libb") + c.run("create pkg") + c.run("list pkg:*") + assert f"liba/{pattern}" in c.out + # reverse order also works the same + pkg = GenConanfile("pkg", "0.1").with_requirement("libb/1.2.3")\ + .with_requirement("liba/1.2.3", package_id_mode=mode) + c.save({"pkg/conanfile.py": pkg}) + c.run("create pkg") + c.run("list pkg:*") + assert f"liba/{pattern}" in c.out + + @pytest.mark.parametrize("mode, pattern", + [("patch_mode", "1.2.3"), + ("minor_mode", "1.2.Z"), + ("major_mode", "1.Y.Z")]) + def test_half_diamond_conflict(self, mode, pattern): + # pkg -> libb --(mode)-> liba + # \----(mode)-----------/ + # The libb->liba mode is not propagated down to pkg, so it doesn't really conflict + c = TestClient(light=True) + pkg = GenConanfile("pkg", "0.1").with_requirement("liba/1.2.3", package_id_mode=mode) \ + .with_requirement("libb/1.2.3") + libb = GenConanfile("libb", "1.2.3").with_requirement("liba/1.2.3", + package_id_mode="patch_mode") + c.save({"liba/conanfile.py": GenConanfile("liba", "1.2.3"), + "libb/conanfile.py": libb, + "pkg/conanfile.py": pkg}) + c.run("create liba") + c.run("create libb") + c.run("create pkg") + c.run("list pkg:*") + assert f"liba/{pattern}" in c.out + # reverse order also works the same + pkg = GenConanfile("pkg", "0.1").with_requirement("libb/1.2.3") \ + .with_requirement("liba/1.2.3", package_id_mode=mode) + c.save({"pkg/conanfile.py": pkg}) + c.run("create pkg") + c.run("list pkg:*") + assert f"liba/{pattern}" in c.out