summaryrefslogtreecommitdiffstats
path: root/cmake/QtBuildRepoExamplesHelpers.cmake
blob: 6802d81323200c455499112def55da4302206b2f (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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause

macro(qt_examples_build_begin)
    set(options EXTERNAL_BUILD)
    set(singleOpts "")
    set(multiOpts DEPENDS)

    cmake_parse_arguments(arg "${options}" "${singleOpts}" "${multiOpts}" ${ARGN})

    # Examples are not unity-ready.
    set(CMAKE_UNITY_BUILD OFF)

    # Skip running deployment steps when the developer asked to deploy a minimal subset of examples.
    # Each example can then decide whether it wants to be deployed as part of the minimal subset
    # by unsetting the QT_INTERNAL_SKIP_DEPLOYMENT variable before its qt_internal_add_example call.
    # This will be used by our CI.
    if(NOT DEFINED QT_INTERNAL_SKIP_DEPLOYMENT AND QT_DEPLOY_MINIMAL_EXAMPLES)
        set(QT_INTERNAL_SKIP_DEPLOYMENT TRUE)
    endif()

    # Use by qt_internal_add_example.
    set(QT_EXAMPLE_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")

    if(QT_BUILD_STANDALONE_EXAMPLES)
        # Find all qt packages, so that the various if(QT_FEATURE_foo) add_subdirectory()
        # conditions have correct values, regardless whether we will use ExternalProjects or not.
        qt_internal_find_standalone_parts_config_files()
    endif()

    if(arg_EXTERNAL_BUILD AND QT_BUILD_EXAMPLES_AS_EXTERNAL)
        # Examples will be built using ExternalProject.
        # We depend on all plugins built as part of the current repo as well as current repo's
        # dependencies plugins, to prevent opportunities for
        # weird errors associated with loading out-of-date plugins from
        # unrelated Qt modules.
        # We also depend on all targets from this repo's src and tools subdirectories
        # to ensure that we've built anything that a find_package() call within
        # an example might use. Projects can add further dependencies if needed,
        # but that should rarely be necessary.
        set(QT_EXAMPLE_DEPENDENCIES ${qt_repo_plugins_recursive} ${arg_DEPENDS})

        if(TARGET ${qt_repo_targets_name}_src)
            list(APPEND QT_EXAMPLE_DEPENDENCIES ${qt_repo_targets_name}_src_for_examples)
        endif()

        if(TARGET ${qt_repo_targets_name}_tools)
            list(APPEND QT_EXAMPLE_DEPENDENCIES ${qt_repo_targets_name}_tools)
        endif()

        set(QT_IS_EXTERNAL_EXAMPLES_BUILD TRUE)

        string(TOLOWER ${PROJECT_NAME} project_name_lower)
        if(NOT TARGET examples)
            if(QT_BUILD_EXAMPLES_BY_DEFAULT)
                add_custom_target(examples ALL)
            else()
                add_custom_target(examples)
            endif()
        endif()
        if(NOT TARGET examples_${project_name_lower})
            add_custom_target(examples_${project_name_lower})
            add_dependencies(examples examples_${project_name_lower})
        endif()

        include(ExternalProject)
    else()
        # This repo has not yet been updated to build examples in a separate
        # build from this main build, or we can't use that arrangement yet.
        # Build them directly as part of the main build instead for backward
        # compatibility.
        if(NOT BUILD_SHARED_LIBS)
            # Ordinarily, it would be an error to call return() from within a
            # macro(), but in this case we specifically want to return from the
            # caller's scope if we are doing a static build and the project
            # isn't building examples in a separate build from the main build.
            # Configuring static builds requires tools that are not available
            # until build time.
            return()
        endif()

        if(NOT QT_BUILD_EXAMPLES_BY_DEFAULT)
            set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL TRUE)
        endif()
    endif()

    # TODO: Change this to TRUE when all examples in all repos are ported to use
    # qt_internal_add_example.
    # We shouldn't need to call qt_internal_set_up_build_dir_package_paths when
    # QT_IS_EXTERNAL_EXAMPLES_BUILD is TRUE.
    # Due to not all examples being ported, if we don't
    # call qt_internal_set_up_build_dir_package_paths -> set(QT_NO_CREATE_TARGETS TRUE) we'll get
    # CMake configuration errors saying we redefine Qt targets because we both build them and find
    # them as part of find_package.
    set(__qt_all_examples_ported_to_external_projects FALSE)

    # Examples that are built as part of the Qt build need to use the CMake config files from the
    # build dir, because they are not installed yet in a prefix build.
    # Prepending to CMAKE_PREFIX_PATH helps find the initial Qt6Config.cmake.
    # Prepending to QT_BUILD_CMAKE_PREFIX_PATH helps find components of Qt6, because those
    # find_package calls use NO_DEFAULT_PATH, and thus CMAKE_PREFIX_PATH is ignored.
    # Prepending to CMAKE_FIND_ROOT_PATH ensures the components are found while cross-compiling
    # without setting CMAKE_FIND_ROOT_PATH_MODE_PACKAGE to BOTH.
    if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD OR NOT __qt_all_examples_ported_to_external_projects)
        qt_internal_set_up_build_dir_package_paths()
        list(PREPEND CMAKE_FIND_ROOT_PATH "${QT_BUILD_DIR}")
        list(PREPEND QT_BUILD_CMAKE_PREFIX_PATH "${QT_BUILD_DIR}/${INSTALL_LIBDIR}/cmake")
    endif()

    # Because CMAKE_INSTALL_RPATH is empty by default in the repo project, examples need to have
    # it set here, so they can run when installed.
    # This means that installed examples are not relocatable at the moment. We would need to
    # annotate where each example is installed to, to be able to derive a relative rpath, and it
    # seems there's no way to query such information from CMake itself.
    set(CMAKE_INSTALL_RPATH "${_default_install_rpath}")

    install(CODE "
# Backup CMAKE_INSTALL_PREFIX because we're going to change it in each example subdirectory
# and restore it after all examples are processed so that QtFooToolsAdditionalTargetInfo.cmake
# files are installed into the original install prefix.
set(_qt_internal_examples_cmake_install_prefix_backup \"\${CMAKE_INSTALL_PREFIX}\")
")
endmacro()

macro(qt_examples_build_end)
    # We use AUTOMOC/UIC/RCC in the examples. When the examples are part of the
    # main build rather than being built in their own separate project, make
    # sure we do not fail on a fresh Qt build (e.g. the moc binary won't exist
    # yet because it is created at build time).

    _qt_internal_collect_buildsystem_targets(targets
            "${CMAKE_CURRENT_SOURCE_DIR}" EXCLUDE UTILITY ALIAS)

    foreach(target ${targets})
        qt_autogen_tools(${target} ENABLE_AUTOGEN_TOOLS "moc" "rcc")
        if(TARGET Qt::Widgets)
            qt_autogen_tools(${target} ENABLE_AUTOGEN_TOOLS "uic")
        endif()
        set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
    endforeach()

    install(CODE "
# Restore backed up CMAKE_INSTALL_PREFIX.
set(CMAKE_INSTALL_PREFIX \"\${_qt_internal_examples_cmake_install_prefix_backup}\")
")

    set(CMAKE_UNITY_BUILD ${QT_UNITY_BUILD})
endmacro()

# Allows building an example either as an ExternalProject or in-tree with the Qt build.
# Also allows installing the example sources.
function(qt_internal_add_example subdir)
    # Don't show warnings for examples that were added via qt_internal_add_example.
    # Those that are added via add_subdirectory will see the warning, due to the parent scope
    # having the variable set to TRUE.
    if(QT_FEATURE_developer_build AND NOT QT_NO_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY_WARNING)
        set(QT_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY FALSE)
    endif()

    # Pre-compute unique example name based on the subdir, in case of target name clashes.
    qt_internal_get_example_unique_name(unique_example_name "${subdir}")

    # QT_INTERNAL_NO_CONFIGURE_EXAMPLES is not meant to be used by Qt builders, it's here for faster
    # testing of the source installation code path for build system engineers.
    if(NOT QT_INTERNAL_NO_CONFIGURE_EXAMPLES)
        if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD)
            qt_internal_add_example_in_tree("${subdir}")
        else()
            qt_internal_add_example_external_project("${subdir}"
                NAME "${unique_example_name}")
        endif()
    endif()

    if(QT_INSTALL_EXAMPLES_SOURCES)
        string(TOLOWER ${PROJECT_NAME} project_name_lower)

        qt_internal_install_example_sources("${subdir}"
            NAME "${unique_example_name}"
            REPO_NAME "${project_name_lower}")
    endif()
endfunction()

# Gets the install prefix where an example should be installed.
# Used for computing the final installation path.
function(qt_internal_get_example_install_prefix out_var)
    # Allow customizing the installation path of the examples. Will be used in CI.
    if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX)
        set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}")
    elseif(QT_BUILD_STANDALONE_EXAMPLES)
        # TODO: We might need to reset and pipe through an empty CMAKE_STAGING_PREFIX if we ever
        # try to run standalone examples in the CI when cross-compiling, similar how it's done in
        # qt_internal_set_up_fake_standalone_parts_install_prefix.
        qt_internal_get_fake_standalone_install_prefix(qt_example_install_prefix)
    else()
        set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}")
    endif()
    file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)
    set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE)
