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

[incubating][CMakeDeps] Adding more CMake paths #17668

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 48 additions & 32 deletions conan/tools/cmake/cmakedeps2/cmakedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,30 +165,55 @@ def __init__(self, cmakedeps, conanfile):
self._conanfile = conanfile
self._cmakedeps = cmakedeps

def _get_cmake_paths(self, requirements, dirs_name):
paths = {}
cmake_vars = {
"bindirs": "CMAKE_PROGRAM_PATH",
"libdirs": "CMAKE_LIBRARY_PATH",
"includedirs": "CMAKE_INCLUDE_PATH",
}
for req, dep in requirements:
cppinfo = dep.cpp_info.aggregated_components()
cppinfo_dirs = getattr(cppinfo, dirs_name, [])
if not cppinfo_dirs:
continue
previous = paths.get(req.ref.name)
if previous:
self._conanfile.output.info(f"There is already a '{req.ref}' package contributing"
f" to {cmake_vars[dirs_name]}. Using the one"
f" defined by the context={dep.context}.")
paths[req.ref.name] = cppinfo_dirs
return [d for dirs in paths.values() for d in dirs]

def generate(self):
template = textwrap.dedent("""\
{% for pkg_name, folder in pkg_paths.items() %}
set({{pkg_name}}_DIR "{{folder}}")
{% endfor %}
{% if host_runtime_dirs %}
set(CONAN_RUNTIME_LIB_DIRS {{ host_runtime_dirs }} )
# Only for VS, needs CMake>=3.27
set(CMAKE_VS_DEBUGGER_ENVIRONMENT "PATH=${CONAN_RUNTIME_LIB_DIRS};%PATH%")
{% endif %}
{% if cmake_program_path %}
list(PREPEND CMAKE_PROGRAM_PATH {{ cmake_program_path }})
{% endif %}
""")

{% for pkg_name, folder in pkg_paths.items() %}
set({{pkg_name}}_DIR "{{folder}}")
{% endfor %}
{% if host_runtime_dirs %}
set(CONAN_RUNTIME_LIB_DIRS {{ host_runtime_dirs }} )
# Only for VS, needs CMake>=3.27
set(CMAKE_VS_DEBUGGER_ENVIRONMENT "PATH=${CONAN_RUNTIME_LIB_DIRS};%PATH%")
{% endif %}
{% if cmake_program_path %}
list(PREPEND CMAKE_PROGRAM_PATH {{ cmake_program_path }})
{% endif %}
{% if cmake_library_path %}
list(PREPEND CMAKE_LIBRARY_PATH {{ cmake_library_path }})
{% endif %}
{% if cmake_include_path %}
list(PREPEND CMAKE_INCLUDE_PATH {{ cmake_include_path }})
{% endif %}
""")
host_req = self._conanfile.dependencies.host
build_req = self._conanfile.dependencies.direct_build
test_req = self._conanfile.dependencies.test

all_reqs = list(host_req.items()) + list(test_req.items()) + list(build_req.items())
# gen_folder = self._conanfile.generators_folder.replace("\\", "/")
# if not, test_cmake_add_subdirectory test fails
# content.append('set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)')
pkg_paths = {}
for req, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
for req, dep in all_reqs:
cmake_find_mode = self._cmakedeps.get_property("cmake_find_mode", dep)
cmake_find_mode = cmake_find_mode or FIND_MODE_CONFIG
cmake_find_mode = cmake_find_mode.lower()
Expand All @@ -214,26 +239,17 @@ def generate(self):
# content.append(f'set({pkg_name}_ROOT "{gen_folder}")')
pkg_paths[pkg_name] = "${CMAKE_CURRENT_LIST_DIR}"

# CMAKE_PROGRAM_PATH
cmake_program_path = {}
for req, dep in list(host_req.items()) + list(test_req.items()) + list(build_req.items()):
if not req.direct:
continue
cppinfo = dep.cpp_info.aggregated_components()
if not cppinfo.bindirs:
continue
previous = cmake_program_path.get(req.ref.name)
if previous:
self._conanfile.output.info(f"There is already a '{req.ref}' package "
f"contributing to CMAKE_PROGRAM_PATH. The one with "
f"build={req.build} test={req.test} will be used")

cmake_program_path[req.ref.name] = cppinfo.bindirs
cmake_program_path = [d for dirs in cmake_program_path.values() for d in dirs]
# CMAKE_PROGRAM_PATH | CMAKE_LIBRARY_PATH | CMAKE_INCLUDE_PATH
cmake_program_path = self._get_cmake_paths([(req, dep) for req, dep in all_reqs if req.direct], "bindirs")
cmake_library_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "libdirs")
cmake_include_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "includedirs")

context = {"host_runtime_dirs": self._get_host_runtime_dirs(),
"pkg_paths": pkg_paths,
"cmake_program_path": _join_paths(self._conanfile, cmake_program_path)}
"cmake_program_path": _join_paths(self._conanfile, cmake_program_path),
"cmake_library_path": _join_paths(self._conanfile, cmake_library_path),
"cmake_include_path": _join_paths(self._conanfile, cmake_include_path),
}
content = Template(template, trim_blocks=True, lstrip_blocks=True).render(context)
save(self._conanfile, self._conan_cmakedeps_paths, content)

Expand Down
148 changes: 100 additions & 48 deletions test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,51 +102,103 @@ def test_runtime_lib_dirs_multiconf(self):


@pytest.mark.tool("cmake")
@pytest.mark.parametrize("requires, tool_requires", [(True, False), (False, True), (True, True)])
def test_cmaketoolchain_path_find_program(requires, tool_requires):
"""Test that executables in bindirs of tool_requires can be found with
find_program() in consumer CMakeLists.
"""
c = TestClient()

conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "tool"
version = "1.0"
exports_sources = "*"
def package(self):
copy(self, "*", self.source_folder, os.path.join(self.package_folder, "bin"))
""")
c.save({"conanfile.py": conanfile, "hello": "", "hello.exe": ""})
c.run("create .")

