From 05610115bbd52dd25c6c1db72f52fc1a15a9ee7e Mon Sep 17 00:00:00 2001 From: James Date: Wed, 26 Jul 2023 16:35:45 +0200 Subject: [PATCH] no user/channel pattern matches (#14338) * no user/channel pattern matches * do tests for --build and options patterns --- conans/client/graph/build_mode.py | 7 +--- conans/model/recipe_ref.py | 10 +++++ .../test/integration/command/create_test.py | 37 +++++++++++++++++ .../test/integration/options/options_test.py | 40 +++++++++++++++++++ .../settings/per_package_settings_test.py | 20 ++++++++++ 5 files changed, 108 insertions(+), 6 deletions(-) diff --git a/conans/client/graph/build_mode.py b/conans/client/graph/build_mode.py index ad70da848af..efb7d669377 100644 --- a/conans/client/graph/build_mode.py +++ b/conans/client/graph/build_mode.py @@ -36,14 +36,9 @@ def __init__(self, params): else: if param.startswith("missing:"): clean_pattern = param[len("missing:"):] - clean_pattern = clean_pattern[:-1] if param.endswith("@") else clean_pattern - clean_pattern = clean_pattern.replace("@#", "#") self.build_missing_patterns.append(clean_pattern) else: - # Remove the @ at the end, to match for - # "conan install --requires=pkg/0.1@ --build=pkg/0.1@" - clean_pattern = param[:-1] if param.endswith("@") else param - clean_pattern = clean_pattern.replace("@#", "#") + clean_pattern = param if clean_pattern and clean_pattern[0] in ["!", "~"]: self._excluded_patterns.append(clean_pattern[1:]) else: diff --git a/conans/model/recipe_ref.py b/conans/model/recipe_ref.py index a4b2b2f38db..070ed8dde6c 100644 --- a/conans/model/recipe_ref.py +++ b/conans/model/recipe_ref.py @@ -163,9 +163,19 @@ def matches(self, pattern, is_consumer): pattern = pattern[1:] negate = True + no_user_channel = False + if pattern.endswith("@"): # it means we want to match only without user/channel + pattern = pattern[:-1] + no_user_channel = True + elif "@#" in pattern: + pattern = pattern.replace("@#", "#") + no_user_channel = True + condition = ((pattern == "&" and is_consumer) or fnmatch.fnmatchcase(str(self), pattern) or fnmatch.fnmatchcase(self.repr_notime(), pattern)) + if no_user_channel: + condition = condition and not self.user and not self.channel if negate: return not condition return condition diff --git a/conans/test/integration/command/create_test.py b/conans/test/integration/command/create_test.py index da88dc82891..e82558b8dca 100644 --- a/conans/test/integration/command/create_test.py +++ b/conans/test/integration/command/create_test.py @@ -319,6 +319,43 @@ def test_create_build_missing(): assert "ERROR: Missing prebuilt package for 'dep/1.0'" in c.out +def test_create_no_user_channel(): + """ test the --build=pattern and --build=missing:pattern syntax to build missing packages + without user/channel + """ + c = TestClient() + c.save({"dep/conanfile.py": GenConanfile(), + "pkg/conanfile.py": GenConanfile("pkg", "1.0").with_requires("dep1/0.1", "dep2/0.1@user", + "dep3/0.1@user/channel")}) + c.run("export dep --name=dep1 --version=0.1") + c.run("export dep --name=dep2 --version=0.1 --user=user") + c.run("export dep --name=dep3 --version=0.1 --user=user --channel=channel") + + # First test the ``--build=missing:pattern`` + c.run("create pkg --build=missing:*@", assert_error=True) + c.assert_listed_binary({"dep1/0.1": (NO_SETTINGS_PACKAGE_ID, "Build"), + "dep2/0.1": (NO_SETTINGS_PACKAGE_ID, "Missing"), + "dep3/0.1": (NO_SETTINGS_PACKAGE_ID, "Missing")}) + c.run("create pkg --build=missing:!*@", assert_error=True) + c.assert_listed_binary({"dep1/0.1": (NO_SETTINGS_PACKAGE_ID, "Missing"), + "dep2/0.1": (NO_SETTINGS_PACKAGE_ID, "Build"), + "dep3/0.1": (NO_SETTINGS_PACKAGE_ID, "Build")}) + + # Now lets make sure they exist + c.run("create pkg --build=missing") + + # Now test the --build=pattern + c.run("create pkg --build=*@") + c.assert_listed_binary({"dep1/0.1": (NO_SETTINGS_PACKAGE_ID, "Build"), + "dep2/0.1": (NO_SETTINGS_PACKAGE_ID, "Cache"), + "dep3/0.1": (NO_SETTINGS_PACKAGE_ID, "Cache")}) + # The --build=* needs to be said: "build all except those that have user/channel + c.run("create pkg --build=* --build=!*@") + c.assert_listed_binary({"dep1/0.1": (NO_SETTINGS_PACKAGE_ID, "Cache"), + "dep2/0.1": (NO_SETTINGS_PACKAGE_ID, "Build"), + "dep3/0.1": (NO_SETTINGS_PACKAGE_ID, "Build")}) + + def test_create_format_json(): """ Tests the ``conan create . -f json`` result diff --git a/conans/test/integration/options/options_test.py b/conans/test/integration/options/options_test.py index 6b99a4d7d07..520131896d4 100644 --- a/conans/test/integration/options/options_test.py +++ b/conans/test/integration/options/options_test.py @@ -570,6 +570,46 @@ def test_transitive_options_conanfile_py_create(self, client): self.check(client) +def test_options_no_user_channel_patterns(): + c = TestClient() + conanfile = textwrap.dedent("""\ + from conan import ConanFile + class Pkg(ConanFile): + options = {"myoption": [1, 2, 3]} + def configure(self): + self.output.info(f"MYOPTION: {self.options.myoption}") + """) + c.save({"dep/conanfile.py": conanfile, + "pkg/conanfile.py": GenConanfile("pkg", "1.0").with_requires("dep1/0.1", "dep2/0.1@user", + "dep3/0.1@user/channel")}) + c.run("export dep --name=dep1 --version=0.1") + c.run("export dep --name=dep2 --version=0.1 --user=user") + c.run("export dep --name=dep3 --version=0.1 --user=user --channel=channel") + + c.run("graph info pkg -o *:myoption=3 -o *@:myoption=1") + assert "dep1/0.1: MYOPTION: 1" in c.out + assert "dep2/0.1@user: MYOPTION: 3" in c.out + assert "dep3/0.1@user/channel: MYOPTION: 3" in c.out + + # Recall that order is also important latest matching pattern wins + c.run("graph info pkg -o *@:myoption=1 -o *:myoption=1") + assert "dep1/0.1: MYOPTION: 1" in c.out + assert "dep2/0.1@user: MYOPTION: 1" in c.out + assert "dep3/0.1@user/channel: MYOPTION: 1" in c.out + + # This is a bit weird negation approach, but it works = all packages that have user channel + c.run("graph info pkg -o *:myoption=3 -o ~*@:myoption=1") + assert "dep1/0.1: MYOPTION: 3" in c.out + assert "dep2/0.1@user: MYOPTION: 1" in c.out + assert "dep3/0.1@user/channel: MYOPTION: 1" in c.out + + # Which is identical to '~*@' == '*@*' + c.run("graph info pkg -o *:myoption=3 -o *@*:myoption=1") + assert "dep1/0.1: MYOPTION: 3" in c.out + assert "dep2/0.1@user: MYOPTION: 1" in c.out + assert "dep3/0.1@user/channel: MYOPTION: 1" in c.out + + class TestTransitiveOptionsSharedInvisible: """ https://github.com/conan-io/conan/issues/13854 diff --git a/conans/test/integration/settings/per_package_settings_test.py b/conans/test/integration/settings/per_package_settings_test.py index 543eb2381e1..0d9a666fd06 100644 --- a/conans/test/integration/settings/per_package_settings_test.py +++ b/conans/test/integration/settings/per_package_settings_test.py @@ -75,3 +75,23 @@ class Pkg(ConanFile): client.run("create . --name=consumer --version=0.1 --user=user --channel=testing %s -s compiler.libcxx=libstdc++ " "-s pkg*:compiler.libcxx=libstdc++11" % settings) self.assertIn("consumer/0.1@user/testing: Created package", client.out) + + def test_per_package_setting_all_packages_without_user_channel(self): + client = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + settings = "os" + def configure(self): + self.output.info(f"I am a {self.settings.os} pkg!!!") + """) + client.save({"conanfile.py": conanfile}) + client.run("create . --name=pkg1 --version=0.1 -s os=Windows") + client.run("create . --name=pkg2 --version=0.1 --user=user -s os=Linux") + client.run("create . --name=pkg3 --version=0.1 --user=user --channel=channel -s os=Linux") + client.save({"conanfile.py": GenConanfile().with_requires("pkg1/0.1", "pkg2/0.1@user", + "pkg3/0.1@user/channel")}) + client.run("install . -s os=Linux -s *@:os=Windows") + assert "pkg1/0.1: I am a Windows pkg!!!" in client.out + assert "pkg2/0.1@user: I am a Linux pkg!!!" in client.out + assert "pkg3/0.1@user/channel: I am a Linux pkg!!!" in client.out