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

New CMakeDeps #16964

Merged
merged 56 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
37f9e52
cpp_info.exes
memsharded Sep 9, 2024
b28cb9c
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Sep 9, 2024
ea5ab33
first prototype of cpp_info.exes
memsharded Sep 9, 2024
5e35628
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 7, 2024
b907d72
wip
memsharded Oct 7, 2024
867759b
wip CmakeDeps2
memsharded Oct 7, 2024
299310b
wip
memsharded Oct 9, 2024
e83b662
wip
memsharded Oct 9, 2024
7a1d913
wip
memsharded Oct 9, 2024
90c2380
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 10, 2024
357f6cc
wip
memsharded Oct 10, 2024
c3ef1e6
wip
memsharded Oct 10, 2024
29aa374
wip
memsharded Oct 10, 2024
262c1f2
wip
memsharded Oct 10, 2024
77a4445
wip
memsharded Oct 10, 2024
daa1f97
working
memsharded Oct 11, 2024
4fb4c74
wip
memsharded Oct 11, 2024
5c112f7
wip
memsharded Oct 11, 2024
859dbea
wip
memsharded Oct 13, 2024
4cc9578
exploding multi-libs into components
memsharded Oct 13, 2024
5ee6f95
wip
memsharded Oct 13, 2024
64a4a59
wip
memsharded Oct 13, 2024
8417fe0
fix tests
memsharded Oct 13, 2024
f87927b
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 14, 2024
c6913b8
wip
memsharded Oct 14, 2024
dcd2356
wip
memsharded Oct 15, 2024
da042e5
wip
memsharded Oct 15, 2024
f8d0dd4
fix py38 tests type annotations
memsharded Oct 15, 2024
0e663c8
wip
memsharded Oct 15, 2024
86be142
wip
memsharded Oct 16, 2024
239f2dd
fix tests
memsharded Oct 16, 2024
b68ed11
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 16, 2024
1fd4f0f
fix test
memsharded Oct 16, 2024
09aceb6
consider using dep configuration instead of consumer one
memsharded Oct 16, 2024
ee8d31c
working with dependency configuration instead of base configuration
memsharded Oct 17, 2024
60f8b18
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 17, 2024
3e4b483
wip
memsharded Oct 17, 2024
3c6eb34
handling headers=False trait
memsharded Oct 17, 2024
1b27c9e
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 17, 2024
82b9a4d
fallback to consumer config for interface packages
memsharded Oct 17, 2024
e8e34d7
wip try-compile
memsharded Oct 18, 2024
583da82
added conan_cmakedeps_paths.cmake
memsharded Oct 19, 2024
2837f3c
wip
memsharded Oct 19, 2024
1bdc53f
fix tests
memsharded Oct 19, 2024
fc29300
only in-package if existing
memsharded Oct 20, 2024
9f49899
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 21, 2024
8faf457
added CONAN_RUNTIME_LIB_DIRS
memsharded Oct 21, 2024
db8db4e
review, using cmakedeps overriden properties
memsharded Oct 22, 2024
d0d9fb3
wip
memsharded Oct 22, 2024
f3f49f6
added link-languages feature
memsharded Oct 23, 2024
08e39d2
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 23, 2024
5fc1b48
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 25, 2024
30c3799
review
memsharded Oct 25, 2024
6c23b5f
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 25, 2024
a2b4be9
Merge branch 'develop2' into feature/cpp_info_exes
memsharded Oct 28, 2024
a97d81a
experimental warnings
memsharded Oct 28, 2024
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
2 changes: 1 addition & 1 deletion conan/cps/cps.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def from_cpp_info(cpp_info, pkg_type, libname=None):
cps_comp.type = CPSComponentType.INTERFACE
return cps_comp

cpp_info.deduce_cps(pkg_type)
cpp_info.deduce_locations(pkg_type)
cps_comp.type = CPSComponentType.from_conan(cpp_info.type)
cps_comp.location = cpp_info.location
cps_comp.link_location = cpp_info.link_location
Expand Down
12 changes: 11 additions & 1 deletion conan/tools/cmake/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
from conan.tools.cmake.toolchain.toolchain import CMakeToolchain
from conan.tools.cmake.cmake import CMake
from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps
from conan.tools.cmake.layout import cmake_layout


def CMakeDeps(conanfile): # noqa
if conanfile.conf.get("tools.cmake.cmakedeps:new", choices=["will_break_next"]):
from conan.tools.cmake.cmakedeps2.cmakedeps import CMakeDeps2
conanfile.output.warning("Using the new CMakeDeps generator, behind the "
"'tools.cmake.cmakedeps:new' gate conf. This conf will change"
"next release, breaking, so use it only for testing and dev")
return CMakeDeps2(conanfile)
from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps as _CMakeDeps
return _CMakeDeps(conanfile)
Empty file.
211 changes: 211 additions & 0 deletions conan/tools/cmake/cmakedeps2/cmakedeps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import os
import re
import textwrap

