From 60b232f3fb7455099ddd5f2eb0276152e9591ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 25 Aug 2025 15:53:24 +0200 Subject: [PATCH 01/12] Sketch cmake_extra_variables property --- .../cmake/cmakedeps2/target_configuration.py | 17 +++++++++ conan/tools/cmake/toolchain/blocks.py | 37 ++----------------- conan/tools/cmake/utils.py | 36 ++++++++++++++++++ .../cmakedeps2/test_cmakeconfigdeps_new.py | 24 ++++++++++++ 4 files changed, 81 insertions(+), 33 deletions(-) diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index cf11208af48..be089945c97 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -5,6 +5,7 @@ from jinja2 import Template from conan.errors import ConanException +from conan.tools.cmake.utils import parse_extra_variable from conan.internal.api.install.generators import relativize_path from conan.internal.model.pkg_type import PackageType from conan.internal.graph.graph import CONTEXT_BUILD, CONTEXT_HOST @@ -153,6 +154,15 @@ def _context(self): root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile) libraries = root_target_name or f"{pkg_name}::{pkg_name}" + # Reading configuration from "tools.cmake.cmaketoolchain:extra_variables" + extra_variables = prefixes = self._cmakedeps.get_property("cmake_extra_variables", + self._conanfile, check_type=dict + ) or {} + parsed_extra_variables = {} + for key, value in extra_variables.items(): + parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", + key, value) + pkg_folder = relativize_path(pkg_folder, self._cmakedeps._conanfile, "${CMAKE_CURRENT_LIST_DIR}") dependencies = self._get_dependencies() @@ -169,6 +179,7 @@ def _context(self): "include_dirs": include_dirs, "definitions": definitions, "libraries": libraries, + "extra_variables": parsed_extra_variables, } def _get_libs(self, cpp_info, pkg_name, pkg_folder, pkg_folder_var) -> dict: @@ -482,6 +493,12 @@ def _template(self): {% endif %} {% endfor %} + # Definition of extra CMake variables from cmake_extra_variables + + {% for key, value in extra_variables.items() %} + set({{ key }} {{ value }}) + {% endfor %} + ################# Exes information ############## {% for exe, location in exes.items() %} #################### {{exe}} #################### diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 902993fd110..9d233412f34 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -13,7 +13,7 @@ from conan.tools.build.flags import architecture_flag, architecture_link_flag, libcxx_flags, threads_flags from conan.tools.build.cross_building import cross_building from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME -from conan.tools.cmake.utils import is_multi_configuration +from conan.tools.cmake.utils import is_multi_configuration, parse_extra_variable from conan.tools.intel import IntelCC from conan.tools.microsoft.visual import msvc_version_to_toolset_version, msvc_platform_from_arch from conan.internal.api.install.generators import relativize_path @@ -1199,44 +1199,15 @@ class ExtraVariablesBlock(Block): {% endif %} """) - CMAKE_CACHE_TYPES = ["BOOL", "FILEPATH", "PATH", "STRING", "INTERNAL"] - - def get_exact_type(self, key, value): - if isinstance(value, str): - return f"\"{value}\"" - elif isinstance(value, (int, float)): - return value - elif isinstance(value, dict): - var_value = self.get_exact_type(key, value.get("value")) - is_force = value.get("force") - if is_force: - if not isinstance(is_force, bool): - raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "{key}" "force" must be a boolean') - is_cache = value.get("cache") - if is_cache: - if not isinstance(is_cache, bool): - raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "{key}" "cache" must be a boolean') - var_type = value.get("type") - if not var_type: - raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "{key}" needs "type" defined for cache variable') - if var_type not in self.CMAKE_CACHE_TYPES: - raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "{key}" invalid type "{var_type}" for cache variable. Possible types: {", ".join(self.CMAKE_CACHE_TYPES)}') - # Set docstring as variable name if not defined - docstring = value.get("docstring") or key - force_str = " FORCE" if is_force else "" # Support python < 3.11 - return f"{var_value} CACHE {var_type} \"{docstring}\"{force_str}" - else: - if is_force: - raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "{key}" "force" is only allowed for cache variables') - return var_value - def context(self): + from conan.tools.cmake.utils import parse_extra_variable # Reading configuration from "tools.cmake.cmaketoolchain:extra_variables" extra_variables = self._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", default={}, check_type=dict) parsed_extra_variables = {} for key, value in extra_variables.items(): - parsed_extra_variables[key] = self.get_exact_type(key, value) + parsed_extra_variables[key] = parse_extra_variable("tools.cmake.cmaketoolchain:extra_variables", + key, value) return {"extra_variables": parsed_extra_variables} diff --git a/conan/tools/cmake/utils.py b/conan/tools/cmake/utils.py index ffd9fcbd640..203141e27fa 100644 --- a/conan/tools/cmake/utils.py +++ b/conan/tools/cmake/utils.py @@ -1,5 +1,41 @@ +from conan.errors import ConanException + def is_multi_configuration(generator): if not generator: return False return "Visual" in generator or "Xcode" in generator or "Multi-Config" in generator + + +def parse_extra_variable(source, key, value): + CMAKE_CACHE_TYPES = ["BOOL", "FILEPATH", "PATH", "STRING", "INTERNAL"] + if isinstance(value, str): + return f"\"{value}\"" + elif isinstance(value, (int, float)): + return value + elif isinstance(value, dict): + var_value = parse_extra_variable(source, key, value.get("value")) + is_force = value.get("force") + if is_force: + if not isinstance(is_force, bool): + raise ConanException(f'{source} "{key}" "force" must be a boolean') + is_cache = value.get("cache") + if is_cache: + if not isinstance(is_cache, bool): + raise ConanException(f'{source} "{key}" "cache" must be a boolean') + var_type = value.get("type") + if not var_type: + raise ConanException(f'{source} "{key}" needs "type" defined for cache variable') + if var_type not in CMAKE_CACHE_TYPES: + raise ConanException(f'{source} "{key}" invalid type "{var_type}" for cache variable.' + f' Possible types: {", ".join(CMAKE_CACHE_TYPES)}') + # Set docstring as variable name if not defined + docstring = value.get("docstring") or key + force_str = " FORCE" if is_force else "" # Support python < 3.11 + return f"{var_value} CACHE {var_type} \"{docstring}\"{force_str}" + else: + if is_force: + raise ConanException(f'{source} "{key}" "force" is only allowed for cache variables') + return var_value + raise ConanException(f'{source} "{key}" has invalid type. Allowed types: str, int, float, dict,' + f' got {type(value)}') diff --git a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py index 3ca484d4fe3..b771dac40d6 100644 --- a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py +++ b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py @@ -1572,3 +1572,27 @@ def build(self): assert "find_package(matrix)" in c.out assert "target_link_libraries(... matrix::matrix)" in c.out assert "Conan: Target declared imported INTERFACE library 'matrix::matrix'" in c.out + + +def test_toolchain_extra_variables(): + """ Test extra_variables property - This just shows that it works, + there are tests for cmaketoolchain that check the actual behavior + of parsing the variables""" + client = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + + def package_info(self): + self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/"}) + """) + client.save({"conanfile.py": conanfile}) + client.run("create .") + + client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}") + target = client.load("pkg-Targets-release.cmake") + assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target + assert 'set(FOO "42")' in target From 92e6ba19b9155571ca563202b8c755be08487a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 25 Aug 2025 16:12:27 +0200 Subject: [PATCH 02/12] Typo --- conan/tools/cmake/cmakedeps2/target_configuration.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index be089945c97..f2ec18bd68e 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -155,9 +155,8 @@ def _context(self): libraries = root_target_name or f"{pkg_name}::{pkg_name}" # Reading configuration from "tools.cmake.cmaketoolchain:extra_variables" - extra_variables = prefixes = self._cmakedeps.get_property("cmake_extra_variables", - self._conanfile, check_type=dict - ) or {} + extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, + check_type=dict) or {} parsed_extra_variables = {} for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", From 4e48054fa32ca2a4f95045a27fc0ea31466e100d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 25 Aug 2025 17:52:00 +0200 Subject: [PATCH 03/12] Move test, add cache check --- .../cmakedeps2/test_cmakeconfigdeps_new.py | 24 ---------------- .../cmake/cmakedeps2/test_cmakedeps.py | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py index b771dac40d6..3ca484d4fe3 100644 --- a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py +++ b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py @@ -1572,27 +1572,3 @@ def build(self): assert "find_package(matrix)" in c.out assert "target_link_libraries(... matrix::matrix)" in c.out assert "Conan: Target declared imported INTERFACE library 'matrix::matrix'" in c.out - - -def test_toolchain_extra_variables(): - """ Test extra_variables property - This just shows that it works, - there are tests for cmaketoolchain that check the actual behavior - of parsing the variables""" - client = TestClient() - conanfile = textwrap.dedent(""" - from conan import ConanFile - - class Pkg(ConanFile): - name = "pkg" - version = "0.1" - - def package_info(self): - self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/"}) - """) - client.save({"conanfile.py": conanfile}) - client.run("create .") - - client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}") - target = client.load("pkg-Targets-release.cmake") - assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target - assert 'set(FOO "42")' in target diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py index 0f329d469de..a2f440bc149 100644 --- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py +++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py @@ -521,3 +521,31 @@ def generate(self): assert "add_library(component_alias" in targets_data assert "add_library(dep::my_aliased_component" in targets_data + + +def test_package_info_extra_variables(): + """ Test extra_variables property - This just shows that it works, + there are tests for cmaketoolchain that check the actual behavior + of parsing the variables""" + client = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + + def package_info(self): + self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, + "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/", + "CACHE_VAR_DEFAULT_DOC": {"value": "hello world", + "cache": True, "type": "PATH"}}) + """) + client.save({"conanfile.py": conanfile}) + client.run("create .") + + client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}") + target = client.load("pkg-Targets-release.cmake") + assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target + assert 'set(FOO 42)' in target + assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in target From 7ae60dd540819efe54295689503c2115c30dd36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 25 Aug 2025 19:59:33 +0200 Subject: [PATCH 04/12] Add feature to old CMakeDeps too --- .../tools/cmake/cmakedeps/templates/config.py | 20 +++++++++++++ .../cmake/cmakedeps2/target_configuration.py | 2 +- .../cmake/cmakedeps/test_cmakedeps.py | 28 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index f7e9fe1ba60..3c16ed9ec12 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -28,6 +28,19 @@ def additional_variables_prefixes(self): self.cmakedeps.get_property("cmake_additional_variables_prefixes", self.conanfile, check_type=list) or []) return list(set([self.file_name] + prefix_list)) + @property + def parsed_extra_variables(self): + # Reading configuration from "cmake_extra_variables" property + from conan.tools.cmake.utils import parse_extra_variable + extra_variables = self.cmakedeps.get_property("cmake_extra_variables", self.conanfile, + check_type=dict) or {} + parsed_extra_variables = {} + for key, value in extra_variables.items(): + parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", + key, value) + return parsed_extra_variables + + @property def context(self): targets_include = "" if not self.generating_module else "module-" @@ -36,6 +49,7 @@ def context(self): "version": self.conanfile.ref.version, "file_name": self.file_name, "additional_variables_prefixes": self.additional_variables_prefixes, + "extra_variables": self.parsed_extra_variables, "pkg_name": self.pkg_name, "config_suffix": self.config_suffix, "check_components_exist": self.cmakedeps.check_components_exist, @@ -83,6 +97,12 @@ def template(self): {% endfor %} + # Definition of extra CMake variables from cmake_extra_variables + + {% for key, value in extra_variables.items() %} + set({{ key }} {{ value }}) + {% endfor %} + # Only the last installed configuration BUILD_MODULES are included to avoid the collision foreach(_BUILD_MODULE {{ pkg_var(pkg_name, 'BUILD_MODULES_PATHS', config_suffix) }} ) message({% raw %}${{% endraw %}{{ file_name }}_MESSAGE_MODE} "Conan: Including build module from '${_BUILD_MODULE}'") diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index f2ec18bd68e..3c266cfabdc 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -154,7 +154,7 @@ def _context(self): root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile) libraries = root_target_name or f"{pkg_name}::{pkg_name}" - # Reading configuration from "tools.cmake.cmaketoolchain:extra_variables" + # Reading configuration from "cmake_extra_variables" property extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, check_type=dict) or {} parsed_extra_variables = {} diff --git a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py index 457300b91bd..849afc4c2f6 100644 --- a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py +++ b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py @@ -922,3 +922,31 @@ def generate(self): assert "add_library(component_alias" in targets_data assert "add_library(dep::my_aliased_component" in targets_data + + +def test_package_info_extra_variables(): + """ Test extra_variables property - This just shows that it works, + there are tests for cmaketoolchain that check the actual behavior + of parsing the variables""" + client = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + + def package_info(self): + self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, + "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/", + "CACHE_VAR_DEFAULT_DOC": {"value": "hello world", + "cache": True, "type": "PATH"}}) + """) + client.save({"conanfile.py": conanfile}) + client.run("create .") + + client.run(f"install --requires=pkg/0.1 -g CMakeDeps") + target = client.load("pkg-config.cmake") + assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target + assert 'set(FOO 42)' in target + assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in target From e076a9d18ca06e933018b83145a6a948b36b75d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Thu, 4 Sep 2025 15:49:11 +0200 Subject: [PATCH 05/12] Test for dependnecy overriding profile --- .../cmake/test_cmake_extra_variables.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/functional/toolchains/cmake/test_cmake_extra_variables.py diff --git a/test/functional/toolchains/cmake/test_cmake_extra_variables.py b/test/functional/toolchains/cmake/test_cmake_extra_variables.py new file mode 100644 index 00000000000..98173aea068 --- /dev/null +++ b/test/functional/toolchains/cmake/test_cmake_extra_variables.py @@ -0,0 +1,51 @@ +import textwrap + +from conan.test.assets.genconanfile import GenConanfile +from conan.test.utils.tools import TestClient +new_value = "will_break_next" + + +def test_package_info_extra_variables(): + """ Issue: Dependencies have preference over dependencies, bad """ + client = TestClient() + dep_conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + name = "dep" + version = "0.1" + + def package_info(self): + self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42}) + """) + client.save({"dep/conanfile.py": dep_conanfile}) + client.run("create dep") + + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.27) + project(myproject CXX) + find_package(dep CONFIG REQUIRED) + message(STATUS "FOO=${FOO}") + """) + + + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.cmake import CMake + + class Pkg(ConanFile): + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeDeps", "CMakeToolchain" + requires = "dep/0.1" + def build(self): + cmake = CMake(self) + cmake.configure() + """) + client.save({"CMakeLists.txt": cmakelists, + "conanfile.py": conanfile}) + client.run(f"build . -c tools.cmake.cmakedeps:new={new_value} " + """-c tools.cmake.cmaketoolchain:extra_variables="{'FOO': '9'}" """) + + # This fails because the user is seeing -- FOO=42 coming from the dependency + assert "-- FOO=9" in client.out + From 22e67e464c35b2c31711afc74d9841cf193a088b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= <5364255+AbrilRBS@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:59:42 +0200 Subject: [PATCH 06/12] Add missing CMake --- test/functional/toolchains/cmake/test_cmake_extra_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/toolchains/cmake/test_cmake_extra_variables.py b/test/functional/toolchains/cmake/test_cmake_extra_variables.py index 98173aea068..a3c7a8c80d6 100644 --- a/test/functional/toolchains/cmake/test_cmake_extra_variables.py +++ b/test/functional/toolchains/cmake/test_cmake_extra_variables.py @@ -5,6 +5,7 @@ new_value = "will_break_next" +@pytest.mark.tool("cmake", "3.27") def test_package_info_extra_variables(): """ Issue: Dependencies have preference over dependencies, bad """ client = TestClient() From d062644f58b4b6690dcb8f7bc59b7d99e42a4488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= <5364255+AbrilRBS@users.noreply.github.com> Date: Thu, 4 Sep 2025 20:27:20 +0200 Subject: [PATCH 07/12] fix missing import --- test/functional/toolchains/cmake/test_cmake_extra_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/toolchains/cmake/test_cmake_extra_variables.py b/test/functional/toolchains/cmake/test_cmake_extra_variables.py index a3c7a8c80d6..26b2b7e6788 100644 --- a/test/functional/toolchains/cmake/test_cmake_extra_variables.py +++ b/test/functional/toolchains/cmake/test_cmake_extra_variables.py @@ -1,4 +1,5 @@ import textwrap +import pytest from conan.test.assets.genconanfile import GenConanfile from conan.test.utils.tools import TestClient From 4959c6c4fd6f820120a83348e7007e4a6b31b811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Wed, 24 Sep 2025 11:21:27 +0200 Subject: [PATCH 08/12] Fix precedence --- conan/tools/cmake/cmakedeps/templates/config.py | 2 ++ .../cmake/cmakedeps2/target_configuration.py | 2 ++ conan/tools/cmake/toolchain/blocks.py | 1 + .../cmake/test_cmake_extra_variables.py | 16 ++++++++++------ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index 3c16ed9ec12..89983062739 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -100,7 +100,9 @@ def template(self): # Definition of extra CMake variables from cmake_extra_variables {% for key, value in extra_variables.items() %} + if (NOT CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN) set({{ key }} {{ value }}) + endif() {% endfor %} # Only the last installed configuration BUILD_MODULES are included to avoid the collision diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index 3c266cfabdc..2eaca0975ec 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -495,7 +495,9 @@ def _template(self): # Definition of extra CMake variables from cmake_extra_variables {% for key, value in extra_variables.items() %} + if (NOT CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN) set({{ key }} {{ value }}) + endif() {% endfor %} ################# Exes information ############## diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 9d233412f34..362f2de4abd 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -1194,6 +1194,7 @@ class ExtraVariablesBlock(Block): {% if extra_variables %} {% for key, value in extra_variables.items() %} + set(CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN TRUE) set({{ key }} {{ value }}) {% endfor %} {% endif %} diff --git a/test/functional/toolchains/cmake/test_cmake_extra_variables.py b/test/functional/toolchains/cmake/test_cmake_extra_variables.py index 26b2b7e6788..b2919f215e8 100644 --- a/test/functional/toolchains/cmake/test_cmake_extra_variables.py +++ b/test/functional/toolchains/cmake/test_cmake_extra_variables.py @@ -7,8 +7,12 @@ @pytest.mark.tool("cmake", "3.27") -def test_package_info_extra_variables(): - """ Issue: Dependencies have preference over dependencies, bad """ +@pytest.mark.parametrize("generator", ["CMakeDeps", "CMakeConfigDeps"]) +def test_package_info_extra_variables(generator): + """ The dependencies can define extra variables to be used in CMake, + but if the user is setting the cmake_extra_variables conf, + those should have precedence. + """ client = TestClient() dep_conanfile = textwrap.dedent(""" from conan import ConanFile @@ -31,13 +35,13 @@ def package_info(self): """) - conanfile = textwrap.dedent(""" + conanfile = textwrap.dedent(f""" from conan import ConanFile from conan.tools.cmake import CMake class Pkg(ConanFile): settings = "os", "arch", "compiler", "build_type" - generators = "CMakeDeps", "CMakeToolchain" + generators = "{generator}", "CMakeToolchain" requires = "dep/0.1" def build(self): cmake = CMake(self) @@ -45,9 +49,9 @@ def build(self): """) client.save({"CMakeLists.txt": cmakelists, "conanfile.py": conanfile}) - client.run(f"build . -c tools.cmake.cmakedeps:new={new_value} " + conf = f"-c tools.cmake.cmakedeps:new={new_value}" if generator == "CMakeConfigDeps" else "" + client.run(f"build . {conf} " """-c tools.cmake.cmaketoolchain:extra_variables="{'FOO': '9'}" """) - # This fails because the user is seeing -- FOO=42 coming from the dependency assert "-- FOO=9" in client.out From 624386711419f7788898004df25b96863021430c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Thu, 25 Sep 2025 22:32:42 +0200 Subject: [PATCH 09/12] Remove from dict --- conan/tools/cmake/cmakedeps/templates/config.py | 10 ++++++---- conan/tools/cmake/cmakedeps2/target_configuration.py | 10 +++++----- conan/tools/cmake/toolchain/blocks.py | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index 89983062739..789f253527c 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -32,8 +32,12 @@ def additional_variables_prefixes(self): def parsed_extra_variables(self): # Reading configuration from "cmake_extra_variables" property from conan.tools.cmake.utils import parse_extra_variable - extra_variables = self.cmakedeps.get_property("cmake_extra_variables", self.conanfile, - check_type=dict) or {} + conf_extra_variables = self.conanfile._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", + default={}, check_type=dict) + dep_extra_variables = self.cmakedeps.get_property("cmake_extra_variables", self.conanfile, + check_type=dict) or {} + # The configuration variables have precedence over the dependency ones + extra_variables = {**dep_extra_variables, **conf_extra_variables} parsed_extra_variables = {} for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", @@ -100,9 +104,7 @@ def template(self): # Definition of extra CMake variables from cmake_extra_variables {% for key, value in extra_variables.items() %} - if (NOT CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN) set({{ key }} {{ value }}) - endif() {% endfor %} # Only the last installed configuration BUILD_MODULES are included to avoid the collision diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index 2eaca0975ec..5b6f5b91f5a 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -154,9 +154,11 @@ def _context(self): root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile) libraries = root_target_name or f"{pkg_name}::{pkg_name}" - # Reading configuration from "cmake_extra_variables" property - extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, - check_type=dict) or {} + conf_extra_variables = self._conanfile._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", + default={}, check_type=dict) + dep_extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, check_type=dict) or {} + # The configuration variables have precedence over the dependency ones + extra_variables = {**dep_extra_variables, **conf_extra_variables} parsed_extra_variables = {} for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", @@ -495,9 +497,7 @@ def _template(self): # Definition of extra CMake variables from cmake_extra_variables {% for key, value in extra_variables.items() %} - if (NOT CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN) set({{ key }} {{ value }}) - endif() {% endfor %} ################# Exes information ############## diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 362f2de4abd..9d233412f34 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -1194,7 +1194,6 @@ class ExtraVariablesBlock(Block): {% if extra_variables %} {% for key, value in extra_variables.items() %} - set(CONAN_ORIGIN_EXTRA_VARIABLE_{{ key }}_IS_TOOLCHAIN TRUE) set({{ key }} {{ value }}) {% endfor %} {% endif %} From 0af3898ab4139bad2b6cee701d55eaa435bd22f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Fri, 26 Sep 2025 10:38:54 +0200 Subject: [PATCH 10/12] Give access to conf to ConanFileInterface --- conan/internal/model/conanfile_interface.py | 4 ++++ conan/tools/cmake/cmakedeps/templates/config.py | 4 ++-- conan/tools/cmake/cmakedeps2/target_configuration.py | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/conan/internal/model/conanfile_interface.py b/conan/internal/model/conanfile_interface.py index 76aed619e1f..6f76bafaebc 100644 --- a/conan/internal/model/conanfile_interface.py +++ b/conan/internal/model/conanfile_interface.py @@ -142,3 +142,7 @@ def url(self): @property def extension_properties(self): return getattr(self._conanfile, "extension_properties", {}) + + @property + def conf(self): + return self._conanfile.conf diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index 789f253527c..258e09a50b5 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -32,8 +32,8 @@ def additional_variables_prefixes(self): def parsed_extra_variables(self): # Reading configuration from "cmake_extra_variables" property from conan.tools.cmake.utils import parse_extra_variable - conf_extra_variables = self.conanfile._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", - default={}, check_type=dict) + conf_extra_variables = self.conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", + default={}, check_type=dict) dep_extra_variables = self.cmakedeps.get_property("cmake_extra_variables", self.conanfile, check_type=dict) or {} # The configuration variables have precedence over the dependency ones diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index 5b6f5b91f5a..ac0237df9e6 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -154,9 +154,10 @@ def _context(self): root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile) libraries = root_target_name or f"{pkg_name}::{pkg_name}" - conf_extra_variables = self._conanfile._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", - default={}, check_type=dict) - dep_extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, check_type=dict) or {} + conf_extra_variables = self._conanfile.conf.get("tools.cmake.cmaketoolchain:extra_variables", + default={}, check_type=dict) + dep_extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, + check_type=dict) or {} # The configuration variables have precedence over the dependency ones extra_variables = {**dep_extra_variables, **conf_extra_variables} parsed_extra_variables = {} From e57f9f9883dc6ea5cd4a8973da258213b57b95d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 29 Sep 2025 10:43:38 +0200 Subject: [PATCH 11/12] Fix testing for new cmakedeps2 --- conan/tools/cmake/cmakedeps2/config.py | 1 + test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conan/tools/cmake/cmakedeps2/config.py b/conan/tools/cmake/cmakedeps2/config.py index a1b2a68cf78..5f9cbb70e0f 100644 --- a/conan/tools/cmake/cmakedeps2/config.py +++ b/conan/tools/cmake/cmakedeps2/config.py @@ -76,6 +76,7 @@ def _context(self): for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", key, value) + result["extra_variables"] = parsed_extra_variables result.update(self._get_legacy_vars()) return result diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py index 1b301002b6d..6784ae500b3 100644 --- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py +++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py @@ -550,7 +550,7 @@ def package_info(self): client.run("create .") client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}") - target = client.load("pkg-Targets-release.cmake") + target = client.load("pkg-config.cmake") assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target assert 'set(FOO 42)' in target assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in target From e67da91fd38a0d5dd2d2977c64ed5f8449fd4fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 29 Sep 2025 11:18:27 +0200 Subject: [PATCH 12/12] Don't set them althogether if defined in conf --- conan/tools/cmake/cmakedeps/templates/config.py | 3 ++- conan/tools/cmake/cmakedeps2/config.py | 3 ++- .../integration/toolchains/cmake/cmakedeps/test_cmakedeps.py | 5 ++++- .../toolchains/cmake/cmakedeps2/test_cmakedeps.py | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index 258e09a50b5..9f31e993aa0 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -37,7 +37,8 @@ def parsed_extra_variables(self): dep_extra_variables = self.cmakedeps.get_property("cmake_extra_variables", self.conanfile, check_type=dict) or {} # The configuration variables have precedence over the dependency ones - extra_variables = {**dep_extra_variables, **conf_extra_variables} + extra_variables = {dep: value for dep, value in dep_extra_variables.items() + if dep not in conf_extra_variables} parsed_extra_variables = {} for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", diff --git a/conan/tools/cmake/cmakedeps2/config.py b/conan/tools/cmake/cmakedeps2/config.py index 5f9cbb70e0f..00e81e5b6b3 100644 --- a/conan/tools/cmake/cmakedeps2/config.py +++ b/conan/tools/cmake/cmakedeps2/config.py @@ -71,7 +71,8 @@ def _context(self): dep_extra_variables = self._cmakedeps.get_property("cmake_extra_variables", self._conanfile, check_type=dict) or {} # The configuration variables have precedence over the dependency ones - extra_variables = {**dep_extra_variables, **conf_extra_variables} + extra_variables = {dep: value for dep, value in dep_extra_variables.items() + if dep not in conf_extra_variables} parsed_extra_variables = {} for key, value in extra_variables.items(): parsed_extra_variables[key] = parse_extra_variable("cmake_extra_variables", diff --git a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py index 849afc4c2f6..539f2876909 100644 --- a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py +++ b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py @@ -938,6 +938,7 @@ class Pkg(ConanFile): def package_info(self): self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, + "BAR": 42, "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/", "CACHE_VAR_DEFAULT_DOC": {"value": "hello world", "cache": True, "type": "PATH"}}) @@ -945,8 +946,10 @@ def package_info(self): client.save({"conanfile.py": conanfile}) client.run("create .") - client.run(f"install --requires=pkg/0.1 -g CMakeDeps") + client.run("install --requires=pkg/0.1 -g CMakeDeps " + """-c tools.cmake.cmaketoolchain:extra_variables="{'BAR': 9}" """) target = client.load("pkg-config.cmake") + assert 'set(BAR' not in target assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target assert 'set(FOO 42)' in target assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in target diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py index 6784ae500b3..4ae809e6e63 100644 --- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py +++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py @@ -542,6 +542,7 @@ class Pkg(ConanFile): def package_info(self): self.cpp_info.set_property("cmake_extra_variables", {"FOO": 42, + "BAR": 42, "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/", "CACHE_VAR_DEFAULT_DOC": {"value": "hello world", "cache": True, "type": "PATH"}}) @@ -549,8 +550,10 @@ def package_info(self): client.save({"conanfile.py": conanfile}) client.run("create .") - client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}") + client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value} " + """-c tools.cmake.cmaketoolchain:extra_variables="{'BAR': 9}" """) target = client.load("pkg-config.cmake") + assert 'set(BAR' not in target assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in target assert 'set(FOO 42)' in target assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in target