Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions conan/internal/api/new/meson_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from conan.tools.meson import MesonToolchain, Meson
from conan.tools.layout import basic_layout
from conan.tools.files import copy
{% if requires is defined %}
from conan.tools.gnu import PkgConfigDeps
{% endif %}


class {{package_name}}Conan(ConanFile):
name = "{{name}}"
Expand All @@ -30,7 +34,18 @@ def configure(self):
def layout(self):
basic_layout(self)

{% if requires is defined %}
def requirements(self):
{% for require in requires -%}
self.requires("{{ require }}")
{% endfor %}
{%- endif %}

def generate(self):
{% if requires is defined %}
deps = PkgConfigDeps(self)
deps.generate()
{%- endif %}
tc = MesonToolchain(self)
tc.generate()

Expand Down
1 change: 1 addition & 0 deletions conan/internal/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"tools.build:download_source": "Force download of sources for every package",
"tools.build:jobs": "Default compile jobs number -jX Ninja, Make, /MP VS (default: max CPUs)",
"tools.build:sysroot": "Pass the --sysroot=<tools.build:sysroot> flag if available. (None by default)",
"tools.build:add_rpath_link": "Add -Wl,-rpath-link flags pointing to all lib directories for host dependencies (CMake and Meson toolchains)",
"tools.build.cross_building:can_run": "(boolean) Indicates whether is possible to run a non-native app on the same architecture. It's used by 'can_run' tool",
"tools.build.cross_building:cross_build": "(boolean) Decides whether cross-building or not regardless of arch/OS settings. Used by 'cross_building' tool",
"tools.build:verbosity": "Verbosity of build systems if set. Possible values are 'quiet' and 'verbose'",
Expand Down
23 changes: 23 additions & 0 deletions conan/tools/cmake/toolchain/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,29 @@ def context(self):
return {"arch_flag": arch_flag, "arch_link_flag": arch_link_flag,
"thread_flags_list": thread_flags_list}

class RpathLinkFlagsBlock(Block):
template = textwrap.dedent("""\
# Pass -rpath-link pointing to all directories with runtime libraries
{% if rpath_link_flags %}
string(APPEND CONAN_EXE_LINKER_FLAGS " {{ rpath_link_flags }}")
string(APPEND CONAN_SHARED_LINKER_FLAGS " {{ rpath_link_flags }}")
{% endif %}
""")

def context(self):
add_rpath_link = self._toolchain.add_rpath_link or self._conanfile.conf.get("tools.build:add_rpath_link", check_type=bool)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that the user adds checks? Like:

def generate(self):
    tc = CMakeToolchain(self)
    if self.settings.os == "Linux" and self.settings.compiler == "gcc":  # assume LD
         tc.add_rpath_link = True
    tc.generate()

Otherwise, this could break compilers/linkers by injecting an unknown -rpath-link to all compilers, including msvc for example?

if add_rpath_link:
runtime_dirs = []
host_req = self._conanfile.dependencies.filter({"build": False}).values()
for req in host_req:
cppinfo = req.cpp_info.aggregated_components()
runtime_dirs.extend(cppinfo.libdirs)

# surround each dir with escaped quotes, to avoid problems with spaces in paths
rpath_link_flags = " ".join([f'-Wl,-rpath-link=\\"{d}\\"' for d in runtime_dirs]) if runtime_dirs else None
else:
rpath_link_flags = None
return {"rpath_link_flags": rpath_link_flags}

