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

Add components to cmake_find_package generator (jinja2) #7108

Merged
merged 36 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
650ca94
Add jinja2 template for cmake_find_package with components
danimtb May 26, 2020
4090530
Add components to cmake_find_package
danimtb May 27, 2020
ee3f99b
Add components to cmake_find_package generator (jinja2)
danimtb May 27, 2020
2612927
fix
danimtb May 27, 2020
b936911
tag tests as slow
danimtb May 27, 2020
25f241f
try fix
danimtb May 27, 2020
5652556
try and print
danimtb May 28, 2020
96a0a44
Fix find
danimtb May 28, 2020
169f93c
try without example2
danimtb May 28, 2020
53b01b1
add link flags, use set_target_property(), fix test
danimtb May 28, 2020
cc0b8ce
push test
danimtb May 28, 2020
734bb58
Use conan_basic_setup()
danimtb May 28, 2020
ef52ab5
Fix test
danimtb May 29, 2020
f75fc57
Fix ALL tests FINALLY!
danimtb May 29, 2020
fcb26e6
add example2, fix tests, add reverse and format messages
danimtb May 29, 2020
88afd45
add sorted_components() to base generator class
danimtb May 29, 2020
c3f8af3
fix includes and remove messages
danimtb May 29, 2020
bb65e78
Change jinja syntax
danimtb May 29, 2020
d897f79
test component with same name as global
danimtb Jun 1, 2020
6fa9ad1
remove dependencies from global target
danimtb Jun 1, 2020
b7d4580
Link mini-targets to other components
danimtb Jun 1, 2020
a0f2b48
Update test with slightly different behavior
danimtb Jun 1, 2020
593041c
use name and classmethod
danimtb Jun 1, 2020
2ea3868
reuse macros
danimtb Jun 1, 2020
e418657
use split
danimtb Jun 1, 2020
91f1ab6
fix with test
danimtb Jun 1, 2020
02955c8
refactor
memsharded Jun 3, 2020
94b2f5e
fix
memsharded Jun 3, 2020
8957c44
Merge pull request #6 from memsharded/refactor/components_find_package
danimtb Jun 3, 2020
76d11c2
use pkg name as prefix in variables
danimtb Jun 3, 2020
8d0c054
reuse macro
danimtb Jun 3, 2020
bfb52ba
find_package(COMPONENTS) check and test
danimtb Jun 3, 2020
4358605
fix textwrap for python 2 and a test
danimtb Jun 3, 2020
5a60e37
remove find components check in old implementation
danimtb Jun 4, 2020
9bb523a
keep global variables
danimtb Jun 4, 2020
6daf697
include and populate all global variables
danimtb Jun 5, 2020
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
295 changes: 255 additions & 40 deletions conans/client/generators/cmake_find_package.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from jinja2 import Template
import textwrap

from conans.client.generators.cmake import DepsCppCmake
from conans.client.generators.cmake_find_package_common import (target_template,
CMakeFindPackageCommonMacros,
find_transitive_dependencies)
from conans.client.generators.cmake_multi import extend
from conans.errors import ConanException
from conans.model import Generator
from conans.model.build_info import COMPONENT_SCOPE


class CMakeFindPackageGenerator(Generator):
name = "cmake_find_package"

find_template = textwrap.dedent("""
{macros_and_functions}

Expand Down Expand Up @@ -43,52 +48,262 @@ class CMakeFindPackageGenerator(Generator):
endif()
""")