requires = 'requires = "tool/1.0"' if requires else ""
tool_requires = 'tool_requires = "tool/1.0"' if tool_requires else ""
conanfile = textwrap.dedent(f"""
from conan import ConanFile
from conan.tools.cmake import CMake
class PkgConan(ConanFile):
{requires}
{tool_requires}
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeToolchain", "CMakeDeps"
def build(self):
cmake = CMake(self)
cmake.configure()
""")
consumer = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MyHello)
find_program(HELLOPROG hello)
if(HELLOPROG)
message(STATUS "Found hello prog: ${HELLOPROG}")
endif()
""")
c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
assert "Found hello prog" in c.out
if requires and tool_requires:
assert "There is already a 'tool/1.0' package contributing to CMAKE_PROGRAM_PATH" in c.out
class TestCMakeDepsPaths:

@pytest.mark.parametrize("requires, tool_requires", [(True, False), (False, True), (True, True)])
def test_find_program_path(self, requires, tool_requires):
"""Test that executables in bindirs of tool_requires can be found with
find_program() in consumer CMakeLists.
"""
c = TestClient()

conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "tool"
version = "1.0"
exports_sources = "*"
def package(self):
copy(self, "*", self.source_folder, os.path.join(self.package_folder, "bin"))
""")
c.save({"conanfile.py": conanfile, "hello": "", "hello.exe": ""})
c.run("create .")

