qt5/cmake/QtIRGitHelpers.cmake
Alexandru Croitor 29019c9cae CMake: Rewrite init-repository using CMake and .sh / .bat scripts
init-repository is now implemented using CMake + .sh / .bat scripts.

The intent behind the change is not to require Perl to checkout and
build Qt, because it can be troublesome to acquire on Windows and it
can also lead to issues during builds due to CMake picking up a Perl
distribution-shipped compiler.

All previous options were ported over like
- module-subset
- alternates
- etc.

A few new options were added:
- --resolve-deps / --no-resolve-deps
- --optional-deps / --no-optional-deps
- --verbose
and some other internal ones for testing reasons.

The new script does automatic resolving of dependencies
based on the depends / recommends keys in .gitmodules unless
--no-resolve-deps is passed.
So if you configure with --module-subset=qtsvg, the script will also
initialize qtbase.
If --no-optional-deps is passed, only required dependencies ('depends'
ky) will be included and optional dependencies ('recommends' key) will
be excluded.

The new script now has a new default behavior when calling
init-repository a second time with --force, without specifying a
--module-subset option. Instead of initializing all submodules, it
will just update the existing / previously initialized submodules.

It also understands a new module-subset keyword "existing", which
expands to the previously initialized submodules, so someone can
initialize an additional submodule by calling
 init-repository -f --module-subset=existing,qtsvg

Implementation notes:

The overall code flow is init-repository -> cmake/QtIRScript.cmake
-> qt_ir_run_main_script -> qt_ir_run_after_args_parsed ->
qt_ir_handle_init_submodules (recursive) -> qt_ir_clone_one_submodule
with some bells and whistles on the side.

The command line parsing is an adapted copy of the functions
in qtbase/cmake/QtProcessConfigureArgs.cmake. We can't use those exact
functions because qtbase is not available when init-repository is
initially called, and force cloning qtbase was deemed undesirable.

We also have a new mechanism to detect whether init-repository was
previously called. The perl script used the existence of the qtbase
submodule as the check. In the cmake script, we instead set a custom
marker into the local repo config file.

Otherwise the code logic should be a faithful reimplementation of
init-repository.pl aside from some small things like logging and
progress reporting.

The pre-existing git cloning logic in QtTopLevelHelpers was not used
because it would not be compatible with the alternates option and I
didn't want to accidentally break the pre-existing code. Plus
init-repository is a bit opinionated about how it clones and checks
out repos.
The dependency collection and sorting logic uses the pre-existing code
though.

See follow up commit about implicitly calling init-repository when
qt5/configure is called and the repo was not initialized before.

[ChangeLog][General] init-repository was rewritten using CMake. Perl
is no longer required to initialize the qt5.git super repo.

Task-number: QTBUG-120030
Task-number: QTBUG-122622
Change-Id: Ibc38ab79d3fdedd62111ebbec496eabd64c20d2b
Reviewed-by:  Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
2024-02-28 06:23:36 +01:00

1114 lines
43 KiB
CMake

# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Returns the git version.
function(qt_ir_get_git_version out_var)
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
set(extra_args "")
if(perl_identical_output_for_tests)
set(extra_args FORCE_QUIET)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git version ${extra_args}
OUT_OUTPUT_VAR git_output
ERROR_MESSAGE "Failed to retrieve git version")
string(REGEX REPLACE "^git version ([0-9]+)\\.([0-9]+)\\.([0-9]+).*$" "\\1.\\2.\\3"
version "${git_output}")
if(NOT version)
message(FATAL_ERROR "Failed to parse git version: ${git_output}, expected [d]+.[d]+.[d]+")
endif()
set(${out_var} "${version}" PARENT_SCOPE)
endfunction()
# Returns the git version, but caches the result in a global property.
function(qt_ir_get_git_version_cached out_var)
get_property(version GLOBAL PROPERTY _qt_git_version)
if(NOT version)
qt_ir_get_git_version(version)
endif()
set_property(GLOBAL PROPERTY _qt_git_version "${version}")
set(${out_var} "${version}" PARENT_SCOPE)
endfunction()
# Returns whether git supports the git submodule --progress option.
function(qt_ir_is_git_progress_supported out_var)
qt_ir_get_git_version_cached(version)
if(version VERSION_GREATER_EQUAL "2.11")
set(${out_var} TRUE PARENT_SCOPE)
else()
set(${out_var} FALSE PARENT_SCOPE)
endif()
endfunction()
# Get the mirror with trailing slashes removed.
function(qt_ir_get_mirror out_var)
qt_ir_get_option_value(mirror mirror)
qt_ir_get_option_value(berlin berlin)
qt_ir_get_option_value(oslo oslo)
if(berlin)
set(mirror "git://hegel/qt/")
elseif(oslo)
set(mirror "git://qilin/qt/")
endif()
# Replace any double trailing slashes from end of mirror
string(REGEX REPLACE "//+$" "/" mirror "${mirror}")
set(${out_var} "${mirror}" PARENT_SCOPE)
endfunction()
# Sets up the commit template for a submodule.
function(qt_ir_setup_commit_template commit_template_dir working_directory)
set(template "${commit_template_dir}/.commit-template")
if(NOT EXISTS "${template}")
return()
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config commit.template "${template}"
ERROR_MESSAGE "Failed to setup commit template"
WORKING_DIRECTORY "${working_directory}")
endfunction()
# Initializes a list of submodules. This does not them, but just
# sets up the .git/config file submodule.$submodule_name.url based on the .gitmodules template file.
function(qt_ir_run_git_submodule_init submodules working_directory)
set(submodule_dirs "")
foreach(submodule_name IN LISTS submodules)
set(submodule_path "${${prefix}_${submodule_name}_path}")
list(APPEND submodule_dirs "${submodule_name}")
endforeach()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git submodule init ${submodule_dirs}
ERROR_MESSAGE "Failed to git submodule init ${submodule_dirs}"
WORKING_DIRECTORY "${working_directory}")
qt_ir_setup_commit_template("${working_directory}" "${working_directory}")
endfunction()
# Add gerrit remotes to the repository.
function(qt_ir_add_git_remotes repo_name working_directory)
set(gerrit_ssh_base "ssh://@USER@codereview.qt-project.org@PORT@/qt/")
set(gerrit_repo_url "${gerrit_ssh_base}")
qt_ir_get_option_value(codereview-username username)
# If given a username, make a "verbose" remote.
# Otherwise, rely on proper SSH configuration.
if(username)
string(REPLACE "@USER@" "${username}@" gerrit_repo_url "${gerrit_repo_url}")
string(REPLACE "@PORT@" ":29418" gerrit_repo_url "${gerrit_repo_url}")
else()
string(REPLACE "@USER@" "" gerrit_repo_url "${gerrit_repo_url}")
string(REPLACE "@PORT@" "" gerrit_repo_url "${gerrit_repo_url}")
endif()
string(APPEND gerrit_repo_url "${repo_name}")
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config remote.gerrit.url "${gerrit_repo_url}"
ERROR_MESSAGE "Failed to set gerrit repo url"
WORKING_DIRECTORY "${working_directory}")
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS
git config remote.gerrit.fetch "+refs/heads/*:refs/remotes/gerrit/*" "/heads/"
ERROR_MESSAGE "Failed to set gerrit repo fetch refspec"
WORKING_DIRECTORY "${working_directory}")
endfunction()
# Handles the copy-objects option, which is used to detach alternates.
# A copy of all git objects are made from the alternate repository to the current repository.
# Then the alternates reference is removed.
function(qt_ir_handle_detach_alternates working_directory)
qt_ir_get_option_value(copy-objects should_detach)
if(NOT should_detach)
return()
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git repack -a
ERROR_MESSAGE "Failed to repack objects to detach alternates"
WORKING_DIRECTORY "${working_directory}")
set(alternates_path "${working_directory}/.git/objects/info/alternates")
if(EXISTS "${alternates_path}")
file(REMOVE "${alternates_path}")
if(EXISTS "${alternates_path}")
message(FATAL_ERROR "Failed to remove alternates file: ${alternates_path}")
endif()
endif()
endfunction()
# Clones a submodule, unless it was previously cloned.
# When cloning, checks out a specific branch if requested, otherwise does not
# checkout any files yet, mimicking a bare repo.
# Sets up an alternates link if requested.
# Detaches alternates if requested.
# Fetches refs if requested.
# Adds a gerrit git remote.
# Sets up the commit template for the submodule.
function(qt_ir_clone_one_submodule submodule_name)
set(options
CHECKOUT_BRANCH
FETCH
)
set(oneValueArgs
ALTERNATES
BASE_URL
WORKING_DIRECTORY
)
set(multiValueArgs "")
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
qt_ir_get_working_directory_from_arg(working_directory)
set(clone_args "")
set(submodule_path "${${prefix}_${submodule_name}_path}")
if(arg_ALTERNATES)
# alternates is a qt5 repo, so the submodule will be under that.
set(alternates_dir "${arg_ALTERNATES}/${submodule_path}/.git")
if(EXISTS "${alternates_dir}")
list(APPEND clone_args --reference "${arg_ALTERNATES}/${submodule_path}")
else()
message(WARNING "'${arg_ALTERNATES}/${submodule_path}' not found, "
"ignoring alternate for this submodule")
endif()
endif()
if(NOT EXISTS "${working_directory}/${submodule_path}/.git")
set(should_clone TRUE)
else()
set(should_clone FALSE)
endif()
set(submodule_base_git_path "${${prefix}_${submodule_name}_base_git_path}")
set(submodule_url "${submodule_base_git_path}")
qt_ir_has_url_scheme("${submodule_url}" has_url_scheme)
if(NOT has_url_scheme AND arg_BASE_URL)
set(submodule_url "${arg_BASE_URL}${submodule_url}")
endif()
qt_ir_get_mirror(mirror_url)
set(mirror "")
if(NOT has_url_scheme AND mirror_url AND (should_clone OR arg_FETCH))
set(mirror "${mirror_url}${submodule_base_git_path}")
endif()
set(mirror_or_original_url "${submodule_url}")
if(mirror)
# Only use the mirror if it can be reached.
# Access a non-existing ref so no output is shown. It should still
# succeed if the mirror is accessible.
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git ls-remote "${mirror}" "test/if/mirror/exists"
WORKING_DIRECTORY "${working_directory}"
NO_HANDLE_ERROR
OUT_RESULT_VAR proc_result)
if(NOT proc_result EQUAL 0)
message("mirror [${mirror}] is not accessible; ${submodule_url} will be used")
set(mirror "")
else()
set(mirror_or_original_url "${mirror}")
endif()
endif()
set(submodule_branch "${${prefix}_${submodule_name}_branch}")
qt_ir_is_git_progress_supported(is_git_progress_supported)
qt_ir_get_option_value(quiet quiet)
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
set(progress_args "")
if(is_git_progress_supported AND NOT quiet AND NOT perl_identical_output_for_tests)
set(progress_args --progress)
endif()
if(should_clone)
if(arg_CHECKOUT_BRANCH)
list(APPEND clone_args --branch "${submodule_branch}")
else()
list(APPEND clone_args --no-checkout)
endif()
list(APPEND clone_args ${progress_args})
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git clone ${clone_args} "${mirror_or_original_url}" "${submodule_path}"
ERROR_MESSAGE "Failed to clone submodule '${submodule_name}'"
WORKING_DIRECTORY "${working_directory}")
endif()
set(submodule_working_dir "${working_directory}/${submodule_path}")
if(mirror)
# This is only for the user's convenience - we make no use of it.
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config "remote.mirror.url" "${mirror}"
ERROR_MESSAGE "Failed to set git config remote.mirror.url to ${mirror}"
WORKING_DIRECTORY "${submodule_working_dir}")
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config "remote.mirror.fetch" "+refs/heads/*:refs/remotes/mirror/*"
ERROR_MESSAGE "Failed to set git config remote.mirror.fetch"
WORKING_DIRECTORY "${submodule_working_dir}")
endif()
if(NOT should_clone AND arg_FETCH)
# If we didn't clone, fetch from the right location. We always update
# the origin remote, so that submodule update --remote works.
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config remote.origin.url "${mirror_or_original_url}"
ERROR_MESSAGE "Failed to set remote origin url"
WORKING_DIRECTORY "${submodule_working_dir}")
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git fetch origin ${progress_args}
ERROR_MESSAGE "Failed to fetch origin"
WORKING_DIRECTORY "${submodule_working_dir}")
endif()
if(NOT (should_clone OR arg_FETCH) OR mirror)
# Leave the origin configured to the canonical URL. It's already correct
# if we cloned/fetched without a mirror; otherwise it may be anything.
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config remote.origin.url "${submodule_url}"
ERROR_MESSAGE "Failed to set remote origin url"
WORKING_DIRECTORY "${submodule_working_dir}")
endif()
set(commit_template_dir "${working_directory}")
qt_ir_setup_commit_template("${commit_template_dir}" "${submodule_working_dir}")
if(NOT has_url_scheme)
qt_ir_add_git_remotes("${submodule_base_git_path}" "${submodule_working_dir}")
endif()
qt_ir_handle_detach_alternates("${submodule_working_dir}")
endfunction()
# Get list of submodules that were previously initialized, by looking at the .git/config file.
function(qt_ir_get_already_initialized_submodules prefix
out_var_already_initialized_submodules
parent_repo_base_git_path
working_directory
)
qt_ir_parse_git_config_file_contents("${prefix}"
READ_GIT_CONFIG
PARENT_REPO_BASE_GIT_PATH "${parent_repo_base_git_path}"
WORKING_DIRECTORY "${working_directory}"
)
set(${out_var_already_initialized_submodules} "${${prefix}_submodules}" PARENT_SCOPE)
endfunction()
# If init-repository --force is called with a different subset, remove
# previously initialized submodules from the .git/config file.
# Also mark submodules as ignored if requested.
function(qt_ir_handle_submodule_removal_and_ignoring prefix
included_submodules
parent_repo_base_git_path
working_directory
)
qt_ir_get_option_value(ignore-submodules ignore_submodules)
qt_ir_get_already_initialized_submodules("${prefix}"
already_initialized_submodules
"${parent_repo_base_git_path}"
"${working_directory}")
foreach(submodule_name IN LISTS already_initialized_submodules)
if(NOT submodule_name IN_LIST included_submodules)
# If a submodule is not included in the list of submodules to be initialized,
# and it was previously initialized, then remove it from the config.
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config --remove-section "submodule.${submodule_name}"
ERROR_MESSAGE "Failed to deinit submodule '${submodule_name}'"
WORKING_DIRECTORY "${working_directory}")
continue()
endif()
if(ignore_submodules)
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config "submodule.${submodule_name}.ignore" all
ERROR_MESSAGE "Failed to ignore submodule '${submodule_name}'"
WORKING_DIRECTORY "${working_directory}")
endif()
endforeach()
endfunction()
# Checks if the submodule is dirty (has uncommited changes).
function(qt_ir_check_if_dirty_submodule submodule_name working_directory out_is_dirty)
set(submodule_path "${working_directory}/${${prefix}_${submodule_name}_path}")
if(NOT EXISTS "${submodule_path}/.git")
return()
endif()
qt_ir_execute_process_and_log_and_handle_error(
FORCE_QUIET
COMMAND_ARGS git status --porcelain --untracked=no --ignore-submodules=all
WORKING_DIRECTORY "${submodule_path}"
ERROR_MESSAGE "Failed to get dirty status for '${submodule_name}'"
OUT_OUTPUT_VAR git_output)
string(STRIP "${git_output}" git_output)
string(REPLACE "\n" ";" git_lines "${git_output}")
# After a git clone --no-checkout, git status reports all files as
# staged for deletion, but we still want to update the submodule.
# It's unlikely that a genuinely dirty index would have _only_ this
# type of modifications, and it doesn't seem like a horribly big deal
# to lose them anyway, so ignore them.
# @sts = grep(!/^D /, @sts);
# Filter list that starts with the regex
list(FILTER git_lines EXCLUDE REGEX "^D ")
if(git_lines)
message(STATUS "${submodule_name} is dirty.")
set(is_dirty TRUE)
else()
set(is_dirty FALSE)
endif()
set(${out_is_dirty} "${is_dirty}" PARENT_SCOPE)
endfunction()
# Checks if any submodules are dirty and exits early if any are.
function(qt_ir_handle_dirty_submodule submodules working_directory)
set(any_is_dirty FALSE)
foreach(submodule_name IN LISTS submodules)
qt_ir_check_if_dirty_submodule("${submodule_name}" "${working_directory}" is_dirty)
if(is_dirty)
set(any_is_dirty TRUE)
endif()
endforeach()
if(any_is_dirty)
message(FATAL_ERROR "Dirty submodule(s) present; cannot proceed.")
endif()
endfunction()
# If the branch option is set, checkout the branch specified in the .gitmodules file.
function(qt_ir_handle_branch_option prefix submodule_name working_directory)
set(branch_name "${${prefix}_${submodule_name}_branch}")
if(NOT branch_name)
message(FATAL_ERROR "No branch defined for submodule '${submodule_name}'")
endif()
set(repo_dir "${working_directory}/${${prefix}_${submodule_name}_path}")
qt_ir_execute_process_and_log_and_handle_error(
FORCE_QUIET
COMMAND_ARGS git rev-parse -q --verify ${branch_name}
WORKING_DIRECTORY "${repo_dir}"
NO_HANDLE_ERROR
OUT_RESULT_VAR proc_result)
# If the branch exists locally, check it out.
# Otherwise check it out from origin and create a local branch.
if(proc_result EQUAL 0)
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git checkout ${branch_name}
WORKING_DIRECTORY "${repo_dir}"
ERROR_MESSAGE
"Failed to checkout branch '${branch_name}' in submodule '${submodule_name}'")
else()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git checkout -b ${branch_name} origin/${branch_name}
WORKING_DIRECTORY "${repo_dir}"
ERROR_MESSAGE
"Failed to checkout branch '${branch_name}' in submodule '${submodule_name}'")
endif()
endfunction()
# If the update option is set, update the submodules, without fetching.
function(qt_ir_handle_update_option will_checkout_branch working_directory)
set(extra_args "")
if(will_checkout_branch)
list(APPEND extra_args --remote --rebase)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git submodule update --force --no-fetch ${extra_args}
ERROR_MESSAGE "Failed to update submodule '${submodule_name}'"
WORKING_DIRECTORY "${working_directory}")
endfunction()
# Looks for the 'default' and 'existing' keys, and replaces them with appropriate
# values, while making sure to prepend '-' to the values if the original key had it.
function(qt_ir_handle_dash_in_module_subset_expansion out_var
module_subset already_initialized_submodules)
set(expanded_module_subset "")
foreach(submodule_name IN LISTS module_subset)
set(has_dash FALSE)
string(REGEX REPLACE "^(-)" "" submodule_name "${submodule_name}")
if(CMAKE_MATCH_1)
set(has_dash TRUE)
endif()
# Replace the default keyword in the input, with the the list of default submodules types,
# which will be further replaced.
if(submodule_name STREQUAL "default")
set(replacement "preview;essential;addon;deprecated")
# Replace the existing keyword, with the list of already initialized submodules
# from a previous run.
elseif(submodule_name STREQUAL "existing")
set(replacement "${already_initialized_submodules}")
if(has_dash)
# We can't properly support this with the existing algorithm, because we will
# then exclude it also after dependency resolution, and it can cause an empty list
# of submodules in certain situations.
message(FATAL_ERROR "Excluding existing submodules with '-existing' "
"is not supported, just don't include them.")
endif()
else()
set(replacement "${submodule_name}")
endif()
# Prepend dash to all expanded values
if(has_dash)
list(TRANSFORM replacement PREPEND "-")
endif()
list(APPEND expanded_module_subset "${replacement}")
endforeach()
set(${out_var} "${expanded_module_subset}" PARENT_SCOPE)
endfunction()
# Processes the given module subset using values that were set by parsing the .gitmodules file.
#
# The module subset is a comma-separated list of module names, with an optional '-' at the start.
# If a - is present, the module (or special expanded keyword) is excluded from the subset.
# If the value is empty, the default subset is used on initial runs, or the previously
# existing submodules are used on subsequent runs.
# If the value is "all", all known submodules are included.
# If the value is a status like 'addon' or 'essential', only submodules with that status are
# included.
# If the value is 'existing', only submodules that were previously initialized are included.
# This evaluates to an empty list for the first script run.
# If the value is a module name, only that module is included.
# The modules to exclude are also set separately, so they can be excluded even after dependency
# resolution which is done later.
function(qt_ir_process_module_subset_values prefix)
set(options
PREVIOUSLY_INITIALIZED
)
set(oneValueArgs
OUT_VAR_INCLUDE
OUT_VAR_EXCLUDE
)
set(multiValueArgs
ALREADY_INITIALIZED_SUBMODULES
EXTRA_IMPLICIT_SUBMODULES
MODULE_SUBSET
)
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
string(REPLACE "," ";" module_subset "${arg_MODULE_SUBSET}")
# If a module subset is not specified, either use the default list for the very first run,
# or use the previously initialized submodules for a subsequent run.
if(NOT module_subset)
if(arg_PREVIOUSLY_INITIALIZED)
set(module_subset "existing")
else()
set(module_subset "default")
endif()
endif()
qt_ir_handle_dash_in_module_subset_expansion(
expanded_module_subset "${module_subset}" "${arg_ALREADY_INITIALIZED_SUBMODULES}")
set(include_modules "")
set(exclude_modules "")
if(arg_EXTRA_IMPLICIT_SUBMODULES)
list(APPEND include_modules ${arg_EXTRA_IMPLICIT_SUBMODULES})
endif()
foreach(value IN LISTS expanded_module_subset ${prefix}_submodules_to_remove)
# An '-' at the start means we should exclude those modules.
string(REGEX REPLACE "^(-)" "" value "${value}")
set(list_op "APPEND")
if(CMAKE_MATCH_1)
set(list_op "REMOVE_ITEM")
endif()
if(value STREQUAL "all")
list(${list_op} include_modules "${${prefix}_submodules}")
if("${list_op}" STREQUAL "REMOVE_ITEM")
list(APPEND exclude_modules "${${prefix}_submodules}")
endif()
elseif(value IN_LIST ${prefix}_statuses)
list(${list_op} include_modules "${${prefix}_status_${value}_submodules}")
if("${list_op}" STREQUAL "REMOVE_ITEM")
list(APPEND exclude_modules "${${prefix}_status_${value}_submodules}")
endif()
elseif(NOT "${${prefix}_${value}_path}" STREQUAL "")
list(${list_op} include_modules "${value}")
if("${list_op}" STREQUAL "REMOVE_ITEM")
list(APPEND exclude_modules "${value}")
endif()
else()
if(list_op STREQUAL "REMOVE_ITEM")
message(WARNING "Excluding non-existent module: ${value}")
else()
message(FATAL_ERROR
"Invalid module subset specified, module name is non-existent: ${value}")
endif()
endif()
endforeach()
set(${arg_OUT_VAR_INCLUDE} "${include_modules}" PARENT_SCOPE)
set(${arg_OUT_VAR_EXCLUDE} "${exclude_modules}" PARENT_SCOPE)
endfunction()
# Sort the modules and add dependencies if dependency resolving is enabled.
function(qt_ir_get_module_subset_including_deps prefix out_var initial_modules)
qt_ir_get_option_value(resolve-deps resolve_deps)
qt_ir_get_option_value(optional-deps include_optional_deps)
if(resolve_deps)
set(exclude_optional_deps "")
if(NOT include_optional_deps)
set(exclude_optional_deps EXCLUDE_OPTIONAL_DEPS)
endif()
qt_internal_sort_module_dependencies("${initial_modules}" out_repos
${exclude_optional_deps}
PARSE_GITMODULES
GITMODULES_PREFIX_VAR "${prefix}"
)
else()
set(out_repos "${initial_modules}")
endif()
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
if(NOT perl_identical_output_for_tests)
message(DEBUG "repos that will be initialized after dependency handling: ${out_repos}")
endif()
set(${out_var} "${out_repos}" PARENT_SCOPE)
endfunction()
# Check whether init-repository has been run before, perl style.
# We assume that if the submodule qtbase has been initialized, then init-repository has been run.
function(qt_ir_check_if_already_initialized_perl_style out_var_is_initialized working_directory)
set(cmd git config --get submodule.qtbase.url)
set(extra_args "")
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
if(perl_identical_output_for_tests)
list(APPEND extra_args FORCE_QUIET)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS ${cmd}
OUT_RESULT_VAR git_result
OUT_OUTPUT_VAR git_output
OUT_ERROR_VAR git_error
${extra_args}
NO_HANDLE_ERROR
WORKING_DIRECTORY "${working_directory}")
if(git_result EQUAL 1 AND NOT git_output)
set(is_initialized FALSE)
elseif(git_result EQUAL 0 AND git_output)
set(is_initialized TRUE)
else()
message(FATAL_ERROR "Failed to get result of ${cmd}: ${git_output}")
endif()
set(${out_var_is_initialized} "${is_initialized}" PARENT_SCOPE)
endfunction()
# Check whether init-repository has been run before, cmake style.
# Check for the presence of the initrepository.initialized git config key.
function(qt_ir_check_if_already_initialized_cmake_style out_var_is_initialized working_directory)
set(options
FORCE_QUIET
)
set(oneValueArgs "")
set(multiValueArgs "")
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(cmd git config --get initrepository.initialized)
set(extra_args "")
if(arg_FORCE_QUIET)
list(APPEND extra_args FORCE_QUIET)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS ${cmd}
OUT_RESULT_VAR git_result
OUT_OUTPUT_VAR git_output
OUT_ERROR_VAR git_error
${extra_args}
NO_HANDLE_ERROR
WORKING_DIRECTORY "${working_directory}")
if(git_result EQUAL 1 AND NOT git_output)
set(is_initialized FALSE)
elseif(git_result EQUAL 0 AND git_output)
set(is_initialized TRUE)
else()
message(FATAL_ERROR "Failed to get result of ${cmd}: ${git_output}")
endif()
set(${out_var_is_initialized} "${is_initialized}" PARENT_SCOPE)
endfunction()
# Check whether init-repository has been run before.
# The CMake and perl script do it differently, choose which way to do it based
# on the active options.
function(qt_ir_check_if_already_initialized out_var_is_initialized working_directory)
qt_ir_get_option_value(perl-init-check perl_init_check)
if(perl_init_check)
qt_ir_check_if_already_initialized_perl_style(is_initialized "${working_directory}")
else()
qt_ir_check_if_already_initialized_cmake_style(is_initialized "${working_directory}")
endif()
set(${out_var_is_initialized} "${is_initialized}" PARENT_SCOPE)
endfunction()
# Marks the repository as initialized.
# The perl script used to determine this by checking whether the qtbase submodule was initialized.
# In the CMake script, we instead opt to set an explicit marker in the repository.
function(qt_ir_set_is_initialized working_directory)
# If emulating perl style initialization check, don't set the marker and exit early.
qt_ir_get_option_value(perl-init-check perl_init_check)
if(perl_init_check)
return()
endif()
set(cmd git config initrepository.initialized true)
set(extra_args "")
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
if(perl_identical_output_for_tests)
list(APPEND extra_args FORCE_QUIET)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS ${cmd}
ERROR_MESSAGE "Failed to mark repository as initialized"
${extra_args}
WORKING_DIRECTORY "${working_directory}")
endfunction()
# If the repository has already been initialized, exit early.
function(qt_ir_handle_if_already_initialized out_var_should_exit working_directory)
set(should_exit FALSE)
qt_ir_check_if_already_initialized(is_initialized "${working_directory}")
qt_ir_get_option_value(force force)
qt_ir_get_option_value(quiet quiet)
if(is_initialized)
if(NOT force)
set(should_exit TRUE)
if(NOT quiet)
message(
"Will not reinitialize already initialized repository (use -f to force)!")
endif()
endif()
endif()
set(${out_var_should_exit} ${should_exit} PARENT_SCOPE)
endfunction()
# Parses git remote.origin.url and extracts the base url and the repository name.
#
# base_url example: git://code.qt.io/qt
# repo name example: qt5 or tqtc-qt5
function(qt_ir_get_qt5_repo_name_and_base_url)
set(options "")
set(oneValueArgs
OUT_VAR_QT5_REPO_NAME
OUT_VAR_BASE_URL
WORKING_DIRECTORY
)
set(multiValueArgs "")
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT arg_WORKING_DIRECTORY)
message(FATAL_ERROR "qt_ir_get_qt5_repo_name_and_base_url: No working directory specified")
endif()
set(working_directory "${arg_WORKING_DIRECTORY}")
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
set(extra_args "")
if(perl_identical_output_for_tests)
set(extra_args FORCE_QUIET)
endif()
qt_ir_execute_process_and_log_and_handle_error(
COMMAND_ARGS git config remote.origin.url ${extra_args}
ERROR_MESSAGE "No origin remote found for qt5 repository"
OUT_OUTPUT_VAR git_output
WORKING_DIRECTORY "${working_directory}")
string(STRIP "${git_output}" git_output)
# Remove the .git at the end, with an optional slash
string(REGEX REPLACE ".git/?$" "" qt5_repo_name "${git_output}")
# Remove the tqtc- prefix, if it exists, and the qt5 suffix and that will be the base_url
# The qt5_repo_name is qt5 or tqtc-qt5.
string(REGEX REPLACE "((tqtc-)?qt5)$" "" base_url "${qt5_repo_name}")
set(qt5_repo_name "${CMAKE_MATCH_1}")
if(NOT qt5_repo_name)
set(qt5_repo_name "qt5")
endif()
if(NOT base_url)
message(FATAL_ERROR "Failed to parse base url from origin remote: ${git_output}")
endif()
set(${arg_OUT_VAR_QT5_REPO_NAME} "${qt5_repo_name}" PARENT_SCOPE)
set(${arg_OUT_VAR_BASE_URL} "${base_url}" PARENT_SCOPE)
endfunction()
# Creates a symlink or a forwarding script to the target path.
# Use for setting up git hooks.
function(qt_ir_ensure_link source_path target_path)
qt_ir_get_option_value(force-hooks force_hooks)
if(EXISTS "${target_path}" AND NOT force_hooks)
return()
endif()
# In case we have a dead symlink or pre-existing hook
file(REMOVE "${target_path}")
qt_ir_get_option_value(quiet quiet)
if(NOT quiet)
message("Aliasing ${source_path}\n as ${target_path} ...")
endif()
if(NOT CMAKE_HOST_WIN32)
file(CREATE_LINK "${source_path}" "${target_path}" RESULT result SYMBOLIC)
# Don't continue upon success. If symlinking failed, fallthrough to creating
# a forwarding script.
if(result EQUAL 0)
return()
endif()
endif()
# Windows doesn't do (proper) symlinks. As the post_commit script needs
# them to locate itself, we write a forwarding script instead.
# Make the path palatable for MSYS.
string(REGEX REPLACE "^(.):/" "/\\1/" source_path "${source_path}")
set(contents "#!/bin/sh\nexec ${source_path} \"$@\"\n")
file(WRITE "${target_path}" "${contents}")
endfunction()
# Installs the git hooks from the qtrepotools module.
function(qt_ir_install_git_hooks)
set(options "")
set(oneValueArgs
PARENT_REPO_BASE_GIT_PATH
WORKING_DIRECTORY
)
set(multiValueArgs "")
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT arg_WORKING_DIRECTORY)
message(FATAL_ERROR "qt_ir_install_git_hooks: No working directory specified")
endif()
set(working_directory "${arg_WORKING_DIRECTORY}")
if(NOT arg_PARENT_REPO_BASE_GIT_PATH)
message(FATAL_ERROR "qt_ir_install_git_hooks: No PARENT_REPO_BASE_GIT_PATH specified")
endif()
set(parent_repo_base_git_path "${arg_PARENT_REPO_BASE_GIT_PATH}")
set(hooks_dir "${CMAKE_CURRENT_SOURCE_DIR}/qtrepotools/git-hooks")
if(NOT EXISTS "${hooks_dir}")
message("Warning: cannot find Git hooks, qtrepotools module might be absent")
return()
endif()
set(prefix ir_hooks)
qt_ir_parse_git_config_file_contents("${prefix}"
READ_GIT_CONFIG_LOCAL
PARENT_REPO_BASE_GIT_PATH "${parent_repo_base_git_path}"
WORKING_DIRECTORY "${working_directory}"
)
foreach(submodule_name IN LISTS ${prefix}_submodules)
set(submodule_git_dir "${working_directory}/${submodule_name}/.git")
if(NOT IS_DIRECTORY "${submodule_git_dir}")
# Get first line
file(STRINGS "${submodule_git_dir}" submodule_git_dir_contents LIMIT_COUNT 1)
# Remove the gitdir: prefix
string(REGEX REPLACE "^(gitdir: )" "" submodule_git_dir
"${submodule_git_dir_contents}")
if("${CMAKE_MATCH_1}" STREQUAL "")
message(FATAL_ERROR "Malformed .git file ${submodule_git_dir}")
endif()
# Make it an absolute path, because gitdir: is usually relative to the submodule
get_filename_component(submodule_git_dir "${submodule_git_dir}"
ABSOLUTE BASE_DIR "${working_directory}/${submodule_name}")
# Untested
set(common_dir "${submodule_git_dir}/commondir")
if(EXISTS "${common_dir}")
file(STRINGS "${common_dir}" common_dir_contents LIMIT_COUNT 1)
string(STRIP "${common_dir_contents}" common_dir_path)
set(submodule_git_dir "${submodule_git_dir}/${common_dir_path}")
get_filename_component(submodule_git_dir "${submodule_git_dir}" ABSOLUTE)
endif()
endif()
qt_ir_ensure_link("${hooks_dir}/gerrit_commit_msg_hook"
"${submodule_git_dir}/hooks/commit-msg")
qt_ir_ensure_link("${hooks_dir}/git_post_commit_hook"
"${submodule_git_dir}/hooks/post-commit")
qt_ir_ensure_link("${hooks_dir}/clang-format-pre-commit"
"${submodule_git_dir}/hooks/pre-commit")
endforeach()
endfunction()
# Parses the .gitmodules file and proceses the submodules based on the module-subset option
# or the given SUBMODULES argument.
# Also adds dependencies if requested.
#
# This is a macro because we want the variables set by
# qt_ir_parse_gitmodules_file_contents to be available in the calling scope, because it's
# essentially setting a dictionarty, and we don't want to propagate all the variables manually.
macro(qt_ir_get_submodules prefix out_var_submodules)
set(options
PREVIOUSLY_INITIALIZED
PROCESS_SUBMODULES_FROM_COMMAND_LINE
)
set(oneValueArgs
PARENT_REPO_BASE_GIT_PATH
WORKING_DIRECTORY
)
set(multiValueArgs
ALREADY_INITIALIZED_SUBMODULES
SUBMODULES
)
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
qt_ir_get_working_directory_from_arg(working_directory)
# Parse the .gitmodules content here, so the parsed data is available downstream
# in other functions and recursive calls of the same function.
qt_ir_parse_git_config_file_contents("${prefix}"
READ_GITMODULES
PARENT_REPO_BASE_GIT_PATH "${arg_PARENT_REPO_BASE_GIT_PATH}"
WORKING_DIRECTORY "${working_directory}"
)
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
set(extra_implict_submodules "")
# Get which modules should be initialized, based on the module-subset option.
if(arg_PROCESS_SUBMODULES_FROM_COMMAND_LINE)
qt_ir_get_option_value(module-subset initial_module_subset)
# Implicitly add qtrepotools, so we can install git hooks and don't get the
# missing qtrepotools warning.
if(NOT perl_identical_output_for_tests)
list(APPEND extra_implict_submodules "qtrepotools")
qt_ir_is_verbose(verbose)
if(verbose)
message("Implicitly adding qtrepotools to the list of submodules "
"to initialize for access to git commit hooks, etc. "
"(use --module-subset=<values>,-qtrepotools to exclude it)")
endif()
endif()
if(NOT perl_identical_output_for_tests)
message(DEBUG "module-subset from command line: ${initial_module_subset}")
endif()
elseif(arg_SUBMODULES)
set(initial_module_subset "${arg_SUBMODULES}")
if(NOT perl_identical_output_for_tests)
message(DEBUG "module-subset from args: ${initial_module_subset}")
endif()
else()
message(FATAL_ERROR "No submodules specified")
endif()
qt_ir_get_cmake_flag(PREVIOUSLY_INITIALIZED previously_initialized_opt)
qt_ir_process_module_subset_values("${prefix}"
${previously_initialized_opt}
${perl_identical_output_opt}
ALREADY_INITIALIZED_SUBMODULES ${arg_ALREADY_INITIALIZED_SUBMODULES}
EXTRA_IMPLICIT_SUBMODULES ${extra_implict_submodules}
MODULE_SUBSET "${initial_module_subset}"
OUT_VAR_INCLUDE processed_module_subset
OUT_VAR_EXCLUDE modules_to_exclude
)
if(NOT perl_identical_output_for_tests)
message(DEBUG "Processed module subset: ${processed_module_subset}")
endif()
# We only resolve dependencies for the top-level call, not for recursive calls.
if(arg_PROCESS_SUBMODULES_FROM_COMMAND_LINE)
# Resolve which submodules should be initialized, including dependencies.
qt_ir_get_module_subset_including_deps("${prefix}"
submodules_with_deps "${processed_module_subset}")
# Then remove any explicitly specified submodules.
set(submodules_with_deps_and_excluded "${submodules_with_deps}")
list(REMOVE_ITEM submodules_with_deps_and_excluded ${modules_to_exclude})
if(NOT perl_identical_output_for_tests AND modules_to_exclude)
message(DEBUG "Repos that will be excluded after dependency handling: ${modules_to_exclude}")
endif()
set(submodules "${submodules_with_deps_and_excluded}")
else()
set(submodules "${processed_module_subset}")
endif()
# Remove duplicates
set(submodules_maybe_duplicates "${submodules}")
list(REMOVE_DUPLICATES submodules)
if(NOT perl_identical_output_for_tests AND NOT submodules STREQUAL submodules_maybe_duplicates)
message(DEBUG "Removed duplicates from submodules, final list: ${submodules}")
endif()
set(${out_var_submodules} "${submodules}" PARENT_SCOPE)
endmacro()
# Recursively initialize submodules starting from the given current working directory.
# This is the equivalent of the perl script's git_clone_all_submodules function.
function(qt_ir_handle_init_submodules prefix)
set(options
CHECKOUT_BRANCH
PREVIOUSLY_INITIALIZED
PROCESS_SUBMODULES_FROM_COMMAND_LINE
)
set(oneValueArgs
ALTERNATES
BASE_URL
PARENT_REPO_BASE_GIT_PATH
WORKING_DIRECTORY
)
set(multiValueArgs
ALREADY_INITIALIZED_SUBMODULES
SUBMODULES
)
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
qt_ir_get_working_directory_from_arg(working_directory)
# Get the submodules that should be initialized.
qt_ir_get_cmake_flag(PROCESS_SUBMODULES_FROM_COMMAND_LINE
process_submodules_from_command_line_opt)
qt_ir_get_cmake_flag(PREVIOUSLY_INITIALIZED
previously_initialized_opt)
qt_ir_get_submodules(${prefix} submodules
${process_submodules_from_command_line_opt}
${previously_initialized_opt}
ALREADY_INITIALIZED_SUBMODULES ${arg_ALREADY_INITIALIZED_SUBMODULES}
PARENT_REPO_BASE_GIT_PATH "${arg_PARENT_REPO_BASE_GIT_PATH}"
SUBMODULES "${arg_SUBMODULES}"
WORKING_DIRECTORY "${working_directory}"
)
qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests)
if(NOT submodules AND NOT perl_identical_output_for_tests)
message("No submodules were given to initialize or they were all excluded.")
return()
endif()
# Initialize the submodules, but don't clone them yet.
qt_ir_run_git_submodule_init("${submodules}" "${working_directory}")
# Deinit submodules that are not in the list of submodules to be initialized.
qt_ir_handle_submodule_removal_and_ignoring("${prefix}"
"${submodules}" "${arg_PARENT_REPO_BASE_GIT_PATH}" "${working_directory}")
# Check for dirty submodules.
qt_ir_handle_dirty_submodule("${submodules}" "${working_directory}")
qt_ir_get_cmake_flag(CHECKOUT_BRANCH branch_flag)
qt_ir_get_option_as_cmake_flag_option(fetch "FETCH" fetch_flag)
# Manually clone each submodule if it was not previously cloned, so we can easily
# use reference (alternates) repos, mirrors, etc.
# If already cloned, just fetch new data.
#
# Note that manually cloning the submodules, as opposed to running git submodule update,
# places the .git directories inside the submodule directories, but latest git versions
# expect it in $super_repo/.git/modules.
# When de-initializing submodules manually, git will absorb the .git directories into the super
# repo.
# In case if the super repo already has a copy of the submodule .git dir, git will fail
# to absorb the .git dir and error out. In that case the already existing .git dir needs to be
# removed manually, there is no git command to do it afaik.
foreach(submodule_name IN LISTS submodules)
qt_ir_clone_one_submodule(${submodule_name}
ALTERNATES ${arg_ALTERNATES}
BASE_URL ${arg_BASE_URL}
WORKING_DIRECTORY "${working_directory}"
${branch_flag}
${fetch_flag}
)
endforeach()
# Checkout branches instead of the default detached HEAD.
if(branch_flag)
foreach(submodule_name IN LISTS submodules)
qt_ir_handle_branch_option("${prefix}" ${submodule_name} "${working_directory}")
endforeach()
endif()
qt_ir_get_option_value(update will_update)
if(will_update)
# Update the checked out refs without fetching.
qt_ir_handle_update_option("${branch_flag}" "${working_directory}")
# Recursively initialize submodules of submodules.
foreach(submodule_name IN LISTS submodules)
set(submodule_path "${${prefix}_${submodule_name}_path}")
set(submodule_gitmodules_path "${working_directory}/${submodule_path}/.gitmodules")
if(EXISTS "${submodule_gitmodules_path}")
set(alternates_option "")
if(arg_ALTERNATES)
set(alternates_option ALTERNATES "${arg_ALTERNATES}/${submodule_name}")
endif()
set(submodule_base_git_path "${${prefix}_${submodule_name}_base_git_path}")
qt_ir_handle_init_submodules(
# Use a different prefix to store new gitmodules data
ir_sub_${submodule_name}
# Check out all submodules recursively
SUBMODULES "all"
BASE_URL "${base_url}"
PARENT_REPO_BASE_GIT_PATH "${submodule_base_git_path}"
WORKING_DIRECTORY "${working_directory}/${submodule_name}"
# The CHECKOUT_BRANCH option is not propagated on purpose
${alternates_option}
)
endif()
endforeach()
endif()
endfunction()