From 29315889b45f24f83c6530a8bdb18b4c547e1cba Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Fri, 17 May 2024 16:30:56 +0200 Subject: [PATCH] feat: Add CONAN_RUNTIME_LIB_DIRS to the conan_toolchain.cmake (#15914) * add: CONAN_RUNTIME_LIB_DIRS to the conan_toolchain.cmake * Fix: hash collision with "win" word * review changes * support multi-conf * refactor macro and rename host_runtime_dirs * fix * removing variables and improve if condition * fix variable * test fix * fix * fix quotes * fix windows paths * fix escape error * replace \ to \\ on win paths * change \ to / * replace \ to / always * fix test * Update conan/tools/cmake/toolchain/blocks.py * refactor CONAN_RUNTIME_LIB_DIRS --------- Co-authored-by: Luis Caro Campos <3535649+jcar87@users.noreply.github.com> --- conan/tools/cmake/toolchain/blocks.py | 39 ++++++++++++ .../lockfile/test_lock_requires_revisions.py | 8 +-- .../toolchains/cmake/test_cmaketoolchain.py | 62 +++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 1855f8f60f5..02d6c4723f5 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -480,6 +480,9 @@ class FindFiles(Block): {% if cmake_include_path %} list(PREPEND CMAKE_INCLUDE_PATH {{ cmake_include_path }}) {% endif %} + {% if host_runtime_dirs %} + set(CONAN_RUNTIME_LIB_DIRS {{ host_runtime_dirs }} ) + {% endif %} {% if cross_building %} if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_PACKAGE OR CMAKE_FIND_ROOT_PATH_MODE_PACKAGE STREQUAL "ONLY") @@ -502,6 +505,40 @@ class FindFiles(Block): {% endif %} """) + def _runtime_dirs_value(self, dirs): + if is_multi_configuration(self._toolchain.generator): + return ' '.join(f'"$<$:{i}>"' for c, v in dirs.items() for i in v) + else: + return ' '.join(f'"{item}"' for _, items in dirs.items() for item in items) + + def _get_host_runtime_dirs(self, host_req): + settings = self._conanfile.settings + host_runtime_dirs = {} + is_win = self._conanfile.settings.get_safe("os") == "Windows" + + # Get the previous configuration + if is_multi_configuration(self._toolchain.generator) and os.path.exists(CONAN_TOOLCHAIN_FILENAME): + existing_toolchain = load(CONAN_TOOLCHAIN_FILENAME) + pattern_lib_dirs = r"set\(CONAN_RUNTIME_LIB_DIRS ([^)]*)\)" + variable_match = re.search(pattern_lib_dirs, existing_toolchain) + if variable_match: + capture = variable_match.group(1) + matches = re.findall(r'"\$<\$:([^>]*)>"', capture) + 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] + + return host_runtime_dirs + @staticmethod def _join_paths(paths): return " ".join(['"{}"'.format(p.replace('\\', '/') @@ -524,6 +561,7 @@ def context(self): host_req = self._conanfile.dependencies.filter({"build": False}).values() build_paths = [] host_lib_paths = [] + host_runtime_dirs = self._get_host_runtime_dirs(host_req) host_framework_paths = [] host_include_paths = [] for req in host_req: @@ -552,6 +590,7 @@ def context(self): "cmake_include_path": self._join_paths(host_include_paths), "is_apple": is_apple_, "cross_building": cross_building(self._conanfile), + "host_runtime_dirs": self._runtime_dirs_value(host_runtime_dirs) } diff --git a/conans/test/integration/lockfile/test_lock_requires_revisions.py b/conans/test/integration/lockfile/test_lock_requires_revisions.py index e84680c0839..1f6e16ab7ad 100644 --- a/conans/test/integration/lockfile/test_lock_requires_revisions.py +++ b/conans/test/integration/lockfile/test_lock_requires_revisions.py @@ -132,20 +132,20 @@ def requirements(self): client.run("install consumer --lockfile=consumer.lock -s os=Windows -s:b os=Windows") assert "REV1!!!" in client.out assert "REV2!!!" not in client.out - assert "nix" not in client.out + assert "nix/0.1" not in client.out client.run("install consumer -s os=Windows -s:b os=Windows") assert "REV2!!!" in client.out assert "REV1!!!" not in client.out - assert "nix" not in client.out + assert "nix/0.1" not in client.out client.run("install consumer --lockfile=consumer.lock -s os=Linux -s:b os=Linux") assert "REV1!!!" in client.out assert "REV2!!!" not in client.out - assert "win" not in client.out + assert "win/0.1" not in client.out client.run("install consumer -s os=Linux -s:b os=Linux") assert "REV2!!!" in client.out assert "REV1!!!" not in client.out - assert "win" not in client.out + assert "win/0.1" not in client.out @pytest.mark.parametrize("requires", ["requires", "tool_requires"]) diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py index a9b108d8322..25601b1dfa1 100644 --- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py +++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py @@ -1,6 +1,7 @@ import json import os import platform +import re import textwrap import pytest @@ -352,6 +353,67 @@ def generate(self): assert "/path/to/builddir" in contents +@pytest.fixture +def lib_dir_setup(): + client = TestClient() + client.save({"conanfile.py": GenConanfile().with_generator("CMakeToolchain")}) + client.run("create . --name=onelib --version=1.0") + client.run("create . --name=twolib --version=1.0") + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Conan(ConanFile): + requires = "onelib/1.0", "twolib/1.0" + + """) + client.save({"conanfile.py": conanfile}) + client.run("create . --name=dep --version=1.0") + + conanfile = (GenConanfile().with_requires("dep/1.0").with_generator("CMakeToolchain") + .with_settings("os", "arch", "compiler", "build_type")) + + client.save({"conanfile.py": conanfile}) + return client + +def test_runtime_lib_dirs_single_conf(lib_dir_setup): + client = lib_dir_setup + generator = "" + is_windows = platform.system() == "Windows" + if is_windows: + generator = '-c tools.cmake.cmaketoolchain:generator=Ninja' + + client.run(f'install . -s build_type=Release {generator}') + contents = client.load("conan_toolchain.cmake") + pattern_lib_path = r'list\(PREPEND CMAKE_LIBRARY_PATH (.*)\)' + pattern_lib_dirs = r'set\(CONAN_RUNTIME_LIB_DIRS (.*) \)' + + # On *nix platforms: the list in `CMAKE_LIBRARY_PATH` + # is the same as `CONAN_RUNTIME_LIB_DIRS` + # On windows, it's the same but with `bin` instead of `lib` + cmake_library_path = re.search(pattern_lib_path, contents).group(1) + conan_runtime_lib_dirs = re.search(pattern_lib_dirs, contents).group(1) + lib_path = cmake_library_path.replace("/p/lib", "/p/bin") if is_windows else cmake_library_path + + assert lib_path == conan_runtime_lib_dirs + + +def test_runtime_lib_dirs_multiconf(lib_dir_setup): + client = lib_dir_setup + generator = "" + if platform.system() != "Windows": + generator = '-c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config"' + + client.run(f'install . -s build_type=Release {generator}') + client.run(f'install . -s build_type=Debug {generator}') + + contents = client.load("conan_toolchain.cmake") + pattern_lib_dirs = r"set\(CONAN_RUNTIME_LIB_DIRS ([^)]*)\)" + runtime_lib_dirs = re.search(pattern_lib_dirs, contents).group(1) + + assert "" in runtime_lib_dirs + assert "" in runtime_lib_dirs + + @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") def test_cmaketoolchain_cmake_system_processor_cross_apple(): """