class LinkerScriptsBlock(Block):
template = textwrap.dedent("""\
Expand Down
4 changes: 3 additions & 1 deletion conan/tools/cmake/toolchain/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
SkipRPath, SharedLibBock, OutputDirsBlock,
ExtraFlagsBlock, CompilersBlock, LinkerScriptsBlock,
VSDebuggerEnvironment, VariablesBlock,
PreprocessorBlock)
PreprocessorBlock, RpathLinkFlagsBlock)
from conan.tools.cmake.utils import is_multi_configuration
from conan.tools.env import VirtualBuildEnv, VirtualRunEnv
from conan.tools.intel import IntelCC
Expand Down Expand Up @@ -98,6 +98,7 @@ def __init__(self, conanfile, generator=None):
self.extra_cflags = []
self.extra_sharedlinkflags = []
self.extra_exelinkflags = []
self.add_rpath_link = False

self.blocks = ToolchainBlocks(self._conanfile, self,
[("user_toolchain", UserToolchain),
Expand All @@ -108,6 +109,7 @@ def __init__(self, conanfile, generator=None):
("fpic", FPicBlock),
("arch_flags", ArchitectureBlock),
("linker_scripts", LinkerScriptsBlock),
("rpath_link_flags", RpathLinkFlagsBlock),
("libcxx", GLibCXXBlock),
("vs_runtime", VSRuntimeBlock),
("vs_debugger_environment", VSDebuggerEnvironment),
Expand Down
17 changes: 15 additions & 2 deletions conan/tools/meson/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,20 @@ def _resolve_android_cross_compilation(self):
self.c = os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang{compile_ext}")
self.cpp = os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang++{compile_ext}")
self.ar = os.path.join(ndk_bin, "llvm-ar")


@property
def _rpath_link_flag(self):
add_rpath_link = self._conanfile.conf.get("tools.build:add_rpath_link", check_type=bool)
if not add_rpath_link:
return []
runtime_dirs = []
host_req = self._conanfile.dependencies.filter({"build": False}).values()
for req in host_req:
cppinfo = req.cpp_info.aggregated_components()
runtime_dirs.extend(cppinfo.libdirs)
# TODO: check if this handles spaces in paths correctly
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to visually check where and how these flags are passed - but it does work with path with spaces, since that's what the test is using

return ["-Wl,-rpath-link=" + ":".join(runtime_dirs)] if runtime_dirs else []

def _get_extra_flags(self):
# Now, it's time to get all the flags defined by the user
cxxflags = self._conanfile_conf.get("tools.build:cxxflags", default=[], check_type=list)
Expand All @@ -474,7 +487,7 @@ def _get_extra_flags(self):
"cxxflags": [self.arch_flag] + cxxflags + sys_root + self.extra_cxxflags
+ self.threads_flags,
"cflags": [self.arch_flag] + cflags + sys_root + self.extra_cflags + self.threads_flags,
"ldflags": [self.arch_flag] + [self.arch_link_flag] + ld,
"ldflags": [self.arch_flag] + [self.arch_link_flag] + ld + self._rpath_link_flag,
"defines": [f"-D{d}" for d in (defines + self.extra_defines)]
}

Expand Down
244 changes: 244 additions & 0 deletions test/functional/toolchains/cmake/test_cmake_transitive_rpath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import platform
import textwrap
import pytest

from conan.test.utils.mocks import ConanFileMock
from conan.tools.env.environment import environment_wrap_command
from conan.test.utils.test_files import temp_folder
from conan.test.utils.tools import TestClient

@pytest.mark.skipif(platform.system() != "Linux", reason="Linux/gcc required for -rpath/-rpath-link testing")
@pytest.mark.tool("cmake", "3.27")
@pytest.mark.parametrize("use_cmake_config_deps", [True, False])
def test_cmake_sysroot_transitive_rpath(use_cmake_config_deps):
c = TestClient()


extra_profile = textwrap.dedent("""
[conf]
tools.build:sysroot=/path/to/nowhere
""")

# Avoid using any C or C++ standard functionality, so that we can "redirect" the sysroot
# to an empty or non-existing directory
foo_h = textwrap.dedent("""
#pragma once
int foo(int x, int y);
""")
foo_cpp = textwrap.dedent("""
#include "foo.h"
int foo(int x, int y) {
return x + y;
}
""")
foo_test = textwrap.dedent("""
#include "foo.h"
int main() { return foo(2, 3) == 5 ? 0 : 1; }
""")
bar_h = textwrap.dedent("""
#pragma once
int bar(int x, int y);
""")
bar_cpp = textwrap.dedent("""
#include "bar.h"
#include "foo.h"
int bar(int x, int y) {
return foo(x, y) * 2;
}
""")
bar_test = textwrap.dedent("""
#include "bar.h"
int main() { return bar(2, 3) == 10 ? 0 : 1; }
""")

c.save({"extra_profile": extra_profile})
extra_conf = "-c tools.cmake.cmakedeps:new=will_break_next" if use_cmake_config_deps else ""
if not use_cmake_config_deps:
# CMakeConfigDeps does not fail, so nothing extra is needed
# this is only needed to cover the case of CMakeDeps
extra_conf += " -c tools.build:add_rpath_link=True"
with c.chdir("foo"):
c.run("new cmake_lib -d name=foo -d version=0.1")
c.save({"include/foo.h": foo_h,
"src/foo.cpp": foo_cpp,
"test_package/src/example.cpp": foo_test})
c.run(f"create . -o '*:shared=True' -pr=default -pr=../extra_profile {extra_conf}")

with c.chdir("bar"):
c.run("new cmake_lib -d name=bar -d version=0.1 -d requires=foo/0.1")
c.save({"include/bar.h": bar_h,
"src/bar.cpp": bar_cpp,
"test_package/src/example.cpp": bar_test})
# skip test package, which fails with CMakeToolchain+CMakeDeps
c.run(f"create . -o '*:shared=True' -tf= -pr=default -pr=../extra_profile {extra_conf}")
with c.chdir("app"):
c.run("new cmake_exe -d name=app -d version=0.1 -d requires=bar/0.1")
c.save({"src/main.cpp": bar_test,
"src/app.cpp": ""})
c.run(f"create . -o '*:shared=True' -pr=default -pr=../extra_profile {extra_conf}")



@pytest.mark.skipif(platform.system() != "Linux", reason="Linux/gcc required for -rpath/-rpath-link testing")
@pytest.mark.tool("cmake", "3.27")
@pytest.mark.parametrize("use_cmake_config_deps", [True, False])
def test_cmake_transitive_rpath_private_internal(use_cmake_config_deps):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the test for CMakeConfigDeps issue with the package interface target usage.

c = TestClient()

foo_h = textwrap.dedent("""
#pragma once
int foo(int x, int y);
""")
foo_cpp = textwrap.dedent("""
#include "foo.h"
int foo(int x, int y) {
return x + y;
}
""")
bar_h = textwrap.dedent("""
#pragma once
int bar(int x, int y);
""")
bar_cpp = textwrap.dedent("""
#include "bar.h"
#include "foo.h"
int bar(int x, int y) {
return foo(x, y) * 2;
}
""")

foobar_cmakelists = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(foobar CXX)

add_library(foo src/foo.cpp)
target_include_directories(foo PUBLIC include)
set_target_properties(foo PROPERTIES PUBLIC_HEADER "include/foo.h")

add_library(bar src/bar.cpp)
target_include_directories(bar PUBLIC include)
set_target_properties(bar PROPERTIES PUBLIC_HEADER "include/bar.h")
target_link_libraries(bar PRIVATE foo)

install(TARGETS foo bar)
""")