find_components_tpl = Template(textwrap.dedent("""\
########## MACROS ###########################################################################
#############################################################################################
{{ conan_message }}
{{ conan_find_apple_frameworks }}
{{ conan_package_library_targets }}

########### FOUND PACKAGE ###################################################################
#############################################################################################

include(FindPackageHandleStandardArgs)

conan_message(STATUS "Conan: Using autogenerated Find{{ pkg_name }}.cmake")
set({{ pkg_name }}_FOUND 1)
set({{ pkg_name }}_VERSION "{{ pkg_version }}")
set({{ pkg_name }}_COMPONENTS {{ pkg_components }})

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)

if({{ pkg_name }}_FIND_COMPONENTS)
foreach(_FIND_COMPONENT {{ '${'+pkg_name+'_FIND_COMPONENTS}' }})
list(FIND {{ pkg_name }}_COMPONENTS "{{ pkg_name }}::${_FIND_COMPONENT}" _index)
if(${_index} EQUAL -1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to consider here the inputs QUIET and REQUIRED?

Copy link
Member Author

Choose a reason for hiding this comment

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

Aren't those variables managed by CMake itself? From https://cmake.org/cmake/help/v3.0/command/find_package.html

The QUIET option disables messages if the package cannot be found.
The MODULE option disables the second signature documented below.
The REQUIRED option stops processing with an error message if the package cannot be found.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! Maybe it uses the variable <PKG>_FOUND?

conan_message(FATAL_ERROR "Conan: Component '${_FIND_COMPONENT}' NOT found in package '{{ pkg_name }}'")
else()
conan_message(STATUS "Conan: Component '${_FIND_COMPONENT}' found in package '{{ pkg_name }}'")
endif()
endforeach()
endif()

########### VARIABLES #######################################################################
#############################################################################################

{{ global_target_variables }}

{%- for comp_name, comp in components %}

########### COMPONENT {{ comp_name }} VARIABLES #############################################

set({{ pkg_name }}_{{ comp_name }}_INCLUDE_DIRS {{ comp.include_paths }})
set({{ pkg_name }}_{{ comp_name }}_INCLUDE_DIR {{ comp.include_path }})
set({{ pkg_name }}_{{ comp_name }}_INCLUDES {{ comp.include_paths }})
set({{ pkg_name }}_{{ comp_name }}_LIB_DIRS {{ comp.lib_paths }})
set({{ pkg_name }}_{{ comp_name }}_RES_DIRS {{ comp.res_paths }})
set({{ pkg_name }}_{{ comp_name }}_DEFINITIONS {{ comp.defines }})
set({{ pkg_name }}_{{ comp_name }}_COMPILE_DEFINITIONS {{ comp.compile_definitions }})
set({{ pkg_name }}_{{ comp_name }}_COMPILE_OPTIONS_LIST "{{ comp.cxxflags_list }}" "{{ comp.cflags_list }}")
set({{ pkg_name }}_{{ comp_name }}_LIBS {{ comp.libs }})
set({{ pkg_name }}_{{ comp_name }}_SYSTEM_LIBS {{ comp.system_libs }})
set({{ pkg_name }}_{{ comp_name }}_FRAMEWORK_DIRS {{ comp.framework_paths }})
set({{ pkg_name }}_{{ comp_name }}_FRAMEWORKS {{ comp.frameworks }})
set({{ pkg_name }}_{{ comp_name }}_BUILD_MODULES_PATHS {{ comp.build_modules_paths }})
set({{ pkg_name }}_{{ comp_name }}_DEPENDENCIES {{ comp.public_deps }})
set({{ pkg_name }}_{{ comp_name }}_LINKER_FLAGS_LIST
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:{{ comp.sharedlinkflags_list }}>
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:{{ comp.sharedlinkflags_list }}>
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:{{ comp.exelinkflags_list }}>
)

{%- endfor %}


########## FIND PACKAGE DEPENDENCY ##########################################################
#############################################################################################

include(CMakeFindDependencyMacro)

{%- for public_dep in pkg_public_deps %}

if(NOT {{ public_dep }}_FOUND)
find_dependency({{ public_dep }} REQUIRED)
else()
conan_message(STATUS "Conan: Dependency {{ public_dep }} already found")
endif()

{%- endfor %}


########## FIND LIBRARIES & FRAMEWORKS / DYNAMIC VARS #######################################
#############################################################################################

{%- for comp_name, comp in components %}

########## COMPONENT {{ comp_name }} FIND LIBRARIES & FRAMEWORKS / DYNAMIC VARS #############

set({{ pkg_name }}_{{ comp_name }}_FRAMEWORKS_FOUND "")
conan_find_apple_frameworks({{ pkg_name }}_{{ comp_name }}_FRAMEWORKS_FOUND "{{ '${'+pkg_name+'_'+comp_name+'_FRAMEWORKS}' }}" "{{ '${'+pkg_name+'_'+comp_name+'_FRAMEWORK_DIRS}' }}")

set({{ pkg_name }}_{{ comp_name }}_LIB_TARGETS "")
set({{ pkg_name }}_{{ comp_name }}_NOT_USED "")
set({{ pkg_name }}_{{ comp_name }}_LIBS_FRAMEWORKS_DEPS {{ '${'+pkg_name+'_'+comp_name+'_FRAMEWORKS_FOUND}' }} {{ '${'+pkg_name+'_'+comp_name+'_SYSTEM_LIBS}' }} {{ '${'+pkg_name+'_'+comp_name+'_DEPENDENCIES}' }})
conan_package_library_targets("{{ '${'+pkg_name+'_'+comp_name+'_LIBS}' }}"
"{{ '${'+pkg_name+'_'+comp_name+'_LIB_DIRS}' }}"
"{{ '${'+pkg_name+'_'+comp_name+'_LIBS_FRAMEWORKS_DEPS}' }}"
{{ pkg_name }}_{{ comp_name }}_NOT_USED
{{ pkg_name }}_{{ comp_name }}_LIB_TARGETS
""
"{{ pkg_name }}_{{ comp_name }}")

set({{ pkg_name }}_{{ comp_name }}_LINK_LIBS {{ '${'+pkg_name+'_'+comp_name+'_LIB_TARGETS}' }} {{ '${'+pkg_name+'_'+comp_name+'_DEPENDENCIES}' }})
Copy link
Contributor

Choose a reason for hiding this comment

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

The variable {{pkg_name}}_{{compo_name}}_LIB_TARGETS has already been linked with everything listed in {{ pkg_name }}_{{ comp_name }}_LIBS_FRAMEWORKS_DEPS (and also with system libraries that are wrongly listed in libs). So, {{ pkg_name }}_{{ comp_name }}_LIB_TARGETS should be all be need in line L177. Am I right?


set(CMAKE_MODULE_PATH {{ comp.build_paths }} ${CMAKE_MODULE_PATH})
set(CMAKE_PREFIX_PATH {{ comp.build_paths }} ${CMAKE_PREFIX_PATH})

foreach(_BUILD_MODULE_PATH {{ '${'+pkg_name+'_'+comp_name+'_BUILD_MODULES_PATHS}' }})
include(${_BUILD_MODULE_PATH})
endforeach()

{%- endfor %}


########## TARGETS ##########################################################################
#############################################################################################

{%- for comp_name, comp in components %}

########## COMPONENT {{ comp_name }} TARGET #################################################

if(NOT ${CMAKE_VERSION} VERSION_LESS "3.0")
# Target approach
if(NOT TARGET {{ pkg_name }}::{{ comp_name }})
add_library({{ pkg_name }}::{{ comp_name }} INTERFACE IMPORTED)
set_target_properties({{ pkg_name }}::{{ comp_name }} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"{{ '${'+pkg_name+'_'+comp_name+'_INCLUDE_DIRS}' }}")
set_target_properties({{ pkg_name }}::{{ comp_name }} PROPERTIES INTERFACE_LINK_DIRECTORIES
"{{ '${'+pkg_name+'_'+comp_name+'_LIB_DIRS}' }}")
set_target_properties({{ pkg_name }}::{{ comp_name }} PROPERTIES INTERFACE_LINK_LIBRARIES
"{{ '${'+pkg_name+'_'+comp_name+'_LINK_LIBS}' }};{{ '${'+pkg_name+'_'+comp_name+'_LINKER_FLAGS_LIST}' }}")
set_target_properties({{ pkg_name }}::{{ comp_name }} PROPERTIES INTERFACE_COMPILE_DEFINITIONS
"{{ '${'+pkg_name+'_'+comp_name+'_COMPILE_DEFINITIONS}' }}")
set_target_properties({{ pkg_name }}::{{ comp_name }} PROPERTIES INTERFACE_COMPILE_OPTIONS
"{{ '${'+pkg_name+'_'+comp_name+'_COMPILE_OPTIONS_LIST}' }}")
endif()
endif()

{%- endfor %}

########## GLOBAL TARGET ####################################################################

if(NOT ${CMAKE_VERSION} VERSION_LESS "3.0")
if(NOT TARGET {{ pkg_name }}::{{ pkg_name }})
add_library({{ pkg_name }}::{{ pkg_name }} INTERFACE IMPORTED)
set_target_properties({{ pkg_name }}::{{ pkg_name }} PROPERTIES INTERFACE_LINK_LIBRARIES
"{{ '${'+pkg_name+'_COMPONENTS}' }}")
endif()
endif()

"""))