requires = 'requires = "tool/1.0"' if requires else ""
tool_requires = 'tool_requires = "tool/1.0"' if tool_requires else ""
conanfile = textwrap.dedent(f"""
from conan import ConanFile
from conan.tools.cmake import CMake
class PkgConan(ConanFile):
{requires}
{tool_requires}
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeToolchain", "CMakeDeps"
def build(self):
cmake = CMake(self)
cmake.configure()
""")
consumer = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MyHello)
find_program(HELLOPROG hello)
if(HELLOPROG)
message(STATUS "Found hello prog: ${HELLOPROG}")
endif()
""")
c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
assert "Found hello prog" in c.out
if requires and tool_requires:
assert "There is already a 'tool/1.0' package contributing to CMAKE_PROGRAM_PATH" in c.out

def test_find_include_and_lib_paths(self):
c = TestClient()
conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "hello"
version = "1.0"
exports_sources = "*"
def package(self):
copy(self, "*.h", self.source_folder, os.path.join(self.package_folder, "include"))
copy(self, "*.lib", self.source_folder, os.path.join(self.package_folder, "lib"))
copy(self, "*.a", self.source_folder, os.path.join(self.package_folder, "lib"))
copy(self, "*.so", self.source_folder, os.path.join(self.package_folder, "lib"))
copy(self, "*.dll", self.source_folder, os.path.join(self.package_folder, "lib"))
""")
c.save({"conanfile.py": conanfile,
"hello.h": "", "hello.lib": "", "libhello.a": "",
"libhello.so": "", "libhello.dll": ""
})
c.run("create .")
conanfile = textwrap.dedent(f"""
from conan import ConanFile
from conan.tools.cmake import CMake
class PkgConan(ConanFile):
requires = "hello/1.0"
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeToolchain", "CMakeDeps"
def build(self):
cmake = CMake(self)
cmake.configure()
""")
consumer = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MyHello)
find_file(HELLOINC hello.h)
find_library(HELLOLIB hello)
if(HELLOINC)
message(STATUS "Found hello header: ${HELLOINC}")
endif()
if(HELLOLIB)
message(STATUS "Found hello lib: ${HELLOLIB}")
endif()
""")
c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
assert "Found hello header" in c.out
assert "Found hello lib" in c.out
92 changes: 92 additions & 0 deletions test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import re
import textwrap

from conan.test.utils.tools import TestClient

new_value = "will_break_next"


def test_cmakedeps_direct_deps_paths():
c = TestClient()
conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "lib"
version = "1.0"
def package_info(self):
self.cpp_info.includedirs = ["myincludes"]
self.cpp_info.libdirs = ["mylib"]
""")
c.save({"conanfile.py": conanfile})
c.run("create .")
conanfile = textwrap.dedent(f"""
from conan import ConanFile
from conan.tools.cmake import CMake
class PkgConan(ConanFile):
requires = "lib/1.0"
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeDeps"
def build(self):
cmake = CMake(self)
cmake.configure()
""")
c.save({"conanfile.py": conanfile}, clean_first=True)
c.run(f"install . -c tools.cmake.cmakedeps:new={new_value}")
cmake_paths = c.load("conan_cmakedeps_paths.cmake")
assert re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH \".*/bin\"", cmake_paths) # default
assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/mylib\"", cmake_paths)
assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/myincludes\"", cmake_paths)


def test_cmakedeps_transitive_paths():
c = TestClient()
conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "liba"
version = "1.0"
def package_info(self):
self.cpp_info.includedirs = ["includea"]
self.cpp_info.libdirs = ["liba"]
self.cpp_info.bindirs = ["bina"]
""")
c.save({"conanfile.py": conanfile})
c.run("create .")
conanfile = textwrap.dedent("""
import os
from conan.tools.files import copy
from conan import ConanFile
class TestConan(ConanFile):
name = "libb"
version = "1.0"
requires = "liba/1.0"
def package_info(self):
self.cpp_info.includedirs = ["includeb"]
self.cpp_info.libdirs = ["libb"]
self.cpp_info.bindirs = ["binb"]
""")
c.save({"conanfile.py": conanfile})
c.run("create .")
conanfile = textwrap.dedent(f"""
from conan import ConanFile
from conan.tools.cmake import CMake
class PkgConan(ConanFile):
requires = "libb/1.0"
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeDeps"
def build(self):
cmake = CMake(self)
cmake.configure()
""")
c.save({"conanfile.py": conanfile}, clean_first=True)
c.run(f"install . -c tools.cmake.cmakedeps:new={new_value}")
cmake_paths = c.load("conan_cmakedeps_paths.cmake")
cmake_paths.replace("\\", "/")
assert re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH \".*/libb.*/p/binb\"\)", cmake_paths)
assert not re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH /bina\"", cmake_paths)
assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/libb.*/p/libb\" \".*/liba.*/p/liba\"\)", cmake_paths)
assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/libb.*/p/includeb\" \".*/liba.*/p/includea\"\)", cmake_paths)
Loading