endfunction()

# Gets the install prefix where an example's sources should be installed.
# Used for computing the final installation path.
function(qt_internal_get_examples_sources_install_prefix out_var)
    # Allow customizing the installation path of the examples source specifically.
    if(QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX)
        set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX}")
    else()
        qt_internal_get_example_install_prefix(qt_example_install_prefix)
    endif()
    file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)
    set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE)
endfunction()

# Gets the relative path of an example, relative to the current repo's examples source dir.
# QT_EXAMPLE_BASE_DIR is meant to be already set in a parent scope.
function(qt_internal_get_example_rel_path out_var subdir)
    file(RELATIVE_PATH example_rel_path
         "${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")
    set(${out_var} "${example_rel_path}" PARENT_SCOPE)
endfunction()

# Gets the install path where an example should be installed.
function(qt_internal_get_example_install_path out_var subdir)
    qt_internal_get_example_install_prefix(qt_example_install_prefix)
    qt_internal_get_example_rel_path(example_rel_path "${subdir}")
    set(example_install_path "${qt_example_install_prefix}/${example_rel_path}")

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

# Gets the install path where an example's sources should be installed.
function(qt_internal_get_examples_sources_install_path out_var subdir)
    qt_internal_get_examples_sources_install_prefix(qt_example_install_prefix)
    qt_internal_get_example_rel_path(example_rel_path "${subdir}")
    set(example_install_path "${qt_example_install_prefix}/${example_rel_path}")

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