@property
def filename(self):
return None

@classmethod
def _get_name(cls, obj):
get_name = getattr(obj, "get_name")
return get_name(cls.name)

@property
def content(self):
ret = {}
for _, cpp_info in self.deps_build_info.dependencies:
depname = cpp_info.get_name("cmake_find_package")
ret["Find%s.cmake" % depname] = self._find_for_dep(depname, cpp_info)
for pkg_name, cpp_info in self.deps_build_info.dependencies:
pkg_findname = self._get_name(cpp_info)
ret["Find%s.cmake" % pkg_findname] = self._find_for_dep(pkg_name, pkg_findname, cpp_info)
return ret

def _find_for_dep(self, name, cpp_info):
# The common macros
macros_and_functions = "\n".join([
CMakeFindPackageCommonMacros.conan_message,
CMakeFindPackageCommonMacros.apple_frameworks_macro,
CMakeFindPackageCommonMacros.conan_package_library_targets,
])

# compose the cpp_info with its "debug" or "release" specific config
dep_cpp_info = cpp_info
build_type = self.conanfile.settings.get_safe("build_type")
if build_type:
dep_cpp_info = extend(dep_cpp_info, build_type.lower())

# The find_libraries_block, all variables for the package, and creation of targets
public_deps_names = [self.deps_build_info[dep].get_name("cmake_find_package") for dep in
dep_cpp_info.public_deps]
deps_names = ";".join(["{n}::{n}".format(n=n) for n in public_deps_names])

