aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/Qt6QmlDeploySupport.cmake
blob: dc16261170beda6b1e5ef28991ea6186940692c6 (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
# NOTE: This code should only ever be executed in script mode. It expects to be
#       used either as part of an install(CODE) call or called by a script
#       invoked via cmake -P as a POST_BUILD step. It would not normally be
#       included directly, it should be pulled in automatically by the deploy
#       support set up by qtbase.

cmake_minimum_required(VERSION 3.16...3.21)

# This function is currently in Technical Preview.
# Its signature and behavior might change.
function(qt_deploy_qml_imports)
    set(no_value_options
        NO_QT_IMPORTS
    )
    set(single_value_options
        TARGET
        PLUGINS_DIR     # Internal option, only used for macOS app bundle targets
        QML_DIR
        PLUGINS_FOUND   # Name of an output variable
    )
    set(multi_value_options "")
    cmake_parse_arguments(PARSE_ARGV 0 arg
        "${no_value_options}" "${single_value_options}" "${multi_value_options}"
    )

    if(arg_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Unparsed arguments: ${arg_UNPARSED_ARGUMENTS}")
    endif()

    if(NOT arg_TARGET)
        message(FATAL_ERROR "TARGET must be specified")
    endif()

    if(NOT arg_QML_DIR)
        set(arg_QML_DIR "${QT_DEPLOY_QML_DIR}")
    endif()

    if(NOT arg_PLUGINS_DIR)
        set(arg_PLUGINS_DIR "${QT_DEPLOY_PLUGINS_DIR}")
    endif()

    # The target's finalizer should have written out this file
    string(MAKE_C_IDENTIFIER "${arg_TARGET}" target_id)
    set(filename "${__QT_DEPLOY_IMPL_DIR}/deploy_qml_imports/${target_id}")
    if(__QT_DEPLOY_GENERATOR_IS_MULTI_CONFIG)
        string(APPEND filename "-${__QT_DEPLOY_ACTIVE_CONFIG}")
    endif()
    string(APPEND filename ".cmake")
    if(NOT EXISTS "${filename}")
        message(FATAL_ERROR
            "No QML imports information recorded for target ${arg_TARGET}. "
            "The target must be an executable and qt_add_qml_module() must "
            "have been called with it. If using a CMake version lower than 3.19, ensure "
            "that the executable is manually finalized with qt_finalize_target(). "
            "Missing file:\n    ${filename}"
        )
    endif()
    include(${filename})

endfunction()

function(_qt_internal_deploy_qml_imports_for_target)
    set(no_value_options
        BUNDLE
        NO_QT_IMPORTS
    )
    set(single_value_options
        IMPORTS_FILE
        PLUGINS_FOUND
        QML_DIR
        PLUGINS_DIR
    )
    set(multi_value_options "")

    cmake_parse_arguments(PARSE_ARGV 0 arg
        "${no_value_options}" "${single_value_options}" "${multi_value_options}"
    )

    if(arg_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
    endif()
    foreach(opt IN LISTS single_value_options)
        if(NOT arg_${opt})
            message(FATAL_ERROR "Required argument not provided: ${opt}")
        endif()
    endforeach()

    include("${arg_IMPORTS_FILE}")

    macro(_qt_internal_parse_qml_imports_entry prefix index)
        cmake_parse_arguments("${prefix}"
            ""
            "CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;LINKTARGET"
            ""
            ${qml_import_scanner_import_${index}}
        )
    endmacro()

    get_filename_component(install_prefix_abs "${QT_DEPLOY_PREFIX}" ABSOLUTE)
    set(plugins_found "")

    if(__QT_DEPLOY_POST_BUILD)
        message(STATUS "Running macOS bundle QML support POST_BUILD routine.")
    endif()

    # Parse the generated cmake file. It is possible for the scanner to find no
    # usage of QML, in which case the import count is 0.
    if(qml_import_scanner_imports_count GREATER 0)
        set(processed_names "")
        math(EXPR last_index "${qml_import_scanner_imports_count} - 1")
        foreach(index RANGE 0 ${last_index})
            _qt_internal_parse_qml_imports_entry(entry ${index})

            if("${entry_NAME}" STREQUAL "")
                message(WARNING "No NAME at scan index ${index}: ${imports_file}")
                continue()
            endif()

            # A plugin might have multiple entries (e.g. for different versions).
            # Only copy it once.
            if("${entry_NAME}" IN_LIST processed_names)
                continue()
            endif()
            # Even if we skip this one, we don't need to process it again
            list(APPEND processed_names ${entry_NAME})

            if("${entry_PATH}" STREQUAL "" OR
                "${entry_PLUGIN}" STREQUAL "" OR
                "${entry_RELATIVEPATH}" STREQUAL "")
                # These might be a valid QML module, but not have a plugin library.
                # We only care about modules that have a plugin we need to copy.
                continue()
            endif()

            if(arg_NO_QT_IMPORTS AND
                    "${entry_LINKTARGET}" MATCHES "${__QT_CMAKE_EXPORT_NAMESPACE}::")
                continue()
            endif()

            # For installation, we want the qmldir file and its plugin. If the
            # CMake project generating the plugin sets version details on its
            # CMake target, we might have symlinks and version numbers in the
            # file names, so account for those. There should never be plugin
            # libraries for more than one QML module in the directory, so we
            # shouldn't need to worry about matching plugins we don't want.
            set(dest_qmldir "${QT_DEPLOY_PREFIX}/${arg_QML_DIR}/${entry_RELATIVEPATH}")
            if(arg_BUNDLE)
                set(dest_plugin "${QT_DEPLOY_PREFIX}/${arg_PLUGINS_DIR}")
            else()
                set(dest_plugin "${dest_qmldir}")
            endif()

            file(INSTALL "${entry_PATH}/qmldir" DESTINATION "${dest_qmldir}")

            if(__QT_DEPLOY_POST_BUILD)
                # We are being invoked as a post-build step. The plugin might
                # not exist yet, so we can't even glob for it, let alone copy
                # it. We know what its name should be though, so we can create
                # a symlink to where it will eventually be, which will be enough
                # to allow it to run from the build tree. It won't matter if
                # the plugin gets updated later in the build, the symlink will
                # still be pointing at the right location.
                # In theory, this could be possible for any platform that
                # supports symlinks (which all do in some form now, even
                # Windows if the right permissions are set), but we only really
                # expect to need this for macOS app bundles.
                set(final_destination "${dest_qmldir}/lib${entry_PLUGIN}.dylib")
                message(STATUS "Symlinking: ${final_destination}")
                file(CREATE_LINK
                    "${entry_PATH}/lib${entry_PLUGIN}.dylib"
                    "${final_destination}"
                    SYMBOLIC
                )

                # We don't add this plugin to plugins_found because we don't
                # actually make a copy of the plugin. We don't want the caller
                # thinking they should further process what would still be the
                # original plugin in the build tree.
                continue()
            endif()

            file(GLOB files LIST_DIRECTORIES false "${entry_PATH}/*${entry_PLUGIN}*")
            list(FILTER files
                 INCLUDE REGEX "^(.*/)?(lib)?${entry_PLUGIN}.*\\.(so|dylib|dll)(\\.[0-9]+)*$")
            file(INSTALL ${files} DESTINATION "${dest_plugin}" USE_SOURCE_PERMISSIONS)

            get_filename_component(dest_plugin_abs "${dest_plugin}" ABSOLUTE)
            file(RELATIVE_PATH rel_path "${install_prefix_abs}" "${dest_plugin_abs}")
            foreach(file IN LISTS files)
                get_filename_component(filename "${file}" NAME)
                list(APPEND plugins_found "${rel_path}/${filename}")
            endforeach()

            if(arg_BUNDLE)
                # Actual plugin binaries will be in PlugIns, but qmldir files
                # expect them to be in the same directory as themselves
                # (i.e. under Resources/qml/...). Add a symlink at the place
                # the qmldir expects the binary to be. This arrangement keeps
                # binaries under PlugIns and non-binaries under Resources,
                # which is required for code signing to work properly.
                get_filename_component(dest_qmldir_abs "${dest_qmldir}" ABSOLUTE)
                file(RELATIVE_PATH rel_path "${dest_qmldir_abs}" "${dest_plugin_abs}")
                foreach(plugin_file IN LISTS files)
                    get_filename_component(filename "${plugin_file}" NAME)

                    set(final_destination "${dest_qmldir}/${filename}")
                    message(STATUS "Symlinking: ${final_destination}")
                    file(CREATE_LINK "${rel_path}/${filename}" "${final_destination}" SYMBOLIC)
                endforeach()
            endif()
        endforeach()
    endif()

    set(${arg_PLUGINS_FOUND} ${plugins_found} PARENT_SCOPE)

endfunction()

function(_qt_internal_show_skip_qml_runtime_deploy_message)
    # Don't show the message in static Qt builds, it can be misleading, because we still
    # run qmlimportscanner / link the static qml plguins into the binary despite not having
    # a qml deployment step.
    if(__QT_DEPLOY_IS_SHARED_LIBS_BUILD)
        message(STATUS "Skipping QML module deployment steps.")
    endif()
endfunction()