1# This module provides generate_and_install_pkg_config_file() function. 2# The function takes target name and expects a fully configured project, i.e. with set version and 3# description. The function extracts interface libraries, include dirs, definitions and options 4# from the target and generates pkg-config file with install() command 5# The function expands imported targets and generator expressions 6 7# save the current file dir for later use in the generate_and_install_pkg_config_file() function 8set(_GeneratePkGConfigDir "${CMAKE_CURRENT_LIST_DIR}/GeneratePkgConfig") 9 10include(GNUInstallDirs) 11 12function(_get_target_property_merging_configs _var_name _target_name _propert_name) 13 get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name} SET) 14 if (prop_set) 15 get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}) 16 else() 17 if (CMAKE_BUILD_TYPE) 18 list(APPEND configs ${CMAKE_BUILD_TYPE}) 19 elseif(CMAKE_CONFIGURATION_TYPES) 20 list(APPEND configs ${CMAKE_CONFIGURATION_TYPES}) 21 endif() 22 foreach(cfg ${configs}) 23 string(TOUPPER "${cfg}" UPPERCFG) 24 get_property(mapped_configs TARGET ${_target_name} PROPERTY "MAP_IMPORTED_CONFIG_${UPPERCFG}") 25 if (mapped_configs) 26 list(GET "${mapped_configs}" 0 target_cfg) 27 else() 28 set(target_cfg "${UPPERCFG}") 29 endif() 30 get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg} SET) 31 if (prop_set) 32 get_property(val_for_cfg TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg}) 33 list(APPEND vals "$<$<CONFIG:${cfg}>:${val_for_cfg}>") 34 break() 35 endif() 36 endforeach() 37 if (NOT prop_set) 38 get_property(imported_cfgs TARGET ${_target_name} PROPERTY IMPORTED_CONFIGURATIONS) 39 # CMake docs say we can use any of the imported configs 40 list(GET imported_cfgs 0 imported_config) 41 get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}_${imported_config}) 42 # remove config generator expression. Only in this case! Notice we use such expression 43 # ourselves in the loop above 44 string(REPLACE "$<$<CONFIG:${imported_config}>:" "$<1:" vals "${vals}") 45 endif() 46 endif() 47 # HACK for static libraries cmake populates link dependencies as $<LINK_ONLY:lib_name>. 48 # pkg-config does not support special handling for static libraries and as such we will remove 49 # that generator expression 50 string(REPLACE "$<LINK_ONLY:" "$<1:" vals "${vals}") 51 # HACK file(GENERATE), which we use for expanding generator expressions, is BUILD_INTERFACE, 52 # but we need INSTALL_INTERFACE here. 53 # See https://gitlab.kitware.com/cmake/cmake/issues/17984 54 string(REPLACE "$<BUILD_INTERFACE:" "$<0:" vals "${vals}") 55 string(REPLACE "$<INSTALL_INTERFACE:" "@CMAKE_INSTALL_PREFIX@/$<1:" vals "${vals}") 56 set(${_var_name} "${vals}" PARENT_SCOPE) 57endfunction() 58 59# This helper function expands imported targets from the provided targets list, collecting their 60# interface link libraries and imported locations, include directories, compile options and definitions 61# into the specified variables 62function(_expand_targets _targets _libraries_var _include_dirs_var _compile_options_var _compile_definitions_var) 63 set(_any_target_was_expanded True) 64 set(_libs "${${_libraries_var}}") 65 set(_includes "${${_include_dirs_var}}") 66 set(_defs "${${_compile_definitions_var}}") 67 set(_options "${${_compile_options_var}}") 68 69 list(APPEND _libs "${_targets}") 70 71 while(_any_target_was_expanded) 72 set(_any_target_was_expanded False) 73 set(_new_libs "") 74 foreach (_dep ${_libs}) 75 if (TARGET ${_dep}) 76 set(_any_target_was_expanded True) 77 78 get_target_property(_type ${_dep} TYPE) 79 if ("${_type}" STREQUAL "INTERFACE_LIBRARY") 80 # this library may not have IMPORTED_LOCATION property 81 set(_imported_location "") 82 else() 83 _get_target_property_merging_configs(_imported_location ${_dep} IMPORTED_LOCATION) 84 endif() 85 86 _get_target_property_merging_configs(_iface_link_libraries ${_dep} INTERFACE_LINK_LIBRARIES) 87 _get_target_property_merging_configs(_iface_include_dirs ${_dep} INTERFACE_INCLUDE_DIRECTORIES) 88 _get_target_property_merging_configs(_iface_compile_options ${_dep} INTERFACE_COMPILE_OPTIONS) 89 _get_target_property_merging_configs(_iface_definitions ${_dep} INTERFACE_COMPILE_DEFINITIONS) 90 91 if (_imported_location) 92 list(APPEND _new_libs "${_imported_location}") 93 endif() 94 95 if (_iface_link_libraries) 96 list(APPEND _new_libs "${_iface_link_libraries}") 97 endif() 98 99 if(_iface_include_dirs) 100 list(APPEND _includes "${_iface_include_dirs}") 101 endif() 102 103 if(_iface_compile_options) 104 list(APPEND _options "${_iface_compile_options}") 105 endif() 106 107 if(_iface_definitions) 108 list(APPEND _defs "${_iface_definitions}") 109 endif() 110 111 list(REMOVE_DUPLICATES _new_libs) 112 list(REMOVE_DUPLICATES _includes) 113 # Options order is important, thus leave duplicates as they are, skipping duplicates removal 114 list(REMOVE_DUPLICATES _defs) 115 else() 116 list(APPEND _new_libs "${_dep}") 117 endif() 118 endforeach() 119 set(_libs "${_new_libs}") 120 endwhile() 121 set(${_libraries_var} "${_libs}" PARENT_SCOPE) 122 set(${_include_dirs_var} "${_includes}" PARENT_SCOPE) 123 set(${_compile_options_var} "${_options}" PARENT_SCOPE) 124 set(${_compile_definitions_var} "${_defs}" PARENT_SCOPE) 125endfunction() 126 127# Generates and installs a pkg-config file for a given target 128function(generate_and_install_pkg_config_file _target _packageName) 129 # collect target properties 130 _expand_targets(${_target} 131 _interface_link_libraries _interface_include_dirs 132 _interface_compile_options _interface_definitions) 133 134 get_target_property(_output_name ${_target} OUTPUT_NAME) 135 if (NOT _output_name) 136 set(_output_name "${_target}") 137 endif() 138 139 set(_package_name "${_packageName}") 140 141 # remove standard include directories 142 foreach(d IN LISTS CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) 143 list(REMOVE_ITEM _interface_include_dirs "${d}") 144 endforeach() 145 146 set(_generate_target_dir "${CMAKE_CURRENT_BINARY_DIR}/${_target}-pkgconfig") 147 set(_pkg_config_file_template_filename "${_GeneratePkGConfigDir}/pkg-config.cmake.in") 148 149 # Since CMake 3.18 FindThreads may include a generator expression requiring a target, which gets propagated to us through INTERFACE_OPTIONS. 150 # Before CMake 3.19 there's no way to solve this in a general way, so we work around the specific case. See #4956 and CMake bug #21074. 151 if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) 152 set(_target_arg TARGET ${_target}) 153 else() 154 string(REPLACE "<COMPILE_LANG_AND_ID:CUDA,NVIDIA>" "<COMPILE_LANGUAGE:CUDA>" _interface_compile_options "${_interface_compile_options}") 155 endif() 156 157 # put target and project properties into a file 158 configure_file("${_GeneratePkGConfigDir}/target-compile-settings.cmake.in" 159 "${_generate_target_dir}/compile-settings.cmake" @ONLY) 160 161 get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 162 if (NOT _isMultiConfig) 163 set(_variables_file_name "${_generate_target_dir}/compile-settings-expanded.cmake") 164 165 file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" ${_target_arg}) 166 167 configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" 168 "${_generate_target_dir}/generate-pkg-config.cmake" @ONLY) 169 170 install(SCRIPT "${_generate_target_dir}/generate-pkg-config.cmake") 171 else() 172 foreach(cfg IN LISTS CMAKE_CONFIGURATION_TYPES) 173 set(_variables_file_name "${_generate_target_dir}/${cfg}/compile-settings-expanded.cmake") 174 175 file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" CONDITION "$<CONFIG:${cfg}>" ${_target_arg}) 176 177 configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" 178 "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake" @ONLY) 179 180 install(SCRIPT "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake") 181 endforeach() 182 endif() 183endfunction() 184