deps = DepsCppCmake(dep_cpp_info)
find_libraries_block = target_template.format(name=name, deps=deps, build_type_suffix="",
deps_names=deps_names)

# The find_transitive_dependencies block
find_dependencies_block = ""
if dep_cpp_info.public_deps:
# Here we are generating FindXXX, so find_modules=True
f = find_transitive_dependencies(public_deps_names, find_modules=True)
# proper indentation
find_dependencies_block = ''.join(" " + line if line.strip() else line
for line in f.splitlines(True))

tmp = self.find_template.format(name=name, version=dep_cpp_info.version,
find_libraries_block=find_libraries_block,
find_dependencies_block=find_dependencies_block,
macros_and_functions=macros_and_functions)
return tmp
def _get_components(self, pkg_name, pkg_findname, cpp_info):
find_package_components = []
for comp_name, comp in self.sorted_components(cpp_info).items():
comp_findname = self._get_name(cpp_info.components[comp_name])
deps_cpp_cmake = DepsCppCmake(comp)
deps_cpp_cmake.public_deps = self._get_component_requires(pkg_name, pkg_findname, comp)
find_package_components.append((comp_findname, deps_cpp_cmake))
find_package_components.reverse() # From the less dependent to most one
return find_package_components

def _get_component_requires(self, pkg_name, pkg_findname, comp):
comp_requires_findnames = []
for require in comp.requires:
if COMPONENT_SCOPE in require:
comp_require_pkg_name, comp_require_comp_name = require.split(COMPONENT_SCOPE)
comp_require_pkg = self.deps_build_info[comp_require_pkg_name]
comp_require_pkg_findname = self._get_name(comp_require_pkg)
if comp_require_comp_name == comp_require_pkg_name:
comp_require_comp_findname = comp_require_pkg_findname
elif comp_require_comp_name in self.deps_build_info[comp_require_pkg_name].components:
comp_require_comp = comp_require_pkg.components[comp_require_comp_name]
comp_require_comp_findname = self._get_name(comp_require_comp)
else:
raise ConanException("Component '%s' not found in '%s' package requirement"
% (require, comp_require_pkg_name))
else:
comp_require_pkg_findname = pkg_findname
comp_require_comp = self.deps_build_info[pkg_name].components[require]
comp_require_comp_findname = self._get_name(comp_require_comp)
f = "{}::{}".format(comp_require_pkg_findname, comp_require_comp_findname)
comp_requires_findnames.append(f)
return " ".join(comp_requires_findnames)

