From ea2a31efa6efd28f90c48199412a6a3e064c3aeb Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 07:28:56 +0100 Subject: [PATCH 01/11] wip --- conan/tools/cmake/toolchain/toolchain.py | 3 + conan/tools/env/virtualbuildenv.py | 20 +++-- conan/tools/env/virtualrunenv.py | 15 ++-- .../toolchains/cmake/test_cmake_toolchain.py | 76 +++++++++++++++++++ 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py index f60259c4525..01970ff254f 100644 --- a/conan/tools/cmake/toolchain/toolchain.py +++ b/conan/tools/cmake/toolchain/toolchain.py @@ -220,6 +220,9 @@ def generate(self): if self._conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="", check_type=str, choices=("disabled", "")) != "disabled": + benv = self._conanfile.buildenv + renv = self._conanfile.runenv + build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars() run_env = VirtualRunEnv(self._conanfile, auto_generate=True).vars() diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py index cdc80f4dd5e..0da4b925066 100644 --- a/conan/tools/env/virtualbuildenv.py +++ b/conan/tools/env/virtualbuildenv.py @@ -9,6 +9,7 @@ class VirtualBuildEnv: """ def __init__(self, conanfile, auto_generate=False): + self._buildenv = None self._conanfile = conanfile if not auto_generate: self._conanfile.virtualbuildenv = False @@ -42,33 +43,36 @@ def environment(self): :return: an ``Environment`` object instance containing the obtained variables. """ - # FIXME: Cache value? - build_env = Environment() + + if self._buildenv is None: + self._buildenv = Environment() + else: + return self._buildenv # Top priority: profile profile_env = self._conanfile.buildenv - build_env.compose_env(profile_env) + self._buildenv.compose_env(profile_env) build_requires = self._conanfile.dependencies.build.topological_sort for require, build_require in reversed(build_requires.items()): if require.direct: # Only buildenv_info from direct deps is propagated # higher priority, explicit buildenv_info if build_require.buildenv_info: - build_env.compose_env(build_require.buildenv_info) + self._buildenv.compose_env(build_require.buildenv_info) # Lower priority, the runenv of all transitive "requires" of the build requires if build_require.runenv_info: - build_env.compose_env(build_require.runenv_info) + self._buildenv.compose_env(build_require.runenv_info) # Then the implicit os_name = self._conanfile.settings_build.get_safe("os") - build_env.compose_env(runenv_from_cpp_info(build_require, os_name)) + self._buildenv.compose_env(runenv_from_cpp_info(build_require, os_name)) # Requires in host context can also bring some direct buildenv_info host_requires = self._conanfile.dependencies.host.topological_sort for require in reversed(host_requires.values()): if require.buildenv_info: - build_env.compose_env(require.buildenv_info) + self._buildenv.compose_env(require.buildenv_info) - return build_env + return self._buildenv def vars(self, scope="build"): """ diff --git a/conan/tools/env/virtualrunenv.py b/conan/tools/env/virtualrunenv.py index bd9cda077e5..ce8575f7a75 100644 --- a/conan/tools/env/virtualrunenv.py +++ b/conan/tools/env/virtualrunenv.py @@ -34,6 +34,7 @@ def __init__(self, conanfile, auto_generate=False): :param conanfile: The current recipe object. Always use ``self``. """ + self._runenv = None self._conanfile = conanfile if not auto_generate: self._conanfile.virtualrunenv = False @@ -60,23 +61,27 @@ def environment(self): :return: an ``Environment`` object instance containing the obtained variables. """ - runenv = Environment() + + if self._runenv is None: + self._runenv = Environment() + else: + return self._runenv # Top priority: profile profile_env = self._conanfile.runenv - runenv.compose_env(profile_env) + self._runenv.compose_env(profile_env) # FIXME: Cache value? host_req = self._conanfile.dependencies.host test_req = self._conanfile.dependencies.test for require, dep in list(host_req.items()) + list(test_req.items()): if dep.runenv_info: - runenv.compose_env(dep.runenv_info) + self._runenv.compose_env(dep.runenv_info) if require.run: # Only if the require is run (shared or application to be run) _os = self._conanfile.settings.get_safe("os") - runenv.compose_env(runenv_from_cpp_info(dep, _os)) + self._runenv.compose_env(runenv_from_cpp_info(dep, _os)) - return runenv + return self._runenv def vars(self, scope="run"): """ diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index a7467411c55..aa9698c2316 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1607,3 +1607,79 @@ def package_info(self): c.run_command("ctest --preset conan-debug") assert "tests passed" in c.out + + +#@pytest.mark.tool("cmake", "3.23") +def test_add_generate_env_to_presets(): + c = TestClient() + + tool = textwrap.dedent(r""" + import os + from conan import ConanFile + from conan.tools.files import chdir, save + class Tool(ConanFile): + version = "0.1" + name = "tool" + settings = "os", "compiler", "arch", "build_type" + def package_info(self): + self.buildenv_info.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE") + """) + + consumer = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + from conan.tools.env import VirtualBuildEnv + class mypkgRecipe(ConanFile): + name = "mypkg" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + tool_requires = "tool/0.1" + #generators = "CMakeDeps", "CMakeToolchain" + def layout(self): + cmake_layout(self) + + def generate(self): + buildenv = VirtualBuildEnv(self) + buildenv.environment().define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") + buildenv.environment().define("MY_ADDED_BUILD_VAR", "MY_ADDED_BUILD_VAR_VALUE") + buildenv.generate() + + #buildenv = VirtualBuildEnv(self) + #envvars = buildenv.environment() + #envvars.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") + #envvarsbuild = envvars.vars(self, scope="run") + #envvarsbuild.save_script("myproject-run-{}".format(str(self.settings.build_type).lower())) + + + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + """) + + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.15) + project(MyProject) + # build var should be available at configure + set(MY_BUILD_VAR $ENV{MY_BUILD_VAR}) + set(MY_ADDED_BUILD_VAR $ENV{MY_BUILD_VAR}) + if (MY_BUILD_VAR) + message("MY_BUILD_VAR:${MY_BUILD_VAR}") + else() + message("MY_BUILD_VAR NOT FOUND") + endif() + """) + + c.save({"tool.py": tool, + "conanfile.py": consumer, + "CMakeLists.txt": cmakelists}) + + c.run("create tool.py") + + c.run("install .") + + preset = "conan-default" if platform.system() == "Windows" else "conan-release" + + c.run_command(f"cmake --preset {preset}") + assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE" in c.out From b5526db9d09a40784ea5933cf403a3afbd924fa9 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 08:35:19 +0100 Subject: [PATCH 02/11] wip --- conans/test/functional/toolchains/cmake/test_cmake_toolchain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index aa9698c2316..cafe211b69f 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1655,6 +1655,8 @@ def generate(self): deps = CMakeDeps(self) deps.generate() tc = CMakeToolchain(self) + tc.presets_build_environment = buildenv + tc.presets_run_environment = runenv tc.generate() """) From c44b9d798c01a2e8cf689ca0d83f212841dedb50 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 11:14:05 +0100 Subject: [PATCH 03/11] wip --- conan/tools/cmake/toolchain/toolchain.py | 9 +++---- .../toolchains/cmake/test_cmake_toolchain.py | 25 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py index 01970ff254f..1f0acafc7c3 100644 --- a/conan/tools/cmake/toolchain/toolchain.py +++ b/conan/tools/cmake/toolchain/toolchain.py @@ -160,6 +160,8 @@ def __init__(self, conanfile, generator=None): self.find_builddirs = True self.user_presets_path = "CMakeUserPresets.json" self.presets_prefix = "conan" + self.presets_build_environment = None + self.presets_run_environment = None def _context(self): """ Returns dict, the context for the template @@ -220,11 +222,8 @@ def generate(self): if self._conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="", check_type=str, choices=("disabled", "")) != "disabled": - benv = self._conanfile.buildenv - renv = self._conanfile.runenv - - build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars() - run_env = VirtualRunEnv(self._conanfile, auto_generate=True).vars() + build_env = self.presets_build_environment.vars() if self.presets_build_environment else VirtualBuildEnv(self._conanfile, auto_generate=True).vars() + run_env = self.presets_run_environment.vars() if self.presets_run_environment else VirtualRunEnv(self._conanfile, auto_generate=True).vars() buildenv = {name: value for name, value in build_env.items(variable_reference="$penv{{{name}}}")} diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index cafe211b69f..c860b7a479f 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1656,21 +1656,25 @@ def generate(self): deps.generate() tc = CMakeToolchain(self) tc.presets_build_environment = buildenv - tc.presets_run_environment = runenv tc.generate() """) cmakelists = textwrap.dedent(""" cmake_minimum_required(VERSION 3.15) project(MyProject) - # build var should be available at configure - set(MY_BUILD_VAR $ENV{MY_BUILD_VAR}) - set(MY_ADDED_BUILD_VAR $ENV{MY_BUILD_VAR}) - if (MY_BUILD_VAR) - message("MY_BUILD_VAR:${MY_BUILD_VAR}") - else() - message("MY_BUILD_VAR NOT FOUND") - endif() + + function(check_and_report_variable var_name) + set(var_value $ENV{${var_name}}) + if (var_value) + message("${var_name}:${var_value}") + else() + message(FATAL_ERROR "${var_name} NOT FOUND") + endif() + endfunction() + + check_and_report_variable("MY_BUILD_VAR") + check_and_report_variable("MY_ADDED_BUILD_VAR") + check_and_report_variable("PATH_VAR") """) c.save({"tool.py": tool, @@ -1684,4 +1688,5 @@ def generate(self): preset = "conan-default" if platform.system() == "Windows" else "conan-release" c.run_command(f"cmake --preset {preset}") - assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE" in c.out + assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE_OVERRIDEN" in c.out + assert "MY_ADDED_BUILD_VAR:MY_ADDED_BUILD_VAR_VALUE" in c.out From 9f9c73feed78f92b8f90f93fc502ed5971772e97 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 11:25:21 +0100 Subject: [PATCH 04/11] wip --- conans/test/functional/toolchains/cmake/test_cmake_toolchain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index c860b7a479f..e1e392cf139 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1674,7 +1674,6 @@ def generate(self): check_and_report_variable("MY_BUILD_VAR") check_and_report_variable("MY_ADDED_BUILD_VAR") - check_and_report_variable("PATH_VAR") """) c.save({"tool.py": tool, From 3c9a39c76dcfa77f84fa7098c892582271a2b412 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 11:33:35 +0100 Subject: [PATCH 05/11] wip --- conan/tools/env/virtualrunenv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conan/tools/env/virtualrunenv.py b/conan/tools/env/virtualrunenv.py index ce8575f7a75..c7308418b82 100644 --- a/conan/tools/env/virtualrunenv.py +++ b/conan/tools/env/virtualrunenv.py @@ -70,7 +70,6 @@ def environment(self): # Top priority: profile profile_env = self._conanfile.runenv self._runenv.compose_env(profile_env) - # FIXME: Cache value? host_req = self._conanfile.dependencies.host test_req = self._conanfile.dependencies.test From 0d509379da6f590648144c19a1528bca518957a5 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 11:34:24 +0100 Subject: [PATCH 06/11] wip --- conans/test/functional/toolchains/cmake/test_cmake_toolchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index e1e392cf139..a95ad4dc181 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1649,7 +1649,7 @@ def generate(self): #envvars = buildenv.environment() #envvars.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") #envvarsbuild = envvars.vars(self, scope="run") - #envvarsbuild.save_script("myproject-run-{}".format(str(self.settings.build_type).lower())) + #envvarsbuild.save_script("myproject-build-{}".format(str(self.settings.build_type).lower())) deps = CMakeDeps(self) From 36c7e8736c1105d1fc93b4040146acc0d87de701 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 18 Jan 2024 12:21:49 +0100 Subject: [PATCH 07/11] wip --- conans/test/functional/toolchains/cmake/test_cmake_toolchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index a95ad4dc181..1bece152caf 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1609,7 +1609,7 @@ def package_info(self): assert "tests passed" in c.out -#@pytest.mark.tool("cmake", "3.23") +@pytest.mark.tool("cmake", "3.23") def test_add_generate_env_to_presets(): c = TestClient() From 537bf2fc484b47f3a48adb895cfd99aa78f6acfa Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 22 Jan 2024 12:01:43 +0100 Subject: [PATCH 08/11] add environment via inherited presets --- conan/tools/cmake/presets.py | 50 ++++++----- conan/tools/cmake/toolchain/toolchain.py | 17 +--- conan/tools/env/environment.py | 10 +++ conans/client/generators/__init__.py | 33 ++++++- .../toolchains/cmake/test_cmake_toolchain.py | 88 +------------------ 5 files changed, 77 insertions(+), 121 deletions(-) diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py index 27792af73b9..b7cdafd5a25 100644 --- a/conan/tools/cmake/presets.py +++ b/conan/tools/cmake/presets.py @@ -14,9 +14,9 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables, - user_presets_path=None, preset_prefix=None, buildenv=None, runenv=None): + user_presets_path=None, preset_prefix=None): preset_path, preset_data = _CMakePresets.generate(conanfile, toolchain_file, generator, - cache_variables, preset_prefix, buildenv, runenv) + cache_variables, preset_prefix) _IncludingPresets.generate(conanfile, preset_path, user_presets_path, preset_prefix, preset_data) @@ -24,7 +24,7 @@ class _CMakePresets: """ Conan generated main CMakePresets.json inside the generators_folder """ @staticmethod - def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefix, buildenv, runenv): + def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefix): cache_variables = cache_variables or {} if platform.system() == "Windows" and generator == "MinGW Makefiles": if "CMAKE_SH" not in cache_variables: @@ -55,18 +55,17 @@ def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefi if os.path.exists(preset_path) and multiconfig: data = json.loads(load(preset_path)) build_preset = _CMakePresets._build_preset_fields(conanfile, multiconfig, preset_prefix) - test_preset = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, - runenv) + test_preset = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix) _CMakePresets._insert_preset(data, "buildPresets", build_preset) _CMakePresets._insert_preset(data, "testPresets", test_preset) configure_preset = _CMakePresets._configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig, - preset_prefix, buildenv) + preset_prefix) # Conan generated presets should have only 1 configurePreset, no more, overwrite it data["configurePresets"] = [configure_preset] else: data = _CMakePresets._contents(conanfile, toolchain_file, cache_variables, generator, - preset_prefix, buildenv, runenv) + preset_prefix) preset_content = json.dumps(data, indent=4) save(preset_path, preset_content) @@ -84,29 +83,35 @@ def _insert_preset(data, preset_type, preset): data[preset_type].append(preset) @staticmethod - def _contents(conanfile, toolchain_file, cache_variables, generator, preset_prefix, buildenv, - runenv): + def _contents(conanfile, toolchain_file, cache_variables, generator, preset_prefix): """ Contents for the CMakePresets.json It uses schema version 3 unless it is forced to 2 """ + add_env = conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="", + check_type=str, choices=("disabled", "")) != "disabled" multiconfig = is_multi_configuration(generator) conf = _CMakePresets._configure_preset(conanfile, generator, cache_variables, toolchain_file, - multiconfig, preset_prefix, buildenv) + multiconfig, preset_prefix, add_env) build = _CMakePresets._build_preset_fields(conanfile, multiconfig, preset_prefix) - test = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, runenv) - ret = {"version": 3, + test = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, add_env) + + ret = {"version": 4, "vendor": {"conan": {}}, "cmakeMinimumRequired": {"major": 3, "minor": 15, "patch": 0}, "configurePresets": [conf], "buildPresets": [build], "testPresets": [test] } + + if add_env: + ret.update({"include": [os.path.join(conanfile.generators_folder, "conan-build-presets.json"), + os.path.join(conanfile.generators_folder, "conan-run-presets.json")]}) return ret @staticmethod def _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig, - preset_prefix, buildenv): + preset_prefix, add_env=False): build_type = conanfile.settings.get_safe("build_type") name = _CMakePresets._configure_preset_name(conanfile, multiconfig) if preset_prefix: @@ -115,14 +120,12 @@ def _configure_preset(conanfile, generator, cache_variables, toolchain_file, mul cache_variables["CMAKE_BUILD_TYPE"] = build_type ret = { "name": name, - "displayName": "'{}' config".format(name), - "description": "'{}' configure using '{}' generator".format(name, generator), "generator": generator, "cacheVariables": cache_variables, } - if buildenv: - ret["environment"] = buildenv + if add_env: + ret.update({"inherits": _CMakePresets.environment_preset_name(conanfile, "build")}) if is_msvc(conanfile): # We can force the generator Visual even if it is Ninja, to define the toolset @@ -195,12 +198,19 @@ def _build_preset_fields(conanfile, multiconfig, preset_prefix): return ret @staticmethod - def _test_preset_fields(conanfile, multiconfig, preset_prefix, runenv): + def _test_preset_fields(conanfile, multiconfig, preset_prefix, add_env=False): ret = _CMakePresets._common_preset_fields(conanfile, multiconfig, preset_prefix) - if runenv: - ret["environment"] = runenv + if add_env: + ret.update({"inherits": _CMakePresets.environment_preset_name(conanfile, "run")}) return ret + @staticmethod + def environment_preset_name(conanfile, scope): + build_type = conanfile.settings.get_safe("build_type") + arch = conanfile.settings.get_safe("arch") + preset_name = "-".join(filter(None, ["conan", scope, "environment", build_type, arch])).lower() + return preset_name + @staticmethod def _build_and_test_preset_name(conanfile): build_type = conanfile.settings.get_safe("build_type") diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py index 1f0acafc7c3..419df3f453b 100644 --- a/conan/tools/cmake/toolchain/toolchain.py +++ b/conan/tools/cmake/toolchain/toolchain.py @@ -160,8 +160,6 @@ def __init__(self, conanfile, generator=None): self.find_builddirs = True self.user_presets_path = "CMakeUserPresets.json" self.presets_prefix = "conan" - self.presets_build_environment = None - self.presets_run_environment = None def _context(self): """ Returns dict, the context for the template @@ -217,21 +215,8 @@ def generate(self): else: cache_variables[name] = value - buildenv, runenv = None, None - - if self._conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="", - check_type=str, choices=("disabled", "")) != "disabled": - - build_env = self.presets_build_environment.vars() if self.presets_build_environment else VirtualBuildEnv(self._conanfile, auto_generate=True).vars() - run_env = self.presets_run_environment.vars() if self.presets_run_environment else VirtualRunEnv(self._conanfile, auto_generate=True).vars() - - buildenv = {name: value for name, value in - build_env.items(variable_reference="$penv{{{name}}}")} - runenv = {name: value for name, value in - run_env.items(variable_reference="$penv{{{name}}}")} - write_cmake_presets(self._conanfile, toolchain, self.generator, cache_variables, - self.user_presets_path, self.presets_prefix, buildenv, runenv) + self.user_presets_path, self.presets_prefix) def _get_generator(self, recipe_generator): # Returns the name of the generator to be used by CMake diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py index aac022c1f5b..2060e08db9a 100644 --- a/conan/tools/env/environment.py +++ b/conan/tools/env/environment.py @@ -1,3 +1,4 @@ +import json import os import textwrap from collections import OrderedDict @@ -508,6 +509,12 @@ def save_sh(self, file_location, generate_deactivate=True): content = f'script_folder="{os.path.abspath(filepath)}"\n' + content save(file_location, content) + def save_json(self, file_location): + env_vars = EnvVars(self._conanfile, self._values, self._scope) + env = {name: value for name, value in env_vars.items(variable_reference="$penv{{{name}}}")} + content = json.dumps(env, indent=1) + save(file_location, content) + def save_script(self, filename): """ Saves a script file (bat, sh, ps1) with a launcher to set the environment. @@ -540,7 +547,10 @@ def save_script(self, filename): self.save_sh(path) if self._scope: + json_path = os.path.splitext(path)[0] + ".json" + self.save_json(json_path) register_env_script(self._conanfile, path, self._scope) + register_env_script(self._conanfile, json_path, self._scope) class ProfileEnvironment: diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index c3f55c07b3d..e10728d197a 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -1,4 +1,5 @@ import inspect +import json import os import traceback import importlib @@ -121,7 +122,6 @@ def write_generators(conanfile, app): from conan.tools.env import VirtualRunEnv env = VirtualRunEnv(conanfile) env.generate() - _generate_aggregated_env(conanfile) hook_manager.execute("post_generate", conanfile=conanfile) @@ -156,6 +156,7 @@ def deactivates(filenames): bats = [] shs = [] ps1s = [] + jsons = [] for env_script in env_scripts: path = os.path.join(conanfile.generators_folder, env_script) # Only the .bat and .ps1 are made relative to current script @@ -164,6 +165,8 @@ def deactivates(filenames): bats.append("%~dp0/"+path) elif env_script.endswith(".sh"): shs.append(subsystem_path(subsystem, path)) + elif env_script.endswith(".json"): + jsons.append(subsystem_path(subsystem, path)) elif env_script.endswith(".ps1"): path = os.path.relpath(path, conanfile.generators_folder) # This $PSScriptRoot uses the current script directory @@ -176,6 +179,34 @@ def sh_content(files): save(os.path.join(conanfile.generators_folder, filename), sh_content(shs)) save(os.path.join(conanfile.generators_folder, "deactivate_{}".format(filename)), sh_content(deactivates(shs))) + if jsons: + def jsons_content(files): + combined_environment = {} + for file in files: + with open(file, 'r') as f: + file_content = f.read() + file_json = json.loads(file_content) + combined_environment.update(file_json) + preset_type = "testPresets" if group == "run" else "configurePresets" + + from conan.tools.cmake.presets import _CMakePresets + preset_name = _CMakePresets.environment_preset_name(conanfile, group) + + preset = { + "version": 4, + preset_type: [ + { + "vendor": {"conan": dict()}, + "name": preset_name, + "hidden": True, + "environment": combined_environment, + } + ] + } + return json.dumps(preset, indent=4) + filename = "conan-{}-presets.json".format(group) + generated.append(filename) + save(os.path.join(conanfile.generators_folder, filename), jsons_content(jsons)) if bats: def bat_content(files): return "\r\n".join(["@echo off"] + ['call "{}"'.format(b) for b in files]) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index 1bece152caf..87ecddfa49d 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1495,7 +1495,7 @@ def package(self): assert re.search("Install stderr: ''", client.out) -@pytest.mark.tool("cmake", "3.23") +#@pytest.mark.tool("cmake", "3.23") def test_add_env_to_presets(): c = TestClient() @@ -1578,7 +1578,9 @@ def package_info(self): if platform.system() != "Windows" else os.path.join("build", "generators", "CMakePresets.json") presets = json.loads(c.load(presets_path)) - assert presets["configurePresets"][0].get("env") is None + assert presets["configurePresets"][0].get("inherits") is None + assert presets["testPresets"][0].get("inherits") is None + assert presets.get("include") is None c.run("install . -g CMakeToolchain -g CMakeDeps") c.run("install . -g CMakeToolchain -g CMakeDeps -s:h build_type=Debug") @@ -1607,85 +1609,3 @@ def package_info(self): c.run_command("ctest --preset conan-debug") assert "tests passed" in c.out - - -@pytest.mark.tool("cmake", "3.23") -def test_add_generate_env_to_presets(): - c = TestClient() - - tool = textwrap.dedent(r""" - import os - from conan import ConanFile - from conan.tools.files import chdir, save - class Tool(ConanFile): - version = "0.1" - name = "tool" - settings = "os", "compiler", "arch", "build_type" - def package_info(self): - self.buildenv_info.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE") - """) - - consumer = textwrap.dedent(""" - import os - from conan import ConanFile - from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps - from conan.tools.env import VirtualBuildEnv - class mypkgRecipe(ConanFile): - name = "mypkg" - version = "1.0" - settings = "os", "compiler", "build_type", "arch" - tool_requires = "tool/0.1" - #generators = "CMakeDeps", "CMakeToolchain" - def layout(self): - cmake_layout(self) - - def generate(self): - buildenv = VirtualBuildEnv(self) - buildenv.environment().define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") - buildenv.environment().define("MY_ADDED_BUILD_VAR", "MY_ADDED_BUILD_VAR_VALUE") - buildenv.generate() - - #buildenv = VirtualBuildEnv(self) - #envvars = buildenv.environment() - #envvars.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") - #envvarsbuild = envvars.vars(self, scope="run") - #envvarsbuild.save_script("myproject-build-{}".format(str(self.settings.build_type).lower())) - - - deps = CMakeDeps(self) - deps.generate() - tc = CMakeToolchain(self) - tc.presets_build_environment = buildenv - tc.generate() - """) - - cmakelists = textwrap.dedent(""" - cmake_minimum_required(VERSION 3.15) - project(MyProject) - - function(check_and_report_variable var_name) - set(var_value $ENV{${var_name}}) - if (var_value) - message("${var_name}:${var_value}") - else() - message(FATAL_ERROR "${var_name} NOT FOUND") - endif() - endfunction() - - check_and_report_variable("MY_BUILD_VAR") - check_and_report_variable("MY_ADDED_BUILD_VAR") - """) - - c.save({"tool.py": tool, - "conanfile.py": consumer, - "CMakeLists.txt": cmakelists}) - - c.run("create tool.py") - - c.run("install .") - - preset = "conan-default" if platform.system() == "Windows" else "conan-release" - - c.run_command(f"cmake --preset {preset}") - assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE_OVERRIDEN" in c.out - assert "MY_ADDED_BUILD_VAR:MY_ADDED_BUILD_VAR_VALUE" in c.out From 248905167716bb7db0f32b079846e724196006f0 Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 22 Jan 2024 12:42:15 +0100 Subject: [PATCH 09/11] wip --- .../toolchains/cmake/test_cmake_toolchain.py | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index 87ecddfa49d..aaf442d9eb0 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1495,7 +1495,7 @@ def package(self): assert re.search("Install stderr: ''", client.out) -#@pytest.mark.tool("cmake", "3.23") +@pytest.mark.tool("cmake", "3.23") def test_add_env_to_presets(): c = TestClient() @@ -1609,3 +1609,78 @@ def package_info(self): c.run_command("ctest --preset conan-debug") assert "tests passed" in c.out + + +@pytest.mark.tool("cmake", "3.23") +def test_add_generate_env_to_presets(): + c = TestClient() + + tool = textwrap.dedent(r""" + import os + from conan import ConanFile + from conan.tools.files import chdir, save + class Tool(ConanFile): + version = "0.1" + name = "tool" + settings = "os", "compiler", "arch", "build_type" + def package_info(self): + self.buildenv_info.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE") + """) + + consumer = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + from conan.tools.env import VirtualBuildEnv + class mypkgRecipe(ConanFile): + name = "mypkg" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + tool_requires = "tool/0.1" + #generators = "CMakeDeps", "CMakeToolchain" + def layout(self): + cmake_layout(self) + def generate(self): + buildenv = VirtualBuildEnv(self) + buildenv.environment().define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") + buildenv.environment().define("MY_ADDED_BUILD_VAR", "MY_ADDED_BUILD_VAR_VALUE") + buildenv.generate() + #buildenv = VirtualBuildEnv(self) + #envvars = buildenv.environment() + #envvars.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN") + #envvarsbuild = envvars.vars(self, scope="run") + #envvarsbuild.save_script("myproject-build-{}".format(str(self.settings.build_type).lower())) + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + """) + + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.15) + project(MyProject) + function(check_and_report_variable var_name) + set(var_value $ENV{${var_name}}) + if (var_value) + message("${var_name}:${var_value}") + else() + message(FATAL_ERROR "${var_name} NOT FOUND") + endif() + endfunction() + check_and_report_variable("MY_BUILD_VAR") + check_and_report_variable("MY_ADDED_BUILD_VAR") + """) + + c.save({"tool.py": tool, + "conanfile.py": consumer, + "CMakeLists.txt": cmakelists}) + + c.run("create tool.py") + + c.run("install .") + + preset = "conan-default" if platform.system() == "Windows" else "conan-release" + + c.run_command(f"cmake --preset {preset}") + assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE_OVERRIDEN" in c.out + assert "MY_ADDED_BUILD_VAR:MY_ADDED_BUILD_VAR_VALUE" in c.out From 295c5014164a7b51f6879babd493f5fb4e193539 Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 22 Jan 2024 12:45:12 +0100 Subject: [PATCH 10/11] conditional ver --- conan/tools/cmake/presets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py index b7cdafd5a25..904de97b94f 100644 --- a/conan/tools/cmake/presets.py +++ b/conan/tools/cmake/presets.py @@ -96,7 +96,7 @@ def _contents(conanfile, toolchain_file, cache_variables, generator, preset_pref build = _CMakePresets._build_preset_fields(conanfile, multiconfig, preset_prefix) test = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, add_env) - ret = {"version": 4, + ret = {"version": 4 if add_env else 3, "vendor": {"conan": {}}, "cmakeMinimumRequired": {"major": 3, "minor": 15, "patch": 0}, "configurePresets": [conf], From b8b89387c87747e604613f4c99f3637059eb5841 Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 22 Jan 2024 15:06:58 +0100 Subject: [PATCH 11/11] wip --- conan/tools/cmake/presets.py | 5 +---- conans/client/generators/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py index 904de97b94f..ef7da94dd71 100644 --- a/conan/tools/cmake/presets.py +++ b/conan/tools/cmake/presets.py @@ -206,10 +206,7 @@ def _test_preset_fields(conanfile, multiconfig, preset_prefix, add_env=False): @staticmethod def environment_preset_name(conanfile, scope): - build_type = conanfile.settings.get_safe("build_type") - arch = conanfile.settings.get_safe("arch") - preset_name = "-".join(filter(None, ["conan", scope, "environment", build_type, arch])).lower() - return preset_name + return "environment-" + _CMakePresets._build_and_test_preset_name(conanfile) + "-" + scope @staticmethod def _build_and_test_preset_name(conanfile): diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index e10728d197a..4e8631b9ea5 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -142,6 +142,8 @@ def _receive_conf(conanfile): def _generate_aggregated_env(conanfile): + from conan.tools.cmake.presets import _CMakePresets + def deactivates(filenames): # FIXME: Probably the order needs to be reversed result = [] @@ -188,8 +190,6 @@ def jsons_content(files): file_json = json.loads(file_content) combined_environment.update(file_json) preset_type = "testPresets" if group == "run" else "configurePresets" - - from conan.tools.cmake.presets import _CMakePresets preset_name = _CMakePresets.environment_preset_name(conanfile, group) preset = {