summaryrefslogtreecommitdiffstats
path: root/cmake/QtPublicWalkLibsHelpers.cmake
blob: 038d5d06258cfbbf3936928111c733728dae81ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# Add libraries to variable ${out_libs_var} in a way that duplicates
# are added at the end. This ensures the library order needed for the
# linker.
function(__qt_internal_merge_libs out_libs_var)
    foreach(dep ${ARGN})
        list(REMOVE_ITEM ${out_libs_var} ${dep})
        list(APPEND ${out_libs_var} ${dep})
    endforeach()
    set(${out_libs_var} ${${out_libs_var}} PARENT_SCOPE)
endfunction()


# Extracts value from per-target dict key and assigns it to out_var.
# Assumes dict_name to be an existing INTERFACE target.
function(__qt_internal_get_dict_key_values out_var target_infix dict_name dict_key)
    get_target_property(values "${dict_name}" "INTERFACE_${target_infix}_${dict_key}")
    set(${out_var} "${values}" PARENT_SCOPE)
endfunction()


# Assigns 'values' to per-target dict key, including for aliases of the target.
# Assumes dict_name to be an existing INTERFACE target.
function(__qt_internal_memoize_values_in_dict target dict_name dict_key values)
    # Memoize the computed values for the target as well as its aliases.
    #
    # Aka assigns the contents of ${values} to INTERFACE_Core, INTERFACE_Qt::Core,
    # INTERFACE_Qt6::Core.
    #
    # Yes, i know it's crazy that target names are legal property names.
    #
    # Assigning for library aliases is needed to avoid multiple recomputation of values.
    # Scenario in the context of __qt_internal_walk_libs:
    # 'values' are computed for Core target and memoized to INTERFACE_Core.
    # When processing Gui, it depends on Qt::Core, but there are no values for INTERFACE_Qt::Core.
    set_target_properties(${dict_name} PROPERTIES INTERFACE_${target}_${dict_key} "${values}")

    get_target_property(versionless_alias "${target}" "_qt_versionless_alias")
    if(versionless_alias)
        __qt_internal_get_dict_key_values(
            versionless_values "${versionless_alias}" "${dict_name}" "${dict_key}")
        if(versionless_values MATCHES "-NOTFOUND$")
            set_target_properties(${dict_name}
                                  PROPERTIES INTERFACE_${versionless_alias}_${dict_key} "${values}")
        endif()
    endif()

    get_target_property(versionfull_alias "${target}" "_qt_versionfull_alias")
    if(versionfull_alias)
        __qt_internal_get_dict_key_values(
            versionfull_values "${versionfull_alias}" "${dict_name}" "${dict_key}")
        if(versionfull_values MATCHES "-NOTFOUND$")
            set_target_properties(${dict_name}
                                  PROPERTIES INTERFACE_${versionfull_alias}_${dict_key} "${values}")
        endif()
    endif()
endfunction()


