From aae78eacb5a301374d09e902688d0e30aa07e6ad Mon Sep 17 00:00:00 2001 From: Luis Martinez de Bartolome Izquierdo Date: Wed, 1 Sep 2021 14:02:58 +0200 Subject: [PATCH] CMakeDeps/CMakeToolchain: Several improvements for open issues (#9455) * To explore, to validate * Fix names * Rename * fix tests * Fix win tests * Added tests for new cmake_find_mode * Fixing win test * opt-in for toolchain and test * review * findpackagehandlestardardargs --- conan/tools/cmake/cmakedeps/cmakedeps.py | 46 ++++++--- .../cmake/cmakedeps/templates/__init__.py | 62 ++++++------ .../tools/cmake/cmakedeps/templates/config.py | 32 +++++-- .../templates/target_configuration.py | 17 ++-- .../cmake/cmakedeps/templates/target_data.py | 20 ++-- .../cmake/cmakedeps/templates/targets.py | 17 +++- conan/tools/cmake/toolchain.py | 45 +++++++-- conan/tools/cmake/utils.py | 6 +- .../cmakedeps/test_cmakedeps_components.py | 4 +- .../test_cmakedeps_find_module_and_config.py | 94 +++++++++++++++++++ .../cmake/test_cmakedeps_custom_configs.py | 6 +- .../cmake/cmakedeps/test_cmakedeps.py | 2 +- .../test_cmakedeps_find_module_and_config.py | 63 +++++++++++++ .../toolchains/cmake/test_cmaketoolchain.py | 50 ++++++++++ .../unittests/tools/cmake/test_cmakedeps.py | 6 +- 15 files changed, 381 insertions(+), 89 deletions(-) create mode 100644 conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py create mode 100644 conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py index 9ee9319c648..2e26b657c5a 100644 --- a/conan/tools/cmake/cmakedeps/cmakedeps.py +++ b/conan/tools/cmake/cmakedeps/cmakedeps.py @@ -1,4 +1,5 @@ import os +from fnmatch import fnmatch from conan.tools._check_build_profile import check_using_build_profile from conan.tools.cmake.cmakedeps.templates.config import ConfigTemplate @@ -11,6 +12,11 @@ from conans.util.files import save +FIND_MODE_MODULE = "module" +FIND_MODE_CONFIG = "config" +FIND_MODE_NONE = "none" +FIND_MODE_BOTH = "both" + class CMakeDeps(object): def __init__(self, conanfile): @@ -75,26 +81,38 @@ def content(self): if dep.is_build_context and dep.ref.name not in self.build_context_activated: continue - if dep.new_cpp_info.get_property("skip_deps_file", "CMakeDeps"): + cmake_find_mode = dep.new_cpp_info.get_property("cmake_find_mode", "CMakeDeps") or FIND_MODE_CONFIG + cmake_find_mode = cmake_find_mode.lower() + # Skip from the requirement + if cmake_find_mode == FIND_MODE_NONE: # Skip the generation of config files for this node, it will be located externally continue + if cmake_find_mode in (FIND_MODE_CONFIG, FIND_MODE_BOTH): + self._generate_files(require, dep, ret, find_module_mode=False) + + if cmake_find_mode in (FIND_MODE_MODULE, FIND_MODE_BOTH): + self._generate_files(require, dep, ret, find_module_mode=True) + + return ret + + def _generate_files(self, require, dep, ret, find_module_mode): + if not find_module_mode: config_version = ConfigVersionTemplate(self, require, dep) ret[config_version.filename] = config_version.render() - data_target = ConfigDataTemplate(self, require, dep) - ret[data_target.filename] = data_target.render() + data_target = ConfigDataTemplate(self, require, dep, find_module_mode) + ret[data_target.filename] = data_target.render() - target_configuration = TargetConfigurationTemplate(self, require, dep) - ret[target_configuration.filename] = target_configuration.render() + target_configuration = TargetConfigurationTemplate(self, require, dep, find_module_mode) + ret[target_configuration.filename] = target_configuration.render() - targets = TargetsTemplate(self, require, dep) - ret[targets.filename] = targets.render() + targets = TargetsTemplate(self, require, dep, find_module_mode) + ret[targets.filename] = targets.render() - config = ConfigTemplate(self, require, dep) - # Check if the XXConfig.cmake exists to keep the first generated configuration - # to only include the build_modules from the first conan install. The rest of the - # file is common for the different configurations. - if not os.path.exists(config.filename): - ret[config.filename] = config.render() - return ret + config = ConfigTemplate(self, require, dep, find_module_mode) + # Check if the XXConfig.cmake exists to keep the first generated configuration + # to only include the build_modules from the first conan install. The rest of the + # file is common for the different configurations. + if not os.path.exists(config.filename): + ret[config.filename] = config.render() diff --git a/conan/tools/cmake/cmakedeps/templates/__init__.py b/conan/tools/cmake/cmakedeps/templates/__init__.py index 7bd1a70c8a4..cb72da28498 100644 --- a/conan/tools/cmake/cmakedeps/templates/__init__.py +++ b/conan/tools/cmake/cmakedeps/templates/__init__.py @@ -7,10 +7,11 @@ class CMakeDepsFileTemplate(object): - def __init__(self, cmakedeps, require, conanfile): + def __init__(self, cmakedeps, require, conanfile, find_module_mode=False): self.cmakedeps = cmakedeps self.require = require self.conanfile = conanfile + self.find_module_mode = find_module_mode @property def pkg_name(self): @@ -18,11 +19,11 @@ def pkg_name(self): @property def target_namespace(self): - return get_target_namespace(self.conanfile) + self.suffix + return self.get_target_namespace(self.conanfile) + self.suffix @property def file_name(self): - return get_file_name(self.conanfile) + self.suffix + return get_file_name(self.conanfile, self.find_module_mode) + self.suffix @property def suffix(self): @@ -74,29 +75,34 @@ def arch(self): def config_suffix(self): return "_{}".format(self.configuration.upper()) if self.configuration else "" - def get_target_namespace(self): - return get_target_namespace(self.conanfile) - def get_file_name(self): - return get_file_name(self.conanfile) - - -def get_target_namespace(req): - ret = req.new_cpp_info.get_property("cmake_target_name", "CMakeDeps") - if not ret: - ret = req.cpp_info.get_name("cmake_find_package_multi", default_name=False) - return ret or req.ref.name - - -def get_component_alias(req, comp_name): - if comp_name not in req.new_cpp_info.components: - # foo::foo might be referencing the root cppinfo - if req.ref.name == comp_name: - return get_target_namespace(req) - raise ConanException("Component '{name}::{cname}' not found in '{name}' " - "package requirement".format(name=req.ref.name, cname=comp_name)) - ret = req.new_cpp_info.components[comp_name].get_property("cmake_target_name", "CMakeDeps") - if not ret: - ret = req.cpp_info.components[comp_name].get_name("cmake_find_package_multi", - default_name=False) - return ret or comp_name + return get_file_name(self.conanfile, find_module_mode=self.find_module_mode) + + def get_target_namespace(self, req): + if self.find_module_mode: + ret = req.new_cpp_info.get_property("cmake_module_target_name", "CMakeDeps") + if ret: + return ret + + ret = req.new_cpp_info.get_property("cmake_target_name", "CMakeDeps") + if not ret: + ret = req.cpp_info.get_name("cmake_find_package_multi", default_name=False) + return ret or req.ref.name + + def get_component_alias(self, req, comp_name): + if comp_name not in req.new_cpp_info.components: + # foo::foo might be referencing the root cppinfo + if req.ref.name == comp_name: + return self.get_target_namespace(req) + raise ConanException("Component '{name}::{cname}' not found in '{name}' " + "package requirement".format(name=req.ref.name, cname=comp_name)) + if self.find_module_mode: + ret = req.new_cpp_info.components[comp_name].get_property("cmake_module_target_name", + "CMakeDeps") + if ret: + return ret + ret = req.new_cpp_info.components[comp_name].get_property("cmake_target_name", "CMakeDeps") + if not ret: + ret = req.cpp_info.components[comp_name].get_name("cmake_find_package_multi", + default_name=False) + return ret or comp_name diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py index bfd457583db..22df2cb01f4 100644 --- a/conan/tools/cmake/cmakedeps/templates/config.py +++ b/conan/tools/cmake/cmakedeps/templates/config.py @@ -14,31 +14,51 @@ class ConfigTemplate(CMakeDepsFileTemplate): @property def filename(self): - if self.file_name == self.file_name.lower(): - return "{}-config.cmake".format(self.file_name) + if self.find_module_mode: + return "Find{}.cmake".format(self.file_name) else: - return "{}Config.cmake".format(self.file_name) + if self.file_name == self.file_name.lower(): + return "{}-config.cmake".format(self.file_name) + else: + return "{}Config.cmake".format(self.file_name) @property def context(self): - return {"file_name": self.file_name, + targets_include = "" if not self.find_module_mode else "module-" + targets_include += "{}Targets.cmake".format(self.file_name) + return {"is_module": self.find_module_mode, + "version": self.conanfile.ref.version, + "file_name": self.file_name, "pkg_name": self.pkg_name, "config_suffix": self.config_suffix, "target_namespace": self.target_namespace, - "check_components_exist": self.cmakedeps.check_components_exist} + "check_components_exist": self.cmakedeps.check_components_exist, + "targets_include_file": targets_include} @property def template(self): return textwrap.dedent("""\ ########## MACROS ########################################################################### ############################################################################################# + # Requires CMake > 3.15 if(${CMAKE_VERSION} VERSION_LESS "3.15") message(FATAL_ERROR "The 'CMakeDeps' generator only works with CMake >= 3.15") endif() + {% if is_module %} + include(FindPackageHandleStandardArgs) + set({{ pkg_name }}_FOUND 1) + set({{ pkg_name }}_VERSION "{{ version }}") + + find_package_handle_standard_args({{ pkg_name }} + REQUIRED_VARS {{ pkg_name }}_VERSION + VERSION_VAR {{ pkg_name }}_VERSION) + mark_as_advanced({{ pkg_name }}_FOUND {{ pkg_name }}_VERSION) + {% endif %} + include(${CMAKE_CURRENT_LIST_DIR}/cmakedeps_macros.cmake) - include(${CMAKE_CURRENT_LIST_DIR}/{{ file_name }}Targets.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/{{ targets_include_file }}) include(CMakeFindDependencyMacro) foreach(_DEPENDENCY {{ '${' + pkg_name + '_FIND_DEPENDENCY_NAMES' + '}' }} ) diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py index a3514eb240b..6e427d89335 100644 --- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py +++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py @@ -1,7 +1,6 @@ import textwrap -from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate, get_component_alias, \ - get_target_namespace +from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate """ @@ -14,8 +13,9 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate): @property def filename(self): - return "{}Target-{}.cmake".format(self.file_name, - self.cmakedeps.configuration.lower()) + name = "" if not self.find_module_mode else "module-" + name += "{}-Target-{}.cmake".format(self.file_name, self.cmakedeps.configuration.lower()) + return name @property def context(self): @@ -147,7 +147,7 @@ def get_required_components_names(self): ret = [] sorted_comps = self.conanfile.new_cpp_info.get_sorted_components() for comp_name, comp in sorted_comps.items(): - ret.append(get_component_alias(self.conanfile, comp_name)) + ret.append(self.get_component_alias(self.conanfile, comp_name)) ret.reverse() return ret @@ -163,16 +163,15 @@ def get_deps_targets_names(self): for dep_name, component_name in self.conanfile.new_cpp_info.required_components: if not dep_name: # Internal dep (no another component) - dep_name = get_target_namespace(self.conanfile) req = self.conanfile else: req = self.conanfile.dependencies.host[dep_name] - dep_name = get_target_namespace(req) - component_name = get_component_alias(req, component_name) + dep_name = self.get_target_namespace(req) + component_name = self.get_component_alias(req, component_name) ret.append("{}::{}".format(dep_name, component_name)) elif self.conanfile.dependencies.direct_host: # Regular external "conanfile.requires" declared, not cpp_info requires - ret = ["{p}::{p}".format(p=get_target_namespace(r)) + ret = ["{p}::{p}".format(p=self.get_target_namespace(r)) for r in self.conanfile.dependencies.direct_host.values()] return ret diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py index 156fecb9b02..b10c50720a5 100644 --- a/conan/tools/cmake/cmakedeps/templates/target_data.py +++ b/conan/tools/cmake/cmakedeps/templates/target_data.py @@ -1,8 +1,7 @@ import os import textwrap -from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate, get_component_alias, \ - get_target_namespace +from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate from conan.tools.cmake.utils import get_file_name """ @@ -16,7 +15,8 @@ class ConfigDataTemplate(CMakeDepsFileTemplate): @property def filename(self): - data_fname = "{}-{}".format(self.file_name, self.configuration.lower()) + data_fname = "" if not self.find_module_mode else "module-" + data_fname += "{}-{}".format(self.file_name, self.configuration.lower()) if self.arch: data_fname += "-{}".format(self.arch) data_fname += "-data.cmake" @@ -122,14 +122,14 @@ def get_required_components_cpp(self): if "::" in require: # Points to a component of a different package pkg, cmp_name = require.split("::") req = self.conanfile.dependencies.direct_host[pkg] - public_comp_deps.append("{}::{}".format(get_target_namespace(req), - get_component_alias(req, cmp_name))) + public_comp_deps.append("{}::{}".format(self.get_target_namespace(req), + self.get_component_alias(req, cmp_name))) else: # Points to a component of same package public_comp_deps.append("{}::{}".format(self.target_namespace, - get_component_alias(self.conanfile, - require))) + self.get_component_alias(self.conanfile, + require))) deps_cpp_cmake.public_deps = " ".join(public_comp_deps) - component_rename = get_component_alias(self.conanfile, comp_name) + component_rename = self.get_component_alias(self.conanfile, comp_name) ret.append((component_rename, deps_cpp_cmake)) ret.reverse() return ret @@ -143,9 +143,9 @@ def _get_dependency_filenames(self): for dep_name, _ in self.conanfile.new_cpp_info.required_components: if dep_name and dep_name not in ret: # External dep req = direct_host[dep_name] - ret.append(get_file_name(req)) + ret.append(get_file_name(req, self.find_module_mode)) elif direct_host: - ret = [get_file_name(r) for r in direct_host.values()] + ret = [get_file_name(r, self.find_module_mode) for r in direct_host.values()] return ret diff --git a/conan/tools/cmake/cmakedeps/templates/targets.py b/conan/tools/cmake/cmakedeps/templates/targets.py index 59bdd3e1c16..118cb572f94 100644 --- a/conan/tools/cmake/cmakedeps/templates/targets.py +++ b/conan/tools/cmake/cmakedeps/templates/targets.py @@ -13,13 +13,22 @@ class TargetsTemplate(CMakeDepsFileTemplate): @property def filename(self): - return "{}Targets.cmake".format(self.file_name) + name = "" if not self.find_module_mode else "module-" + name += self.file_name + "Targets.cmake" + return name @property def context(self): + data_pattern = "${_DIR}/" if not self.find_module_mode else "${_DIR}/module-" + data_pattern += "{}-*-data.cmake".format(self.file_name) + + target_pattern = "" if not self.find_module_mode else "module-" + target_pattern += "{}-Target-*.cmake".format(self.file_name) + ret = {"pkg_name": self.pkg_name, "target_namespace": self.target_namespace, - "file_name": self.file_name} + "data_pattern": data_pattern, + "target_pattern": target_pattern} return ret @property @@ -27,7 +36,7 @@ def template(self): return textwrap.dedent("""\ # Load the debug and release variables get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) - file(GLOB DATA_FILES "${_DIR}/{{ file_name }}-*-data.cmake") + file(GLOB DATA_FILES "{{data_pattern}}") foreach(f ${DATA_FILES}) include(${f}) @@ -48,7 +57,7 @@ def template(self): # Load the debug and release library finders get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) - file(GLOB CONFIG_FILES "${_DIR}/{{ file_name }}Target-*.cmake") + file(GLOB CONFIG_FILES "${_DIR}/{{ target_pattern }}") foreach(f ${CONFIG_FILES}) include(${f}) diff --git a/conan/tools/cmake/toolchain.py b/conan/tools/cmake/toolchain.py index ebcfc40239a..0c2b81b3ee2 100644 --- a/conan/tools/cmake/toolchain.py +++ b/conan/tools/cmake/toolchain.py @@ -68,7 +68,16 @@ def get_rendered_content(self): context = self.values if context is None: return - return Template(self.template, trim_blocks=True, lstrip_blocks=True).render(**context) + + def cmake_value(value): + if isinstance(value, bool): + return "ON" if value else "OFF" + else: + return '"{}"'.format(value) + + template = Template(self.template, trim_blocks=True, lstrip_blocks=True) + template.environment.filters["cmake_value"] = cmake_value + return template.render(**context) def context(self): return {} @@ -387,10 +396,10 @@ class FindConfigFiles(Block): {% endif %} # To support the generators based on find_package() {% if cmake_module_path %} - set(CMAKE_MODULE_PATH "{{ cmake_module_path }}" ${CMAKE_MODULE_PATH}) + set(CMAKE_MODULE_PATH {{ cmake_module_path }} ${CMAKE_MODULE_PATH}) {% endif %} {% if cmake_prefix_path %} - set(CMAKE_PREFIX_PATH "{{ cmake_prefix_path }}" ${CMAKE_PREFIX_PATH}) + set(CMAKE_PREFIX_PATH {{ cmake_prefix_path }} ${CMAKE_PREFIX_PATH}) {% endif %} {% if android_prefix_path %} set(CMAKE_FIND_ROOT_PATH {{ android_prefix_path }} ${CMAKE_FIND_ROOT_PATH}) @@ -416,11 +425,28 @@ def context(self): host_req = self._conanfile.dependencies.host.values() find_names_needed = os_ in ('iOS', "watchOS", "tvOS") - find_names = [get_file_name(req) for req in host_req] if find_names_needed else [] + find_names = [get_file_name(req) + for req in host_req] if find_names_needed else [] + + # Read the buildirs + build_paths = [] + for req in host_req: + cppinfo = req.new_cpp_info.copy() + cppinfo.aggregate_components() + build_paths.extend([os.path.join(req.package_folder, + p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')) + for p in cppinfo.builddirs]) + + if self._toolchain.find_builddirs: + build_paths = " ".join(['"{}"'.format(b.replace('\\', '/') + .replace('$', '\\$') + .replace('"', '\\"')) for b in build_paths]) + else: + build_paths = "" return {"find_package_prefer_config": find_package_prefer_config, - "cmake_prefix_path": "${CMAKE_CURRENT_LIST_DIR}", - "cmake_module_path": "${CMAKE_CURRENT_LIST_DIR}", + "cmake_prefix_path": "${CMAKE_CURRENT_LIST_DIR} " + build_paths, + "cmake_module_path": "${CMAKE_CURRENT_LIST_DIR} " + build_paths, "android_prefix_path": android_prefix, "find_names": find_names, "generators_folder": "${CMAKE_CURRENT_LIST_DIR}"} @@ -674,7 +700,7 @@ class CMakeToolchain(object): # Variables {% for it, value in variables.items() %} - set({{ it }} "{{ value }}" CACHE STRING "Variable {{ it }} conan-toolchain defined") + set({{ it }} {{ value|cmake_value }} CACHE STRING "Variable {{ it }} conan-toolchain defined") {% endfor %} # Variables per configuration {{ iterate_configs(variables_config, action='set') }} @@ -711,6 +737,9 @@ def __init__(self, conanfile, generator=None): ("rpath", SkipRPath), ("shared", SharedLibBock)]) + # Set the CMAKE_MODULE_PATH and CMAKE_PREFIX_PATH to the deps .builddirs + self.find_builddirs = True + check_using_build_profile(self._conanfile) def _context(self): @@ -723,7 +752,7 @@ def _context(self): "variables_config": self.variables.configuration_types, "preprocessor_definitions": self.preprocessor_definitions, "preprocessor_definitions_config": self.preprocessor_definitions.configuration_types, - "conan_blocks": blocks, + "conan_blocks": blocks } return ctxt_toolchain diff --git a/conan/tools/cmake/utils.py b/conan/tools/cmake/utils.py index 4f4e809f40f..2cab4a96654 100644 --- a/conan/tools/cmake/utils.py +++ b/conan/tools/cmake/utils.py @@ -4,10 +4,14 @@ def is_multi_configuration(generator): return "Visual" in generator or "Xcode" in generator or "Multi-Config" in generator -def get_file_name(conanfile): +def get_file_name(conanfile, find_module_mode=False): """Get the name of the file for the find_package(XXX)""" # This is used by the CMakeToolchain to adjust the XXX_DIR variables and the CMakeDeps. Both # to know the file name that will have the XXX-config.cmake files. + if find_module_mode: + ret = conanfile.new_cpp_info.get_property("cmake_module_file_name", "CMakeDeps") + if ret: + return ret ret = conanfile.new_cpp_info.get_property("cmake_file_name", "CMakeDeps") if not ret: ret = conanfile.cpp_info.get_filename("cmake_find_package_multi", default_name=False) diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py index bb39cc5e5fb..a3ec6c303fd 100644 --- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py +++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py @@ -61,7 +61,7 @@ def test_cmakedeps_app(self): t = TestClient(cache_folder=self.cache_folder) t.save({'conanfile.py': self.app}) t.run("install . -g CMakeDeps") - config = t.load("middleTarget-release.cmake") + config = t.load("middle-Target-release.cmake") self.assertIn('top::cmp1', config) self.assertNotIn("top::top", config) @@ -76,7 +76,7 @@ def test_cmakedeps_multi(self): content = t.load('middle-config.cmake') self.assertIn("find_dependency(${_DEPENDENCY} REQUIRED NO_MODULE)", content) - content = t.load('middleTarget-release.cmake') + content = t.load('middle-Target-release.cmake') self.assertNotIn("top::top", content) self.assertNotIn("top::cmp2", content) self.assertIn("top::cmp1", content) diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py new file mode 100644 index 00000000000..cb16621ba57 --- /dev/null +++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py @@ -0,0 +1,94 @@ +import textwrap + +import pytest + +from conans.test.assets.cmake import gen_cmakelists +from conans.test.assets.genconanfile import GenConanfile +from conans.test.assets.sources import gen_function_cpp, gen_function_h +from conans.test.utils.tools import TestClient + + +@pytest.fixture(scope="module") +def client(): + t = TestClient() + cpp = gen_function_cpp(name="mydep") + h = gen_function_h(name="mydep") + cmake = gen_cmakelists(libname="mydep", libsources=["mydep.cpp"]) + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile + from conan.tools.cmake import CMake + + class Conan(ConanFile): + name = "mydep" + version = "1.0" + settings = "os", "arch", "compiler", "build_type" + exports_sources = "*.cpp", "*.h", "CMakeLists.txt" + generators = "CMakeToolchain" + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + self.copy("*.h", dst="include") + self.copy("*.lib", dst="lib", keep_path=False) + self.copy("*.dll", dst="bin", keep_path=False) + self.copy("*.dylib*", dst="lib", keep_path=False) + self.copy("*.so", dst="lib", keep_path=False) + self.copy("*.a", dst="lib", keep_path=False) + + def package_info(self): + + self.cpp_info.set_property("cmake_find_mode", "both") + + self.cpp_info.set_property("cmake_file_name", "MyDep") + self.cpp_info.set_property("cmake_target_name", "MyDepTarget") + + self.cpp_info.set_property("cmake_module_file_name", "mi_dependencia") + self.cpp_info.set_property("cmake_module_target_name", "mi_dependencia_target") + + self.cpp_info.components["crispin"].libs = ["mydep"] + self.cpp_info.components["crispin"].set_property("cmake_target_name", "MyCrispinTarget") + self.cpp_info.components["crispin"].set_property("cmake_module_target_name", "mi_crispin_target") + """) + + t.save({"conanfile.py": conanfile, + "mydep.cpp": cpp, + "mydep.h": h, + "CMakeLists.txt": cmake}) + + t.run("create .") + return t + + +@pytest.mark.tool_cmake +def test_reuse_with_modules_and_config(client): + cpp = gen_function_cpp(name="main") + cmake = """ + set(CMAKE_CXX_COMPILER_WORKS 1) + set(CMAKE_CXX_ABI_COMPILED 1) + set(CMAKE_C_COMPILER_WORKS 1) + set(CMAKE_C_ABI_COMPILED 1) + + cmake_minimum_required(VERSION 3.15) + project(project CXX) + + add_executable(myapp main.cpp) + find_package(MyDep) # This one will find the config + target_link_libraries(myapp MyDepTarget::MyCrispinTarget) + + add_executable(myapp2 main.cpp) + find_package(mi_dependencia) # This one will find the module + target_link_libraries(myapp2 mi_dependencia_target::mi_crispin_target) + + """ + conanfile = GenConanfile().with_name("myapp")\ + .with_cmake_build().with_exports_sources("*.cpp", "*.txt").with_require("mydep/1.0") + client.save({"conanfile.py": conanfile, + "main.cpp": cpp, + "CMakeLists.txt": cmake}) + + client.run("install . -if=install") + client.run("build . -if=install") diff --git a/conans/test/functional/toolchains/cmake/test_cmakedeps_custom_configs.py b/conans/test/functional/toolchains/cmake/test_cmakedeps_custom_configs.py index 8ac644066c7..7ea78608fe5 100644 --- a/conans/test/functional/toolchains/cmake/test_cmakedeps_custom_configs.py +++ b/conans/test/functional/toolchains/cmake/test_cmakedeps_custom_configs.py @@ -84,9 +84,9 @@ def test_generator_multi(self): self.client.run("install .. %s -o hello:shared=True" % settings) self.client.run("install .. %s -o hello:shared=False" % settings) self.assertTrue(os.path.isfile(os.path.join(self.client.current_folder, - "helloTarget-releaseshared.cmake"))) + "hello-Target-releaseshared.cmake"))) self.assertTrue(os.path.isfile(os.path.join(self.client.current_folder, - "helloTarget-release.cmake"))) + "hello-Target-release.cmake"))) self.client.run_command('cmake .. -G "Visual Studio 15 Win64"') self.client.run_command('cmake --build . --config Release') @@ -177,7 +177,7 @@ def test_generator_multi(self): with self.client.chdir(build_directory): self.client.run("install .. %s" % settings) self.assertTrue(os.path.isfile(os.path.join(self.client.current_folder, - "helloTarget-myrelease.cmake"))) + "hello-Target-myrelease.cmake"))) self.client.run_command('cmake .. -G "Visual Studio 15 Win64"') self.client.run_command('cmake --build . --config MyRelease') diff --git a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py index e2656d034bb..2da2ef31b35 100644 --- a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py +++ b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py @@ -16,7 +16,7 @@ def test_package_from_system(): .with_settings("os", "arch", "build_type", "compiler")) dep2 += """ def package_info(self): - self.cpp_info.set_property("skip_deps_file", True) + self.cpp_info.set_property("cmake_find_mode", "None") self.cpp_info.set_property("cmake_file_name", "custom_dep2") """ diff --git a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py new file mode 100644 index 00000000000..6f4257fc77f --- /dev/null +++ b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps_find_module_and_config.py @@ -0,0 +1,63 @@ +import os +import textwrap + +import pytest + +from conan.tools.cmake.cmakedeps.cmakedeps import FIND_MODE_CONFIG, FIND_MODE_MODULE, FIND_MODE_BOTH, \ + FIND_MODE_NONE +from conans.test.assets.genconanfile import GenConanfile +from conans.test.utils.tools import TestClient + + +@pytest.mark.parametrize("cmake_find_mode", [FIND_MODE_CONFIG, FIND_MODE_MODULE, + FIND_MODE_BOTH, FIND_MODE_NONE, None]) +def test_reuse_with_modules_and_config(cmake_find_mode): + t = TestClient() + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile + + class Conan(ConanFile): + name = "mydep" + version = "1.0" + settings = "os", "arch", "compiler", "build_type" + + def package_info(self): + {} + + """) + + if cmake_find_mode is not None: + s = 'self.cpp_info.set_property("cmake_find_mode", "{}")'.format(cmake_find_mode) + conanfile = conanfile.format(s) + t.save({"conanfile.py": conanfile}) + t.run("create .") + + conanfile = GenConanfile().with_name("myapp").with_require("mydep/1.0")\ + .with_generator("CMakeDeps")\ + .with_settings("build_type", "os", "arch", "compiler") + t.save({"conanfile.py": conanfile}) + + t.run("install . -if=install") + + ifolder = os.path.join(t.current_folder, "install") + + def exists_config(ifolder): + return os.path.exists(os.path.join(ifolder, "mydep-config.cmake")) + + def exists_module(ifolder): + return os.path.exists(os.path.join(ifolder, "Findmydep.cmake")) + + if cmake_find_mode == FIND_MODE_CONFIG or cmake_find_mode is None: + # None is default "config" + assert exists_config(ifolder) + assert not exists_module(ifolder) + elif cmake_find_mode == FIND_MODE_MODULE: + assert not exists_config(ifolder) + assert exists_module(ifolder) + elif cmake_find_mode == FIND_MODE_BOTH: + assert exists_config(ifolder) + assert exists_module(ifolder) + elif cmake_find_mode == FIND_MODE_NONE: + assert not exists_config(ifolder) + assert not exists_module(ifolder) diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py index 8e674005f45..95aec16afea 100644 --- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py +++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py @@ -1,4 +1,7 @@ import textwrap +import os + +import pytest from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient @@ -188,3 +191,50 @@ def test_cross_build_conf(): assert "set(CMAKE_SYSTEM_NAME Custom)" in toolchain assert "set(CMAKE_SYSTEM_VERSION 42)" in toolchain assert "set(CMAKE_SYSTEM_PROCESSOR myarm)" in toolchain + + +@pytest.mark.parametrize("find_builddir", [True, False, None]) +def test_find_builddirs(find_builddir): + client = TestClient() + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile + from conan.tools.cmake import CMakeToolchain + + class Conan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + + def package_info(self): + self.cpp_info.builddirs = ["/path/to/builddir"] + """) + client.save({"conanfile.py": conanfile}) + client.run("create . dep/1.0@") + + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile + from conan.tools.cmake import CMakeToolchain + + class Conan(ConanFile): + name = "mydep" + version = "1.0" + settings = "os", "arch", "compiler", "build_type" + requires = "dep/1.0@" + + def generate(self): + cmake = CMakeToolchain(self) + {} + cmake.generate() + """) + + if find_builddir is not None: + conanfile = conanfile.format('cmake.find_builddirs = {}'.format(str(find_builddir))) + + client.save({"conanfile.py": conanfile}) + client.run("install . ") + with open(os.path.join(client.current_folder, "conan_toolchain.cmake")) as f: + contents = f.read() + if find_builddir is True or find_builddir is None: + assert "/path/to/builddir" in contents + else: + assert "/path/to/builddir" not in contents diff --git a/conans/test/unittests/tools/cmake/test_cmakedeps.py b/conans/test/unittests/tools/cmake/test_cmakedeps.py index c25d127d43f..ce05d2136bd 100644 --- a/conans/test/unittests/tools/cmake/test_cmakedeps.py +++ b/conans/test/unittests/tools/cmake/test_cmakedeps.py @@ -45,7 +45,7 @@ def test_cpp_info_name_cmakedeps(using_properties): cmakedeps = CMakeDeps(conanfile) files = cmakedeps.content - assert "TARGET MySuperPkg1::MySuperPkg1" in files["ComplexFileName1Target-release.cmake"] + assert "TARGET MySuperPkg1::MySuperPkg1" in files["ComplexFileName1-Target-release.cmake"] assert 'set(OriginalDepName_INCLUDE_DIRS_RELEASE ' \ '"${OriginalDepName_PACKAGE_FOLDER_RELEASE}/include")' \ in files["ComplexFileName1-release-x86-data.cmake"] @@ -87,7 +87,7 @@ def test_cpp_info_name_cmakedeps_components(using_properties): cmakedeps = CMakeDeps(conanfile) files = cmakedeps.content - assert "TARGET GlobakPkgName1::MySuperPkg1" in files["ComplexFileName1Target-debug.cmake"] + assert "TARGET GlobakPkgName1::MySuperPkg1" in files["ComplexFileName1-Target-debug.cmake"] assert 'set(OriginalDepName_INCLUDE_DIRS_DEBUG ' \ '"${OriginalDepName_PACKAGE_FOLDER_DEBUG}/include")' \ in files["ComplexFileName1-debug-x64-data.cmake"] @@ -165,7 +165,7 @@ def test_component_name_same_package(): cmakedeps = CMakeDeps(conanfile) files = cmakedeps.content - target_cmake = files["mypkgTarget-release.cmake"] + target_cmake = files["mypkg-Target-release.cmake"] assert "$<$:${mypkg_mypkg_INCLUDE_DIRS_RELEASE}> APPEND)" in target_cmake data_cmake = files["mypkg-release-x86-data.cmake"]