Skip to content

Commit

Permalink
Non-intrusive integration with CMake (conan-io#2875)
Browse files Browse the repository at this point in the history
* mac passing

* Fixed runtime windows

* Tested priority

* fixed zlib find test

* Fixed test and added interface_compile_definitions

* Removed priority of the cmake install folder from the install_folder

* Fixed test, added _LIBS var

* PENDING WORK TO MAKE IT TRANSITIVE

* Transitive targets

* Removed msg

* Force CI

* Automatic module path

* Add new priorize test

* Fixed test

* Replace

* Fixed win
  • Loading branch information
lasote authored May 29, 2018
1 parent 3da3392 commit af371eb
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 2 deletions.
2 changes: 1 addition & 1 deletion conans/client/build/autotools_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def configure(self, configure_dir=None, args=None, build=None, host=None, target
else:
# If we are using pkg_config generator automate the pcs location, otherwise it could
# read wrong files
pkg_env = {"PKG_CONFIG_PATH": self._conanfile.build_folder} \
pkg_env = {"PKG_CONFIG_PATH": self._conanfile.install_folder} \
if "pkg_config" in self._conanfile.generators else {}

if self._conanfile.package_folder is not None:
Expand Down
4 changes: 4 additions & 0 deletions conans/client/build/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ def add_cmake_flag(cmake_flags, name, flag):
shared = self._conanfile.options.get_safe("shared")
ret["CONAN_CMAKE_POSITION_INDEPENDENT_CODE"] = "ON" if (fpic or shared) else "OFF"

# Adjust automatically the module path in case the conanfile is using the cmake_find_package
if "cmake_find_package" in self._conanfile.generators:
ret["CMAKE_MODULE_PATH"] = self._conanfile.install_folder.replace("\\", "/")

return ret

def _get_dirs(self, source_folder, build_folder, source_dir, build_dir, cache_build_folder):
Expand Down
6 changes: 5 additions & 1 deletion conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from os.path import join

from conans.client.generators.cmake_find_package import CMakeFindPackageGenerator
from conans.client.generators.compiler_args import CompilerArgsGenerator
from conans.client.generators.pkg_config import PkgConfigGenerator
from conans.errors import ConanException
Expand All @@ -9,6 +10,8 @@
from .text import TXTGenerator
from .gcc import GCCGenerator
from .cmake import CMakeGenerator
from .cmake_paths import CMakePathsGenerator
from .cmake_multi import CMakeMultiGenerator
from .qmake import QmakeGenerator
from .qbs import QbsGenerator
from .scons import SConsGenerator
Expand All @@ -18,7 +21,6 @@
from .xcode import XCodeGenerator
from .ycm import YouCompleteMeGenerator
from .virtualenv import VirtualEnvGenerator
from .cmake_multi import CMakeMultiGenerator
from .virtualbuildenv import VirtualBuildEnvGenerator
from .boostbuild import BoostBuildGenerator
from .json_generator import JsonGenerator
Expand Down Expand Up @@ -52,6 +54,8 @@ def __getitem__(self, key):
registered_generators.add("compiler_args", CompilerArgsGenerator)
registered_generators.add("cmake", CMakeGenerator)
registered_generators.add("cmake_multi", CMakeMultiGenerator)
registered_generators.add("cmake_paths", CMakePathsGenerator)
registered_generators.add("cmake_find_package", CMakeFindPackageGenerator)
registered_generators.add("qmake", QmakeGenerator)
registered_generators.add("qbs", QbsGenerator)
registered_generators.add("scons", SConsGenerator)
Expand Down
99 changes: 99 additions & 0 deletions conans/client/generators/cmake_find_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from conans.client.generators.cmake import DepsCppCmake
from conans.model import Generator


generic_find_package_template = """
message(STATUS "Conan: Using autogenerated Find{name}.cmake")
# Global approach
SET({name}_FOUND 1)
SET({name}_INCLUDE_DIRS {deps.include_paths})
SET({name}_INCLUDES {deps.include_paths})
SET({name}_DEFINITIONS {deps.defines})
SET({name}_LIBRARIES "") # Will be filled later
SET({name}_LIBRARIES_TARGETS "") # Will be filled later, if CMake 3
SET({name}_LIBS "") # Same as {name}_LIBRARIES
mark_as_advanced({name}_FOUND {name}_INCLUDE_DIRS {name}_INCLUDES
{name}_DEFINITIONS {name}_LIBRARIES {name}_LIBS)
# Find the real .lib/.a and add them to {name}_LIBS and {name}_LIBRARY_LIST
SET({name}_LIBRARY_LIST {deps.libs})
SET({name}_LIB_DIRS {deps.lib_paths})
foreach(_LIBRARY_NAME ${{{name}_LIBRARY_LIST}})
unset(CONAN_FOUND_LIBRARY CACHE)
find_library(CONAN_FOUND_LIBRARY NAME ${{_LIBRARY_NAME}} PATHS ${{{name}_LIB_DIRS}}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
if(CONAN_FOUND_LIBRARY)
if(${{CMAKE_VERSION}} VERSION_LESS "3.0")
list(APPEND {name}_LIBRARIES ${{CONAN_FOUND_LIBRARY}})
else() # Create a micro-target for each lib/a found
set(_LIB_NAME CONAN_LIB::{name}_${{_LIBRARY_NAME}})
add_library(${{_LIB_NAME}} UNKNOWN IMPORTED)
set_target_properties(${{_LIB_NAME}} PROPERTIES IMPORTED_LOCATION ${{CONAN_FOUND_LIBRARY}})
list(APPEND {name}_LIBRARIES_TARGETS ${{_LIB_NAME}})
endif()
message(STATUS "Found: ${{CONAN_FOUND_LIBRARY}}")
else()
message(STATUS "Library ${{_LIBRARY_NAME}} not found in package, might be system one")
endif()
endforeach()
set({name}_LIBS ${{{name}_LIBRARIES}})
if(NOT ${{CMAKE_VERSION}} VERSION_LESS "3.0")
# Target approach
if(NOT TARGET {name}::{name})
add_library({name}::{name} INTERFACE IMPORTED)
if({name}_INCLUDE_DIRS)
set_target_properties({name}::{name} PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${{{name}_INCLUDE_DIRS}}")
endif()
set_property(TARGET {name}::{name} PROPERTY INTERFACE_LINK_LIBRARIES ${{{name}_LIBRARIES_TARGETS}})
set_property(TARGET {name}::{name} PROPERTY INTERFACE_COMPILE_DEFINITIONS {deps.defines})
endif()
{find_dependencies}
endif()
"""


class CMakeFindPackageGenerator(Generator):

@property
def filename(self):
pass

@property
def content(self):
ret = {}
for depname, cpp_info in self.deps_build_info.dependencies:
ret["Find%s.cmake" % depname] = self._single_find_package(depname, cpp_info)
return ret

@staticmethod
def _single_find_package(name, cpp_info):
deps = DepsCppCmake(cpp_info)
lines = []
if cpp_info.public_deps:
lines = CMakeFindPackageGenerator._transitive_lines(name, cpp_info)
tmp = generic_find_package_template.format(name=name, deps=deps,
find_dependencies="\n".join(lines))
return tmp

@staticmethod
def _transitive_lines(name, cpp_info):
lines = ["# Library dependencies", "include(CMakeFindDependencyMacro)"]
for dep in cpp_info.public_deps:
def property_lines(prop):
lib_t = "%s::%s" % (name, name)
dep_t = "%s::%s" % (dep, dep)
return ["get_target_property(tmp %s %s)" % (dep_t, prop),
"if(tmp)",
" set_property(TARGET %s APPEND PROPERTY %s ${tmp})" % (lib_t, prop),
' message("${tmp}")',
'endif()']

lines.append("find_dependency(%s REQUIRED)" % dep)
lines.extend(property_lines("INTERFACE_LINK_LIBRARIES"))
lines.extend(property_lines("INTERFACE_COMPILE_DEFINITIONS"))
lines.extend(property_lines("INTERFACE_INCLUDE_DIRECTORIES"))
return lines
22 changes: 22 additions & 0 deletions conans/client/generators/cmake_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from conans.client.generators.cmake import DepsCppCmake
from conans.model import Generator


class CMakePathsGenerator(Generator):

@property
def filename(self):
return "conan_paths.cmake"

@property
def content(self):
deps = DepsCppCmake(self.deps_build_info)
# We want to prioritize the FindXXX.cmake files:
# 1. First the files found in the packages
# 2. The previously set (by default CMAKE_MODULE_PATH is empty)
# 3. The "install_folder" ones, in case there is no FindXXX.cmake, try with the install dir
# if the user used the "cmake_find_package" will find the auto-generated
# 4. The CMake installation dir/Modules ones.
return """set(CMAKE_MODULE_PATH {deps.build_paths} ${{CMAKE_MODULE_PATH}} ${{CMAKE_CURRENT_LIST_DIR}})
set(CMAKE_PREFIX_PATH {deps.build_paths} ${{CMAKE_PREFIX_PATH}} ${{CMAKE_CURRENT_LIST_DIR}})
""".format(deps=deps)
103 changes: 103 additions & 0 deletions conans/test/generators/cmake_find_package_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import unittest
from conans.test.utils.cpp_test_files import cpp_hello_conan_files
from conans.test.utils.tools import TestClient
from nose.plugins.attrib import attr


@attr('slow')
class CMakeFindPathGeneratorTest(unittest.TestCase):

def cmake_find_package_test(self):
"""First package without custom find_package"""
client = TestClient()
files = cpp_hello_conan_files(name="Hello0",
settings='"os", "compiler", "arch", "build_type"')
client.save(files)
client.run("create . user/channel -s build_type=Release")

# Consume the previous Hello0 with auto generated FindHello0.cmake
# The module path will point to the "install" folder automatically (CMake helper)
files = cpp_hello_conan_files(name="Hello1", deps=["Hello0/0.1@user/channel"],
settings='"os", "compiler", "arch", "build_type"')
files["conanfile.py"] = files["conanfile.py"].replace(
'generators = "cmake", "gcc"',
'generators = "cmake_find_package"')
files["CMakeLists.txt"] = """
set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CXX_ABI_COMPILED 1)
project(MyHello CXX)
cmake_minimum_required(VERSION 2.8)
find_package(Hello0 REQUIRED)
add_library(helloHello1 hello.cpp)
target_link_libraries(helloHello1 PUBLIC Hello0::Hello0)
add_executable(say_hello main.cpp)
target_link_libraries(say_hello helloHello1)
"""
client.save(files, clean_first=True)
client.run("create . user/channel -s build_type=Release")
self.assertIn("Conan: Using autogenerated FindHello0.cmake", client.out)

# Now link with old cmake
files["CMakeLists.txt"] = """
set(CMAKE_VERSION "2.8")
set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CXX_ABI_COMPILED 1)
project(MyHello CXX)
cmake_minimum_required(VERSION 2.8)
message(${CMAKE_BINARY_DIR})
set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR} ${CMAKE_MODULE_PATH})
find_package(Hello0 REQUIRED)
add_library(helloHello1 hello.cpp)
if(NOT DEFINED Hello0_FOUND)
message(FATAL_ERROR "Hello0_FOUND not declared")
endif()
if(NOT DEFINED Hello0_INCLUDE_DIRS)
message(FATAL_ERROR "Hello0_INCLUDE_DIRS not declared")
endif()
if(NOT DEFINED Hello0_INCLUDES)
message(FATAL_ERROR "Hello0_INCLUDES not declared")
endif()
if(NOT DEFINED Hello0_LIBRARIES)
message(FATAL_ERROR "Hello0_LIBRARIES not declared")
endif()
include_directories(${Hello0_INCLUDE_DIRS})
target_link_libraries(helloHello1 PUBLIC ${Hello0_LIBS})
add_executable(say_hello main.cpp)
target_link_libraries(say_hello helloHello1)
"""
client.save(files, clean_first=True)
client.run("create . user/channel -s build_type=Release")
self.assertIn("Conan: Using autogenerated FindHello0.cmake", client.out)

# Now a transitive consumer, but the consumer only find_package the first level Hello1
files = cpp_hello_conan_files(name="Hello2", deps=["Hello1/0.1@user/channel"],
settings='"os", "compiler", "arch", "build_type"')
files["CMakeLists.txt"] = """
set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CXX_ABI_COMPILED 1)
project(MyHello CXX)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR} ${CMAKE_MODULE_PATH})
find_package(Hello1 REQUIRED) # We don't need to find Hello0, it is transitive
add_library(helloHello2 hello.cpp)
target_link_libraries(helloHello2 PUBLIC Hello1::Hello1)
add_executable(say_hello main.cpp)
target_link_libraries(say_hello helloHello2)
"""
files["conanfile.py"] = files["conanfile.py"].replace(
'generators = "cmake", "gcc"',
'generators = "cmake_find_package"')
client.save(files, clean_first=True)
client.run("create . user/channel -s build_type=Release")
self.assertIn("Conan: Using autogenerated FindHello0.cmake", client.out)
self.assertIn("Conan: Using autogenerated FindHello1.cmake", client.out)
Loading

0 comments on commit af371eb

Please sign in to comment.