Repository: conan-io/conan
Pull request #17829 (2025-02-21 13:04:12)

fix cps prefix
Changelog: Omit
Docs: Omit


Just to prepare for using std::cpp 25 and align with latest CMake developments


Patch:
diff --git a/conan/cli/commands/remote.py b/conan/cli/commands/remote.py
index 2770f3f36..a22529f54 100644
--- a/conan/cli/commands/remote.py
+++ b/conan/cli/commands/remote.py
@@ -278,7 +278,7 @@ def print_auth_json(results):
 @conan_subcommand(formatters={"text": print_auth, "json": print_auth_json})
 def remote_auth(conan_api, parser, subparser, *args):
     """
-    Authenticate in the defined remotes. Use CONAN_LOGIN* and CONAN_PASSWORD* variables if available.
+    Authenticate in the defined remotes. Use CONAN_LOGIN_USERNAME* and CONAN_PASSWORD* variables if available.
     Ask for username and password interactively in case (re-)authentication is required and there are
     no CONAN_LOGIN* and CONAN_PASSWORD* variables available which could be used.
     Usually you'd use this method over conan remote login for scripting which needs to run in CI
@@ -288,7 +288,7 @@ def remote_auth(conan_api, parser, subparser, *args):
                                           " The pattern uses 'fnmatch' style wildcards.")
     subparser.add_argument("--with-user", action="store_true",
                            help="Only try to auth in those remotes that already "
-                                "have a username or a CONAN_LOGIN_ env-var defined")
+                                "have a username or a CONAN_LOGIN_USERNAME* env-var defined")
     subparser.add_argument("--force", action="store_true",
                            help="Force authentication for anonymous-enabled repositories. "
                                 "Can be used for force authentication in case your Artifactory "
diff --git a/conan/cps/cps.py b/conan/cps/cps.py
index 4fab7911a..d8c9fb15c 100644
--- a/conan/cps/cps.py
+++ b/conan/cps/cps.py
@@ -115,12 +115,16 @@ class CPS:
         self.description = None
         self.license = None
         self.website = None
+        self.prefix = None
 
     def serialize(self):
-        cps = {"cps_version": "0.12.0",
+        cps = {"cps_version": "0.13.0",
                "name": self.name,
                "version": self.version}
 
+        if self.prefix is not None:
+            cps["prefix"] = self.prefix
+
         # Supplemental
         for data in "license", "description", "website":
             if getattr(self, data, None):
@@ -143,6 +147,7 @@ class CPS:
     def deserialize(data):
         cps = CPS()
         cps.name = data.get("name")
+        cps.prefix = data.get("prefix")
         cps.version = data.get("version")
         cps.license = data.get("license")
         cps.description = data.get("description")
@@ -157,6 +162,7 @@ class CPS:
     @staticmethod
     def from_conan(dep):
         cps = CPS(dep.ref.name, str(dep.ref.version))
+        cps.prefix = dep.package_folder.replace("\\", "/")
         # supplemental
         cps.license = dep.license
         cps.description = dep.description
diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py
index 61c0d7b85..fe553891f 100644
--- a/conan/internal/api/install/generators.py
+++ b/conan/internal/api/install/generators.py
@@ -11,6 +11,7 @@ from conans.util.files import save, mkdir, chdir
 
 _generators = {"CMakeToolchain": "conan.tools.cmake",
                "CMakeDeps": "conan.tools.cmake",
+               "CMakeConfigDeps": "conan.tools.cmake",
                "MesonToolchain": "conan.tools.meson",
                "MSBuildDeps": "conan.tools.microsoft",
                "MSBuildToolchain": "conan.tools.microsoft",
diff --git a/conan/tools/cmake/__init__.py b/conan/tools/cmake/__init__.py
index e57f14a65..d6a1c431f 100644
--- a/conan/tools/cmake/__init__.py
+++ b/conan/tools/cmake/__init__.py
@@ -1,14 +1,31 @@
+from conan.errors import ConanException
 from conan.tools.cmake.toolchain.toolchain import CMakeToolchain
 from conan.tools.cmake.cmake import CMake
 from conan.tools.cmake.layout import cmake_layout
 
 
 def CMakeDeps(conanfile):  # noqa
-    if conanfile.conf.get("tools.cmake.cmakedeps:new", choices=["will_break_next"]):
+    if conanfile.conf.get("tools.cmake.cmakedeps:new",
+                          choices=["will_break_next", "recipe_will_break"]) == "will_break_next":
         from conan.tools.cmake.cmakedeps2.cmakedeps import CMakeDeps2
-        conanfile.output.warning("Using the new CMakeDeps generator, behind the "
+        conanfile.output.warning("Using the new CMakeConfigDeps generator, behind the "
                                  "'tools.cmake.cmakedeps:new' gate conf. This conf will change "
                                  "next release, breaking, so use it only for testing and dev")
         return CMakeDeps2(conanfile)
     from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps as _CMakeDeps
     return _CMakeDeps(conanfile)
+
+
+
+def CMakeConfigDeps(conanfile):  # noqa
+    if conanfile.conf.get("tools.cmake.cmakedeps:new",
+                          choices=["will_break_next", "recipe_will_break"]):
+        from conan.tools.cmake.cmakedeps2.cmakedeps import CMakeDeps2
+        conanfile.output.warning("Using the new CMakeConfigDeps generator, behind the "
+                                 "'tools.cmake.cmakedeps:new' gate conf. This conf will change "
+                                 "next release, breaking, so use it only for testing and dev")
+        return CMakeDeps2(conanfile)
+    else:
+        raise ConanException("CMakeConfigDeps is being used in conanfile, but the conf "
+                             "'tools.cmake.cmakedeps:new' is not enabled. This is an incubating"
+                             "feature, please define it to enable CMakeConfigDeps usage")
diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py
index 578a87c01..d416d4ad1 100644
--- a/conan/tools/cps/cps_deps.py
+++ b/conan/tools/cps/cps_deps.py
@@ -32,7 +32,7 @@ class CPSDeps:
     def generate(self):
         cps_folder = os.path.join(self._conanfile.folders.base_build, "build", "cps")
         config_name = self._config_name()
-        folder = os.path.join(cps_folder, config_name)
+        folder = os.path.join(cps_folder, config_name, "cps")  # CMake wants a "/cps/" folder
         self._conanfile.output.info(f"[CPSDeps] folder {cps_folder}")
         deps = self._conanfile.dependencies.host.items()
         mapping = {}


Test patch:
diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py
index e82527398..95280638e 100644
--- a/test/integration/cps/test_cps.py
+++ b/test/integration/cps/test_cps.py
@@ -17,7 +17,7 @@ def test_cps():
 
     settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64"
     c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps")
-    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps"))
+    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/cps/pkg.cps"))
     assert pkg["name"] == "pkg"
     assert pkg["version"] == "0.1"
     assert pkg["license"] == "MIT"
@@ -39,7 +39,7 @@ def test_cps_static_lib():
 
     settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64"
     c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps")
-    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps"))
+    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/cps/pkg.cps"))
     assert pkg["name"] == "pkg"
     assert pkg["version"] == "0.1"
     assert pkg["configurations"] == ["release"]
@@ -56,7 +56,7 @@ def test_cps_header():
 
     settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64"
     c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps")
-    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps"))
+    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/cps/pkg.cps"))
     assert pkg["name"] == "pkg"
     assert pkg["version"] == "0.1"
     assert "configurations" not in "pkg"
@@ -173,7 +173,7 @@ def test_extended_cpp_info():
 
     settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64"
     c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps")
-    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps"))
+    pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/cps/pkg.cps"))
     assert pkg["name"] == "pkg"
     assert pkg["version"] == "0.1"
     assert pkg["default_components"] == ["pkg"]
diff --git a/test/integration/toolchains/cmake/cmakedeps2/__init__.py b/test/integration/toolchains/cmake/cmakedeps2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
index 4b6001749..83420d6d6 100644
--- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -1,6 +1,7 @@
 import re
 import textwrap
 
+from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
 
 new_value = "will_break_next"
@@ -90,3 +91,64 @@ def test_cmakedeps_transitive_paths():
     assert not re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH /bina\"", cmake_paths)
     assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/libb.*/p/libb\" \".*/liba.*/p/liba\"\)", cmake_paths)
     assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/libb.*/p/includeb\" \".*/liba.*/p/includea\"\)", cmake_paths)
+
+
+def test_cmakeconfigdeps_recipe():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan.tools.cmake import CMakeConfigDeps
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            settings = "build_type"
+            requires = "dep/0.1"
+            def generate(self):
+                deps = CMakeConfigDeps(self)
+                deps.generate()
+    """)
+    c.save({"dep/conanfile.py": GenConanfile("dep", "0.1"),
+            "app/conanfile.py": conanfile})
+    c.run("create dep")
+    c.run("install app", assert_error=True)
+    assert "CMakeConfigDeps is being used in conanfile, but the conf " \
+           "'tools.cmake.cmakedeps:new' is not enabled" in c.out
+    c.run("install app -c tools.cmake.cmakedeps:new=will_break_next")
+    # will not fail, still warn
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+    # The only-recipe also not fails
+    c.run("install app -c tools.cmake.cmakedeps:new=recipe_will_break")
+    # will not fail
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+
+    # attribute generator
+    conanfile = textwrap.dedent("""
+        from conan.tools.cmake import CMakeConfigDeps
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            settings = "build_type"
+            requires = "dep/0.1"
+            generators = "CMakeConfigDeps"
+        """)
+    c.save({"app/conanfile.py": conanfile}, clean_first=True)
+    c.run("install app", assert_error=True)
+    assert "CMakeConfigDeps is being used in conanfile, but the conf " \
+           "'tools.cmake.cmakedeps:new' is not enabled" in c.out
+    c.run("install app -c tools.cmake.cmakedeps:new=will_break_next")
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+    c.run("install app -c tools.cmake.cmakedeps:new=recipe_will_break")
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+
+    # conanfile.txt
+    conanfile = textwrap.dedent("""
+        [requires]
+        dep/0.1
+        [generators]
+        CMakeConfigDeps
+        """)
+    c.save({"app/conanfile.txt": conanfile}, clean_first=True)
+    c.run("install app", assert_error=True)
+    assert "CMakeConfigDeps is being used in conanfile, but the conf " \
+           "'tools.cmake.cmakedeps:new' is not enabled" in c.out
+    c.run("install app -c tools.cmake.cmakedeps:new=will_break_next")
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+    c.run("install app -c tools.cmake.cmakedeps:new=recipe_will_break")
+    assert "WARN: Using the new CMakeConfigDeps generator" in c.out