def _find_for_dep(self, pkg_name, pkg_findname, cpp_info):
# return the content of the FindXXX.cmake file for the package "pkg_name"
pkg_version = cpp_info.version
pkg_public_deps = [self._get_name(self.deps_build_info[public_dep]) for public_dep in
cpp_info.public_deps]
pkg_public_deps_names = ";".join(["{n}::{n}".format(n=n) for n in pkg_public_deps])
if cpp_info.components:
components = self._get_components(pkg_name, pkg_findname, cpp_info)
# Note these are in reversed order, from more dependent to less dependent
pkg_components = " ".join(["{p}::{c}".format(p=pkg_findname, c=comp_findname) for
comp_findname, _ in reversed(components)])
pkg_info = DepsCppCmake(cpp_info)
global_target_variables = target_template.format(name=pkg_findname, deps=pkg_info,
build_type_suffix="",
deps_names=pkg_public_deps_names)
return self.find_components_tpl.render(
pkg_name=pkg_findname,
pkg_version=pkg_version,
pkg_components=pkg_components,
global_target_variables=global_target_variables,
pkg_public_deps=pkg_public_deps,
components=components,
conan_message=CMakeFindPackageCommonMacros.conan_message,
conan_find_apple_frameworks=CMakeFindPackageCommonMacros.apple_frameworks_macro,
conan_package_library_targets=CMakeFindPackageCommonMacros.conan_package_library_targets)
else:
# The common macros
macros_and_functions = "\n".join([
CMakeFindPackageCommonMacros.conan_message,
CMakeFindPackageCommonMacros.apple_frameworks_macro,
CMakeFindPackageCommonMacros.conan_package_library_targets,
])

# compose the cpp_info with its "debug" or "release" specific config
dep_cpp_info = cpp_info
build_type = self.conanfile.settings.get_safe("build_type")
if build_type:
dep_cpp_info = extend(dep_cpp_info, build_type.lower())

# The find_libraries_block, all variables for the package, and creation of targets
deps = DepsCppCmake(dep_cpp_info)
find_libraries_block = target_template.format(name=pkg_findname, deps=deps,
build_type_suffix="",
deps_names=pkg_public_deps_names)

# The find_transitive_dependencies block
find_dependencies_block = ""
if dep_cpp_info.public_deps:
# Here we are generating FindXXX, so find_modules=True
f = find_transitive_dependencies(pkg_public_deps, find_modules=True)
# proper indentation
find_dependencies_block = ''.join(" " + line if line.strip() else line
for line in f.splitlines(True))

return self.find_template.format(name=pkg_findname, version=pkg_version,
find_libraries_block=find_libraries_block,
find_dependencies_block=find_dependencies_block,
macros_and_functions=macros_and_functions)
1 change: 1 addition & 0 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(self, factory):
def __getitem__(self, key):
if key not in self.keys():
super(DefaultOrderedDict, self).__setitem__(key, self.factory())
super(DefaultOrderedDict, self).__getitem__(key).name = key
return super(DefaultOrderedDict, self).__getitem__(key)

def __copy__(self):
Expand Down
3 changes: 3 additions & 0 deletions conans/model/conan_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ def content(self):
@abstractproperty
def filename(self):
raise NotImplementedError()

def sorted_components(self, cpp_info):
return cpp_info._get_sorted_components()
Loading