from jinja2 import Template

from conan.internal import check_duplicated_generator
from conan.tools.cmake.cmakedeps2.config import ConfigTemplate2
from conan.tools.cmake.cmakedeps2.config_version import ConfigVersionTemplate2
from conan.tools.cmake.cmakedeps2.target_configuration import TargetConfigurationTemplate2
from conan.tools.cmake.cmakedeps2.targets import TargetsTemplate2
from conan.tools.files import save
from conan.errors import ConanException
from conans.model.dependencies import get_transitive_requires
from conans.util.files import load

FIND_MODE_MODULE = "module"
FIND_MODE_CONFIG = "config"
FIND_MODE_NONE = "none"
FIND_MODE_BOTH = "both"


class CMakeDeps2:

def __init__(self, conanfile):
self._conanfile = conanfile
self.configuration = str(self._conanfile.settings.build_type)

# These are just for legacy compatibility, but not use at al
self.build_context_activated = []
self.build_context_build_modules = []
self.build_context_suffix = {}
# Enable/Disable checking if a component target exists or not
self.check_components_exist = False

self._properties = {}

def generate(self):
check_duplicated_generator(self, self._conanfile)
# Current directory is the generators_folder
generator_files = self._content()
for generator_file, content in generator_files.items():
save(self._conanfile, generator_file, content)
_PathGenerator(self, self._conanfile).generate()

def _content(self):
host_req = self._conanfile.dependencies.host
build_req = self._conanfile.dependencies.direct_build
test_req = self._conanfile.dependencies.test

# Iterate all the transitive requires
ret = {}
for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
cmake_find_mode = self.get_property("cmake_find_mode", dep)
cmake_find_mode = cmake_find_mode or FIND_MODE_CONFIG
cmake_find_mode = cmake_find_mode.lower()
if cmake_find_mode == FIND_MODE_NONE:
continue

config = ConfigTemplate2(self, dep)
ret[config.filename] = config.content()
config_version = ConfigVersionTemplate2(self, dep)
ret[config_version.filename] = config_version.content()

targets = TargetsTemplate2(self, dep)
ret[targets.filename] = targets.content()
target_configuration = TargetConfigurationTemplate2(self, dep, require)
ret[target_configuration.filename] = target_configuration.content()
return ret

def set_property(self, dep, prop, value, build_context=False):
"""
Using this method you can overwrite the :ref:`property<CMakeDeps Properties>` values set by
the Conan recipes from the consumer.

:param dep: Name of the dependency to set the :ref:`property<CMakeDeps Properties>`. For
components use the syntax: ``dep_name::component_name``.
:param prop: Name of the :ref:`property<CMakeDeps Properties>`.
:param value: Value of the property. Use ``None`` to invalidate any value set by the
upstream recipe.
:param build_context: Set to ``True`` if you want to set the property for a dependency that
belongs to the build context (``False`` by default).
"""
build_suffix = "&build" if build_context else ""
self._properties.setdefault(f"{dep}{build_suffix}", {}).update({prop: value})

def get_property(self, prop, dep, comp_name=None, check_type=None):
dep_name = dep.ref.name
build_suffix = "&build" if dep.context == "build" else ""
dep_comp = f"{str(dep_name)}::{comp_name}" if comp_name else f"{str(dep_name)}"
try:
value = self._properties[f"{dep_comp}{build_suffix}"][prop]
if check_type is not None and not isinstance(value, check_type):
raise ConanException(f'The expected type for {prop} is "{check_type.__name__}", '
f'but "{type(value).__name__}" was found')
return value
except KeyError:
# Here we are not using the cpp_info = deduce_cpp_info(dep) because it is not
# necessary for the properties
return dep.cpp_info.get_property(prop, check_type=check_type) if not comp_name \
else dep.cpp_info.components[comp_name].get_property(prop, check_type=check_type)

def get_cmake_filename(self, dep, module_mode=None):
"""Get the name of the file for the find_package(XXX)"""
# This is used by CMakeDeps to determine:
# - The filename to generate (XXX-config.cmake or FindXXX.cmake)
# - The name of the defined XXX_DIR variables
# - The name of transitive dependencies for calls to find_dependency
if module_mode and self._get_find_mode(dep) in [FIND_MODE_MODULE, FIND_MODE_BOTH]:
ret = self.get_property("cmake_module_file_name", dep)
if ret:
return ret
ret = self.get_property("cmake_file_name", dep)
return ret or dep.ref.name

def _get_find_mode(self, dep):
"""
:param dep: requirement
:return: "none" or "config" or "module" or "both" or "config" when not set
"""
tmp = self.get_property("cmake_find_mode", dep)
if tmp is None:
return "config"
return tmp.lower()