foobar_conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout


class foobarRecipe(ConanFile):
name = "foobar"
version = "1.0"
package_type = "library"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False]}
default_options = {"shared": True}

exports_sources = "CMakeLists.txt", "src/*", "include/*"

generators = "CMakeDeps", "CMakeToolchain"

def layout(self):
cmake_layout(self)

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()

def package_info(self):
self.cpp_info.components["foo"].libs = ["foo"]
self.cpp_info.components["bar"].libs = ["bar"]
self.cpp_info.components["bar"].requires = ["foo"]
""")

consumer_conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout

class consumerRecipe(ConanFile):
name = "consumer"
version = "1.0"
package_type = "library"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False]}
default_options = {"shared": True}
generators = "CMakeDeps", "CMakeToolchain"
exports_sources = "CMakeLists.txt", "src/*", "include/*"

def layout(self):
cmake_layout(self)

def requirements(self):
self.requires("foobar/1.0")

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()

def package_info(self):
self.cpp_info.libs = ["consumer"]
""")

consumer_cmakelists = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(consumer CXX)

find_package(foobar CONFIG REQUIRED)

add_library(consumer src/consumer.cpp)
target_include_directories(consumer PUBLIC include)
target_link_libraries(consumer PRIVATE ${foobar_LIBRARIES}) # foobar_LIBRARIES is foobar::foobar
set_target_properties(consumer PROPERTIES PUBLIC_HEADER "include/consumer.h")
install(TARGETS consumer)

add_executable(my_app src/my_app.cpp)
target_link_libraries(my_app PRIVATE consumer)
""")

consumer_cpp = textwrap.dedent("""
#include "consumer.h"
#include "bar.h"
int consumer(int x, int y) {return bar(x, y) * 2;}
""")

consumer_h = textwrap.dedent("""
#pragma once
int consumer(int x, int y);
""")

my_app_cpp = textwrap.dedent("""
#include "consumer.h"
int main() { return consumer(2, 3) == 20 ? 0 : 1; }
""")

extra_conf = "-c tools.cmake.cmakedeps:new=will_break_next" if use_cmake_config_deps else ""
extra_conf += " -c tools.build:add_rpath_link=True" # removing this should break the test

with c.chdir("foobar"):
c.save({"include/foo.h": foo_h,
"include/bar.h": bar_h,
"src/foo.cpp": foo_cpp,
"src/bar.cpp": bar_cpp,
"CMakeLists.txt": foobar_cmakelists,
"conanfile.py": foobar_conanfile})
c.run(f"create . {extra_conf} ")

with c.chdir("consumer"):
c.save({"src/consumer.cpp": consumer_cpp,
"include/consumer.h": consumer_h,
"src/my_app.cpp": my_app_cpp,
"CMakeLists.txt": consumer_cmakelists,
"conanfile.py": consumer_conanfile})
c.run(f"create . {extra_conf}")
Loading
Loading