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