diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 575f520bf21..26cb7ba0707 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -1048,6 +1048,46 @@ def context(self): "winsdk_version": winsdk_version, "gen_platform_sdk_version": gen_platform_sdk_version} +class ExtraVariablesBlock(Block): + template = textwrap.dedent(r""" + {% if extra_variables %} + {% for key, value in extra_variables.items() %} + set({{ key }} {{ value }}) + {% endfor %} + {% 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_cache = value.get("cache") + if is_cache: + if not isinstance(is_cache, bool): + raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables "cache" must be a boolean (True/False)') + var_type = value.get("type") + if not var_type: + raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables needs "type" defined for cache variable "{key}"') + if var_type not in self.CMAKE_CACHE_TYPES: + raise ConanException(f'tools.cmake.cmaketoolchain:extra_variables invalid type "{var_type}" for cache variable "{key}". Possible types: {", ".join(self.CMAKE_CACHE_TYPES)}') + # Set docstring as variable name if not defined + docstring = value.get("docstring") or key + return f"{var_value} CACHE {var_type} \"{docstring}\"" + else: + return var_value + + def context(self): + # 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) + return {"extra_variables": parsed_extra_variables} class OutputDirsBlock(Block): diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py index 6924a1d9fb7..0692751eb94 100644 --- a/conan/tools/cmake/toolchain/toolchain.py +++ b/conan/tools/cmake/toolchain/toolchain.py @@ -9,7 +9,7 @@ from conan.tools.build import use_win_mingw from conan.tools.cmake.presets import write_cmake_presets from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME -from conan.tools.cmake.toolchain.blocks import ToolchainBlocks, UserToolchain, GenericSystemBlock, \ +from conan.tools.cmake.toolchain.blocks import ExtraVariablesBlock, ToolchainBlocks, UserToolchain, GenericSystemBlock, \ AndroidSystemBlock, AppleSystemBlock, FPicBlock, ArchitectureBlock, GLibCXXBlock, VSRuntimeBlock, \ CppStdBlock, ParallelBlock, CMakeFlagsInitBlock, TryCompileBlock, FindFiles, PkgConfigBlock, \ SkipRPath, SharedLibBock, OutputDirsBlock, ExtraFlagsBlock, CompilersBlock, LinkerScriptsBlock, \ @@ -165,6 +165,7 @@ def __init__(self, conanfile, generator=None): ("parallel", ParallelBlock), ("extra_flags", ExtraFlagsBlock), ("cmake_flags_init", CMakeFlagsInitBlock), + ("extra_variables", ExtraVariablesBlock), ("try_compile", TryCompileBlock), ("find_paths", FindFiles), ("pkg_config", PkgConfigBlock), diff --git a/conans/model/conf.py b/conans/model/conf.py index 7809f4d9da8..e110378455e 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -73,6 +73,7 @@ "tools.cmake.cmaketoolchain:toolset_arch": "Toolset architecture to be used as part of CMAKE_GENERATOR_TOOLSET in CMakeToolchain", "tools.cmake.cmaketoolchain:toolset_cuda": "(Experimental) Path to a CUDA toolset to use, or version if installed at the system level", "tools.cmake.cmaketoolchain:presets_environment": "String to define wether to add or not the environment section to the CMake presets. Empty by default, will generate the environment section in CMakePresets. Can take values: 'disabled'.", + "tools.cmake.cmaketoolchain:extra_variables": "Dictionary with variables to be injected in CMakeToolchain (potential override of CMakeToolchain defined variables)", "tools.cmake.cmake_layout:build_folder_vars": "Settings and Options that will produce a different build folder and different CMake presets names", "tools.cmake.cmake_layout:build_folder": "(Experimental) Allow configuring the base folder of the build for local builds", "tools.cmake.cmake_layout:test_folder": "(Experimental) Allow configuring the base folder of the build for test_package", diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py index df1fff5ef2c..c21b35ffc49 100644 --- a/test/integration/toolchains/cmake/test_cmaketoolchain.py +++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py @@ -1501,7 +1501,6 @@ def layout(self): presets = json.loads(c.load(user_presets["include"][0])) assert os.path.isabs(presets["configurePresets"][0]["toolchainFile"]) - def test_output_dirs_gnudirs_local_default(): # https://github.com/conan-io/conan/issues/14733 c = TestClient() @@ -1575,3 +1574,59 @@ def _assert_install(out): c.run("build .") _assert_install(c.out) assert "CMAKE_INSTALL_PREFIX" not in c.out + + +def test_toolchain_extra_variables(): + windows_profile = textwrap.dedent(""" + [settings] + os=Windows + arch=x86_64 + [conf] + tools.cmake.cmaketoolchain:extra_variables={'CMAKE_GENERATOR_INSTANCE': '${GENERATOR_INSTANCE}/buildTools/', 'FOO': '42' } + """) + + client = TestClient() + client.save({"conanfile.txt": "[generators]\nCMakeToolchain", + "windows": windows_profile}) + + # Test passing extra_variables from pro ile + client.run("install . --profile:host=windows") + toolchain = client.load("conan_toolchain.cmake") + assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in toolchain + assert 'set(FOO "42")' in toolchain + + # Test input from command line passing dict between doble quotes + client.run(textwrap.dedent(r""" + install . -c tools.cmake.cmaketoolchain:extra_variables="{'CMAKE_GENERATOR_INSTANCE': '${GENERATOR_INSTANCE}/buildTools/', 'FOO': 42.2, 'DICT': {'value': 1}, 'CACHE_VAR': {'value': 'hello world', 'cache': True, 'type': 'BOOL', 'docstring': 'test variable'}}" + """) + ) + + toolchain = client.load("conan_toolchain.cmake") + assert 'set(CMAKE_GENERATOR_INSTANCE "${GENERATOR_INSTANCE}/buildTools/")' in toolchain + assert 'set(FOO 42.2)' in toolchain + assert 'set(DICT 1)' in toolchain + assert 'set(CACHE_VAR "hello world" CACHE BOOL "test variable")' in toolchain + + + client.run(textwrap.dedent(""" + install . -c tools.cmake.cmaketoolchain:extra_variables="{'invalid': {'value': 'hello world', 'cache': 'true'}}" + """) , assert_error=True) + assert 'tools.cmake.cmaketoolchain:extra_variables "cache" must be a boolean (True/False)' in client.out + + # Test invalid cache variable + client.run(textwrap.dedent(""" + install . -c tools.cmake.cmaketoolchain:extra_variables="{'invalid': {'value': 'hello world', 'cache': True}}" + """) , assert_error=True) + assert 'tools.cmake.cmaketoolchain:extra_variables needs "type" defined for cache variable "invalid"' in client.out + + client.run(textwrap.dedent(""" + install . -c tools.cmake.cmaketoolchain:extra_variables="{'invalid': {'value': 'hello world', 'cache': True, 'type': 'INVALID_TYPE'}}" + """) , assert_error=True) + assert 'tools.cmake.cmaketoolchain:extra_variables invalid type "INVALID_TYPE" for cache variable "invalid". Possible types: BOOL, FILEPATH, PATH, STRING, INTERNAL' in client.out + + client.run(textwrap.dedent(""" + install . -c tools.cmake.cmaketoolchain:extra_variables="{'CACHE_VAR_DEFAULT_DOC': {'value': 'hello world', 'cache': True, 'type': 'PATH'}}" + """)) + toolchain = client.load("conan_toolchain.cmake") + assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in toolchain +