From 6b3ae4613e0948fd7bab740e4d263099d90e5180 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 5 Feb 2024 01:36:13 +0100 Subject: [PATCH 1/4] fix relativize paths in generated files --- conan/tools/env/environment.py | 27 ++++++++++++------- conans/client/generators/__init__.py | 15 ++++++++--- .../test/integration/environment/test_env.py | 11 ++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py index aac022c1f5b..732078d9d52 100644 --- a/conan/tools/env/environment.py +++ b/conan/tools/env/environment.py @@ -3,7 +3,7 @@ from collections import OrderedDict from contextlib import contextmanager -from conans.client.generators import relativize_generated_file +from conans.client.generators import relativize_paths from conans.client.subsystems import deduce_subsystem, WINDOWS, subsystem_path from conan.errors import ConanException from conans.model.recipe_ref import ref_matches @@ -135,12 +135,14 @@ def compose_env_value(self, other): new_value[index:index + 1] = other._values # replace the placeholder self._values = new_value - def get_str(self, placeholder, subsystem, pathsep): + def get_str(self, placeholder, subsystem, pathsep, root_path=None, script_path=None): """ :param subsystem: :param placeholder: a OS dependant string pattern of the previous env-var value like $PATH, %PATH%, et :param pathsep: The path separator, typically ; or : + :param root_path: To do a relativize of paths, the base root path to be replaced + :param script_path: the replacement instead of the script path :return: a string representation of the env-var value, including the $NAME-like placeholder """ values = [] @@ -151,6 +153,8 @@ def get_str(self, placeholder, subsystem, pathsep): else: if self._path: v = subsystem_path(subsystem, v) + if root_path is not None and v.startswith(root_path): + v = v.replace(root_path, script_path, 1) values.append(v) if self._path: return pathsep.join(values) @@ -329,7 +333,7 @@ class EnvVars: """ def __init__(self, conanfile, values, scope): - self._values = values # {var_name: _EnvValue}, just a reference to the Environment + self._values = values # {var_name: _EnvValue}, just a reference to the Environment self._conanfile = conanfile self._scope = scope self._subsystem = deduce_subsystem(conanfile, scope) @@ -418,12 +422,13 @@ def save_bat(self, file_location, generate_deactivate=True): {deactivate} """).format(deactivate=deactivate if generate_deactivate else "") result = [capture] + abs_base_path, new_path = relativize_paths(self._conanfile, "%~dp0") for varname, varvalues in self._values.items(): - value = varvalues.get_str("%{name}%", subsystem=self._subsystem, pathsep=self._pathsep) + value = varvalues.get_str("%{name}%", subsystem=self._subsystem, pathsep=self._pathsep, + root_path=abs_base_path, script_path=new_path) result.append('set "{}={}"'.format(varname, value)) content = "\n".join(result) - content = relativize_generated_file(content, self._conanfile, "%~dp0") # It is very important to save it correctly with utf-8, the Conan util save() is broken os.makedirs(os.path.dirname(os.path.abspath(file_location)), exist_ok=True) open(file_location, "w", encoding="utf-8").write(content) @@ -459,8 +464,10 @@ def save_ps1(self, file_location, generate_deactivate=True,): {deactivate} """).format(deactivate=deactivate if generate_deactivate else "") result = [capture] + abs_base_path, new_path = relativize_paths(self._conanfile, "$PSScriptRoot") for varname, varvalues in self._values.items(): - value = varvalues.get_str("$env:{name}", subsystem=self._subsystem, pathsep=self._pathsep) + value = varvalues.get_str("$env:{name}", subsystem=self._subsystem, pathsep=self._pathsep, + root_path=abs_base_path, script_path=new_path) if value: value = value.replace('"', '`"') # escape quotes result.append('$env:{}="{}"'.format(varname, value)) @@ -468,7 +475,6 @@ def save_ps1(self, file_location, generate_deactivate=True,): result.append('if (Test-Path env:{0}) {{ Remove-Item env:{0} }}'.format(varname)) content = "\n".join(result) - content = relativize_generated_file(content, self._conanfile, "$PSScriptRoot") # It is very important to save it correctly with utf-16, the Conan util save() is broken # and powershell uses utf-16 files!!! os.makedirs(os.path.dirname(os.path.abspath(file_location)), exist_ok=True) @@ -476,7 +482,7 @@ def save_ps1(self, file_location, generate_deactivate=True,): def save_sh(self, file_location, generate_deactivate=True): filepath, filename = os.path.split(file_location) - deactivate_file = os.path.join(filepath, "deactivate_{}".format(filename)) + deactivate_file = os.path.join("$script_folder", "deactivate_{}".format(filename)) deactivate = textwrap.dedent("""\ echo "echo Restoring environment" > "{deactivate_file}" for v in {vars} @@ -495,8 +501,10 @@ def save_sh(self, file_location, generate_deactivate=True): {deactivate} """).format(deactivate=deactivate if generate_deactivate else "") result = [capture] + abs_base_path, new_path = relativize_paths(self._conanfile, "$script_folder") for varname, varvalues in self._values.items(): - value = varvalues.get_str("${name}", self._subsystem, pathsep=self._pathsep) + value = varvalues.get_str("${name}", self._subsystem, pathsep=self._pathsep, + root_path=abs_base_path, script_path=new_path) value = value.replace('"', '\\"') if value: result.append('export {}="{}"'.format(varname, value)) @@ -504,7 +512,6 @@ def save_sh(self, file_location, generate_deactivate=True): result.append('unset {}'.format(varname)) content = "\n".join(result) - content = relativize_generated_file(content, self._conanfile, "$script_folder") content = f'script_folder="{os.path.abspath(filepath)}"\n' + content save(file_location, content) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index c3f55c07b3d..e7292fe8257 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -198,14 +198,21 @@ def ps1_content(files): def relativize_generated_file(content, conanfile, placeholder): + abs_base_path, new_path = relativize_paths(conanfile, placeholder) + if abs_base_path is None: + return content + content = content.replace(abs_base_path, new_path) + content = content.replace(abs_base_path.replace("\\", "/"), new_path.replace("\\", "/")) + return content + + +def relativize_paths(conanfile, placeholder): abs_base_path = conanfile.folders._base_generators if not abs_base_path or not os.path.isabs(abs_base_path): - return content + return None, None abs_base_path = os.path.join(abs_base_path, "") # For the trailing / to dissambiguate matches generators_folder = conanfile.generators_folder rel_path = os.path.relpath(abs_base_path, generators_folder) new_path = placeholder if rel_path == "." else os.path.join(placeholder, rel_path) new_path = os.path.join(new_path, "") # For the trailing / to dissambiguate matches - content = content.replace(abs_base_path, new_path) - content = content.replace(abs_base_path.replace("\\", "/"), new_path.replace("\\", "/")) - return content + return abs_base_path, new_path diff --git a/conans/test/integration/environment/test_env.py b/conans/test/integration/environment/test_env.py index 0408c3329a9..c6d1a364354 100644 --- a/conans/test/integration/environment/test_env.py +++ b/conans/test/integration/environment/test_env.py @@ -841,3 +841,14 @@ def test(self): c.run("create tool --build-require -s:b build_type=Release -s:h build_type=Debug") assert "tool/0.1 (test package): Building TEST_PACKAGE IN Debug!!" in c.out assert "MYLIBVAR=MYLIBVALUE:Release" in c.out + + +def test_deactivate_relocatable_substitute(): + c = TestClient() + # this cannot be tested in CI, because permissions over root folder + # c.current_folder = "/build" + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + c.run("install . -s os=Linux -s:b os=Linux") + conanbuild = c.load("conanbuildenv.sh") + result = os.path.join("$script_folder", "deactivate_conanbuildenv.sh") + assert fr'"{result}"' in conanbuild From f7ddd38fabc94613170aed8ddb727656d2adc29b Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 5 Feb 2024 23:40:16 +0100 Subject: [PATCH 2/4] wip --- conan/tools/cmake/toolchain/blocks.py | 19 ++++++++++++++++--- conans/client/generators/__init__.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 455cc5743c9..4594328956f 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -14,6 +14,7 @@ from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME from conan.tools.intel import IntelCC from conan.tools.microsoft.visual import msvc_version_to_toolset_version +from conans.client.generators import relativize_path from conans.client.subsystems import deduce_subsystem, WINDOWS from conan.errors import ConanException from conans.util.files import load @@ -194,6 +195,8 @@ def context(self): if not linker_scripts: return linker_scripts = [linker_script.replace('\\', '/') for linker_script in linker_scripts] + linker_scripts = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") + for p in linker_scripts] linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts] return {"linker_script_flags": " ".join(linker_script_flags)} @@ -280,6 +283,8 @@ def context(self): if not android_ndk_path: raise ConanException('CMakeToolchain needs tools.android:ndk_path configuration defined') android_ndk_path = android_ndk_path.replace("\\", "/") + android_ndk_path = relativize_path(android_ndk_path, self._conanfile, + "${CMAKE_CURRENT_LIST_DIR}") use_cmake_legacy_toolchain = self._conanfile.conf.get("tools.android:cmake_legacy_toolchain", check_type=bool) @@ -377,6 +382,8 @@ def context(self): "enable_visibility": enable_visibility } if host_sdk_name: + host_sdk_name = relativize_path(host_sdk_name, self._conanfile, + "${CMAKE_CURRENT_LIST_DIR}") ctxt_toolchain["cmake_osx_sysroot"] = host_sdk_name # this is used to initialize the OSX_ARCHITECTURES property on each target as it is created if host_architecture: @@ -538,7 +545,9 @@ def context(self): # This is global [conf] injection of extra toolchain files user_toolchain = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_toolchain", default=[], check_type=list) - return {"paths": [ut.replace("\\", "/") for ut in user_toolchain]} + paths = [relativize_path(p.replace("\\", "/"), self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") + for p in user_toolchain] + return {"paths": paths} class ExtraFlagsBlock(Block): @@ -681,8 +690,7 @@ class GenericSystemBlock(Block): {% endif %} """) - @staticmethod - def get_toolset(generator, conanfile): + def get_toolset(self, generator, conanfile): toolset = None if generator is None or ("Visual" not in generator and "Xcode" not in generator): return None @@ -713,6 +721,8 @@ def get_toolset(generator, conanfile): toolset = toolset_arch if toolset is None else "{},{}".format(toolset, toolset_arch) toolset_cuda = conanfile.conf.get("tools.cmake.cmaketoolchain:toolset_cuda") if toolset_cuda is not None: + toolset_cuda = relativize_path(toolset_cuda, self._conanfile, + "${CMAKE_CURRENT_LIST_DIR}") toolset_cuda = f"cuda={toolset_cuda}" toolset = toolset_cuda if toolset is None else f"{toolset},{toolset_cuda}" return toolset @@ -829,6 +839,9 @@ def context(self): # This is handled by the tools.apple:sdk_path and CMAKE_OSX_SYSROOT in Apple cmake_sysroot = self._conanfile.conf.get("tools.build:sysroot") cmake_sysroot = cmake_sysroot.replace("\\", "/") if cmake_sysroot is not None else None + if cmake_sysroot is not None: + cmake_sysroot = relativize_path(cmake_sysroot, self._conanfile, + "${CMAKE_CURRENT_LIST_DIR}") result = self._get_winsdk_version(system_version, generator_platform) system_version, winsdk_version, gen_platform_sdk_version = result diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index e7292fe8257..d30e340430f 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -216,3 +216,17 @@ def relativize_paths(conanfile, placeholder): new_path = placeholder if rel_path == "." else os.path.join(placeholder, rel_path) new_path = os.path.join(new_path, "") # For the trailing / to dissambiguate matches return abs_base_path, new_path + + +def relativize_path(path, conanfile, placeholder): + abs_base_path, new_path = relativize_paths(conanfile, placeholder) + if abs_base_path is None: + return path + if path.startswith(abs_base_path): + path = path.replace(abs_base_path, new_path, 1) + else: + abs_base_path = abs_base_path.replace("\\", "/") + new_path = new_path.replace("\\", "/") + if path.startswith(abs_base_path): + path = path.replace(abs_base_path, new_path, 1) + return path From a4756b3f8cc6875cba9de6b9f66fad84244cee32 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 22 May 2024 12:11:01 +0200 Subject: [PATCH 3/4] wip --- conan/tools/cmake/cmakedeps/cmakedeps.py | 4 +--- conan/tools/cmake/cmakedeps/templates/target_data.py | 5 ++++- conan/tools/cmake/toolchain/blocks.py | 10 +++++----- conan/tools/cmake/toolchain/toolchain.py | 2 -- conans/client/generators/__init__.py | 11 ----------- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py index f8a9cca02f0..dc8758f2eda 100644 --- a/conan/tools/cmake/cmakedeps/cmakedeps.py +++ b/conan/tools/cmake/cmakedeps/cmakedeps.py @@ -15,7 +15,6 @@ from conan.tools.cmake.cmakedeps.templates.target_data import ConfigDataTemplate from conan.tools.cmake.cmakedeps.templates.targets import TargetsTemplate from conan.tools.files import save -from conans.client.generators import relativize_generated_file from conan.errors import ConanException from conans.model.dependencies import get_transitive_requires @@ -120,8 +119,7 @@ def _generate_files(self, require, dep, ret, find_module_mode): ret[config_version.filename] = config_version.render() data_target = ConfigDataTemplate(self, require, dep, find_module_mode) - data_content = relativize_generated_file(data_target.render(), self._conanfile, - "${CMAKE_CURRENT_LIST_DIR}") + data_content = data_target.render() ret[data_target.filename] = data_content target_configuration = TargetConfigurationTemplate(self, require, dep, find_module_mode) diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py index e86d89be409..315e2cf30de 100644 --- a/conan/tools/cmake/cmakedeps/templates/target_data.py +++ b/conan/tools/cmake/cmakedeps/templates/target_data.py @@ -5,7 +5,7 @@ FIND_MODE_BOTH from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate from conan.errors import ConanException - +from conans.client.generators import relativize_path """ @@ -57,6 +57,9 @@ def context(self): # Make the root_folder relative to the generated xxx-data.cmake file root_folder = self._root_folder root_folder = root_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"') + # it is the relative path of the caller, not the dependency + root_folder = relativize_path(root_folder, self.cmakedeps._conanfile, + "${CMAKE_CURRENT_LIST_DIR}") return {"global_cpp": global_cpp, "has_components": self.conanfile.cpp_info.has_components, diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 35ae24a81d2..0d7494622cb 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -534,13 +534,13 @@ def _get_host_runtime_dirs(self, host_req): host_runtime_dirs = {} for k, v in matches: host_runtime_dirs.setdefault(k, []).append(v) - + # Calculate the dirs for the current build_type runtime_dirs = [] for req in host_req: cppinfo = req.cpp_info.aggregated_components() runtime_dirs.extend(cppinfo.bindirs if is_win else cppinfo.libdirs) - + build_type = settings.get_safe("build_type") host_runtime_dirs[build_type] = [s.replace("\\", "/") for s in runtime_dirs] @@ -845,7 +845,8 @@ class GenericSystemBlock(Block): {% endif %} """) - def get_toolset(self, generator, conanfile): + @staticmethod + def get_toolset(generator, conanfile): toolset = None if generator is None or ("Visual" not in generator and "Xcode" not in generator): return None @@ -875,8 +876,7 @@ def get_toolset(self, generator, conanfile): toolset = toolset_arch if toolset is None else "{},{}".format(toolset, toolset_arch) toolset_cuda = conanfile.conf.get("tools.cmake.cmaketoolchain:toolset_cuda") if toolset_cuda is not None: - toolset_cuda = relativize_path(toolset_cuda, self._conanfile, - "${CMAKE_CURRENT_LIST_DIR}") + toolset_cuda = relativize_path(toolset_cuda, conanfile, "${CMAKE_CURRENT_LIST_DIR}") toolset_cuda = f"cuda={toolset_cuda}" toolset = toolset_cuda if toolset is None else f"{toolset},{toolset_cuda}" return toolset diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py index 549e2c0b3bc..6924a1d9fb7 100644 --- a/conan/tools/cmake/toolchain/toolchain.py +++ b/conan/tools/cmake/toolchain/toolchain.py @@ -19,7 +19,6 @@ from conan.tools.intel import IntelCC from conan.tools.microsoft import VCVars from conan.tools.microsoft.visual import vs_ide_version -from conans.client.generators import relativize_generated_file from conan.errors import ConanException from conans.model.options import _PackageOption from conans.util.files import save @@ -202,7 +201,6 @@ def content(self): context = self._context() content = Template(self._template, trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True).render(**context) - content = relativize_generated_file(content, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") return content @property diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 464513651ea..de0d06fd146 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -207,17 +207,6 @@ def ps1_content(files): conanfile.output.info(f"Generated aggregated env files: {generated}") -def relativize_generated_file(content, conanfile, placeholder): - abs_base_path, new_path = relativize_paths(conanfile, placeholder) - print(abs_base_path) - print(new_path) - if abs_base_path is None: - return content - content = content.replace(abs_base_path, new_path) - content = content.replace(abs_base_path.replace("\\", "/"), new_path.replace("\\", "/")) - return content - - def relativize_paths(conanfile, placeholder): abs_base_path = conanfile.folders._base_generators if not abs_base_path or not os.path.isabs(abs_base_path): From 0925957be23873342b4c2685010f3f0d42eb8320 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 22 May 2024 12:26:43 +0200 Subject: [PATCH 4/4] more paths relative --- conan/tools/cmake/toolchain/blocks.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 0d7494622cb..6f50c12f96e 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -147,8 +147,9 @@ def context(self): bin_dirs = [p for dep in host_deps for p in dep.cpp_info.aggregated_components().bindirs] test_bindirs = [p for dep in test_deps for p in dep.cpp_info.aggregated_components().bindirs] bin_dirs.extend(test_bindirs) + bin_dirs = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") + for p in bin_dirs] bin_dirs = [p.replace("\\", "/") for p in bin_dirs] - bin_dirs = ";".join(bin_dirs) if bin_dirs else None if bin_dirs: config_dict[build_type] = bin_dirs @@ -546,11 +547,10 @@ def _get_host_runtime_dirs(self, host_req): return host_runtime_dirs - @staticmethod - def _join_paths(paths): - return " ".join(['"{}"'.format(p.replace('\\', '/') - .replace('$', '\\$') - .replace('"', '\\"')) for p in paths]) + def _join_paths(self, paths): + paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") for p in paths] + paths = [p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"') for p in paths] + return " ".join([f'"{p}"' for p in paths]) def context(self): # To find the generated cmake_find_package finders @@ -637,8 +637,9 @@ def context(self): # This is global [conf] injection of extra toolchain files user_toolchain = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_toolchain", default=[], check_type=list) - paths = [relativize_path(p.replace("\\", "/"), self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") + paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") for p in user_toolchain] + paths = [p.replace("\\", "/") for p in paths] return {"paths": paths}