Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add CONAN_RUNTIME_LIB_DIRS to the conan_toolchain.cmake #15914

Merged
merged 20 commits into from May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 40 additions & 1 deletion conan/tools/cmake/toolchain/blocks.py
Expand Up @@ -154,7 +154,7 @@ def context(self):

if not config_dict:
return None

vs_debugger_path = ""
for config, value in config_dict.items():
vs_debugger_path += f"$<$<CONFIG:{config}>:{value}>"
Expand Down 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
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
@@ -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