Skip to content
Open
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
70 changes: 59 additions & 11 deletions conan/tools/cmake/cmakedeps2/cmakedeps.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import os
import re
import shutil
import textwrap

from jinja2 import Template

from conan.api.output import Color, ConanOutput
from conan.errors import ConanException
from conan.internal import check_duplicated_generator
from conan.internal.api.install.generators import relativize_path
from conan.internal.model.dependencies import get_transitive_requires
from conan.internal.model.conanfile_interface import ConanFileInterface
from conan.internal.model.conan_file import ConanFile
from conan.internal.model.cpp_info import CppInfo
from conan.internal.model.requires import Requirement
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
Expand All @@ -20,6 +24,7 @@
FIND_MODE_CONFIG = "config"
FIND_MODE_NONE = "none"
FIND_MODE_BOTH = "both"
FIND_MODE_COPY = "copy"


class CMakeDeps2:
Expand Down Expand Up @@ -68,19 +73,62 @@ def _content(self):

if require.direct:
direct_deps.append((require, dep))
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()
if cmake_find_mode == FIND_MODE_COPY:
ConanOutput(self._conanfile.ref).info("Copying project provided CMake configuration files...")
config_filename = ConfigTemplate2(self, dep).filename
version_filename = ConfigVersionTemplate2(self, dep).filename
targets_filename = TargetsTemplate2(self, dep).filename
target_configuration_filename = self._project_provided_target_configuration_filename(dep)
# The version and config files are the ones that matter and are typically different when provided by the project.
ret[config_filename] = self._read_project_provided_cmake_file(config_filename, dep)
ret[version_filename] = self._read_project_provided_cmake_file(version_filename, dep)
ret[targets_filename] = self._read_project_provided_cmake_file(targets_filename, dep)
ret[target_configuration_filename] = self._read_project_provided_cmake_file(target_configuration_filename, dep)
deduced_cpp_info : CppInfo = dep.cpp_info.deduce_full_cpp_info(self._conanfile)
Comment on lines +83 to +87
Copy link
Member

Choose a reason for hiding this comment

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

The problem with this is that the files that projects provide might have different names, the "Targets" files is mostly a convention, but not mandatory. This will not work in many cases, and it is necessary a more general approach

Copy link
Author

Choose a reason for hiding this comment

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

Should we provide the ability to pass in the corresponding names of these files then? I tried to use mostly existing code to generate these filenames.

Or just leverage the existing conan variables such as cmake_file_name etc?

# Generate CMake files have their ${IMPORT_PREFIX} tied to the parent folder relative to the location of the file, so we need to copy over our headers and libs
# so that they load correctly.
ConanOutput(self._conanfile.ref).info(f"Installing libs found in {deduced_cpp_info.location} and includes in {deduced_cpp_info.includedirs} to IMPORT_PREFIX location")
lib_folder = os.path.join("..", "lib", self.configuration)
os.makedirs(lib_folder, exist_ok=True)
shutil.copy(deduced_cpp_info.location, lib_folder)
shutil.copytree(deduced_cpp_info.includedir, os.path.join("..", "include"), dirs_exist_ok=True)
# Copy the component libraries.
for name, lib in deduced_cpp_info.components.items():
shutil.copy(lib.location, lib_folder)
Comment on lines +90 to +97
Copy link
Member

Choose a reason for hiding this comment

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

A generator shouldn't be copying artifacts like headers or libs around, that is more the responsibility of a deployer.

Copy link
Author

Choose a reason for hiding this comment

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

Would you recommend moving this somewhere else in the code base, or just not doing this step entirely? This is how I attempted to make the files relocatable using the ${_IMPORT_PREFIX} which points to parent directory of the folder.

Another alternative might be to edit this path so that include/lib references for each target point back to the conan package.

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

self._print_help(direct_deps)
return ret

def _project_provided_target_configuration_filename(self, dep):
# This function produces a slightly different Target-<config>.cmake filename
# than the template here. When these files are generated in a project using
# the expected EXPORT keywords, there is no dash between the project name and
# Targets. I will not change this in the conan template class, as that could break
# user's packages.
return f"{self.get_cmake_filename(dep)}Targets-{self.configuration.lower()}.cmake"

def _read_project_provided_cmake_file(self, cmake_file, dep):
# Just return first hit. There won't be multiple matching files in a given package.
for dirpath, dirnames, filenames in os.walk(dep.package_folder):
for filename in filenames:
if cmake_file in filename:
ConanOutput(self._conanfile.ref).info(f"Match found: {dirpath + filename}")
with open(os.path.join(dirpath, filename), 'r') as file:
return file.read()

# If we got here, the file was not found.
raise FileNotFoundError(f"The file {cmake_file} was not available to be copied.")

def _print_help(self, direct_deps):
if direct_deps:
msg = ["CMakeDeps necessary find_package() and targets for your CMakeLists.txt"]
Expand Down Expand Up @@ -147,7 +195,7 @@ def get_cmake_filename(self, dep, module_mode=None):
def _get_find_mode(self, dep):
"""
:param dep: requirement
:return: "none" or "config" or "module" or "both" or "config" when not set
:return: "none" or "config" or "module" or "both" or "config" or "copy" when not set
"""
tmp = self.get_property("cmake_find_mode", dep)
if tmp is None:
Expand Down