diff options
Diffstat (limited to 'cmake/QtBuildRepoExamplesHelpers.cmake')
-rw-r--r-- | cmake/QtBuildRepoExamplesHelpers.cmake | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/cmake/QtBuildRepoExamplesHelpers.cmake b/cmake/QtBuildRepoExamplesHelpers.cmake new file mode 100644 index 0000000000..6802d81323 --- /dev/null +++ b/cmake/QtBuildRepoExamplesHelpers.cmake @@ -0,0 +1,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() |