diff --git a/tests/manual/RunCMake/CMakeLists.txt b/tests/manual/RunCMake/CMakeLists.txt index 8510e81a..f18b192c 100644 --- a/tests/manual/RunCMake/CMakeLists.txt +++ b/tests/manual/RunCMake/CMakeLists.txt @@ -10,3 +10,6 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/Common.cmake") add_RunCMake_test(InitRepository -DEXTRA_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR} ) +add_RunCMake_test(ConfigureBuildQt + -DEXTRA_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/tests/manual/RunCMake/ConfigureBuildQt/RunCMakeTest.cmake b/tests/manual/RunCMake/ConfigureBuildQt/RunCMakeTest.cmake new file mode 100644 index 00000000..43b86f1d --- /dev/null +++ b/tests/manual/RunCMake/ConfigureBuildQt/RunCMakeTest.cmake @@ -0,0 +1,171 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +list(APPEND CMAKE_MODULE_PATH "${EXTRA_MODULE_PATH}") +include(Common) + +# The file is included separately from Common.cmake because it has side-effects +# that we want to apply only in the RunCMake part of the test. +include(RunCMake) + +# Include the test specific utilities. +include(Utils) + +macro(test_per_repo_prefix_qt) + add_test_case( + TEST_NAME "per_repo_prefix" + TEST_GROUPS per_repo prefix + TEST_ARGS + BUILD_STANDALONE_TESTS + BUILD_STANDALONE_EXAMPLES_IN_TREE + BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS + RECONFIGURE_WITHOUT_ARGS_IMMEDIATELY + ) +endmacro() + +macro(test_per_repo_no_prefix_qt) + add_test_case( + TEST_NAME "per_repo_no_prefix" + TEST_GROUPS per_repo no_prefix + TEST_ARGS + NO_PREFIX + BUILD_STANDALONE_TESTS + BUILD_STANDALONE_EXAMPLES_IN_TREE + BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS + RECONFIGURE_WITHOUT_ARGS_AFTER_BUILD + BUILD_AFTER_RECONFIGURE + ) +endmacro() + +macro(test_per_repo_prefix_in_tree_tests_and_examples_qt) + add_test_case( + TEST_NAME "per_repo_prefix_in_tree_tests_and_examples" + TEST_GROUPS per_repo prefix in_tree_tests_and_examples + TEST_ARGS + BUILD_IN_TREE_TESTS + BUILD_IN_TREE_EXAMPLES + ) +endmacro() + +macro(test_per_repo_no_prefix_in_tree_tests_and_examples_qt) + add_test_case( + TEST_NAME "per_repo_no_prefix_in_tree_tests_and_examples" + TEST_GROUPS per_repo no_prefix in_tree_tests_and_examples + TEST_ARGS + NO_PREFIX + BUILD_IN_TREE_TESTS + BUILD_IN_TREE_EXAMPLES + ) +endmacro() + +macro(test_per_repo_no_prefix_in_source) + add_test_case( + TEST_NAME "per_repo_no_prefix_in_source" + TEST_GROUPS per_repo no_prefix in_source + TEST_ARGS + NO_PREFIX + IN_SOURCE + ) +endmacro() + +macro(test_top_level_prefix_qt) + add_test_case( + TEST_NAME "top_level_prefix" + TEST_GROUPS top_level prefix + TEST_ARGS + TOP_LEVEL + BUILD_STANDALONE_TESTS + BUILD_STANDALONE_EXAMPLES_IN_TREE + BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS + ) +endmacro() + +macro(test_top_level_no_prefix_qt) + add_test_case( + TEST_NAME "top_level_no_prefix" + TEST_GROUPS top_level no_prefix + TEST_ARGS + TOP_LEVEL + NO_PREFIX + BUILD_STANDALONE_TESTS + BUILD_STANDALONE_EXAMPLES_IN_TREE + BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS + ) +endmacro() + +macro(test_top_level_no_prefix_in_source) + add_test_case( + TEST_NAME "top_level_no_prefix_in_source" + TEST_GROUPS top_level no_prefix in_source + TEST_ARGS + TOP_LEVEL + NO_PREFIX + IN_SOURCE + ) +endmacro() + +macro(test_top_level_prefix_in_tree_tests_and_examples_qt) + add_test_case( + TEST_NAME "top_level_prefix_in_tree_tests_and_examples" + TEST_GROUPS top_level prefix in_tree_tests_and_examples + TEST_ARGS + TOP_LEVEL + BUILD_IN_TREE_TESTS + BUILD_IN_TREE_EXAMPLES + ) +endmacro() + +macro(test_top_level_no_prefix_in_tree_tests_and_examples_qt) + add_test_case( + TEST_NAME "top_level_no_prefix_in_tree_tests_and_examples" + TEST_GROUPS top_level no_prefix in_tree_tests_and_examples + TEST_ARGS + TOP_LEVEL + NO_PREFIX + BUILD_IN_TREE_TESTS + BUILD_IN_TREE_EXAMPLES + ) +endmacro() + +# Collect all test cases and groups. +macro(collect_tests) + set(TEST_CASES "") + set(TEST_GROUPS "") + + test_per_repo_no_prefix_qt() + test_top_level_no_prefix_qt() + + # This usually tested in regular CI as well. + test_per_repo_prefix_qt() + + test_top_level_prefix_qt() + + # TODO: These don't work atm due to some failed include(Targets) files. + #test_per_repo_no_prefix_in_tree_tests_and_examples_qt() + #test_top_level_no_prefix_in_tree_tests_and_examples_qt() + #test_per_repo_prefix_in_tree_tests_and_examples_qt() + #test_top_level_prefix_in_tree_tests_and_examples_qt() + + test_per_repo_no_prefix_in_source() + test_top_level_no_prefix_in_source() + + # TODO: Cross-builds. + # TODO: qt5.git builds with all submodules. Current limitation is that the sync-to + # script can't handle multiple modules at once, nor an "all repos" case. + # so we might have to call init-repository in that case. + # TODO: Unix Makefile builds. + # TODO: Build examples and tests, not only configure them. + # TODO: Perhaps run some of the cmake auto tests in configs that are not tested in CI + # like no-prefix builds. + + message(STATUS "Available test cases: ${TEST_CASES}") + message(STATUS "Available test groups: ${TEST_GROUPS}") + foreach(group IN LISTS TEST_GROUPS) + message(STATUS "Available test cases for group ${group}: ${TEST_GROUPS_${group}}") + endforeach() +endmacro() + +run_tests() diff --git a/tests/manual/RunCMake/ConfigureBuildQt/Utils.cmake b/tests/manual/RunCMake/ConfigureBuildQt/Utils.cmake new file mode 100644 index 00000000..337b99b9 --- /dev/null +++ b/tests/manual/RunCMake/ConfigureBuildQt/Utils.cmake @@ -0,0 +1,1320 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Wrapper for RunCMake's run_cmake, with extra logging and early failure handling. +function(run_command prefix name) + set(RunCMake_TEST_COMMAND "${ARGN}") + set(RunCMake-check-file "check.cmake") + + set(args ${ARGN}) + list(JOIN args " " args_str) + set(working_dir "${RunCMake_TEST_COMMAND_WORKING_DIRECTORY}") + message(STATUS "Running command: '${args_str}' in dir: '${working_dir}'") + + run_cmake("${prefix}${name}") + # set by the check file above. + if(should_error_out) + message(FATAL_ERROR "Command ${prefix}${name} failed. Exiting early.") + endif() +endfunction() + +# Query the var name from the CMake cache or the environment or use a default value. +function(get_cmake_or_env_or_default out_var var_name_to_check default_value) + if(${var_name_to_check}) + set(value "${var_name_to_check}") + elseif(DEFINED ENV{${var_name_to_check}}) + set(value "$ENV{${var_name_to_check}}") + else() + set(value "${default_value}") + endif() + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +# Sets verbose command output based on a cmake var or env var for run_command calls. +macro(setup_verbose_command_output) + get_cmake_or_env_or_default(verbose_command_output QT_CI_BUILD_QT_VERBOSE_COMMAND_OUTPUT "ON") + if(verbose_command_output) + # Show output as it arrives. + # This uses a custom qtbase patch of RunCMake. + set(RunCMake_TEST_ECHO_OUTPUT_VARIABLE TRUE) + set(RunCMake_TEST_ECHO_ERROR_VARIABLE TRUE) + endif() +endmacro() + +# Gets the remote git base URL for qt repositories. +function(get_repo_base_url out_var) + # This Coin CI git daemon IP is set in all CI jobs. + # Prefer using it when available, to avoid general network issues. + # Sample value: QT_COIN_GIT_DAEMON=10.215.0.212:9418 + get_cmake_or_env_or_default(coin_git_ip_port QT_COIN_GIT_DAEMON "") + + # Allow opting out of using the coin git daemon. + get_cmake_or_env_or_default(skip_coin_git QT_CI_BUILD_QT_SKIP_COIN_GIT_DAEMON FALSE) + + # Allow override of the default remote. + get_cmake_or_env_or_default(git_remote QT_CI_BUILD_QT_GIT_REMOTE "") + + if(coin_git_ip_port AND NOT skip_coin_git) + set(value "git://${coin_git_ip_port}/qt-project") + elseif(git_remote) + set(value "${git_remote}") + else() + set(value "https://code.qt.io") + endif() + + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +function(get_default_repo_to_sync out_var) + set(${out_var} "qtdeclarative" PARENT_SCOPE) +endfunction() + +function(get_default_ref_to_sync out_var) + set(${out_var} "dev" PARENT_SCOPE) +endfunction() + +# Decides module to clone to sync to and the ref to sync to. +function(setup_qt_repos_to_clone) + # Allow pinning the sha1 or any other git ref, based on a cmake var or env var. + get_default_repo_to_sync(default_module) + get_default_ref_to_sync(default_ref) + get_cmake_or_env_or_default(SYNC_MODULE QT_CI_BUILD_QT_SYNC_MODULE "${default_module}") + get_cmake_or_env_or_default(SYNC_TO_REF QT_CI_BUILD_QT_PIN_GIT_REF "${default_ref}") + + message(STATUS "Will sync to module '${SYNC_MODULE}' at ref '${SYNC_TO_REF}'") + + set(SYNC_TO_REF "${SYNC_TO_REF}" PARENT_SCOPE) + set(SYNC_MODULE "${SYNC_MODULE}" PARENT_SCOPE) +endfunction() + +# A parser extra cherry-pick or checkout refs to apply to the initialized submodules. +# value - the string to parse. +# out_prefix - the prefix to use for the output variables. +# Sets the following variables: +# _repos - a list of the repos to apply the changes to. +# _ - the list of refs to apply to the given repo. +function(parse_cherry_pick_entries out_prefix value) + if(value STREQUAL "") + set(${out_prefix}_repos "" PARENT_SCOPE) + return() + endif() + + set(result "") + set(repos "") + + # Format is as follows. + # "repo sha1,sha2,sha3|repo2 sha1,sha2,sha3" + # E.g. + # set(cherrypicks "qtbase a,b,c|qtdeclarative d,e,f") + string(REPLACE "|" ";" repo_tuples "${value}") + foreach(repo_tuple IN LISTS repo_tuples) + set(result "") + string(REPLACE " " ";" repo_tuple "${repo_tuple}") + list(LENGTH repo_tuple tuple_len) + if(NOT tuple_len EQUAL 2) + message(FATAL_ERROR "Invalid cherry-pick entry: ${repo_tuple}") + endif() + list(GET repo_tuple 0 repo_name) + list(GET repo_tuple 1 refs) + string(REPLACE "," ";" refs "${refs}") + list(APPEND repos "${repo_name}") + foreach(ref IN LISTS refs) + list(APPEND result "${ref}") + endforeach() + + set(${out_prefix}_${repo_name} "${result}" PARENT_SCOPE) + endforeach() + set(${out_prefix}_repos "${repos}" PARENT_SCOPE) +endfunction() + +# Applies extra gerrit cherry-picks or checks out repos to given ref for the initialized repos. +# This allows applying patches before they are merged in gerrit. +# These can be set for example in a Coin integration or when configuring the test locally. +# The changes are applied in the order they are given, with cherry-picks coming after the check +# outs. +# The format for the values is as follows: +# QT_CI_BUILD_QT_EXTRA_CHERRYPICK_CHANGES='qtbase ref1,ref2|qtdeclarative ref3,ref4' +# Example usage on the command line: +# export QT_CI_BUILD_QT_EXTRA_CHERRYPICK_CHANGES='qtbase 6d2b6b810163610a5b56cef19b196286708acabb' +# export QT_CI_BUILD_QT_EXTRA_CHECKOUT_CHANGES='qtsvg refs/changes/10/624710/5' +# ctest -V -R RunCMake.ConfigureBuildQt +function(apply_extra_gerrit_changes) + get_cmake_or_env_or_default(cherrypicks QT_CI_BUILD_QT_EXTRA_CHERRYPICK_CHANGES "") + get_cmake_or_env_or_default(checkouts QT_CI_BUILD_QT_EXTRA_CHECKOUT_CHANGES "") + + set(src_dir_backup "${RunCMake_TEST_COMMAND_WORKING_DIRECTORY}") + + parse_cherry_pick_entries(check_outs "${checkouts}") + if(check_outs_repos) + message(STATUS "Applying extra checkout changes for: ${check_outs_repos}") + endif() + foreach(repo_name IN LISTS check_outs_repos) + set(refs "${check_outs_${repo_name}}") + list(LENGTH refs refs_len) + if(refs_len GREATER 1) + message(FATAL_ERROR "More than one checkout ref specified for: ${repo_name}") + endif() + foreach(ref IN LISTS refs) + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${src_dir_backup}/${repo_name}") + run_command("extra_" git_fetch_checkout_sha1 + git fetch "https://codereview.qt-project.org/qt/${repo_name}" "${ref}" + ) + run_command("extra_" git_checkout_fetch_head + git checkout FETCH_HEAD + ) + endforeach() + endforeach() + + parse_cherry_pick_entries(cherry_picks "${cherrypicks}") + if(cherry_picks_repos) + message(STATUS "Applying extra cherry-pick changes for: ${cherry_picks_repos}") + endif() + foreach(repo_name IN LISTS cherry_picks_repos) + set(refs "${cherry_picks_${repo_name}}") + foreach(ref IN LISTS refs) + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${src_dir_backup}/${repo_name}") + run_command("extra_" git_fetch_cherry_pick_sha1 + git fetch "https://codereview.qt-project.org/qt/${repo_name}" "${ref}" + ) + run_command("extra_" git_cherry_pick_fetch_head + git cherry-pick FETCH_HEAD + ) + endforeach() + endforeach() +endfunction() + +# Maeks a copy of the current qt5.git repo of into a temporary directory, and syncs it to the +# given module and ref, initializing any missing submodules along the way, +# as well as applies any extra gerrit changes. +# Also sets the root build dir path for all future Qt builds. +# +# In the CI this instead does a clean clone from the official mirror, instead of a copy, due to +# missing .git info. +# +# Does not rerun the cloning and syncing if the sources are already synced, based on the presence +# of the sources_synced.txt file. +function(setup_qt_sources) + set(opt_args "") + set(single_args "") + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + # Set in Common.cmake + set(local_clone_url "${top_repo_dir_path}") + + # Path to working dir based on test build path. + set(working_dir "${RunCMake_BINARY_DIR}") + set(top_level_src_dir "${working_dir}/src") + set(builds_path "${working_dir}/builds") + + # Avoid creating many empty build dirs for each run_cmake command, by always reusing the + # same one. + set(RunCMake_TEST_NO_CLEAN TRUE) + set(RunCMake_TEST_NO_CLEAN TRUE PARENT_SCOPE) + set(RunCMake_TEST_BINARY_DIR "${working_dir}") + set(RunCMake_TEST_BINARY_DIR "${working_dir}" PARENT_SCOPE) + + # This used by some of the other functions. + set(TOP_LEVEL_SRC_DIR "${top_level_src_dir}" PARENT_SCOPE) + set(BUILDS_PATH "${builds_path}" PARENT_SCOPE) + + if(EXISTS "${working_dir}/sources_synced.txt") + # Return early for now, not to retouch the sources, and thus + # cause rebuilds when re-running the test. + message(STATUS "Skipping setting up sources as they are already synced. " + "Remove ${working_dir}/sources_synced.txt to re-sync.") + return() + endif() + + # Prepare paths. + file(MAKE_DIRECTORY "${working_dir}") + file(MAKE_DIRECTORY "${builds_path}") + + # prefix is empty for now. + set(prefix "") + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${working_dir}") + + set(code_qt_io_mirror "https://code.qt.io/qt/qt5.git") + set(gerrit_mirror "https://codereview.qt-project.org/qt/qt5") + + # Make a copy of the qt6 repo on which the test will operate on. + # On the CI we clone it from the official mirror, because we don't have a git repo, but only + # a source archive. + # On a local build we use the current qt6 checkout. + set(ci_ref "$ENV{TESTED_MODULE_REVISION_COIN}") + if(ci_ref) + set(final_clone_url "${code_qt_io_mirror}") + else() + set(final_clone_url "${local_clone_url}") + endif() + + setup_verbose_command_output() + + # Make a copy of the qt6 repo to operate on it. + if(NOT EXISTS "${top_level_src_dir}") + message(STATUS "Cloning qt6 repo to: ${top_level_src_dir} from ${final_clone_url}") + run_command("${prefix}" prepare_qt6_clone git clone "${final_clone_url}" + "${top_level_src_dir}" --quiet) + else() + message(STATUS "Skipping cloning qt6 repo as it already exists at: ${top_level_src_dir}") + endif() + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${top_level_src_dir}") + + # On the CI we also checkout the exact tested ref, so it behaves as it would with locally. + if(ci_ref) + run_command("${prefix}" fetch_ci_qt6_ref git fetch "${gerrit_mirror}" "${ci_ref}" --quiet) + run_command("${prefix}" checkout_ci_qt6_ref git checkout FETCH_HEAD --quiet) + endif() + + # Merge stdout with stderr, to avoid having stderr output from git trigger errors. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + # Adjust its remote url not to be the local url. + get_repo_base_url(repo_base_url) + set(remote_clone_url "${repo_base_url}/qt/qt5.git") + run_command("${prefix}" set_remote_url git remote set-url origin "${remote_clone_url}") + + # Sync to given module. + run_command("${prefix}" git_sync_to_${SYNC_MODULE} + ${CMAKE_COMMAND} + "-DSYNC_TO_MODULE=${SYNC_MODULE}" + "-DSYNC_TO_BRANCH=${SYNC_TO_REF}" + -DSHOW_PROGRESS=1 + -DVERBOSE=1 + -DGIT_DEPTH=1 + -P cmake/QtSynchronizeRepo.cmake + ) + + apply_extra_gerrit_changes() + + file(TOUCH "${working_dir}/sources_synced.txt") +endfunction() + +# Compute the list of submodules that will be built and skipped based on the module to sync to. +function(setup_list_of_repos_to_build) + # Fake set the CMAKE_CURRENT_SOURCE_DIR so that we can parse the dependencies. + set(CMAKE_CURRENT_SOURCE_DIR "${TOP_LEVEL_SRC_DIR}") + + qt_internal_sort_module_dependencies("${SYNC_MODULE}" initial_submodules) + set(final_submodules ${initial_submodules}) + + get_default_repo_to_sync(default_module) + + # Skip any submodules that were explicitly set. + get_cmake_or_env_or_default(skip_submodules QT_CI_BUILD_QT_SKIP_SUBMODULES "") + if(skip_submodules) + # Support semicolons and commas. + string(REPLACE "," ";" skip_submodules "${skip_submodules}") + endif() + + # By default we skip some of qtdeclarative's optional dependencies. + if(NOT skip_submodules AND SYNC_MODULE STREQUAL "${default_module}") + set(skip_submodules qtsvg qtimageformats qtlanguageserver) + endif() + + if(skip_submodules) + list(REMOVE_ITEM final_submodules ${skip_submodules}) + endif() + + message(STATUS "Initial submodules: ${initial_submodules}") + if(skip_submodules) + message(STATUS "Skipping submodules: ${skip_submodules}") + endif() + message(STATUS "Final submodules: ${final_submodules}") + + # Will be used by other functions to decide which repos to build. + set(QT_BUILD_SUBMODULES "${final_submodules}" PARENT_SCOPE) + set(QT_SKIP_SUBMODULES "${skip_submodules}" PARENT_SCOPE) +endfunction() + +# Copies the top-level qt sources to the given destination, to facilitate clean in-source builds. +function(copy_qt_sources) + set(opt_args "") + set(single_args + DESTINATION_PATH + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + if(NOT arg_DESTINATION_PATH) + message(FATAL_ERROR "DESTINATION_PATH is required.") + endif() + + if(EXISTS "${arg_DESTINATION_PATH}/CMakeLists.txt") + message(STATUS "Skipping copying sources to ${arg_DESTINATION_PATH} as it already has " + "contents. Remove the root CMakeLists.txt file to re-copy.") + return() + endif() + + message(STATUS "Copying sources to ${arg_DESTINATION_PATH}") + file(GLOB files_top_level "${TOP_LEVEL_SRC_DIR}/*") + foreach(top_path IN LISTS files_top_level) + # Skip copying qt5 .git dir. + if(top_path STREQUAL "${TOP_LEVEL_SRC_DIR}/.git") + continue() + endif() + + if(IS_DIRECTORY "${top_path}") + get_filename_component(top_dir_name "${top_path}" NAME) + set(child_destination_path "${arg_DESTINATION_PATH}/${top_dir_name}") + file(MAKE_DIRECTORY "${child_destination_path}") + file(GLOB child_paths "${top_path}/*") + foreach(child_path IN LISTS child_paths) + # Skip copying repo .git dir. + if(child_path STREQUAL "${top_path}/.git") + continue() + endif() + + file(COPY "${child_path}" DESTINATION "${child_destination_path}") + endforeach() + else() + file(COPY "${top_path}" DESTINATION "${arg_DESTINATION_PATH}") + endif() + endforeach() +endfunction() + +# Common helper used in a few functions. +macro(check_and_set_common_qt_configure_options) + if(NOT arg_TEST_NAME) + message(FATAL_ERROR "TEST_NAME is required.") + endif() + set(test_name "${arg_TEST_NAME}") + + if(NOT arg_REPO_NAME) + message(FATAL_ERROR "REPO_NAME is required.") + endif() + + set(repo_name "${arg_REPO_NAME}") + + if(NOT arg_BUILD_DIR_ROOT_PATH) + message(FATAL_ERROR "BUILD_DIR_ROOT_PATH is required.") + endif() + + set(build_dir_root "${arg_BUILD_DIR_ROOT_PATH}") + + if(NOT arg_REPO_PATH) + message(FATAL_ERROR "REPO_PATH is required.") + endif() + set(repo_path "${arg_REPO_PATH}") +endmacro() + +function(get_libexec_dir out_var) + if(CMAKE_HOST_WIN32) + set(value "bin") + else() + set(value "libexec") + endif() + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +function(get_executable_suffix) + if(CMAKE_HOST_WIN32) + set(value ".bat") + else() + set(value "") + endif() + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +function(get_win_host_batch_file_workaround_prefix out_var) + set(args "") + # execute_process can't handle commands with arguments that contain spaces in both the + # executable path and the arguments, if the executable is a batch script. Work around it + # by prepending 'cmd /c call', which avoids the issue. + # See https://gitlab.kitware.com/cmake/cmake/-/issues/26655 + if(CMAKE_HOST_WIN32) + list(APPEND args "cmd" "/c" "call") + endif() +endfunction() + +# Some common cmake args for configuring qt and standalone tests / examples. +function(get_common_cmake_args out_var) + set(use_ccache_option "") + get_cmake_or_env_or_default(use_ccache QT_CI_BUILD_QT_USE_CCACHE "ON") + find_program(CCACHE_EXECUTABLE ccache) + if(CCACHE_EXECUTABLE AND use_ccache) + set(use_ccache_option -DQT_USE_CCACHE=ON) + endif() + + set(common_cmake_args + ${use_ccache_option} + -DWARNINGS_ARE_ERRORS=OFF + -DQT_GENERATE_SBOM=OFF + # When 6.x.y version bumps are not merged in DAG-dependency order, this avoids + # blocking integrations due to mismatched package versions. + -DQT_NO_PACKAGE_VERSION_INCOMPATIBLE_WARNING=ON + # Avoid using too much disk space. + -DBUILD_WITH_PCH=OFF + --log-level STATUS + ) + + # Get the common CI args, to set the sccache args, etc. + get_cmake_or_env_or_default(ci_common_cmake_args COMMON_CMAKE_ARGS "") + if(ci_common_cmake_args) + separate_arguments(ci_common_cmake_args NATIVE_COMMAND ${ci_common_cmake_args}) + endif() + if(ci_common_cmake_args) + list(APPEND common_cmake_args ${ci_common_cmake_args}) + endif() + + set(${out_var} "${common_cmake_args}" PARENT_SCOPE) +endfunction() + +# Configures a qt repo, either as per-repo or top-level. +# Handles qtbase vs top-level vs other repo case. +# Handles no-prefix builds. +# Configure is not re-ran un repeated test runs, to speed up test runs. Re-configuring can be +# forced by removing the CMakeCache.txt file. +# Passes some additional options like no-pch and no-sbom. +# Uses ccache when available. +function(configure_qt) + set(opt_args + USE_QT_CONFIGURE_MODULE + TOP_LEVEL + NO_PREFIX + ) + set(single_args + TEST_NAME + BUILD_DIR_ROOT_PATH + REPO_NAME + REPO_PATH + INSTALL_PREFIX + TOP_LEVEL_SOURCE_DIR_PATH + ) + set(multi_args + CONFIGURE_ARGS + CMAKE_ARGS + ) + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + check_and_set_common_qt_configure_options() + + if(NOT arg_TOP_LEVEL_SOURCE_DIR_PATH) + message(FATAL_ERROR "TOP_LEVEL_SOURCE_DIR_PATH is required.") + endif() + + set(source_dir "${arg_TOP_LEVEL_SOURCE_DIR_PATH}") + + if(arg_TOP_LEVEL) + set(build_dir_path "${build_dir_root}") + else() + set(build_dir_path "${build_dir_root}/${repo_name}") + endif() + file(MAKE_DIRECTORY "${build_dir_path}") + + message(STATUS "Configuring: ${test_name}") + + if(EXISTS "${build_dir_path}/CMakeCache.txt") + message(STATUS "Skipping configuring '${repo_name}' for ${test_name} in " + "${build_dir_path} as it is already configured. Remove the build dir or the " + "CMakeCache.txt file to re-configure.") + return() + endif() + + if(arg_NO_PREFIX) + # In a no-prefix build, the prefix is the qtbase build dir. + set(install_prefix "${build_dir_root}/qtbase") + set(install_prefix_option -no-prefix) + elseif(arg_INSTALL_PREFIX) + set(install_prefix "${arg_INSTALL_PREFIX}") + set(install_prefix_option -prefix "${arg_INSTALL_PREFIX}") + endif() + + if(repo_name STREQUAL "qtbase" OR arg_TOP_LEVEL) + set(repo_path_option "") + set(use_qt_configure_module FALSE) + else() + set(repo_path_option "${repo_path}") + set(use_qt_configure_module TRUE) + endif() + + set(bin_dir "bin") + get_executable_suffix(executable_suffix) + get_win_host_batch_file_workaround_prefix(exec_prefix) + + if(arg_TOP_LEVEL) + set(configure_path "${source_dir}/configure${executable_suffix}") + elseif(use_qt_configure_module) + set(configure_path "${install_prefix}/${bin_dir}/qt-configure-module${executable_suffix}") + else() + # qtbase case. + set(configure_path "${source_dir}/${repo_name}/configure${executable_suffix}") + endif() + + if(arg_CMAKE_GENERATOR) + set(cmake_generator -G "${arg_CMAKE_GENERATOR}") + else() + set(cmake_generator -G "${RunCMake_GENERATOR}") + endif() + + set(initial_configure_args + ${install_prefix_option} + -release + ) + set(initial_cmake_args "") + set(common_configure_args + ${exec_prefix} + "${configure_path}" + ${repo_path_option} + ${arg_CONFIGURE_ARGS} + ) + get_common_cmake_args(common_cmake_args) + list(APPEND common_cmake_args + ${cmake_generator} + ${arg_CMAKE_ARGS} + ) + + set(final_configure_args ${common_configure_args}) + if(repo_name STREQUAL "qtbase" OR arg_TOP_LEVEL) + list(APPEND final_configure_args ${initial_configure_args}) + endif() + + set(final_cmake_args ${common_cmake_args}) + if(repo_name STREQUAL "qtbase" OR arg_TOP_LEVEL) + list(APPEND final_cmake_args ${initial_cmake_args}) + endif() + + if(final_cmake_args) + list(APPEND final_configure_args "--" ${final_cmake_args}) + endif() + + # Merge stdout with stderr, to avoid having stderr output trigger errors because the + # configure mixes writes to both streams. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${build_dir_path}") + setup_verbose_command_output() + set(prefix "${test_name}_") + run_command("${prefix}" configure ${final_configure_args}) +endfunction() + +# Configures standalone tests or examples for a repo. +function(configure_standalone_part) + set(opt_args + TOP_LEVEL + NO_PREFIX + ) + set(single_args + TEST_NAME + BUILD_DIR_ROOT_PATH + REPO_NAME + REPO_PATH + INSTALL_PREFIX + PART_NAME + PART_VARIANT + OUT_VAR_BUILD_DIR + ) + set(multi_args + CMAKE_ARGS + ) + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + if(NOT arg_PART_NAME) + message(FATAL_ERROR "PART_NAME is required.") + endif() + + set(supported_parts TESTS EXAMPLES) + if(NOT arg_PART_NAME IN_LIST supported_parts) + message(FATAL_ERROR "PART_NAME must be one of: ${supported_parts}") + endif() + string(TOLOWER "${arg_PART_NAME}" part_name) + + if(arg_PART_NAME STREQUAL "EXAMPLES") + if(NOT arg_PART_VARIANT) + message(FATAL_ERROR "PART_VARIANT is required for EXAMPLES") + endif() + set(supported_examples_part_variant EXAMPLES_IN_TREE EXAMPLES_AS_EXTERNAL_PROJECTS) + if(NOT arg_PART_VARIANT IN_LIST supported_examples_part_variant) + message(FATAL_ERROR "PART_VARIANT must be one of: ${supported_examples_part_variant}") + endif() + if(arg_PART_VARIANT STREQUAL "EXAMPLES_IN_TREE") + set(part_variant_suffix "_it") + elseif(arg_PART_VARIANT STREQUAL "EXAMPLES_AS_EXTERNAL_PROJECTS") + set(part_variant_suffix "_ep") + endif() + endif() + + check_and_set_common_qt_configure_options() + + message(STATUS "Configuring standalone parts: ${arg_TEST_NAME}:${repo_name}:${part_name}") + + if(arg_TOP_LEVEL) + set(build_dir_path "${build_dir_root}/standalone_${part_name}${part_variant_suffix}") + else() + set(build_dir_path + "${build_dir_root}/standalone_${part_name}${part_variant_suffix}_${repo_name}") + endif() + file(MAKE_DIRECTORY "${build_dir_path}") + set(${arg_OUT_VAR_BUILD_DIR} "${build_dir_path}" PARENT_SCOPE) + + if(EXISTS "${build_dir_path}/CMakeCache.txt") + message(STATUS "Skipping configuring '${repo_name}' standalone ${part_name} for " + "${arg_TEST_NAME} in ${build_dir_path} as it is already configured. Remove the build " + "dir or the CMakeCache.txt file to re-configure.") + return() + endif() + + if(arg_NO_PREFIX) + # In a no-prefix build, the prefix is the qtbase build dir. + set(install_prefix "${build_dir_root}/qtbase") + elseif(arg_INSTALL_PREFIX) + set(install_prefix "${arg_INSTALL_PREFIX}") + endif() + + get_libexec_dir(libexec_dir) + get_executable_suffix(executable_suffix) + get_win_host_batch_file_workaround_prefix(exec_prefix) + + set(libexec_path "${install_prefix}") + set(configure_script + "${libexec_path}/${libexec_dir}/qt-internal-configure-${part_name}${executable_suffix}") + + set(common_configure_args + ${exec_prefix} + "${configure_script}" + -S "${repo_path}" + -B . + ) + get_common_cmake_args(common_cmake_args) + list(APPEND common_cmake_args + ${arg_CMAKE_ARGS} + ) + + if(arg_PART_NAME STREQUAL "EXAMPLES") + if(arg_PART_VARIANT STREQUAL "EXAMPLES_IN_TREE") + set(variant_value "OFF") + else() + set(variant_value "ON") + endif() + list(APPEND common_configure_args -DQT_BUILD_EXAMPLES_AS_EXTERNAL=${variant_value}) + endif() + + set(final_configure_args "") + list(APPEND final_configure_args ${common_configure_args}) + + set(final_cmake_args "") + list(APPEND final_cmake_args ${common_cmake_args}) + + if(final_cmake_args) + list(APPEND final_configure_args ${final_cmake_args}) + endif() + + # Merge stdout with stderr, to avoid having stderr output trigger errors because the + # configure mixes writes to both streams. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${build_dir_path}") + setup_verbose_command_output() + set(prefix "${test_name}_") + run_command("${prefix}" "configure_${part_name}" ${final_configure_args}) + + set(${arg_OUT_VAR_BUILD_DIR} "${build_dir_path}" PARENT_SCOPE) +endfunction() + +# Calls cmake in the given directory. +function(call_cmake_in_dir) + set(opt_args "") + set(single_args + TEST_NAME + OP_NAME + BUILD_DIR_PATH + LOG_LEVEL + ) + set(multi_args + CMAKE_ARGS + ) + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + set(prefix "${arg_TEST_NAME}_") + + if(NOT arg_BUILD_DIR_PATH) + message(FATAL_ERROR "BUILD_DIR_PATH is required.") + endif() + set(build_dir_path "${arg_BUILD_DIR_PATH}") + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${build_dir_path}") + message(STATUS "Calling cmake: ${arg_TEST_NAME}:${build_dir_path}") + + setup_verbose_command_output() + + if(arg_OP_NAME) + set(op_name "${arg_OP_NAME}") + else() + set(op_name "cmake") + endif() + + + set(extra_cmake_args ${arg_CMAKE_ARGS}) + if(arg_LOG_LEVEL) + list(APPEND extra_cmake_args "--log-level=${arg_LOG_LEVEL}") + endif() + + # Merge stdout with stderr, to avoid having stderr output when configure is re-ran as part of + # the build. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + run_command("${prefix}" ${op_name} + ${CMAKE_COMMAND} + . + ${extra_cmake_args} + ) +endfunction() + +function(call_cmake_in_qt_build_dir) + set(opt_args + TOP_LEVEL + ) + set(single_args + TEST_NAME + BUILD_DIR_ROOT_PATH + REPO_NAME + REPO_PATH + LOG_LEVEL + ) + set(multi_args + CMAKE_ARGS + ) + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + check_and_set_common_qt_configure_options() + + if(arg_TOP_LEVEL) + set(build_dir_path "${build_dir_root}") + else() + set(build_dir_path "${build_dir_root}/${repo_name}") + endif() + + set(op_name "cmake_in_qt") + + set(extra_cmake_args "") + if(arg_CMAKE_ARGS) + list(APPEND extra_cmake_args ${arg_CMAKE_ARGS}) + endif() + + if(arg_LOG_LEVEL) + list(APPEND extra_cmake_args LOG_LEVEL "${arg_LOG_LEVEL}") + endif() + + if(extra_cmake_args) + set(extra_cmake_args CMAKE_ARGS ${extra_cmake_args}) + endif() + + call_cmake_in_dir( + TEST_NAME "${test_name}" + BUILD_DIR_PATH "${build_dir_path}" + OP_NAME "${op_name}" + ${extra_cmake_args} + ) +endfunction() + +# Calls cmake --build in the given directory. +function(build_in_dir) + set(opt_args + BUILD_VERBOSE + BUILD_NINJA_EXPLAIN + ) + set(single_args + TEST_NAME + OP_NAME + BUILD_DIR_PATH + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + set(prefix "${arg_TEST_NAME}_") + + if(NOT arg_BUILD_DIR_PATH) + message(FATAL_ERROR "BUILD_DIR_PATH is required.") + endif() + set(build_dir_path "${arg_BUILD_DIR_PATH}") + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${build_dir_path}") + message(STATUS "Building: ${arg_TEST_NAME}:${build_dir_path}") + + setup_verbose_command_output() + + if(arg_OP_NAME) + set(op_name "${arg_OP_NAME}") + else() + set(op_name "build") + endif() + + # Merge stdout with stderr, to avoid having stderr output when configure is re-ran as part of + # the build. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + set(build_args + ${CMAKE_COMMAND} + --build . + --parallel + ) + + if(arg_BUILD_VERBOSE) + list(APPEND build_args --verbose) + endif() + + set(build_tool_args "") + + if(arg_BUILD_NINJA_EXPLAIN) + list(APPEND build_tool_args -d explain) + endif() + + if(build_tool_args) + list(APPEND build_args -- ${build_tool_args}) + endif() + + run_command("${prefix}" ${op_name} ${build_args}) +endfunction() + +# Builds a qt repo. +function(build_qt) + set(opt_args + TOP_LEVEL + IS_REBUILD + BUILD_VERBOSE + BUILD_NINJA_EXPLAIN + ) + set(single_args + TEST_NAME + REPO_NAME + REPO_PATH + BUILD_DIR_ROOT_PATH + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + check_and_set_common_qt_configure_options() + + if(arg_TOP_LEVEL) + set(build_dir_path "${build_dir_root}") + else() + set(build_dir_path "${build_dir_root}/${repo_name}") + endif() + + if(arg_IS_REBUILD) + set(op_name "rebuild_qt") + else() + set(op_name "build_qt") + endif() + + set(extra_build_args "") + if(arg_BUILD_VERBOSE) + list(APPEND extra_build_args BUILD_VERBOSE) + endif() + + if(arg_BUILD_NINJA_EXPLAIN) + list(APPEND extra_build_args BUILD_NINJA_EXPLAIN) + endif() + + build_in_dir( + TEST_NAME "${test_name}" + BUILD_DIR_PATH "${build_dir_path}" + OP_NAME "${op_name}" + ${extra_build_args} + ) +endfunction() + +# Installs a qt repo. +# Is a no-op for no-prefix builds. +function(install_qt) + set(opt_args + TOP_LEVEL + NO_PREFIX + ) + set(single_args + TEST_NAME + REPO_NAME + REPO_PATH + BUILD_DIR_ROOT_PATH + INSTALL_PREFIX + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + message(STATUS "Installing: ${arg_TEST_NAME}") + + if(arg_NO_PREFIX) + # Installation is a no-op for no-prefix builds. + message(STATUS "Skipping installation for ${arg_TEST_NAME} because it's a no-prefix build.") + return() + endif() + + check_and_set_common_qt_configure_options() + + set(prefix "${test_name}_") + if(arg_TOP_LEVEL) + set(build_dir_path "${build_dir_root}") + else() + set(build_dir_path "${build_dir_root}/${repo_name}") + endif() + + get_libexec_dir(libexec_dir) + + if(arg_TOP_LEVEL) + set(libexec_path "${build_dir_path}/qtbase") + elseif(repo_name STREQUAL "qtbase") + set(libexec_path "${build_dir_path}") + else() + set(libexec_path "${arg_INSTALL_PREFIX}") + endif() + set(install_script "${libexec_path}/${libexec_dir}/qt-cmake-private-install.cmake") + + # Merge stdout with stderr, to avoid having stderr output when runinng install_name_tool on + # test reruns on macOS. + set(RunCMake_TEST_OUTPUT_MERGE TRUE) + + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${build_dir_path}") + setup_verbose_command_output() + run_command("${prefix}" install + ${CMAKE_COMMAND} + "-DQT_BUILD_DIR=${build_dir_path}" + -P "${install_script}" + ) +endfunction() + +# A helper to configure build and install a list of qt repos (or top-level). +# Also configures and builds standalone tests and examples, or in-tree tests and examples. +# Handles no-prefix builds, top-level builds, in-source builds. +function(qt_build_helper) + set(opt_args + NO_PREFIX + IN_SOURCE + TOP_LEVEL + BUILD_STANDALONE_TESTS + BUILD_STANDALONE_EXAMPLES_IN_TREE + BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS + BUILD_IN_TREE_TESTS + BUILD_IN_TREE_EXAMPLES + SKIP_STANDALONE_PARTS + RECONFIGURE_WITHOUT_ARGS_AFTER_BUILD + RECONFIGURE_WITHOUT_ARGS_IMMEDIATELY + BUILD_AFTER_RECONFIGURE + ) + set(single_args + TEST_NAME + BUILD_DIR_NAME + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + if(NOT arg_TEST_NAME) + message(FATAL_ERROR "TEST_NAME is required.") + endif() + + if(NOT arg_BUILD_DIR_NAME) + set(arg_BUILD_DIR_NAME "${arg_TEST_NAME}") + endif() + + message(STATUS "Running test: ${arg_TEST_NAME}") + + set(repos ${QT_BUILD_SUBMODULES}) + set(repos_to_skip ${QT_SKIP_SUBMODULES}) + set(build_dir_root "${BUILDS_PATH}/${arg_BUILD_DIR_NAME}") + set(install_prefix "${BUILDS_PATH}/${arg_BUILD_DIR_NAME}/installed") + + if(arg_IN_SOURCE) + set(source_dir "${build_dir_root}") + copy_qt_sources( + DESTINATION_PATH "${source_dir}" + ) + else() + set(source_dir "${TOP_LEVEL_SRC_DIR}") + endif() + + set(extra_args "") + if(arg_NO_PREFIX) + list(APPEND extra_args + NO_PREFIX + ) + else() + list(APPEND extra_args + INSTALL_PREFIX "${install_prefix}" + ) + endif() + + set(extra_cmake_args "") + if(arg_TOP_LEVEL) + set(repos_to_process "qt6") + + # Explicit skip configuring skipped repos for top-level builds. + foreach(repo_to_skip IN LISTS repos_to_skip) + list(APPEND extra_cmake_args "-DBUILD_${repo_to_skip}=OFF") + endforeach() + + list(APPEND extra_args TOP_LEVEL) + else() + set(repos_to_process ${repos}) + endif() + + foreach(repo_name IN LISTS repos_to_process) + if(NOT arg_TOP_LEVEL) + # For per-repo builds, we just skip calling configure. + if(repo_name IN_LIST repos_to_skip) + message(STATUS "Skipping building ${repo_name}") + continue() + endif() + endif() + + if(arg_TOP_LEVEL) + set(repo_path "${source_dir}") + set(test_name "${arg_TEST_NAME}") + else() + set(repo_path "${source_dir}/${repo_name}") + set(test_name "${arg_TEST_NAME}_${repo_name}") + endif() + + set(common_args + TEST_NAME "${test_name}" + REPO_NAME "${repo_name}" + REPO_PATH "${repo_path}" + TOP_LEVEL_SOURCE_DIR_PATH "${source_dir}" + BUILD_DIR_ROOT_PATH "${build_dir_root}" + ${extra_args} + ) + + set(common_cmake_args ${extra_cmake_args}) + + set(build_in_tree_tests OFF) + if(arg_BUILD_IN_TREE_TESTS) + set(build_in_tree_tests ON) + endif() + + set(build_in_tree_examples OFF) + if(arg_BUILD_IN_TREE_EXAMPLES) + set(build_in_tree_examples ON) + list(APPEND common_cmake_args -DQT_BUILD_EXAMPLES_AS_EXTERNAL=OFF) + endif() + + list(APPEND common_cmake_args + -DQT_BUILD_TESTS=${build_in_tree_tests} + -DQT_BUILD_EXAMPLES=${build_in_tree_examples} + ) + + set(common_args_with_cmake_args ${common_args}) + if(common_cmake_args) + list(APPEND common_args_with_cmake_args + CMAKE_ARGS ${common_cmake_args} + ) + endif() + + configure_qt(${common_args_with_cmake_args}) + + if(arg_RECONFIGURE_WITHOUT_ARGS_IMMEDIATELY) + call_cmake_in_qt_build_dir( + ${common_args} + LOG_LEVEL STATUS + ) + endif() + + build_qt(${common_args}) + + if(arg_RECONFIGURE_WITHOUT_ARGS_AFTER_BUILD) + call_cmake_in_qt_build_dir( + ${common_args} + LOG_LEVEL STATUS + ) + endif() + + if(arg_BUILD_AFTER_RECONFIGURE) + build_qt( + IS_REBUILD + BUILD_VERBOSE + BUILD_NINJA_EXPLAIN + ${common_args} + ) + endif() + + install_qt(${common_args_with_cmake_args}) + + if(arg_BUILD_STANDALONE_TESTS AND NOT arg_SKIP_STANDALONE_PARTS) + configure_standalone_part( + ${common_args_with_cmake_args} + PART_NAME "TESTS" + OUT_VAR_BUILD_DIR tests_build_dir + ) + build_in_dir( + TEST_NAME "${test_name}" + BUILD_DIR_PATH "${tests_build_dir}" + OP_NAME "build_tests" + ) + endif() + + if(arg_BUILD_STANDALONE_EXAMPLES_IN_TREE AND NOT arg_SKIP_STANDALONE_PARTS) + configure_standalone_part( + ${common_args_with_cmake_args} + PART_NAME "EXAMPLES" + PART_VARIANT "EXAMPLES_IN_TREE" + OUT_VAR_BUILD_DIR examples_build_dir + ) + build_in_dir( + TEST_NAME "${test_name}" + BUILD_DIR_PATH "${examples_build_dir}" + OP_NAME "build_examples" + ) + endif() + + if(arg_BUILD_STANDALONE_EXAMPLES_AS_EXTERNAL_PROJECTS AND NOT arg_SKIP_STANDALONE_PARTS) + configure_standalone_part( + ${common_args_with_cmake_args} + PART_NAME "EXAMPLES" + PART_VARIANT "EXAMPLES_AS_EXTERNAL_PROJECTS" + OUT_VAR_BUILD_DIR examples_build_dir + ) + build_in_dir( + TEST_NAME "${test_name}" + BUILD_DIR_PATH "${examples_build_dir}" + OP_NAME "build_examples" + ) + endif() + endforeach() +endfunction() + +function(add_test_case) + set(opt_args "") + set(single_args + TEST_NAME + ) + set(multi_args + TEST_GROUPS + TEST_ARGS + ) + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + + if(NOT arg_TEST_NAME) + message(FATAL_ERROR "TEST_NAME is required.") + endif() + + set(test_cases "${TEST_CASES}") + set(test_groups "${TEST_GROUPS}") + + list(APPEND test_cases "${arg_TEST_NAME}") + if(arg_TEST_GROUPS) + list(APPEND test_groups "${arg_TEST_GROUPS}") + list(REMOVE_DUPLICATES test_groups) + foreach(group IN LISTS arg_TEST_GROUPS) + set(group_cases ${TEST_GROUPS_${group}}) + list(APPEND group_cases "${arg_TEST_NAME}") + set(TEST_GROUPS_${group} "${group_cases}") + set(TEST_GROUPS_${group} "${group_cases}" PARENT_SCOPE) + endforeach() + endif() + + set(TESTS_${arg_TEST_NAME}_ARGS ${arg_TEST_ARGS} PARENT_SCOPE) + + set(TEST_CASES "${test_cases}" PARENT_SCOPE) + set(TEST_GROUPS "${test_groups}" PARENT_SCOPE) +endfunction() + +macro(setup_qt_sources_and_repos_to_build) + setup_qt_repos_to_clone() + setup_qt_sources() + setup_list_of_repos_to_build() +endmacro() + +# Applies a filter to the collected test cases from the QT_CI_BUILD_QT_TESTS cmake or env var. +# The filter is a comma separated list of one of the following: +# 'all' to include all tests +# 'test case name' to include a specific test +# 'group name' to include all tests in a specific group +# '-all' to exclude all tests +# '-test case name' to exclude a specific test +# '-group name' to exclude all tests in a specific group +# '-standalone_parts' to exclude building standalone tests and examples for all tests +# The values are processed in the order they appear. +# Default is all. +macro(apply_filter_to_collected_tests) + get_cmake_or_env_or_default(cases_filter QT_CI_BUILD_QT_FILTER "all") + # Allow both semicolons and commas. + string(REPLACE "," ";" cases_filter "${cases_filter}") + + message(STATUS "Applying filter: ${cases_filter}") + + # Validate filters. + set(special_filters + all + standalone_parts + ) + foreach(filter IN LISTS cases_filter) + set(clean_filter "${filter}") + if(filter MATCHES "^-") + string(SUBSTRING "${filter}" 1 -1 clean_filter) + endif() + if(NOT clean_filter IN_LIST TEST_CASES + AND NOT clean_filter IN_LIST TEST_GROUPS + AND NOT clean_filter IN_LIST special_filters) + message(FATAL_ERROR "Invalid filter: ${filter}") + endif() + endforeach() + + set(TEST_CASES_FINAL "") + + # Allow configuring which test cases to run. + foreach(test_name IN LISTS TEST_CASES) + set(include_test_case FALSE) + foreach(filter IN LISTS cases_filter) + set(clean_group_name "${filter}") + if(filter MATCHES "^-") + string(SUBSTRING "${filter}" 1 -1 clean_group_name) + endif() + + if( + # Check if exact test case is included + test_name STREQUAL filter + + # Check if all test cases should be run + OR filter STREQUAL "all" + + # Check if test case is in a specific filter group + OR test_name IN_LIST TEST_GROUPS_${filter} + ) + set(include_test_case TRUE) + endif() + + if( + # Check if test case is explicitly excluded + "-${test_name}" STREQUAL "${filter}" + + # Check if all test cases should be excluded + OR filter STREQUAL "-all" + + # Check if specific group is excluded + OR (filter MATCHES "^-" + AND test_name IN_LIST TEST_GROUPS_${clean_group_name}) + ) + set(include_test_case FALSE) + endif() + endforeach() + if(include_test_case) + list(APPEND TEST_CASES_FINAL "${test_name}") + endif() + endforeach() + + + if("-standalone_parts" IN_LIST cases_filter) + set(SKIP_STANDALONE_PARTS SKIP_STANDALONE_PARTS) + endif() +endmacro() + +# Runs the full suite of tests for building Qt. +function(run_tests) + setup_qt_sources_and_repos_to_build() + + collect_tests() + apply_filter_to_collected_tests() + + set(no_cases "") + if(NOT TEST_CASES_FINAL) + set(no_cases "No test cases to run.") + endif() + + message(STATUS "Running test cases: ${TEST_CASES_FINAL}${no_cases}") + foreach(test_name IN LISTS TEST_CASES_FINAL) + qt_build_helper( + TEST_NAME "${test_name}" + ${TESTS_${test_name}_ARGS} + ${SKIP_STANDALONE_PARTS} + ) + endforeach() +endfunction() diff --git a/tests/manual/RunCMake/ConfigureBuildQt/check.cmake b/tests/manual/RunCMake/ConfigureBuildQt/check.cmake new file mode 100644 index 00000000..56fbb060 --- /dev/null +++ b/tests/manual/RunCMake/ConfigureBuildQt/check.cmake @@ -0,0 +1,9 @@ +# This file is include()d by run_cmake right after it calls execute_process. +# The msg var is set to a non-empty value when the was an error of +# some kind. Record failure with a separate variable in the parent scope, +# so we can FATAL_ERROR at the end. +if(NOT "${msg}" STREQUAL "") + set(should_error_out TRUE PARENT_SCOPE) +else() + set(should_error_out FALSE PARENT_SCOPE) +endif()