conan-io/conan

View on GitHub
conan/tools/cmake/cmakedeps/templates/target_configuration.py

Summary

Maintainability
A
0 mins
Test Coverage
import textwrap

from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate

"""

FooTarget-release.cmake

"""


class TargetConfigurationTemplate(CMakeDepsFileTemplate):

    @property
    def filename(self):
        name = "" if not self.generating_module else "module-"
        name += "{}-Target-{}.cmake".format(self.file_name, self.cmakedeps.configuration.lower())
        return name

    @property
    def context(self):
        deps_targets_names = self.get_deps_targets_names() \
            if not self.require.build else []

        components_targets_names = self.get_declared_components_targets_names()
        components_names = [(components_target_name.replace("::", "_"), components_target_name)
                            for components_target_name in components_targets_names]

        is_win = self.conanfile.settings.get_safe("os") == "Windows"
        auto_link = self.conanfile.cpp_info.get_property("cmake_set_interface_link_directories")
        return {"pkg_name": self.pkg_name,
                "root_target_name": self.root_target_name,
                "config_suffix": self.config_suffix,
                "config": self.configuration.upper(),
                "deps_targets_names": ";".join(deps_targets_names),
                "components_names": components_names,
                "configuration": self.cmakedeps.configuration,
                "set_interface_link_directories": auto_link and is_win}

    @property
    def template(self):
        return textwrap.dedent("""\
        # Avoid multiple calls to find_package to append duplicated properties to the targets
        include_guard()

        {%- macro tvalue(pkg_name, comp_name, var, config_suffix) -%}
            {{'${'+pkg_name+'_'+comp_name+'_'+var+config_suffix+'}'}}
        {%- endmacro -%}

        ########### VARIABLES #######################################################################
        #############################################################################################
        set({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "") # Will be filled later
        conan_find_apple_frameworks({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${' }}{{ pkg_name }}_FRAMEWORKS{{ config_suffix }}}" "{{ '${' }}{{ pkg_name }}_FRAMEWORK_DIRS{{ config_suffix }}}")

        set({{ pkg_name }}_LIBRARIES_TARGETS "") # Will be filled later


        ######## Create an interface target to contain all the dependencies (frameworks, system and conan deps)
        if(NOT TARGET {{ pkg_name+'_DEPS_TARGET'}})
            add_library({{ pkg_name+'_DEPS_TARGET'}} INTERFACE IMPORTED)
        endif()

        set_property(TARGET {{ pkg_name + '_DEPS_TARGET'}}
                     PROPERTY INTERFACE_LINK_LIBRARIES
                     $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_FRAMEWORKS_FOUND'+config_suffix+'}' }}>
                     $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_SYSTEM_LIBS'+config_suffix+'}' }}>
                     $<$<CONFIG:{{configuration}}>:{{ deps_targets_names }}>
                     APPEND)

        ####### Find the libraries declared in cpp_info.libs, create an IMPORTED target for each one and link the
        ####### {{pkg_name}}_DEPS_TARGET to all of them
        conan_package_library_targets("{{ '${' }}{{ pkg_name }}_LIBS{{ config_suffix }}}"    # libraries
                                      "{{ '${' }}{{ pkg_name }}_LIB_DIRS{{ config_suffix }}}" # package_libdir
                                      {{ pkg_name + '_DEPS_TARGET'}}
                                      {{ pkg_name }}_LIBRARIES_TARGETS  # out_libraries_targets
                                      "{{ config_suffix }}"
                                      "{{ pkg_name }}")    # package_name

        # FIXME: What is the result of this for multi-config? All configs adding themselves to path?
        set(CMAKE_MODULE_PATH {{ '${' }}{{ pkg_name }}_BUILD_DIRS{{ config_suffix }}} {{ '${' }}CMAKE_MODULE_PATH})
        {% if not components_names %}

        ########## GLOBAL TARGET PROPERTIES {{ configuration }} ########################################
            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_LINK_LIBRARIES
                         $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_OBJECTS'+config_suffix+'}' }}>
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS}>
                         APPEND)

            if("{{ '${' }}{{ pkg_name }}_LIBS{{ config_suffix }}}" STREQUAL "")
                # If the package is not declaring any "cpp_info.libs" the package deps, system libs,
                # frameworks etc are not linked to the imported targets and we need to do it to the
                # global target
                set_property(TARGET {{root_target_name}}
                             PROPERTY INTERFACE_LINK_LIBRARIES
                             {{pkg_name}}_DEPS_TARGET
                             APPEND)
            endif()

            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_LINK_OPTIONS
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}> APPEND)
            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_INCLUDE_DIRS{{config_suffix}}}> APPEND)
            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_COMPILE_DEFINITIONS
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_COMPILE_DEFINITIONS{{config_suffix}}}> APPEND)
            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_COMPILE_OPTIONS
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_COMPILE_OPTIONS{{config_suffix}}}> APPEND)

            {%- if set_interface_link_directories %}

            # This is only used for '#pragma comment(lib, "foo")' (automatic link)
            set_property(TARGET {{root_target_name}}
                         PROPERTY INTERFACE_LINK_DIRECTORIES
                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIB_DIRS{{config_suffix}}}> APPEND)
            {%- endif %}


        {%- else %}

        ########## COMPONENTS TARGET PROPERTIES {{ configuration }} ########################################

            {%- for comp_variable_name, comp_target_name in components_names %}


            ########## COMPONENT {{ comp_target_name }} #############

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

                set({{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS "")

                ######## Create an interface target to contain all the dependencies (frameworks, system and conan deps)
                if(NOT TARGET {{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}})
                    add_library({{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}} INTERFACE IMPORTED)
                endif()

                set_property(TARGET {{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}}
                             PROPERTY INTERFACE_LINK_LIBRARIES
                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS_FOUND'+config_suffix+'}' }}>
                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_SYSTEM_LIBS'+config_suffix+'}' }}>
                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_DEPENDENCIES'+config_suffix+'}' }}>
                             APPEND)

                ####### Find the libraries declared in cpp_info.component["xxx"].libs,
                ####### create an IMPORTED target for each one and link the '{{pkg_name}}_{{comp_variable_name}}_DEPS_TARGET' to all of them
                conan_package_library_targets("{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS'+config_suffix+'}' }}"
                                              "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIB_DIRS'+config_suffix+'}' }}"
                                              {{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}}
                                              {{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS
                                              "{{ config_suffix }}"
                                              "{{ pkg_name }}_{{ comp_variable_name }}")

                ########## TARGET PROPERTIES #####################################
                set_property(TARGET {{comp_target_name}}
                             PROPERTY INTERFACE_LINK_LIBRARIES
                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_OBJECTS'+config_suffix+'}' }}>
                             $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_LIBRARIES_TARGETS}>
                             APPEND)

                if("{{ '${' }}{{ pkg_name }}_{{comp_variable_name}}_LIBS{{ config_suffix }}}" STREQUAL "")
                    # If the component is not declaring any "cpp_info.components['foo'].libs" the system, frameworks etc are not
                    # linked to the imported targets and we need to do it to the global target
                    set_property(TARGET {{comp_target_name}}
                                 PROPERTY INTERFACE_LINK_LIBRARIES
                                 {{pkg_name}}_{{comp_variable_name}}_DEPS_TARGET
                                 APPEND)
                endif()

                set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_LINK_OPTIONS
                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'LINKER_FLAGS', config_suffix)}}> APPEND)
                set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'INCLUDE_DIRS', config_suffix)}}> APPEND)
                set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_COMPILE_DEFINITIONS
                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'COMPILE_DEFINITIONS', config_suffix)}}> APPEND)
                set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_COMPILE_OPTIONS
                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'COMPILE_OPTIONS', config_suffix)}}> APPEND)

                {%- if set_interface_link_directories %}
                # This is only used for '#pragma comment(lib, "foo")' (automatic link)
                set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_LINK_DIRECTORIES
                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix)}}> APPEND)

                {%- endif %}
            {%endfor %}


            ########## AGGREGATED GLOBAL TARGET WITH THE COMPONENTS #####################
            {%- for comp_variable_name, comp_target_name in components_names %}

            set_property(TARGET {{root_target_name}} PROPERTY INTERFACE_LINK_LIBRARIES {{ comp_target_name }} APPEND)

            {%- endfor %}


        {%- endif %}


        ########## For the modules (FindXXX)
        set({{ pkg_name }}_LIBRARIES{{ config_suffix }} {{root_target_name}})

        """)

    def get_declared_components_targets_names(self):
        """Returns a list of component_name"""
        ret = []
        sorted_comps = self.conanfile.cpp_info.get_sorted_components()
        for comp_name, comp in sorted_comps.items():
            ret.append(self.get_component_alias(self.conanfile, comp_name))
        ret.reverse()
        return ret

    def get_deps_targets_names(self):
        """
          - [{foo}::{bar}, ] of the required
        """
        ret = []

        # Get a list of dependencies target names
        # Declared cppinfo.requires or .components[].requires
        visible_host = self.conanfile.dependencies.filter({"build": False, "visible": True})
        visible_host_direct = visible_host.filter({"direct": True})
        if self.conanfile.cpp_info.required_components:
            for dep_name, component_name in self.conanfile.cpp_info.required_components:
                if not dep_name:
                    # Internal dep (no another component)
                    req = self.conanfile
                else:
                    req = visible_host[dep_name]

                component_name = self.get_component_alias(req, component_name)
                ret.append(component_name)
        elif visible_host_direct:
            # Regular external "conanfile.requires" declared, not cpp_info requires
            ret = [self.get_root_target_name(r) for r in visible_host_direct.values()]
        return ret