def get_transitive_requires(self, conanfile):
# Prepared to filter transitive tool-requires with visible=True
return get_transitive_requires(self._conanfile, conanfile)


class _PathGenerator:
_conan_cmakedeps_paths = "conan_cmakedeps_paths.cmake"

def __init__(self, cmakedeps, conanfile):
self._conanfile = conanfile
self._cmakedeps = cmakedeps

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 }} )
{% endif %}
""")

host_req = self._conanfile.dependencies.host
build_req = self._conanfile.dependencies.direct_build
test_req = self._conanfile.dependencies.test

# 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()):
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()

pkg_name = self._cmakedeps.get_cmake_filename(dep)
# https://cmake.org/cmake/help/v3.22/guide/using-dependencies/index.html
if cmake_find_mode == FIND_MODE_NONE:
try:
# This is irrespective of the components, it should be in the root cpp_info
# To define the location of the pkg-config.cmake file
build_dir = dep.cpp_info.builddirs[0]
except IndexError:
build_dir = dep.package_folder
pkg_folder = build_dir.replace("\\", "/") if build_dir else None
if pkg_folder:
config_file = ConfigTemplate2(self._cmakedeps, dep).filename
if os.path.isfile(os.path.join(pkg_folder, config_file)):
pkg_paths[pkg_name] = pkg_folder
continue

# If CMakeDeps generated, the folder is this one
# content.append(f'set({pkg_name}_ROOT "{gen_folder}")')
pkg_paths[pkg_name] = "${CMAKE_CURRENT_LIST_DIR}"

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

def _get_host_runtime_dirs(self):
host_runtime_dirs = {}

# Get the previous configuration
if os.path.exists(self._conan_cmakedeps_paths):
existing_toolchain = load(self._conan_cmakedeps_paths)
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)
for config, paths in matches:
host_runtime_dirs.setdefault(config, []).append(paths)

is_win = self._conanfile.settings.get_safe("os") == "Windows"
for req in self._conanfile.dependencies.host.values():
config = req.settings.get_safe("build_type", self._cmakedeps.configuration)
aggregated_cppinfo = req.cpp_info.aggregated_components()
runtime_dirs = aggregated_cppinfo.bindirs if is_win else aggregated_cppinfo.libdirs
for d in runtime_dirs:
d = d.replace("\\", "/")
existing = host_runtime_dirs.setdefault(config, [])
if d not in existing:
existing.append(d)

return ' '.join(f'"$<$<CONFIG:{c}>:{i}>"' for c, v in host_runtime_dirs.items() for i in v)
61 changes: 61 additions & 0 deletions conan/tools/cmake/cmakedeps2/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import textwrap

import jinja2
from jinja2 import Template


class ConfigTemplate2:
"""
FooConfig.cmake
foo-config.cmake
"""
def __init__(self, cmakedeps, conanfile):
self._cmakedeps = cmakedeps
self._conanfile = conanfile

def content(self):
t = Template(self._template, trim_blocks=True, lstrip_blocks=True,
undefined=jinja2.StrictUndefined)
return t.render(self._context)

@property
def filename(self):
f = self._cmakedeps.get_cmake_filename(self._conanfile)
return f"{f}-config.cmake" if f == f.lower() else f"{f}Config.cmake"

@property
def _context(self):
f = self._cmakedeps.get_cmake_filename(self._conanfile)
targets_include = f"{f}Targets.cmake"
pkg_name = self._conanfile.ref.name
build_modules_paths = self._cmakedeps.get_property("cmake_build_modules", self._conanfile,
check_type=list) or []
# FIXME: Proper escaping of paths for CMake and relativization
# FIXME: build_module_paths coming from last config only
build_modules_paths = [f.replace("\\", "/") for f in build_modules_paths]
return {"pkg_name": pkg_name,
"targets_include_file": targets_include,
"build_modules_paths": build_modules_paths}

@property
def _template(self):
return textwrap.dedent("""\
# Requires CMake > 3.15
if(${CMAKE_VERSION} VERSION_LESS "3.15")
message(FATAL_ERROR "The 'CMakeDeps' generator only works with CMake >= 3.15")
endif()

include(${CMAKE_CURRENT_LIST_DIR}/{{ targets_include_file }})

get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT isMultiConfig AND NOT CMAKE_BUILD_TYPE)
message(FATAL_ERROR "Please, set the CMAKE_BUILD_TYPE variable when calling to CMake "
"adding the '-DCMAKE_BUILD_TYPE=<build_type>' argument.")
endif()

# build_modules_paths comes from last configuration only
{% for build_module in build_modules_paths %}
message(STATUS "Conan: Including build module from '{{build_module}}'")
include("{{ build_module }}")
{% endfor %}
""")
Loading