aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/Qt6QmlMacros.cmake
blob: f0235c9b41a691ec7236248e515fa361eeb82d51 (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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
#
# Q6QmlMacros
#

#
# Create a Qml Module. Arguments:
#
# URI: Declares the module identifier of the module. The module identifier is
# the (dotted URI notation) identifier for the module, which must match the
# module's install path. (REQUIRED)
#
# VERSION: The module's version. (REQUIRED)
#
# TARGET_PATH: Overwrite the generated target path. By default the target path
#   is generated from the URI by replacing the '.' with a '/'. However, under
#   certain circumstance this may not be enough. Use this argument to provide
#   a replacement. (OPTIONAL)
#
# RESOURCE_PREFIX: Resource Prefix to be used when generating a static library.
#   When building a static library, the qmldir file is embedded into the library
#   using rcc. It is is also used by the Qt Quick Compiler to embed compiled
#   Qml files into a shared or static library. If none is supplied we will
#   generate the following prefix: /org.qt-project/imports/${target_path}.
#   (OPTIONAL)
#
# OUTPUT_DIRECTORY: If the module is not to be build under
#   ${CMAKE_CURRENT_BINARY_DIR}. This ensures the qmldir file is copied to the
#   right location.  (OPTIONAL)
#
# INSTALL_LOCATION: Intended installation directory for this module. If no
#   value is supplied, the default installation path will be ${Qt6_DIR}/qml.
#   (OPTIONAL).
#
# DO_NOT_INSTALL: When present, will not install the supporting files.
#
# SOURCES: List of C++ sources. (OPTIONAL)
#
# DEPENDENCIES: List of QML Module depdencies and their versions. The module
#   and its version must be separated via a slash(/). E.g. QtQuick/2.0
#
# QML_FILES: List of Qml files. See qt6_target_qml_files for more information
#   on how to specify additional properties on qml files. (OPTIONAL)
#
# CLASSNAME: Provides the class name of the C++ plugin used by the module. This
#   information is required for all the QML modules that depend on a C++ plugin
#   for additional functionality. Qt Quick applications built with static
#   linking cannot resolve the module imports without this information.
#   (REQUIRED for static targets)
#
# DESIGNER_SUPPORTED: Specify this argument if the plugin is supported by Qt
#   Quick Designer. By default, the plugin will not be supported. (OPTIONAL)
#
# TYPEINFO: Path to a file which declares a type description file for the module
#   that can be read by QML tools such as Qt Creator to access information about
#   the types defined by the module's plugins. (OPTIONAL)
#
# IMPORTS: List of other Qml Modules that this module imports. (OPTIONAL)
#
# RESOURCE_EXPORT: In static builds, when Qml files are processed via the Qt
#   Quick Compiler generate a separate static library that will be linked in
#   as an Interface. Supply an output variable to perform any custom actions
#   on these extra generated targets.
#

function(qt6_add_qml_module target)

    set(args_optional
        DESIGNER_SUPPORTED
        DO_NOT_INSTALL
    )

    if (QT_BUILDING_QT)
        list(APPEND args_optional DO_NOT_CREATE_TARGET)
    endif()

    set(args_single
        RESOURCE_PREFIX
        URI
        TARGET_PATH
        VERSION
        OUTPUT_DIRECTORY
        INSTALL_LOCATION
        CLASSNAME
        TYPEINFO
        RESOURCE_EXPORT
    )

    set(args_multi
       SOURCES
       QML_FILES
       IMPORTS
       DEPENDENCIES
    )

    cmake_parse_arguments(arg
       "${args_optional}"
       "${args_single}"
       "${args_multi}"
       ${ARGN}
    )

    if (NOT arg_URI)
        message(FATAL_ERROR "qt6_add_qml_module called without a module URI. Please specify one using the URI argument.")
    endif()

    if (NOT arg_VERSION)
        message(FATAL_ERROR "qt6_add_qml_module called without a module version. Please specify one using the VERSION argument.")
    endif()

    if (NOT "${arg_VERSION}" MATCHES "[0-9]+\\.[0-9]+")
        message(FATAL_ERROR "qt6_add_qml_module called with an invalid version argument: '${arg_VERSION}'. Expected version style: VersionMajor.VersionMinor.")
    endif()

    if (NOT BUILD_SHARED_LIBS AND NOT arg_CLASSNAME)
        message(FATAL_ERROR "qt6_add_qml_module Static builds of Qml modules require a class name, none was provided. Please specify one using the CLASSNAME argument.")
    endif()

    if (arg_DO_NOT_CREATE_TARGET AND NOT TARGET ${target})
        message(FATAL_ERROR "qt6_add_qml_module called with DO_NOT_CREATE_TARGET, but the given target '${target}' is not a cmake target")
    endif()

    if (arg_DO_NOT_CREATE_TARGET)
        get_target_property(target_type ${target} TYPE)
        if (target_type STREQUAL "STATIC_LIBRARY")
            set(is_static TRUE)
        elseif(target_type STREQUAL "MODULE_LIBRARY")
            set(is_static FALSE)
        else()
            message(FATAL_ERROR "qt6_add_qml_module called with DO_NOT_CREATE_TARGET, but target '${target}' is neither a static or a module library.")
        endif()
    else()
        if(NOT BUILD_SHARED_LIBS)
            add_library(${target} STATIC)
            set(is_static TRUE)
        else()
            add_library(${target} MODULE)
            set(is_static FALSE)
        endif()
    endif()

    if (NOT arg_TARGET_PATH)
        string(REPLACE "." "/" arg_TARGET_PATH ${arg_URI})
    endif()

    if (NOT arg_RESOURCE_PREFIX)
        set(arg_RESOURCE_PREFIX "/org.qt-project/imports")
    endif()

    if (NOT arg_INSTALL_LOCATION)
        set(arg_INSTALL_LOCATION "${Qt6_DIR}/../../../qml/${arg_TARGET_PATH}")
    endif()

    set_target_properties(${target}
        PROPERTIES
            QT_QML_MODULE_TARGET_PATH ${arg_TARGET_PATH}
            QT_QML_MODULE_URI ${arg_URI}
            QT_RESOURCE_PREFIX ${arg_RESOURCE_PREFIX}/${arg_TARGET_PATH}
            QT_QML_MODULE_VERSION ${arg_VERSION}
            QT_QML_MODULE_INSTALL_DIR ${arg_INSTALL_LOCATION}
            QT_QML_MODULE_RESOURCE_EXPORT "${arg_RESOURCE_EXPORT}"
    )

    if (arg_OUTPUT_DIRECTORY AND NOT DO_NOT_CREATE_TARGET)
        set_target_properties(${target}
            PROPERTIES
                LIBRARY_OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
                ARCHIVE_OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
         )
    endif()
    if (arg_OUTPUT_DIRECTORY)
        set(target_output_dir ${arg_OUTPUT_DIRECTORY})
    else()
        if(is_static)
            get_target_property(target_output_dir ${target} ARCHIVE_OUTPUT_DIRECTORY)
        else()
            get_target_property(target_output_dir ${target} LIBRARY_OUTPUT_DIRECTORY)
        endif()
    endif()

    if (arg_SOURCES)
        target_sources(${target} PRIVATE ${arg_SOURCES})
    endif()

    # Tracker so we can generate unique resource names for multiple
    # target_qml_files() calls.
    set_target_properties(${target} PROPERTIES QT6_QML_MODULE_ADD_QML_FILES_COUNT 1)

    # Generate qmldir file
    set(qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/qmldir")
    set_target_properties(${target} PROPERTIES QT_QML_MODULE_QMLDIR_FILE ${qmldir_file})
    set(qmldir_file_contents "module ${arg_URI}\n")
    string(APPEND qmldir_file_contents "plugin ${target}\n")
    if (arg_CLASSNAME)
        string(APPEND qmldir_file_contents "classname ${arg_CLASSNAME}\n")
    endif()
    if (arg_DESIGNER_SUPPORTED)
        string(APPEND qmldir_file_contents "designersupported\n")
    endif()
    if (arg_TYPEINFO)
        string(APPEND qmldir_file_contents "typeinfo ${arg_TYPEINFO}\n")
    endif()
    foreach(import IN LISTS arg_IMPORTS)
        string(APPEND qmldir_file_contents "import ${import}\n")
    endforeach()

    foreach(dependency IN LISTS arg_DEPENDENCIES)
        string(FIND ${dependency} "/" slash_position REVERSE)
        if (slash_position EQUAL -1)
            message(FATAL_ERROR "Dependencies should follow the format 'ModuleName/VersionMajor.VersionMinor'")
        endif()
        string(SUBSTRING ${dependency} 0 ${slash_position} dep_module)
        math(EXPR slash_position "${slash_position} + 1")
        string(SUBSTRING ${dependency} ${slash_position} -1 dep_version)
        if (NOT dep_version MATCHES "[0-9]+\\.[0-9]+")
            message(FATAL_ERROR "Invalid module dependency version number. Expected VersionMajor.VersionMinor.")
        endif()
        string(APPEND qmldir_file_contents "dependency ${dep_module} ${dep_version}\n")
    endforeach()

    file(WRITE ${qmldir_file} ${qmldir_file_contents})

    # Process qml files
    if (arg_QML_FILES)
        qt6_target_qml_files(${target} FILES ${arg_QML_FILES})
    endif()

    # Embed qmldir in static builds
    if (is_static)
        string(REPLACE "/" "_" qmldir_resource_name ${arg_TARGET_PATH})
        string(APPEND qmldir_resource_name "_qmldir")

        set_source_files_properties("${qmldir_file}"
            PROPERTIES QT_RESOURCE_ALIAS "qmldir"
        )

        set(resource_target "Foo")
        QT6_ADD_RESOURCES(${target} ${qmldir_resource_name}
            PREFIX ${target_resource_prefix}
            FILES "${qmldir_file}"
            OUTPUT_TARGETS resource_targets
        )

        if (resource_targets AND arg_RESOURCE_EXPORT)
            install(TARGETS ${resource_targets}
                EXPORT "${arg_RESOURCE_EXPORT}"
                DESTINATION ${arg_INSTALL_LOCATION}
            )
        endif()
    else()
        # Copy QMLDIR file to build directory
        add_custom_command(TARGET ${target} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
                ${qmldir_file}
                ${target_output_dir}
        )

        # Install QMLDIR file
        if (NOT DO_NOT_INSTALL)
            install(FILES ${qmldir_file}
                DESTINATION ${arg_INSTALL_LOCATION}
            )
        endif()
    endif()

    # Install and Copy plugin.qmltypes if exists
    set(target_plugin_qmltypes "${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes")
    if (EXISTS ${target_plugin_qmltypes})
        file(APPEND ${qmldir_file} "typeinfo plugins.qmltypes\n")
        if (NOT arg_DO_NOT_INSTALL)
            install(FILES ${target_plugin_qmltypes}
                DESTINATION ${arg_INSTALL_LOCATION}
            )
        endif()

        add_custom_command(TARGET ${target} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
                ${target_plugin_qmltypes}
                ${target_output_dir}
        )
    endif()

    # Copy/Install type info file
    if (EXISTS ${arg_TYPEINFO})
        if (NOT arg_DO_NOT_INSTALL)
            install(FILES ${arg_TYPEINFO}
                DESTINATION ${arg_INSTALL_LOCATION}
            )
        endif()

        add_custom_command(TARGET ${target} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
                ${arg_TYPEINFO}
                ${target_output_dir}
        )
    endif()
endfunction()


#
# Add Qml files (.qml,.js,.mjs) to a Qml module. This will also append the
# qml files to the qmldir file of the module. Two source file properties can
# be used to control the generated qmldir entry.
#
# QT_QML_SOURCE_VERSION: Version for this qml file. If not present the module
#   version will be used.
# QT_QML_SOURCE_TYPENAME: Override the file's type name. If not present the
#   type name will be deduced using the file's basename.
# QT_QML_SINGLETON_TYPE: Set to true if this qml file contains a singleton
#   type.
# QT_QML_SOURCE_INSTALL: When set to true, the file will be installed alongside
#   the module.
# QT_QML_INTERNAL_TYPE: When set to true, the type specified by
#   QT_QML_SOURCE_TYPENAME will not be available to users of this module.
#
#   e.g.:
#       set_source_files_properties(my_qml_file.qml
#           PROPERTIES
#               QT_QML_SOURCE_VERSION 2.0
#               QT_QML_SOURCE_TYPENAME MyQmlFile
#
#       qt6_target_qml_files(my_qml_module
#           FILES
#               my_qml_file.qml
#       )
#
# Will produce the following entry in the qmldir file
#
#   MyQmlFile 2.0 my_qml_file.qml
#
#
function(qt6_target_qml_files target)

    cmake_parse_arguments(arg "" "" "FILES" ${ARGN})
    get_target_property(resource_count ${target} QT6_QML_MODULE_ADD_QML_FILES_COUNT)
    get_target_property(qmldir_file ${target} QT_QML_MODULE_QMLDIR_FILE)
    if (NOT qmldir_file)
        message(FATAL_ERROR "qt6_target_qml_file: ${target} is not a Qml module")
    endif()

    if (NOT arg_FILES)
        return()
    endif()
    math(EXPR new_count "${resource_count} + 1")
    set_target_properties(${target} PROPERTIES QT6_QML_MODULE_ADD_QML_FILES_COUNT ${new_count})

    qt6_add_resources(${target} "qml_files${new_count}"
        FILES ${arg_FILES}
        OUTPUT_TARGETS resource_targets
    )
    get_target_property(target_resource_export ${target} QT_QML_MODULE_RESOURCE_EXPORT)
    get_target_property(qml_module_install_dir ${target} QT_QML_MODULE_INSTALL_DIR)
    if (resource_targets)
        install(TARGETS ${resource_targets}
            EXPORT "${target_resource_export}"
            DESTINATION ${qm_module_install_dir}
        )
    endif()

    set(file_contents "")
    foreach(qml_file IN LISTS arg_FILES)
        get_source_file_property(qml_file_version ${qml_file} QT_QML_SOURCE_VERSION)
        get_source_file_property(qml_file_typename ${qml_file} QT_QML_SOURCE_TYPENAME)
        get_source_file_property(qml_file_singleton ${qml_file} QT_QML_SINGLETON_TYPE)
        get_source_file_property(qml_file_internal ${qml_file} QT_QML_INTERNAL_TYPE)
        get_source_file_property(qml_file_install ${qml_file} QT_QML_SOURCE_INSTALL)
        get_target_property(qml_module_version ${target} QT_QML_MODULE_VERSION)

        if (NOT qml_file_version)
            set(qml_file_version ${qml_module_version})
        endif()

        if (NOT qml_file_typename)
            get_filename_component(qml_file_typename ${qml_file} NAME_WLE)
        endif()

        if (qml_file_singleton)
            string(APPEND file_contents "[singleton] ")
        endif()

        string(APPEND file_contents "${qml_file_typename} ${version} ${qml_file}\n")

        if (qml_file_internal)
            string(APPEND file_contents "internal ${qml_file_typename} ${qml_file}\n")
        endif()

        if (qml_file_install)
            install(FILES ${qml_file} DESTINATION ${qml_module_install_dir})
        endif()

    endforeach()
    file(APPEND ${qmldir_file} ${file_contents})
endfunction()