# Get the unique name of an example project based on its subdir or explicitly given name.
# Makes the name unique by appending a short sha1 hash of the relative path of the example
# if a target of the same name already exist.
function(qt_internal_get_example_unique_name out_var subdir)
    qt_internal_get_example_rel_path(example_rel_path "${subdir}")

    set(name "${subdir}")

    # qtdeclarative has calls like qt_internal_add_example(imagine/automotive)
    # so passing a nested subdirectory. Custom targets (and thus ExternalProjects) can't contain
    # slashes, so extract the last part of the path to be used as a name.
    if(name MATCHES "/")
        string(REPLACE "/" ";" exploded_path "${name}")
        list(POP_BACK exploded_path last_dir)
        if(NOT last_dir)
            message(FATAL_ERROR "Example subdirectory must have a name.")
        else()
            set(name "${last_dir}")
        endif()
    endif()

    # Likely a clash with an example subdir ExternalProject custom target of the same name in a
    # top-level build.
    if(TARGET "${name}")
        string(SHA1 rel_path_hash "${example_rel_path}")
        string(SUBSTRING "${rel_path_hash}" 0 4 short_hash)
        set(name "${name}-${short_hash}")
    endif()

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

# Use old non-ExternalProject approach, aka build in-tree with the Qt build.
function(qt_internal_add_example_in_tree subdir)
    # Unset the default CMAKE_INSTALL_PREFIX that's generated in
    #   ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
    # so we can override it with a different value in
    #   ${CMAKE_CURRENT_BINARY_DIR}/${subdir}/cmake_install.cmake
    #
    install(CODE "
# Unset the CMAKE_INSTALL_PREFIX in the current cmake_install.cmake file so that it can be
# overridden in the included add_subdirectory-specific cmake_install.cmake files instead.
# Also unset the deployment prefix, so it can be recomputed for each example subdirectory.
unset(CMAKE_INSTALL_PREFIX)
unset(QT_DEPLOY_PREFIX)
")

    # Override the install prefix in the subdir cmake_install.cmake, so that
    # relative install(TARGETS DESTINATION) calls in example projects install where we tell them to.
    qt_internal_get_example_install_path(example_install_path "${subdir}")
    set(CMAKE_INSTALL_PREFIX "${example_install_path}")

    # Make sure unclean example projects have their INSTALL_EXAMPLEDIR set to "."
    # Won't have any effect on example projects that don't use INSTALL_EXAMPLEDIR.
    # This plus the install prefix above takes care of installing examples where we want them to
    # be installed, while allowing us to remove INSTALL_EXAMPLEDIR code in each example
    # incrementally.
    # TODO: Remove once all repositories use qt_internal_add_example instead of add_subdirectory.
    set(QT_INTERNAL_SET_EXAMPLE_INSTALL_DIR_TO_DOT ON)

    add_subdirectory(${subdir})
endfunction()

function(qt_internal_add_example_external_project subdir)
    set(options "")
    set(singleOpts NAME)
    set(multiOpts "")

    cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}")

    _qt_internal_get_build_vars_for_external_projects(
        CMAKE_DIR_VAR qt_cmake_dir
        PREFIXES_VAR qt_prefixes
        ADDITIONAL_PACKAGES_PREFIXES_VAR qt_additional_packages_prefixes
    )

    list(APPEND QT_ADDITIONAL_PACKAGES_PREFIX_PATH "${qt_additional_packages_prefixes}")

    set(vars_to_pass_if_defined)
    set(var_defs)
    if(QT_HOST_PATH OR CMAKE_CROSSCOMPILING)
        list(APPEND var_defs
            -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${qt_cmake_dir}/qt.toolchain.cmake
        )
    else()
        list(PREPEND CMAKE_PREFIX_PATH ${qt_prefixes})

        # Setting CMAKE_SYSTEM_NAME affects CMAKE_CROSSCOMPILING, even if it is
        # set to the same as the host, so it should only be set if it is different.
        # See https://gitlab.kitware.com/cmake/cmake/-/issues/21744
        if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND
           NOT CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
            list(APPEND vars_to_pass_if_defined CMAKE_SYSTEM_NAME:STRING)
        endif()
    endif()

    # We we need to augment the CMAKE_MODULE_PATH with the current repo cmake build dir, to find
    # files like FindWrapBundledFooConfigExtra.cmake.
    set(module_paths "${qt_prefixes}")
    list(TRANSFORM module_paths APPEND "/${INSTALL_LIBDIR}/cmake/${QT_CMAKE_EXPORT_NAMESPACE}")
    list(APPEND CMAKE_MODULE_PATH ${module_paths})

    # Pass additional paths where qml plugin config files should be included by Qt6QmlPlugins.cmake.
    # This is needed in prefix builds, where the cmake files are not installed yet.
    set(glob_prefixes "${qt_prefixes}")
    list(TRANSFORM glob_prefixes APPEND "/${INSTALL_LIBDIR}/cmake/${QT_CMAKE_EXPORT_NAMESPACE}Qml")

    set(qml_plugin_cmake_config_file_glob_prefixes "")
    foreach(glob_prefix IN LISTS glob_prefix)
        if(EXISTS "${glob_prefix}")
            list(APPEND qml_plugin_cmake_config_file_glob_prefixes "${glob_prefix}")
        endif()
    endforeach()

    if(qml_plugin_cmake_config_file_glob_prefixes)
        set(QT_ADDITIONAL_QML_PLUGIN_GLOB_PREFIXES ${qml_plugin_cmake_config_file_glob_prefixes})
    endif()

    # In multi-config mode by default we exclude building tools for configs other than the main one.
    # Trying to build an example in a non-default config using the non-installed
    # QtFooConfig.cmake files would error out saying moc is not found.
    # Make sure to build examples only with the main config.
    # When users build an example against an installed Qt they won't have this problem because
    # the generated non-main QtFooTargets-$<CONFIG>.cmake file is empty and doesn't advertise
    # a tool that is not there.
    if(QT_GENERATOR_IS_MULTI_CONFIG)
        set(CMAKE_CONFIGURATION_TYPES "${QT_MULTI_CONFIG_FIRST_CONFIG}")
    endif()

    # We need to pass the modified CXX flags of the parent project so that using sccache works
    # properly and doesn't error out due to concurrent access to the pdb files.
    # See qt_internal_set_up_config_optimizations_like_in_qmake, "/Zi" "/Z7".
    if(MSVC AND QT_FEATURE_msvc_obj_debug_info)
        qt_internal_get_enabled_languages_for_flag_manipulation(enabled_languages)
        set(configs RELWITHDEBINFO DEBUG)
        foreach(lang ${enabled_languages})
            foreach(config ${configs})
                set(flag_var_name "CMAKE_${lang}_FLAGS_${config}")
                list(APPEND vars_to_pass_if_defined "${flag_var_name}:STRING")
            endforeach()
        endforeach()
    endif()

    # When cross-compiling for a qemu target in our CI, we source an environment script
    # that sets environment variables like CC and CXX. These are parsed by CMake on initial
    # configuration to populate the cache vars CMAKE_${lang}_COMPILER.
    # If the environment variable specified not only the compiler path, but also a list of flags
    # to pass to the compiler, CMake parses those out into a separate CMAKE_${lang}_COMPILER_ARG1
    # cache variable. In such a case, we want to ensure that the external project also sees those
    # flags.
    # Unfortunately we can't do that by simply forwarding CMAKE_${lang}_COMPILER_ARG1 to the EP
    # because it breaks the compiler identification try_compile call, it simply doesn't consider
    # the cache var. From what I could gather, it's a limitation of try_compile and the list
    # of variables it considers for forwarding.
    # To fix this case, we ensure not to pass either cache variable, and let the external project
    # and its compiler identification try_compile project pick up the compiler and the flags
    # from the environment variables instead.
    foreach(lang_as_env_var CC CXX OBJC OBJCXX)
        if(lang_as_env_var STREQUAL "CC")
            set(lang_as_cache_var "C")
        else()
            set(lang_as_cache_var "${lang_as_env_var}")
        endif()
        set(lang_env_value "$ENV{${lang_as_env_var}}")
        if(lang_env_value
                AND CMAKE_${lang_as_cache_var}_COMPILER
                AND CMAKE_${lang_as_cache_var}_COMPILER_ARG1)
            # The compiler environment variable is set and specifies a list of extra flags, don't
            # forward the compiler cache vars and rely on the environment variable to be picked up
            # instead.
        else()
            list(APPEND vars_to_pass_if_defined "CMAKE_${lang_as_cache_var}_COMPILER:STRING")
        endif()
    endforeach()
    unset(lang_as_env_var)
    unset(lang_as_cache_var)
    unset(lang_env_value)

    list(APPEND vars_to_pass_if_defined
        CMAKE_BUILD_TYPE:STRING
        CMAKE_CONFIGURATION_TYPES:STRING
        CMAKE_PREFIX_PATH:STRING
        QT_BUILD_CMAKE_PREFIX_PATH:STRING
        QT_ADDITIONAL_PACKAGES_PREFIX_PATH:STRING
        QT_ADDITIONAL_QML_PLUGIN_GLOB_PREFIXES:STRING
        QT_INTERNAL_SKIP_DEPLOYMENT:BOOL
        CMAKE_FIND_ROOT_PATH:STRING
        CMAKE_MODULE_PATH:STRING
        BUILD_SHARED_LIBS:BOOL
        CMAKE_OSX_ARCHITECTURES:STRING
        CMAKE_OSX_DEPLOYMENT_TARGET:STRING
        CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED:BOOL
        CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH:BOOL
        CMAKE_C_COMPILER_LAUNCHER:STRING
        CMAKE_CXX_COMPILER_LAUNCHER:STRING
        CMAKE_OBJC_COMPILER_LAUNCHER:STRING
        CMAKE_OBJCXX_COMPILER_LAUNCHER:STRING
    )

    # QT_EXAMPLE_CMAKE_VARS_TO_PASS can be set by specific repos to pass any additional required
    # CMake cache variables.
    # One use case is passing locations of 3rd party package locations like Protobuf via _ROOT
    # variables.
    set(extra_vars_var_name "")
    if(QT_EXAMPLE_CMAKE_VARS_TO_PASS)
        set(extra_vars_var_name "QT_EXAMPLE_CMAKE_VARS_TO_PASS")
    endif()
    foreach(var_with_type IN LISTS vars_to_pass_if_defined ${extra_vars_var_name})
        string(REPLACE ":" ";" key_as_list "${var_with_type}")
        list(GET key_as_list 0 var)
        if(NOT DEFINED ${var})
            continue()
        endif()

        # Preserve lists
        string(REPLACE ";" "$<SEMICOLON>" varForGenex "${${var}}")

        list(APPEND var_defs -D${var_with_type}=${varForGenex})
    endforeach()

    if(QT_INTERNAL_VERBOSE_EXAMPLES)
        list(APPEND var_defs -DCMAKE_MESSAGE_LOG_LEVEL:STRING=DEBUG)
        list(APPEND var_defs -DCMAKE_AUTOGEN_VERBOSE:BOOL=TRUE)
    endif()

    set(deps "")
    list(REMOVE_DUPLICATES QT_EXAMPLE_DEPENDENCIES)
    foreach(dep IN LISTS QT_EXAMPLE_DEPENDENCIES)
        if(TARGET ${dep})
            list(APPEND deps ${dep})
        endif()
    endforeach()

    set(independent_args)
    cmake_policy(PUSH)
    if(POLICY CMP0114)
        set(independent_args INDEPENDENT TRUE)
        cmake_policy(SET CMP0114 NEW)
    endif()

    # The USES_TERMINAL_BUILD setting forces the build step to the console pool
    # when using Ninja. This has two benefits:
    #
    #   - You see build output as it is generated instead of at the end of the
    #     build step.
    #   - Only one task can use the console pool at a time, so it effectively
    #     serializes all example build steps, thereby preventing CPU
    #     over-commitment.
    #
    # If the loss of interactivity is not so important, one can allow CPU
    # over-commitment for Ninja builds. This may result in better throughput,
    # but is not allowed by default because it can make a machine almost
    # unusable while a compilation is running.
    set(terminal_args USES_TERMINAL_BUILD TRUE)
    if(CMAKE_GENERATOR MATCHES "Ninja")
        option(QT_BUILD_EXAMPLES_WITH_CPU_OVERCOMMIT
            "Allow CPU over-commitment when building examples (Ninja only)"
        )
        if(QT_BUILD_EXAMPLES_WITH_CPU_OVERCOMMIT)
            set(terminal_args)
        endif()
    endif()

    # QT_EXAMPLE_INSTALL_MARKER
    # The goal is to install each example project into a directory that keeps the example source dir
    # hierarchy, without polluting the example projects with dirty INSTALL_EXAMPLEDIR and
    # INSTALL_EXAMPLESDIR usage.
    # E.g. ensure qtbase/examples/widgets/widgets/wiggly is installed to
    # $qt_example_install_prefix/examples/widgets/widgets/wiggly/wiggly.exe
    # $qt_example_install_prefix defaults to ${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLEDIR}
    # but can also be set to a custom location.
    # This needs to work both:
    #  - when using ExternalProject to build examples
    #  - when examples are built in-tree as part of Qt (no ExternalProject).
    # The reason we want to support the latter is for nicer IDE integration: a can developer can
    # work with a Qt repo and its examples using the same build dir.
    #
    # In both case we have to ensure examples are not accidentally installed to $qt_prefix/bin or
    # similar.
    #
    # Example projects installation matrix.
    # 1) ExternalProject + unclean example install rules (INSTALL_EXAMPLEDIR is set) =>
    #    use _qt_internal_override_example_install_dir_to_dot + ExternalProject_Add's INSTALL_DIR
    #    using relative_dir from QT_EXAMPLE_BASE_DIR to example_source_dir
    #
    # 2) ExternalProject + clean example install rules =>
    #    use ExternalProject_Add's INSTALL_DIR using relative_dir from QT_EXAMPLE_BASE_DIR to
    #    example_source_dir, _qt_internal_override_example_install_dir_to_dot would be a no-op
    #
    # 3) in-tree + unclean example install rules (INSTALL_EXAMPLEDIR is set)
    # +
    # 4) in-tree + clean example install rules =>
    #    ensure CMAKE_INSTALL_PREFIX is unset in parent cmake_install.cmake file, set non-cache
    #    CMAKE_INSTALL_PREFIX using relative_dir from QT_EXAMPLE_BASE_DIR to
    #    example_source_dir, use _qt_internal_override_example_install_dir_to_dot to ensure
    #    INSTALL_EXAMPLEDIR does not interfere.

    qt_internal_get_example_install_path(example_install_path "${subdir}")

    set(ep_binary_dir    "${CMAKE_CURRENT_BINARY_DIR}/${subdir}")

    set(build_command "")
    if(QT_INTERNAL_VERBOSE_EXAMPLES AND CMAKE_GENERATOR MATCHES "Ninja")
        set(build_command BUILD_COMMAND "${CMAKE_COMMAND}" --build "." -- -v)
    endif()

    ExternalProject_Add(${arg_NAME}
        EXCLUDE_FROM_ALL TRUE
        SOURCE_DIR       "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}"
        PREFIX           "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep"
        STAMP_DIR        "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep/stamp"
        BINARY_DIR       "${ep_binary_dir}"
        INSTALL_DIR      "${example_install_path}"
        INSTALL_COMMAND  ""
        ${build_command}
        TEST_COMMAND     ""
        DEPENDS          ${deps}
        CMAKE_CACHE_ARGS ${var_defs}
                         -DCMAKE_INSTALL_PREFIX:STRING=<INSTALL_DIR>
                         -DQT_INTERNAL_SET_EXAMPLE_INSTALL_DIR_TO_DOT:BOOL=TRUE
        ${terminal_args}
    )

    # Install the examples when the the user runs 'make install', and not at build time (which is
    # the default for ExternalProjects).
    install(CODE "\
# Install example from inside ExternalProject into the main build's install prefix.
execute_process(
    COMMAND
        \"${CMAKE_COMMAND}\" --build \"${ep_binary_dir}\" --target install
)
")

    # Force configure step to re-run after we configure the main project
    set(reconfigure_check_file ${CMAKE_CURRENT_BINARY_DIR}/reconfigure_${arg_NAME}.txt)
    file(TOUCH ${reconfigure_check_file})
    ExternalProject_Add_Step(${arg_NAME} reconfigure-check
        DEPENDERS configure
        DEPENDS   ${reconfigure_check_file}
        ${independent_args}
    )

    # Create an apk external project step and custom target that invokes the apk target
    # within the external project.
    # Make the global apk target depend on that custom target.
    if(ANDROID)
        ExternalProject_Add_Step(${arg_NAME} apk
            COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target apk
            DEPENDEES configure
            EXCLUDE_FROM_MAIN YES
            ${terminal_args}
        )
        ExternalProject_Add_StepTargets(${arg_NAME} apk)

        if(TARGET apk)
            add_dependencies(apk ${arg_NAME}-apk)
        endif()
    endif()

    cmake_policy(POP)

    string(TOLOWER ${PROJECT_NAME} project_name_lower)
    add_dependencies(examples_${project_name_lower} ${arg_NAME})

endfunction()

function(qt_internal_install_example_sources subdir)
    set(options "")
    set(single_args NAME REPO_NAME)
    set(multi_args "")

    cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${single_args}" "${multi_args}")

    qt_internal_get_examples_sources_install_path(example_install_path "${subdir}")

    # The trailing slash is important to avoid duplicate nested directory names.
    set(example_source_dir "${subdir}/")

    # Allow controlling whether sources should be part of the default install target.
    if(QT_INSTALL_EXAMPLES_SOURCES_BY_DEFAULT)
        set(exclude_from_all "")
    else()
        set(exclude_from_all "EXCLUDE_FROM_ALL")
    endif()

    # Create an install component for all example sources. Can also be part of the default
    # install target if EXCLUDE_FROM_ALL is not passed.
    install(
        DIRECTORY "${example_source_dir}"
        DESTINATION "${example_install_path}"
        COMPONENT "examples_sources"
        USE_SOURCE_PERMISSIONS
        ${exclude_from_all}
    )

    # Also create a specific install component just for this repo's examples.
    install(
        DIRECTORY "${example_source_dir}"
        DESTINATION "${example_install_path}"
        COMPONENT "examples_sources_${arg_REPO_NAME}"
        USE_SOURCE_PERMISSIONS
        EXCLUDE_FROM_ALL
    )

    # Also create a specific install component just for the current example's sources.
    install(
        DIRECTORY "${example_source_dir}"
        DESTINATION "${example_install_path}"
        COMPONENT "examples_sources_${arg_NAME}"
        USE_SOURCE_PERMISSIONS
        EXCLUDE_FROM_ALL
    )
endfunction()