diff --git a/conans/client/generators/cmake.py b/conans/client/generators/cmake.py index 0165ad84f67..fdd1ec6e28e 100644 --- a/conans/client/generators/cmake.py +++ b/conans/client/generators/cmake.py @@ -65,6 +65,8 @@ def join_defines(values, prefix=""): self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags) self.rootpath = join_paths([cpp_info.rootpath]) + self.build_modules_paths = join_paths([path for path in cpp_info.build_modules_paths if + path.endswith(".cmake")]) class CMakeGenerator(Generator): diff --git a/conans/client/generators/cmake_common.py b/conans/client/generators/cmake_common.py index c9982d780e6..b4e16573431 100644 --- a/conans/client/generators/cmake_common.py +++ b/conans/client/generators/cmake_common.py @@ -10,6 +10,7 @@ set(CONAN_PKG_LIBS_{dep}{build_type} {deps.libs}) set(CONAN_SYSTEM_LIBS_{dep}{build_type} {deps.system_libs}) set(CONAN_DEFINES_{dep}{build_type} {deps.defines}) +set(CONAN_BUILD_MODULES_PATHS_{dep}{build_type} {deps.build_modules_paths}) # COMPILE_DEFINITIONS are equal to CONAN_DEFINES without -D, for targets set(CONAN_COMPILE_DEFINITIONS_{dep}{build_type} {deps.compile_definitions}) @@ -88,6 +89,7 @@ def cmake_dependencies(dependencies, build_type=""): set(CONAN_LIBS{build_type} {deps.libs_system_frameworks} ${{CONAN_LIBS{build_type}}}) set(CONAN_SYSTEM_LIBS{build_type} {deps.system_libs} ${{CONAN_SYSTEM_LIBS{build_type}}}) set(CONAN_DEFINES{build_type} {deps.defines} ${{CONAN_DEFINES{build_type}}}) +set(CONAN_BUILD_MODULES_PATHS{build_type} {deps.build_modules_paths} ${{CONAN_BUILD_MODULES_PATHS{build_type}}}) set(CONAN_CMAKE_MODULE_PATH{build_type} {deps.build_paths} ${{CONAN_CMAKE_MODULE_PATH{build_type}}}) set(CONAN_CXX_FLAGS{build_type} "{deps.cxxflags} ${{CONAN_CXX_FLAGS{build_type}}}") @@ -555,6 +557,24 @@ def generate_targets_section(dependencies): endforeach() endif() endmacro() + +macro(conan_include_build_modules) + if(CMAKE_BUILD_TYPE) + if(${CMAKE_BUILD_TYPE} MATCHES "Debug") + set(CONAN_BUILD_MODULES_PATHS ${CONAN_BUILD_MODULES_PATHS_DEBUG} ${CONAN_BUILD_MODULES_PATHS}) + elseif(${CMAKE_BUILD_TYPE} MATCHES "Release") + set(CONAN_BUILD_MODULES_PATHS ${CONAN_BUILD_MODULES_PATHS_RELEASE} ${CONAN_BUILD_MODULES_PATHS}) + elseif(${CMAKE_BUILD_TYPE} MATCHES "RelWithDebInfo") + set(CONAN_BUILD_MODULES_PATHS ${CONAN_BUILD_MODULES_PATHS_RELWITHDEBINFO} ${CONAN_BUILD_MODULES_PATHS}) + elseif(${CMAKE_BUILD_TYPE} MATCHES "MinSizeRel") + set(CONAN_BUILD_MODULES_PATHS ${CONAN_BUILD_MODULES_PATHS_MINSIZEREL} ${CONAN_BUILD_MODULES_PATHS}) + endif() + endif() + + foreach(_BUILD_MODULE_PATH ${CONAN_BUILD_MODULES_PATHS}) + include(${_BUILD_MODULE_PATH}) + endforeach() +endmacro() """ @@ -616,6 +636,7 @@ def _conan_basic_setup_common(addtional_macros, cmake_multi=False): conan_set_libcxx() conan_set_vs_runtime() conan_set_find_paths() + conan_include_build_modules() %%INVOKE_MACROS%% endmacro() """ diff --git a/conans/client/generators/cmake_find_package_common.py b/conans/client/generators/cmake_find_package_common.py index 8f0d537a452..b9022322c2d 100644 --- a/conans/client/generators/cmake_find_package_common.py +++ b/conans/client/generators/cmake_find_package_common.py @@ -9,6 +9,7 @@ set({name}_LIBRARIES{build_type_suffix} "") # Will be filled later set({name}_LIBS{build_type_suffix} "") # Same as {name}_LIBRARIES set({name}_SYSTEM_LIBS{build_type_suffix} {deps.system_libs}) +set({name}_BUILD_MODULES_PATHS{build_type_suffix} {deps.build_modules_paths}) {deps.find_frameworks} @@ -51,4 +52,11 @@ endif() endforeach() set({name}_LIBS{build_type_suffix} ${{{name}_LIBRARIES{build_type_suffix}}}) + +set(CMAKE_MODULE_PATH {deps.build_paths} ${{CMAKE_MODULE_PATH}}) +set(CMAKE_PREFIX_PATH {deps.build_paths} ${{CMAKE_PREFIX_PATH}}) + +foreach(_BUILD_MODULE_PATH ${{{name}_BUILD_MODULES_PATHS{build_type_suffix}}}) + include(${{_BUILD_MODULE_PATH}}) +endforeach() """ diff --git a/conans/client/generators/cmake_find_package_multi.py b/conans/client/generators/cmake_find_package_multi.py index d34f093a56a..99e781000e2 100644 --- a/conans/client/generators/cmake_find_package_multi.py +++ b/conans/client/generators/cmake_find_package_multi.py @@ -74,10 +74,10 @@ def content(self): depname = cpp_info.name deps = DepsCppCmake(cpp_info) ret["{}Config.cmake".format(depname)] = self._find_for_dep(depname, cpp_info) + ret["{}Targets.cmake".format(depname)] = self.targets_file.format(name=depname) find_lib = target_template.format(name=depname, deps=deps, build_type_suffix=build_type_suffix) - ret["{}Targets.cmake".format(depname)] = self.targets_file.format(name=depname) ret["{}Target-{}.cmake".format(depname, build_type.lower())] = find_lib return ret @@ -97,5 +97,4 @@ def _find_for_dep(self, name, cpp_info): version=cpp_info.version, find_dependencies_block="\n".join(lines), target_props_block=targets_props) - return tmp diff --git a/conans/client/generators/cmake_multi.py b/conans/client/generators/cmake_multi.py index af1c0e2e7de..96a4db6fadb 100644 --- a/conans/client/generators/cmake_multi.py +++ b/conans/client/generators/cmake_multi.py @@ -27,6 +27,7 @@ def add_lists(seq1, seq2): result.sharedlinkflags = cpp_info.sharedlinkflags + config_info.sharedlinkflags result.exelinkflags = cpp_info.exelinkflags + config_info.exelinkflags result.system_libs = add_lists(cpp_info.system_libs, config_info.system_libs) + result.build_modules = add_lists(cpp_info.build_modules, config_info.build_modules) return result return cpp_info diff --git a/conans/model/build_info.py b/conans/model/build_info.py index d2d3098b919..f3e9891d96e 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -35,8 +35,10 @@ def __init__(self): self.cxxflags = [] # C++ compilation flags self.sharedlinkflags = [] # linker flags self.exelinkflags = [] # linker flags + self.build_modules = [] self.rootpath = "" self.sysroot = "" + self._build_modules_paths = None self._include_paths = None self._lib_paths = None self._bin_paths = None @@ -57,6 +59,13 @@ def _filter_paths(self, paths): else: return abs_paths + @property + def build_modules_paths(self): + if self._build_modules_paths is None: + self._build_modules_paths = [os.path.join(self.rootpath, p) if not os.path.isabs(p) + else p for p in self.build_modules] + return self._build_modules_paths + @property def include_paths(self): if self._include_paths is None: @@ -174,10 +183,15 @@ def merge_lists(seq1, seq2): self.cflags = merge_lists(dep_cpp_info.cflags, self.cflags) self.sharedlinkflags = merge_lists(dep_cpp_info.sharedlinkflags, self.sharedlinkflags) self.exelinkflags = merge_lists(dep_cpp_info.exelinkflags, self.exelinkflags) + self.build_modules = merge_lists(self.build_modules, dep_cpp_info.build_modules_paths) if not self.sysroot: self.sysroot = dep_cpp_info.sysroot + @property + def build_modules_paths(self): + return self.build_modules + @property def include_paths(self): return self.includedirs diff --git a/conans/test/functional/generators/cmake_find_package_multi_test.py b/conans/test/functional/generators/cmake_find_package_multi_test.py index 0c5db43dd0f..f611f9cf69a 100644 --- a/conans/test/functional/generators/cmake_find_package_multi_test.py +++ b/conans/test/functional/generators/cmake_find_package_multi_test.py @@ -5,8 +5,8 @@ from nose.plugins.attrib import attr -from conans.client.tools import replace_in_file -from conans.test.utils.tools import TestClient +from conans.model.ref import ConanFileReference, PackageReference +from conans.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID, replace_in_file from conans.util.files import load @@ -78,6 +78,72 @@ def test_native_export_multi(self): self.assertIn("bye World {}!".format(bt), c.out) os.remove(os.path.join(c.current_folder, "example")) + def build_modules_test(self): + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile, CMake + + class Conan(ConanFile): + name = "test" + version = "1.0" + exports_sources = ["my-module.cmake", "FindFindModule.cmake"] + + def package(self): + self.copy("*.cmake", dst="share/cmake") + + def package_info(self): + # Only first module is defined + # (the other one should be found by CMAKE_MODULE_PATH in builddirs) + builddir = os.path.join("share", "cmake") + module = os.path.join(builddir, "my-module.cmake") + self.cpp_info.build_modules.append(module) + self.cpp_info.builddirs = [builddir] + """) + # This is a module that has other find_package() calls + my_module = textwrap.dedent(""" + find_package(FindModule REQUIRED) + """) + # This is a module that defines some functionality + find_module = textwrap.dedent(""" + function(conan_message MESSAGE_OUTPUT) + message(${ARGV${0}}) + endfunction() + """) + client = TestClient() + client.save({"conanfile.py": conanfile, "my-module.cmake": my_module, + "FindFindModule.cmake": find_module}) + client.run("create .") + ref = ConanFileReference("test", "1.0", None, None) + pref = PackageReference(ref, NO_SETTINGS_PACKAGE_ID, None) + package_path = client.cache.package_layout(ref).package(pref) + modules_path = os.path.join(package_path, "share", "cmake") + self.assertListEqual(os.listdir(modules_path), ["FindFindModule.cmake", "my-module.cmake"]) + consumer = textwrap.dedent(""" + from conans import ConanFile, CMake + + class Conan(ConanFile): + name = "consumer" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + exports_sources = ["CMakeLists.txt"] + generators = "cmake_find_package_multi" + requires = "test/1.0" + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + """) + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.0) + project(test) + find_package(test) + conan_message("Printing using a external module!") + """) + client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists}) + client.run("create .") + self.assertIn("Printing using a external module!", client.out) + def cmake_find_package_system_libs_test(self): conanfile = textwrap.dedent(""" from conans import ConanFile, tools diff --git a/conans/test/functional/generators/cmake_find_package_test.py b/conans/test/functional/generators/cmake_find_package_test.py index f3aeeb131b0..d8668770b82 100644 --- a/conans/test/functional/generators/cmake_find_package_test.py +++ b/conans/test/functional/generators/cmake_find_package_test.py @@ -1,5 +1,7 @@ import os import platform +import textwrap + import six import textwrap import unittest @@ -7,8 +9,9 @@ from nose.plugins.attrib import attr from conans.client.tools import replace_in_file +from conans.model.ref import ConanFileReference, PackageReference from conans.test.utils.cpp_test_files import cpp_hello_conan_files -from conans.test.utils.tools import TestClient +from conans.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID @attr('slow') @@ -303,6 +306,72 @@ def build(self): six.assertRegex(self, str(client.out), "Target libs: .*Foundation\\.framework") self.assertIn("Version: 0.1", client.out) + def build_modules_test(self): + conanfile = textwrap.dedent(""" + import os + from conans import ConanFile, CMake + + class Conan(ConanFile): + name = "test" + version = "1.0" + exports_sources = ["my-module.cmake", "FindFindModule.cmake"] + + def package(self): + self.copy("*.cmake", dst="share/cmake") + + def package_info(self): + # Only first module is defined + # (the other one should be found by CMAKE_MODULE_PATH in builddirs) + builddir = os.path.join("share", "cmake") + module = os.path.join(builddir, "my-module.cmake") + self.cpp_info.build_modules.append(module) + self.cpp_info.builddirs = [builddir] + """) + # This is a module that has other find_package() calls + my_module = textwrap.dedent(""" + find_package(FindModule REQUIRED) + """) + # This is a module that defines some functionality + find_module = textwrap.dedent(""" + function(conan_message MESSAGE_OUTPUT) + message(${ARGV${0}}) + endfunction() + """) + client = TestClient() + client.save({"conanfile.py": conanfile, "my-module.cmake": my_module, + "FindFindModule.cmake": find_module}) + client.run("create .") + ref = ConanFileReference("test", "1.0", None, None) + pref = PackageReference(ref, NO_SETTINGS_PACKAGE_ID, None) + package_path = client.cache.package_layout(ref).package(pref) + modules_path = os.path.join(package_path, "share", "cmake") + self.assertListEqual(os.listdir(modules_path), ["FindFindModule.cmake", "my-module.cmake"]) + consumer = textwrap.dedent(""" + from conans import ConanFile, CMake + + class Conan(ConanFile): + name = "consumer" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + exports_sources = ["CMakeLists.txt"] + generators = "cmake_find_package" + requires = "test/1.0" + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + """) + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.0) + project(test) + find_package(test) + conan_message("Printing using a external module!") + """) + client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists}) + client.run("create .") + self.assertIn("Printing using a external module!", client.out) + def cpp_info_name_test(self): client = TestClient() client.run("new hello/1.0 -s") diff --git a/conans/test/unittests/client/generators/cmake_test.py b/conans/test/unittests/client/generators/cmake_test.py index 06968b7c738..68b78e88266 100644 --- a/conans/test/unittests/client/generators/cmake_test.py +++ b/conans/test/unittests/client/generators/cmake_test.py @@ -1,9 +1,10 @@ import os import re import unittest - import six +from mock import patch + from conans.client.build.cmake_flags import CMakeDefinitionsBuilder from conans.client.conf import default_settings_yml from conans.client.generators import CMakeFindPackageGenerator, CMakeFindPackageMultiGenerator @@ -247,6 +248,7 @@ def aux_cmake_test_setup_test(self): conan_set_libcxx() conan_set_vs_runtime() conan_set_find_paths() + conan_include_build_modules() conan_set_find_library_paths() endmacro()""", macro) @@ -424,3 +426,87 @@ def cmake_find_package_multi_test(self): self.assertIn("add_library(MyPkG2::MyPkG2 INTERFACE IMPORTED)", content["MyPkG2Targets.cmake"]) self.assertIn("find_dependency(MyPkG REQUIRED NO_MODULE)", content["MyPkG2Config.cmake"]) + + +class CMakeBuildModulesTest(unittest.TestCase): + + def setUp(self): + settings_mock = _MockSettings(build_type="Release") + self.conanfile = ConanFile(TestBufferConanOutput(), None) + self.conanfile.initialize(settings_mock, EnvValues()) + ref = ConanFileReference.loads("my_pkg/0.1@lasote/stables") + cpp_info = CppInfo("dummy_root_folder1") + cpp_info.filter_empty = False # For testing purposes only + cpp_info.name = ref.name + cpp_info.build_modules = ["my-module.cmake"] + self.conanfile.deps_cpp_info.update(cpp_info, ref.name) + ref = ConanFileReference.loads("my_pkg2/0.1@lasote/stables") + cpp_info = CppInfo("dummy_root_folder2") + cpp_info.filter_empty = False # For testing purposes only + cpp_info.name = ref.name + cpp_info.build_modules = ["other-mod.cmake", "not-a-cmake-module.pc"] + cpp_info.release.build_modules = ["release-mod.cmake"] + cpp_info.release.filter_empty = False # For testing purposes only + self.conanfile.deps_cpp_info.update(cpp_info, ref.name) + + def cmake_test(self): + generator = CMakeGenerator(self.conanfile) + content = generator.content + self.assertNotIn("not-a-cmake-module.pc", content) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS "dummy_root_folder1/my-module.cmake"' + '\n\t\t\t"dummy_root_folder2/other-mod.cmake" ${CONAN_BUILD_MODULES_PATHS})', + content) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS_MY_PKG "dummy_root_folder1/my-module.cmake")', + content) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS_MY_PKG2 "dummy_root_folder2/other-mod.cmake")', + content) + self.assertIn("macro(conan_include_build_modules)", content) + self.assertIn("conan_include_build_modules()", content) + + def cmake_multi_test(self): + generator = CMakeMultiGenerator(self.conanfile) + content = generator.content + self.assertNotIn("not-a-cmake-module.pc", content["conanbuildinfo_release.cmake"]) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS_RELEASE ' + '"dummy_root_folder1/my-module.cmake"\n\t\t\t' + '"dummy_root_folder2/other-mod.cmake"\n\t\t\t' + '"dummy_root_folder2/release-mod.cmake" ' + '${CONAN_BUILD_MODULES_PATHS_RELEASE})', + content["conanbuildinfo_release.cmake"]) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS_MY_PKG_RELEASE ' + '"dummy_root_folder1/my-module.cmake")', + content["conanbuildinfo_release.cmake"]) + self.assertIn('set(CONAN_BUILD_MODULES_PATHS_MY_PKG2_RELEASE ' + '"dummy_root_folder2/other-mod.cmake"\n\t\t\t' + '"dummy_root_folder2/release-mod.cmake")', + content["conanbuildinfo_release.cmake"]) + self.assertIn("macro(conan_include_build_modules)", content["conanbuildinfo_multi.cmake"]) + self.assertIn("conan_include_build_modules()", content["conanbuildinfo_multi.cmake"]) + + def cmake_find_package_test(self): + generator = CMakeFindPackageGenerator(self.conanfile) + content = generator.content + self.assertIn("Findmy_pkg.cmake", content.keys()) + self.assertIn("Findmy_pkg2.cmake", content.keys()) + self.assertNotIn("not-a-cmake-module.pc", content["Findmy_pkg2.cmake"]) + self.assertIn('set(CMAKE_MODULE_PATH "dummy_root_folder1/" ${CMAKE_MODULE_PATH})', + content["Findmy_pkg.cmake"]) + self.assertIn('set(CMAKE_PREFIX_PATH "dummy_root_folder1/" ${CMAKE_PREFIX_PATH})', + content["Findmy_pkg.cmake"]) + self.assertIn('set(CMAKE_MODULE_PATH "dummy_root_folder2/" ${CMAKE_MODULE_PATH})', + content["Findmy_pkg2.cmake"]) + self.assertIn('set(CMAKE_PREFIX_PATH "dummy_root_folder2/" ${CMAKE_PREFIX_PATH})', + content["Findmy_pkg2.cmake"]) + self.assertIn('set(my_pkg_BUILD_MODULES_PATHS "dummy_root_folder1/my-module.cmake")', + content["Findmy_pkg.cmake"]) + self.assertIn('set(my_pkg2_BUILD_MODULES_PATHS "dummy_root_folder2/other-mod.cmake")', + content["Findmy_pkg2.cmake"]) + + def cmake_find_package_multi_test(self): + generator = CMakeFindPackageMultiGenerator(self.conanfile) + content = generator.content + self.assertNotIn("not-a-cmake-module.pc", content["my_pkg2Target-release.cmake"]) + self.assertIn('set(my_pkg_BUILD_MODULES_PATHS_RELEASE "dummy_root_folder1/my-module.cmake")', + content["my_pkgTarget-release.cmake"]) + self.assertIn('set(my_pkg2_BUILD_MODULES_PATHS_RELEASE "dummy_root_folder2/other-mod.cmake")', + content["my_pkg2Target-release.cmake"]) diff --git a/conans/test/unittests/model/build_info_test.py b/conans/test/unittests/model/build_info_test.py index cd06b3572c5..8d63c83e64d 100644 --- a/conans/test/unittests/model/build_info_test.py +++ b/conans/test/unittests/model/build_info_test.py @@ -193,3 +193,15 @@ def cpp_info_name_test(self): deps_cpp_info = DepsCppInfo() deps_cpp_info.update(info, "myname") self.assertIn("MyName", deps_cpp_info["myname"].name) + + def cpp_info_build_modules_test(self): + folder = temp_folder() + info = CppInfo(folder) + info.build_modules.append("my_module.cmake") + info.debug.build_modules = ["mod-release.cmake"] + deps_cpp_info = DepsCppInfo() + deps_cpp_info.update(info, "myname") + self.assertListEqual([os.path.join(folder, "my_module.cmake")], + deps_cpp_info["myname"].build_modules_paths) + self.assertListEqual([os.path.join(folder, "mod-release.cmake")], + deps_cpp_info["myname"].debug.build_modules_paths)