22
33include (CMakeFindDependencyMacro )
44
5+ # ##################################################################################################
6+ # Data setup
7+ # ##################################################################################################
8+
9+ # The name of the package this config file is for
10+ set (PACKAGE_NAME "@PROJECT_NAME@" )
11+ string (TOLOWER "${PACKAGE_NAME} " PACKAGE_NAME_LOWER)
12+
13+ # A list of target names contained in this package
14+ set (TARGET_NAMES "@PACKAGE_TARGET_NAMES@" )
15+
16+ # A list of corresponding target files. The order of target files corresponds to the order of target
17+ # names in the TARGET_NAMES list.
18+ set (TARGET_FILES "@PACKAGE_TARGET_FILES@" )
19+ list (TRANSFORM TARGET_FILES PREPEND "${CMAKE_CURRENT_LIST_DIR} /" )
20+
21+ # Separator used in the list of dependencies for a single target
22+ set (TARGET_DEPENDENCY_SEPARATOR "@TARGET_DEPENDENCY_SEPARATOR@" )
23+
24+ # List of target dependencies. The order of these entries corresponds to the order of target names
25+ # in the TARGET_NAMES list. Each entry is itself a list where individual elements are separated by
26+ # TARGET_DEPENDENCY_SEPARATOR.
27+ set (TARGET_DEPENDENCIES "@PACKAGE_TARGET_DEPENDENCIES@" )
28+
29+ # The name of the main target (to be used under all circumstances)
30+ set (MAIN_TARGET "@PACKAGE_MAIN_TARGET@" )
31+
32+ # Components to be selected by default (that is, if no explicit COMPONENTS where specified in the
33+ # find_package command
34+ set (DEFAULT_COMPONENTS "@PROJECT_DEFAULT_COMPONENTS@" )
35+
36+ # Consistency check
37+ foreach (CURRENT_LIST IN ITEMS TARGET_NAMES TARGET_FILES TARGET_DEPENDENCIES)
38+ list (LENGTH ${CURRENT_LIST} LIST_SIZE)
39+
40+ if (NOT DEFINED N_TARGETS)
41+ set (N_TARGETS "${LIST_SIZE} " )
42+ else ()
43+ if (NOT ("${N_TARGETS} " STREQUAL "${LIST_SIZE} " ))
44+ message (
45+ FATAL_ERROR
46+ "Corrupted config file for package '${PACKAGE_NAME} ': target-related lists have inconsistent sizes"
47+ )
48+ endif ()
49+ endif ()
50+ endforeach ()
51+
552# ##################################################################################################
653# Handle potential capitalization differences in the project's name
754# ##################################################################################################
855
9- set (PROJECT_NAME "@PROJECT_NAME@" )
10- string (TOLOWER "${PROJECT_NAME} " PROJECT_NAME_LOWER)
1156string (TOLOWER "${CMAKE_FIND_PACKAGE_NAME} " CMAKE_FIND_PACKAGE_NAME_LOWER)
1257
13- if ("${PROJECT_NAME_LOWER } " STREQUAL "${CMAKE_FIND_PACKAGE_NAME_LOWER} " )
14- if (NOT ("${PROJECT_NAME } " STREQUAL "${CMAKE_FIND_PACKAGE_NAME} " ))
58+ if ("${PACKAGE_NAME_LOWER } " STREQUAL "${CMAKE_FIND_PACKAGE_NAME_LOWER} " )
59+ if (NOT ("${PACKAGE_NAME } " STREQUAL "${CMAKE_FIND_PACKAGE_NAME} " ))
1560 # The find_package call used a different capitalization of '@PROJECT_NAME@' This can lead to
1661 # issues like the config file being found on a platform with a case-insensitive file system but
1762 # not on a platform with a case-sensitive filesystem (in case the config file uses the
@@ -25,25 +70,206 @@ if("${PROJECT_NAME_LOWER}" STREQUAL "${CMAKE_FIND_PACKAGE_NAME_LOWER}")
2570 AUTHOR_WARNING
2671 "Incorrect capitalization in '${CMAKE_FIND_PACKAGE_NAME} '. It should be changed to '@PROJECT_NAME@' to avoid issues."
2772 )
28- set (PROJECT_NAME "${CMAKE_FIND_PACKAGE_NAME} " )
73+ set (PACKAGE_NAME "${CMAKE_FIND_PACKAGE_NAME} " )
2974 endif ()
3075endif ()
3176
3277# ##################################################################################################
33- # Look up all required dependencies
78+ # Helper functions & macros
3479# ##################################################################################################
3580
36- string (REGEX MATCHALL "[^;]+" SEPARATE_DEPENDENCIES "@PROJECT_DEPENDENCIES@" )
81+ macro (package_error MSG )
82+ set (${PACKAGE_NAME} _FOUND FALSE )
83+ set (${PACKAGE_NAME} _NOT_FOUND_MESSAGE "${MSG} " )
3784
38- foreach (dependency ${SEPARATE_DEPENDENCIES} )
39- string (REPLACE " " ";" args "${dependency} " )
40- find_dependency (${args} )
41- endforeach ()
85+ # Since this is a macro, this return will exit this config file
86+ return ()
87+ endmacro ()
88+
89+ function (list_contains LIST ELEMENT OUTPUT_VARIABLE )
90+ list (FIND LIST "${ELEMENT} " IDX)
91+
92+ if ("${IDX} " STREQUAL "-1" )
93+ set (${OUTPUT_VARIABLE}
94+ FALSE
95+ PARENT_SCOPE
96+ )
97+ else ()
98+ set (${OUTPUT_VARIABLE}
99+ TRUE
100+ PARENT_SCOPE
101+ )
102+ endif ()
103+ endfunction ()
104+
105+ macro (verify_all_components_are_valid )
106+ foreach (CURRENT_COMP IN LISTS ${PACKAGE_NAME} _FIND_COMPONENTS)
107+ # Verify that all specified components are actually known
108+ list_contains ("${TARGET_NAMES} " "${CURRENT_COMP} " COMPONENT_IS_KNOWN )
109+
110+ if (NOT COMPONENT_IS_KNOWN)
111+ package_error ("Unknown component '${CURRENT_COMP} ' for package '${PACKAGE_NAME} '" )
112+ endif ()
113+
114+ if ("${CURRENT_COMP} " STREQUAL "${MAIN_TARGET} " )
115+ package_error (
116+ "'${CURRENT_COMP} ' is not a component for package '${PACKAGE_NAME} ' - remove from COMPONENTS list"
117+ )
118+ endif ()
119+ endforeach ()
120+ endmacro ()
121+
122+ function (get_dependencies IDX OUTPUT_VARIABLE )
123+ list (GET TARGET_DEPENDENCIES ${IDX} DEPENDENCIES)
124+
125+ # Convert string to list by turning the custom separator to the cmake list separator (semicolon)
126+ string (REPLACE "${TARGET_DEPENDENCY_SEPARATOR} " ";" DEPENDENCIES "${DEPENDENCIES} " )
127+
128+ # Note: this causes DEPENDENCIES to effectively become an empty list, if it contains only empty
129+ # elements (a quirk of how cmake represents a list containing only a single empty string)
130+ list (REMOVE_DUPLICATES DEPENDENCIES)
131+
132+ set (${OUTPUT_VARIABLE}
133+ "${DEPENDENCIES} "
134+ PARENT_SCOPE
135+ )
136+ endfunction ()
137+
138+ function (to_internal_target TARGET OUTPUT_VARIABLE )
139+ # Check as-given
140+ list_contains ("${TARGET_NAMES} " "${TARGET} " INTERNAL_TARGET )
141+ if (INTERNAL_TARGET)
142+ set (${OUTPUT_VARIABLE}
143+ "${TARGET} "
144+ PARENT_SCOPE
145+ )
146+ else ()
147+ # Not an internal target
148+ unset (${OUTPUT_VARIABLE} PARENT_SCOPE )
149+ endif ()
150+ endfunction ()
42151
43- include ("${CMAKE_CURRENT_LIST_DIR} /@PROJECT_NAME@Targets.cmake" )
152+ macro (include_target TARGET )
153+ if (NOT ${PACKAGE_NAME} _${TARGET} _FOUND)
154+ list (FIND TARGET_NAMES "${TARGET} " "${TARGET} _IDX" )
155+
156+ get_dependencies ("${${TARGET} _IDX}" DEPENDENCIES )
157+
158+ # Dependency resolution
159+ foreach (CURRENT_DEP IN LISTS DEPENDENCIES)
160+ # CURRENT_DEP may be a space-separated list where all additional elements represent special
161+ # options to be passed to find_dependency
162+ string (REPLACE " " ";" DEPENDENCY_DETAILS ${CURRENT_DEP} )
163+ list (GET DEPENDENCY_DETAILS 0 CURRENT_DEP)
164+ list (POP_FRONT DEPENDENCY_DETAILS)
165+
166+ if (NOT ${PACKAGE_NAME} _${CURRENT_DEP} _FOUND)
167+ to_internal_target (${CURRENT_DEP} INTERNAL_TARGET )
168+
169+ if (INTERNAL_TARGET)
170+ if (DEPENDENCY_DETAILS)
171+ # Could we do something clever with this?
172+ message (
173+ AUTHOR_WARNING
174+ "Discarding extra info for dependency ${INTERNAL_TARGET} : ${DEPENDENCY_DETAILS} "
175+ )
176+ endif ()
177+ # Recurse to ensure that the dependency is included before the dependent target
178+ include_target (${INTERNAL_TARGET} )
179+ else ()
180+ # Regular external dependency
181+ find_dependency (${CURRENT_DEP} ${DEPENDENCY_DETAILS} )
182+
183+ set (${PACKAGE_NAME} _${CURRENT_DEP} _FOUND "${CURRENT_DEP} _FOUND" )
184+ endif ()
185+ endif ()
186+
187+ if (NOT ${PACKAGE_NAME} _${CURRENT_DEP} _FOUND)
188+ # Dependency is still not met -> error
189+ if (NOT ("${TARGET} " STREQUAL "${MAIN_TARGET} " ))
190+ set (FOR_COMPONENT "for component '${TARGET} ' " )
191+ endif ()
192+
193+ package_error (
194+ "Unmet dependency '${CURRENT_DEP} ' ${FOR_COMPONENT} of package '${PACKAGE_NAME} '"
195+ )
196+ endif ()
197+ endforeach ()
198+
199+ list (GET TARGET_FILES "${${TARGET} _IDX}" FILE_TO_INCLUDE)
200+
201+ if (NOT EXISTS ${FILE_TO_INCLUDE} )
202+ message (
203+ FATAL_ERROR
204+ "Corrupted config file for package '${PACKAGE_NAME} ': Expected file '${FILE_TO_INCLUDE} ' to exist, but it didn't"
205+ )
206+ endif ()
207+
208+ include ("${FILE_TO_INCLUDE} " )
209+
210+ if (${PACKAGE_NAME} _NOT_FOUND_MESSAGE)
211+ # There has been an error detected in the included target file -> abort processing
212+ return ()
213+ endif ()
214+
215+ set (${PACKAGE_NAME} _${TARGET} _FOUND TRUE )
216+ endif ()
217+ endmacro ()
44218
45219# ##################################################################################################
46- # Final checks
220+ # Preliminaries
47221# ##################################################################################################
48222
49- check_required_components ("${PROJECT_NAME} " )
223+ if (NOT ${PACKAGE_NAME} _FIND_COMPONENTS AND DEFAULT_COMPONENTS)
224+ set (${PACKAGE_NAME} _FIND_COMPONENTS "${DEFAULT_COMPONENTS} " )
225+ endif ()
226+
227+ verify_all_components_are_valid ()
228+
229+ if (NOT ${PACKAGE_NAME} _FIND_COMPONENTS AND NOT MAIN_TARGET)
230+ package_error (
231+ "No components specified for package '${PACKAGE_NAME} ' which doesn't have a non-component part"
232+ )
233+ endif ()
234+
235+ # ##################################################################################################
236+ # Include relevant targets
237+ # ##################################################################################################
238+
239+ if (MAIN_TARGET)
240+ include_target (${MAIN_TARGET} )
241+ endif ()
242+
243+ foreach (CURRENT_COMPONENT IN LISTS ${PACKAGE_NAME} _FIND_COMPONENTS)
244+ include_target (${CURRENT_COMPONENT} )
245+ endforeach ()
246+
247+ # ##################################################################################################
248+ # Final checks and cleanup
249+ # ##################################################################################################
250+
251+ check_required_components ("${PACKAGE_NAME} " )
252+
253+ # Clear up local variables to not pollute the calling scope with them This should all variables
254+ # globally defined or defined in a macro (that don't use explicit prefixing that make name clashes
255+ # unlikely)
256+ unset (PACKAGE_NAME)
257+ unset (PACKAGE_NAME_LOWER)
258+ unset (TARGET_NAMES)
259+ unset (TARGET_FILES)
260+ unset (TARGET_DEPENDENCY_SEPARATOR)
261+ unset (TARGET_DEPENDENCIES)
262+ unset (MAIN_TARGET)
263+ unset (DEFAULT_COMPONENTS)
264+ unset (N_TARGETS)
265+ unset (LIST_SIZE)
266+ unset (N_TARGETS)
267+ unset (CMAKE_FIND_PACKAGE_NAME_LOWER)
268+ unset (COMPONENT_IS_KNOWN)
269+ unset (CURRENT_COMP)
270+ unset (DEPENDENCIES)
271+ unset (CURRENT_DEP)
272+ unset (DEPENDENCY_DETAILS)
273+ unset (INTERNAL_TARGET)
274+ unset (FOR_COMPONENT)
275+ unset (FILE_TO_INCLUDE)
0 commit comments