# Walks a target's public link libraries recursively, and performs some actions (poor man's
# polypmorphism)
#
# Walks INTERFACE_LINK_LIBRARIES for all target types, as well as LINK_LIBRARIES for static
# library targets.
#
# out_var: the name of the variable where the result will be assigned. The result is a list of
#          libraries, mostly in generator expression form.
# rcc_objects_out_var: the name of the variable where the collected rcc object files will be
#                      assigned (for the initial target and its dependencies)
# dict_name: used for caching the results, and preventing the same target from being processed
#            twice
# operation: a string to tell the function what additional behaviors to execute.
#            'collect_libs' (default) operation is to collect linker file paths and flags.
#                           Used for prl file generation.
#            'promote_global' promotes walked imported targets to global scope.
#            'collect_targets' collects all target names (discards framework or link flags)
#
#
function(__qt_internal_walk_libs
         target out_var rcc_objects_out_var dict_name operation)
    set(collected ${ARGN})
    if(target IN_LIST collected)
        return()
    endif()
    list(APPEND collected ${target})

    if(target STREQUAL "${QT_CMAKE_EXPORT_NAMESPACE}::EntryPoint")
        # We can't (and don't need to) process EntryPoint because it brings in $<TARGET_PROPERTY:prop>
        # genexes which get replaced with $<TARGET_PROPERTY:EntryPoint,prop> genexes in the code below
        # and that causes 'INTERFACE_LIBRARY targets may only have whitelisted properties.' errors
        # with CMake versions equal to or lower than 3.18. These errors are super unintuitive to
        # debug because there's no mention that it's happening during a file(GENERATE) call.
        return()
    endif()

    if(NOT TARGET ${dict_name})
        add_library(${dict_name} INTERFACE IMPORTED GLOBAL)
    endif()
    __qt_internal_get_dict_key_values(libs "${target}" "${dict_name}" "libs")
    __qt_internal_get_dict_key_values(rcc_objects "${target}" "${dict_name}" "rcc_objects")

    if(libs MATCHES "-NOTFOUND$")
        unset(libs)
        unset(rcc_objects)
        get_target_property(target_libs ${target} INTERFACE_LINK_LIBRARIES)
        if(NOT target_libs)
            unset(target_libs)
        endif()
        get_target_property(target_type ${target} TYPE)
        if(target_type STREQUAL "STATIC_LIBRARY")
            get_target_property(link_libs ${target} LINK_LIBRARIES)
            if(link_libs)
                list(APPEND target_libs ${link_libs})
            endif()
        endif()

        # Need to record the rcc object file info not only for dependencies, but also for
        # the current target too. Otherwise the saved information is incomplete for prl static
        # build purposes.
        get_target_property(main_target_rcc_objects ${target} _qt_rcc_objects)
        if(main_target_rcc_objects)
            __qt_internal_merge_libs(rcc_objects ${main_target_rcc_objects})
        endif()

        foreach(lib ${target_libs})
            # Cannot use $<TARGET_POLICY:...> in add_custom_command.
            # Check the policy now, and replace the generator expression with the value.
            while(lib MATCHES "\\$<TARGET_POLICY:([^>]+)>")
                cmake_policy(GET ${CMAKE_MATCH_1} value)
                if(value STREQUAL "NEW")
                    set(value "TRUE")
                else()
                    set(value "FALSE")
                endif()
                string(REPLACE "${CMAKE_MATCH_0}" "${value}" lib "${lib}")
            endwhile()

            # Fix up $<TARGET_PROPERTY:FOO> expressions that refer to the "current" target.
            # Those cannot be used with add_custom_command.
            while(lib MATCHES "\\$<TARGET_PROPERTY:([^,>]+)>")
                string(REPLACE "${CMAKE_MATCH_0}" "$<TARGET_PROPERTY:${target},${CMAKE_MATCH_1}>"
                    lib "${lib}")
            endwhile()

            # Skip static plugins.
            set(_is_plugin_marker_genex "\\$<BOOL:QT_IS_PLUGIN_GENEX>")
            if(lib MATCHES "${_is_plugin_marker_genex}")
                continue()
            endif()

            # Strip any directory scope tokens.
            __qt_internal_strip_target_directory_scope_token("${lib}" lib)

            if(lib MATCHES "^\\$<TARGET_OBJECTS:")
                # Skip object files.
                continue()
            elseif(lib MATCHES "^\\$<LINK_ONLY:(.*)>$")
                set(lib_target ${CMAKE_MATCH_1})
            else()
                set(lib_target ${lib})
            endif()

            # Skip CMAKE_DIRECTORY_ID_SEP. If a target_link_libraries is applied to a target
            # that was defined in a different scope, CMake appends and prepends a special directory
            # id separator. Filter those out.
            if(lib_target MATCHES "^::@")
                continue()
            elseif(TARGET ${lib_target})
                if ("${lib_target}" MATCHES "^Qt::(.*)")
                    # If both, Qt::Foo and Foo targets exist, prefer the target name without
                    # namespace. Which one is preferred doesn't really matter. This code exists to
                    # avoid ending up with both, Qt::Foo and Foo in our dependencies.
                    set(namespaceless_lib_target "${CMAKE_MATCH_1}")
                    if(TARGET namespaceless_lib_target)
                        set(lib_target ${namespaceless_lib_target})
                    endif()
                endif()
                get_target_property(lib_target_type ${lib_target} TYPE)
                if(lib_target_type STREQUAL "INTERFACE_LIBRARY")
                    __qt_internal_walk_libs(
                        ${lib_target}
                        lib_libs_${target}
                        lib_rcc_objects_${target}
                        "${dict_name}" "${operation}" ${collected})
                    if(lib_libs_${target})
                        __qt_internal_merge_libs(libs ${lib_libs_${target}})
                        set(is_module 0)
                    endif()
                    if(lib_rcc_objects_${target})
                        __qt_internal_merge_libs(rcc_objects ${lib_rcc_objects_${target}})
                    endif()
                elseif(NOT lib_target_type STREQUAL "OBJECT_LIBRARY")

                    if(operation STREQUAL "collect_targets")
                        __qt_internal_merge_libs(libs ${lib_target})
                    else()
                        __qt_internal_merge_libs(libs "$<TARGET_LINKER_FILE:${lib_target}>")
                    endif()

                    get_target_property(target_rcc_objects "${lib_target}" _qt_rcc_objects)
                    if(target_rcc_objects)
                        __qt_internal_merge_libs(rcc_objects ${target_rcc_objects})
                    endif()

                    __qt_internal_walk_libs(
                        ${lib_target}
                        lib_libs_${target}
                        lib_rcc_objects_${target}
                        "${dict_name}" "${operation}" ${collected})
                    if(lib_libs_${target})
                        __qt_internal_merge_libs(libs ${lib_libs_${target}})
                    endif()
                    if(lib_rcc_objects_${target})
                        __qt_internal_merge_libs(rcc_objects ${lib_rcc_objects_${target}})
                    endif()
                endif()
                if(operation STREQUAL "promote_global")
                    set(lib_target_unaliased "${lib_target}")
                    get_target_property(aliased_target ${lib_target} ALIASED_TARGET)
                    if(aliased_target)
                        set(lib_target_unaliased ${aliased_target})
                    endif()

                    get_property(is_imported TARGET ${lib_target_unaliased} PROPERTY IMPORTED)
                    get_property(is_global TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL)

                    # Allow opting out of promotion. This is useful in certain corner cases
                    # like with WrapLibClang and Threads in qttools.
                    qt_internal_should_not_promote_package_target_to_global(
                        "${lib_target_unaliased}" should_not_promote)
                    if(NOT is_global AND is_imported AND NOT should_not_promote)
                        set_property(TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL TRUE)
                    endif()
                endif()
            elseif("${lib_target}" MATCHES "^Qt::(.*)")
                message(FATAL_ERROR "The ${CMAKE_MATCH_1} target is mentioned as a dependency for \
${target}, but not declared.")
            else()
                if(NOT operation STREQUAL "collect_targets")
                    set(final_lib_name_to_merge "${lib_target}")
                    if(lib_target MATCHES "/([^/]+).framework$")
                        set(final_lib_name_to_merge "-framework ${CMAKE_MATCH_1}")
                    endif()
                    __qt_internal_merge_libs(libs "${final_lib_name_to_merge}")
                endif()
            endif()
        endforeach()
        __qt_internal_memoize_values_in_dict("${target}" "${dict_name}" "libs" "${libs}")
        __qt_internal_memoize_values_in_dict("${target}" "${dict_name}"
                                             "rcc_objects" "${rcc_objects}")

    endif()
    set(${out_var} ${libs} PARENT_SCOPE)
    set(${rcc_objects_out_var} ${rcc_objects} PARENT_SCOPE)
endfunction()


# Given ${target}, collect all its private dependencies that are CMake targets.
#
# Discards non-CMake-target dependencies like linker flags or file paths.
# Does nothing when given an interface library.
#
# To be used to extract the full list of target dependencies of a library or executable.
function(__qt_internal_collect_all_target_dependencies target out_var)
    set(dep_targets "")

    get_target_property(target_type ${target} TYPE)

    if(NOT target_type STREQUAL "INTERFACE_LIBRARY")
        get_target_property(link_libs ${target} LINK_LIBRARIES)
        if(link_libs)
            foreach(lib ${link_libs})
                if(TARGET "${lib}")
                    list(APPEND dep_targets "${lib}")

                    __qt_internal_walk_libs(
                        "${lib}"
                        lib_walked_targets
                        _discarded_out_var
                        "qt_private_link_library_targets"
                        "collect_targets")

                    if(lib_walked_targets)
                        list(APPEND dep_targets ${lib_walked_targets})
                    endif()
                endif()
            endforeach()
        endif()
    endif()

    list(REMOVE_DUPLICATES dep_targets)

    set(${out_var} "${dep_targets}" PARENT_SCOPE)
endfunction()