Skip to content

Commit

Permalink
feat: Add CONAN_RUNTIME_LIB_DIRS to the conan_toolchain.cmake (#15914)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
ErniGH and jcar87 authored May 17, 2024
1 parent efb00f5 commit 2931588
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 4 deletions.
39 changes: 39 additions & 0 deletions conan/tools/cmake/toolchain/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -502,6 +505,40 @@ class FindFiles(Block):
{% endif %}
""")

def _runtime_dirs_value(self, dirs):
if is_multi_configuration(self._toolchain.generator):
return ' '.join(f'"$<$<CONFIG:{c}>:{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'"\$<\$<CONFIG:([A-Za-z]*)>:([^>]*)>"', 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('\\', '/')
Expand All @@ -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:
Expand Down Expand Up @@ -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)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
62 changes: 62 additions & 0 deletions conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import os
import platform
import re
import textwrap

import pytest
Expand Down Expand Up @@ -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 "<CONFIG:Release>" in runtime_lib_dirs
assert "<CONFIG:Debug>" in runtime_lib_dirs


@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX")
def test_cmaketoolchain_cmake_system_processor_cross_apple():
"""
Expand Down

0 comments on commit 2931588

Please sign in to comment.