diff --git a/.gitignore b/.gitignore index d4d4cae1..30b03896 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ CMakeUserPresets.json build build-* .DS_Store +init-repository.opt +init-repository.opt.in diff --git a/cmake/QtIRCommandLineHelpers.cmake b/cmake/QtIRCommandLineHelpers.cmake new file mode 100644 index 00000000..5cc0caed --- /dev/null +++ b/cmake/QtIRCommandLineHelpers.cmake @@ -0,0 +1,369 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# This file contains a modified subset of the qtbase/QtProcessConfigureArgs.cmake commands +# with renamed functions, because we need similar logic for init-repository, but +# we can't access qtbase before we clone it. + +# Call a function with the given arguments. +function(qt_ir_call_function func) + set(call_code "${func}(") + math(EXPR n "${ARGC} - 1") + foreach(i RANGE 1 ${n}) + string(APPEND call_code "\"${ARGV${i}}\" ") + endforeach() + string(APPEND call_code ")") + string(REPLACE "\\" "\\\\" call_code "${call_code}") + if(${CMAKE_VERSION} VERSION_LESS "3.18.0") + set(incfile qt_tmp_func_call.cmake) + file(WRITE "${incfile}" "${call_code}") + include(${incfile}) + file(REMOVE "${incfile}") + else() + cmake_language(EVAL CODE "${call_code}") + endif() +endfunction() + +# Show an error. +function(qt_ir_add_error) + message(FATAL_ERROR ${ARGV}) +endfunction() + +# Check if there are still unhandled command line arguments. +function(qt_ir_args_has_next_command_line_arg out_var) + qt_ir_get_unhandled_args(args) + + list(LENGTH args n) + if(n GREATER 0) + set(result TRUE) + else() + set(result FALSE) + endif() + set(${out_var} ${result} PARENT_SCOPE) +endfunction() + +# Get the next unhandled command line argument without popping it. +function(qt_ir_args_peek_next_command_line_arg out_var) + qt_ir_get_unhandled_args(args) + list(GET args 0 result) + set(${out_var} ${result} PARENT_SCOPE) +endfunction() + +# Get the next unhandled command line argument. +function(qt_ir_args_get_next_command_line_arg out_var) + qt_ir_get_unhandled_args(args) + list(POP_FRONT args result) + qt_ir_set_unhandled_args("${args}") + set(${out_var} ${result} PARENT_SCOPE) +endfunction() + +# Helper macro to parse the arguments for the command line options. +macro(qt_ir_commandline_option_parse_arguments) + set(options UNSUPPORTED) + set(oneValueArgs TYPE NAME SHORT_NAME ALIAS VALUE DEFAULT_VALUE) + set(multiValueArgs VALUES MAPPING) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) +endmacro() + +# We use this to define the command line options that init-repository accepts. +# Arguments +# name - name of the long form option +# e.g. 'module-subset' will parse '--module-subset' +# UNSUPPORTED - mark the option as unsupported in the cmake port of init-repository, +# which means we will fall back to calling the perl script instead +# TYPE - the type of the option, currently we support boolean, string and void +# VALUE - the value to be set for a 'void' type option +# VALUES - the valid values for an option +# MAPPING - currently unused +# SHORT_NAME - an alternative short name flag, +# e.g. 'f' will parse -f for --force +# ALIAS - mark the option as an alias of another option, both will have the +# same value when retrieved. +# DEFAULT_VALUE - the default value to be set for the option when it's not specified +# on the command line +# +# NOTE: Make sure to update the SHORT_NAME code path when adding new options. +function(qt_ir_commandline_option_helper name) + qt_ir_commandline_option_parse_arguments(${ARGN}) + + set(unsupported_options "${commandline_known_unsupported_options}") + if(arg_UNSUPPORTED) + set(commandline_option_${name}_unsupported + "${arg_UNSUPPORTED}" PARENT_SCOPE) + list(APPEND unsupported_options "${name}") + endif() + set(commandline_known_unsupported_options "${unsupported_options}" PARENT_SCOPE) + + set(commandline_known_options + "${commandline_known_options};${name}" PARENT_SCOPE) + + set(commandline_option_${name}_type "${arg_TYPE}" PARENT_SCOPE) + + if(NOT "${arg_VALUE}" STREQUAL "") + set(commandline_option_${name}_value "${arg_VALUE}" PARENT_SCOPE) + endif() + + if(arg_VALUES) + set(commandline_option_${name}_values ${arg_VALUES} PARENT_SCOPE) + elseif(arg_MAPPING) + set(commandline_option_${name}_mapping ${arg_MAPPING} PARENT_SCOPE) + endif() + + if(NOT "${arg_SHORT_NAME}" STREQUAL "") + set(commandline_option_${name}_short_name "${arg_SHORT_NAME}" PARENT_SCOPE) + endif() + + if(NOT "${arg_ALIAS}" STREQUAL "") + set(commandline_option_${name}_alias "${arg_ALIAS}" PARENT_SCOPE) + endif() + + # Should be last, in case alias was specified + if(NOT "${arg_DEFAULT_VALUE}" STREQUAL "") + set(commandline_option_${name}_default_value "${arg_DEFAULT_VALUE}" PARENT_SCOPE) + qt_ir_command_line_set_input("${name}" "${arg_DEFAULT_VALUE}") + endif() +endfunction() + +# Defines an option that init-repository understands. +# Uses qt_ir_commandline_option_helper to define both long and short option names. +macro(qt_ir_commandline_option name) + # Define the main option + qt_ir_commandline_option_helper("${name}" ${ARGN}) + + qt_ir_commandline_option_parse_arguments(${ARGN}) + + # Define the short name option if it's requested + if(NOT "${arg_SHORT_NAME}" STREQUAL "" + AND "${commandline_option_${arg_SHORT_NAME}_type}" STREQUAL "") + set(unsupported "") + if(arg_UNSUPPORTED) + set(unsupported "${arg_UNSUPPORTED}") + endif() + + qt_ir_commandline_option_helper("${arg_SHORT_NAME}" + TYPE "${arg_TYPE}" + ALIAS "${name}" + VALUE "${arg_VALUE}" + VALUES ${arg_VALUES} + MAPPING ${arg_MAPPING} + DEFAULT_VALUE ${arg_DEFAULT_VALUE} + ${unsupported} + ) + endif() +endmacro() + +# Saves the value of a command line option into a global property. +function(qt_ir_command_line_set_input name val) + if(NOT "${commandline_option_${name}_alias}" STREQUAL "") + set(name "${commandline_option_${name}_alias}") + endif() + + set_property(GLOBAL PROPERTY _qt_ir_input_${name} "${val}") + set_property(GLOBAL APPEND PROPERTY _qt_ir_inputs ${name}) +endfunction() + +# Appends a value of a command line option into a global property. +# Currently unused +function(qt_ir_command_line_append_input name val) + if(NOT "${commandline_option_${name}_alias}" STREQUAL "") + set(name "${commandline_option_${name}_alias}") + endif() + + get_property(oldval GLOBAL PROPERTY _qt_ir_input_${name}) + if(NOT "${oldval}" STREQUAL "") + string(PREPEND val "${oldval};") + endif() + qt_ir_command_line_set_input(${name} "${val}" ) +endfunction() + +# Checks if the value of a command line option is valid. +function(qt_ir_validate_value opt val out_var) + set(${out_var} TRUE PARENT_SCOPE) + + set(valid_values ${commandline_option_${arg}_values}) + list(LENGTH valid_values n) + if(n EQUAL 0) + return() + endif() + + foreach(v ${valid_values}) + if(val STREQUAL v) + return() + endif() + endforeach() + + set(${out_var} FALSE PARENT_SCOPE) + list(JOIN valid_values " " valid_values_str) + qt_ir_add_error("Invalid value '${val}' supplied to command line option '${opt}'." + "\nAllowed values: ${valid_values_str}\n") +endfunction() + +# Sets / handles the value of a command line boolean option. +function(qt_ir_commandline_boolean arg val nextok) + if("${val}" STREQUAL "") + set(val "yes") + endif() + if(NOT val STREQUAL "yes" AND NOT val STREQUAL "no") + message(FATAL_ERROR + "Invalid value '${val}' given for boolean command line option '${arg}'.") + endif() + qt_ir_command_line_set_input("${arg}" "${val}") +endfunction() + +# Sets / handles the value of a command line string option. +function(qt_ir_commandline_string arg val nextok) + if(nextok) + qt_ir_args_get_next_command_line_arg(val) + + if("${val}" MATCHES "^-") + qt_ir_add_error("No value supplied to command line options '${arg}'.") + endif() + endif() + qt_ir_validate_value("${arg}" "${val}" success) + if(success) + qt_ir_command_line_set_input("${arg}" "${val}") + endif() +endfunction() + +# Sets / handles the value of a command line void option. +# This is an option like --force, which doesn't take any arguments. +# Currently unused +function(qt_ir_commandline_void arg val nextok) + if(NOT "${val}" STREQUAL "") + qt_i_add_error("Command line option '${arg}' expects no argument ('${val}' given).") + endif() + if(DEFINED commandline_option_${arg}_value) + set(val ${commandline_option_${arg}_value}) + endif() + if("${val}" STREQUAL "") + set(val yes) + endif() + qt_ir_command_line_set_input("${arg}" "${val}") +endfunction() + +# Reads the command line arguments from the optfile_path. +function(qt_ir_get_raw_args_from_optfile optfile_path out_var) + file(STRINGS "${optfile_path}" args) + set(${out_var} "${args}" PARENT_SCOPE) +endfunction() + + # Reads the optfile_path, iterates over the given command line arguments, + # sets the input for recongized options. + # + # IGNORE_UNKNOWN_ARGS tells the function not to fail if it encounters an unknown + # option, needed when the script is called from the configure script with + # configure-only-known options. +function(qt_ir_process_args_from_optfile optfile_path) + set(options IGNORE_UNKNOWN_ARGS) + set(oneValueArgs "") + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + qt_ir_get_raw_args_from_optfile("${optfile_path}" configure_args) + qt_ir_set_unhandled_args("${configure_args}") + + while(1) + qt_ir_args_has_next_command_line_arg(has_next) + if(NOT has_next) + break() + endif() + qt_ir_args_get_next_command_line_arg(arg) + + # parse out opt and val + set(nextok FALSE) + if(arg MATCHES "^--?(disable|no)-(.*)") + set(opt "${CMAKE_MATCH_2}") + set(val "no") + elseif(arg MATCHES "^--([^=]+)=(.*)") + set(opt "${CMAKE_MATCH_1}") + set(val "${CMAKE_MATCH_2}") + elseif(arg MATCHES "^--(.*)") + set(nextok TRUE) + set(opt "${CMAKE_MATCH_1}") + unset(val) + elseif(arg MATCHES "^-(.*)") + set(nextok TRUE) + set(opt "${CMAKE_MATCH_1}") + unset(val) + else() + qt_ir_add_error("Invalid command line parameter '${arg}'.") + endif() + + set(type "${commandline_option_${opt}_type}") + + if("${type}" STREQUAL "") + if(NOT arg_IGNORE_UNKNOWN_ARGS) + qt_ir_add_error("Unknown command line option '${arg}'.") + else() + message(DEBUG "Unknown command line option '${arg}'. Ignoring.") + continue() + endif() + endif() + + if(NOT COMMAND "qt_ir_commandline_${type}") + qt_ir_add_error("Unknown type '${type}' for command line option '${opt}'.") + endif() + qt_ir_call_function("qt_ir_commandline_${type}" "${opt}" "${val}" "${nextok}") + endwhile() +endfunction() + +# Shows help for the command line options. +function(qt_ir_show_help) + set(help_file "${CMAKE_CURRENT_LIST_DIR}/QtIRHelp.txt") + if(EXISTS "${help_file}") + file(READ "${help_file}" content) + message("${content}") + endif() + + message([[ +General Options: +-help, -h ............ Display this help screen +]]) +endfunction() + +# Gets the unhandled command line args. +function(qt_ir_get_unhandled_args out_var) + get_property(args GLOBAL PROPERTY _qt_ir_unhandled_args) + set(${out_var} "${args}" PARENT_SCOPE) +endfunction() + +# Sets the unhandled command line args. +function(qt_ir_set_unhandled_args args) + set_property(GLOBAL PROPERTY _qt_ir_unhandled_args "${args}") +endfunction() + +# Gets the unsupported options that init-repository.pl supports, but the cmake port does +# not support. +function(qt_ir_get_unsupported_options out_var) + set(${out_var} "${commandline_known_unsupported_options}" PARENT_SCOPE) +endfunction() + +# Get the value of a command line option. +function(qt_ir_get_option_value name out_var) + if(NOT "${commandline_option_${name}_alias}" STREQUAL "") + set(name "${commandline_option_${name}_alias}") + endif() + + get_property(value GLOBAL PROPERTY _qt_ir_input_${name}) + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +# Set the value of a command line option manually. +function(qt_ir_set_option_value name value) + if(NOT "${commandline_option_${name}_alias}" STREQUAL "") + set(name "${commandline_option_${name}_alias}") + endif() + + qt_ir_command_line_set_input("${name}" "${value}") +endfunction() + +# Get the value of a command line option as a cmakke flag option, to be passed +# to functions that use cmake_parse_arguments. +function(qt_ir_get_option_as_cmake_flag_option cli_name cmake_option_name out_var) + qt_ir_get_option_value("${cli_name}" bool_value) + set(cmake_option "") + if(bool_value) + set(cmake_option "${cmake_option_name}") + endif() + set(${out_var} "${cmake_option}" PARENT_SCOPE) +endfunction() diff --git a/cmake/QtIRGitHelpers.cmake b/cmake/QtIRGitHelpers.cmake new file mode 100644 index 00000000..0797ed35 --- /dev/null +++ b/cmake/QtIRGitHelpers.cmake @@ -0,0 +1,1113 @@ +# 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=,-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() diff --git a/cmake/QtIRHelp.txt b/cmake/QtIRHelp.txt new file mode 100644 index 00000000..03b92548 --- /dev/null +++ b/cmake/QtIRHelp.txt @@ -0,0 +1,132 @@ +Usage: + ./init-repository [options] + + This script may be run after an initial `git clone' of the Qt supermodule + in order to check out all submodules. It fetches them from canonical URLs + inferred from the clone's origin. + +Options: + Global options: + + --force, -f + Force initialization (even if the submodules are already checked + out). + + --force-hooks + Force initialization of hooks (even if there are already hooks in + checked out submodules). + + --quiet, -q + Be quiet. Will exit cleanly if the repository is already + initialized. + + --verbose + Adds a bit more output when executing processes + + --no-resolve-deps + By default, each submodule specified via the module-subset option + will have its required and optional dependencies also initialized. + This option can be passed to disable automatic initialization of + dependencies, so that the exact list passed to module-subset is + initialized. + + --no-optional-deps + By default, each submodule specified via the module-subset option + will have its optional dependencies also initialized. + This option can be passed to initialize only required dependencies of + the given module-subset. + + Module options: + + --module-subset=,... + Only initialize the specified subset of modules given as the + argument. Specified modules must already exist in .gitmodules. The + string "all" results in cloning all known modules. The strings + "essential", "addon", "preview", "deprecated", "obsolete", + "additionalLibrary", and "ignore" refer to classes of modules + identified by "status=" lines in the .gitmodules file. + You can use "existing" to to reference already initialized submodules. + Additionally, "qtrepotools" is implicitly always added to ensure + relevant git commit hooks are available. It can be excluded as described + below. + You can use "default" in the subset as a short-hand for + "essential,addon,preview,deprecated", which corresponds to the set of + maintained modules included in standard Qt releases; this is also the + default module subset when this option is not given when first running + init-repositoy. If init-repository is rerun a second time (with --force) + the default is to initialize the "existing" submodules, rather than the + default subset. Entries may be prefixed with a dash to exclude them + from a bigger set, e.g. "all,-ignore" or "existing,-qttools". + + --no-update + Skip the `git submodule update' command. + + --no-fetch + Skip the `git fetch' commands. Implied by --no-update. + + --branch + Instead of checking out specific SHA1s, check out the submodule + branches that correspond with the current supermodule commit. By + default, this option will cause local commits in the submodules to + be rebased. With --no-update, the branches will be checked out, but + their heads will not move. + + --ignore-submodules + Set git config to ignore submodules by default when doing operations + on the qt5 repo, such as `pull', `fetch', `diff' etc. + + After using this option, pass `--ignore-submodules=none' to git to + override it as needed. + + Repository options: + + --berlin + Switch to internal URLs and make use of the Berlin git mirrors. + (Implies `--mirror'). + + --oslo + Switch to internal URLs and make use of the Oslo git mirrors. + (Implies `--mirror'). + + --codereview-username + Specify the user name for the (potentially) writable `gerrit' remote + for each module, for use with the Gerrit code review tool. + + If this option is omitted, the gerrit remote is created without a + username and port number, and thus relies on a correct SSH + configuration. + + --alternates + Adds alternates for each submodule to another full qt5 checkout. + This makes this qt5 checkout very small, as it will use the object + store of the alternates before unique objects are stored in its own + object store. + + This option has no effect when using `--no-update'. + + NOTE: This will make this repo dependent on the alternate, which is + potentially dangerous! The dependency can be broken by also using + the `--copy-objects' option, or by running "git repack -a" in each + submodule, where required. Please read the note about the `--shared' + option in the documentation of `git clone' for more information. + + --copy-objects + When `--alternates' is used, automatically do a "git repack -a" in + each submodule after cloning, to ensure that the repositories are + independent from the source used as a reference for cloning. + + Note that this negates the disk usage benefits gained from the use + of `--alternates'. + --mirror + Uses as the base URL for submodule git mirrors. + + For example: + + --mirror user\@machine:/foo/bar/qt/ + + ...will use the following as a mirror for qtbase: + + user\@machine:/foo/bar/qt/qtbase.git + + The mirror is permitted to contain a subset of the submodules; any + missing modules will fall back to the canonical URLs. diff --git a/cmake/QtIRHelpers.cmake b/cmake/QtIRHelpers.cmake new file mode 100644 index 00000000..18a76180 --- /dev/null +++ b/cmake/QtIRHelpers.cmake @@ -0,0 +1,158 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Includes all helper files for access to necessary functions. +macro(qt_ir_include_all_helpers) + include(QtIRCommandLineHelpers) + include(QtIRGitHelpers) + include(QtIROptionsHelpers) + include(QtIRParsingHelpers) + include(QtIRProcessHelpers) + include(QtTopLevelHelpers) +endmacro() + +# Convenience macro to get the working directory from the arguments passed to +# cmake_parse_arguments. Saves a few lines and makes reading the code slightly +# easier. +macro(qt_ir_get_working_directory_from_arg out_var) + if(NOT arg_WORKING_DIRECTORY) + message(FATAL_ERROR "No working directory specified") + endif() + set(${out_var} "${arg_WORKING_DIRECTORY}") +endmacro() + +# Convenience function to set the variable to the name of cmake_parse_arguments +# flag option if it is active. +function(qt_ir_get_cmake_flag flag_name out_var) + if(arg_${flag_name}) + set(${out_var} "${flag_name}" PARENT_SCOPE) + else() + set(${out_var} "" PARENT_SCOPE) + endif() +endfunction() + +# Checks whether any of the arguments passed on the command line are options +# that are marked as unsupported in the cmake port of init-repository. +function(qt_ir_check_if_unsupported_options_used out_var out_var_option_name) + qt_ir_get_unsupported_options(unsupported_options) + + set(unsupported_options_used FALSE) + foreach(unsupported_option IN LISTS unsupported_options) + qt_ir_get_option_value(${unsupported_option} value) + if(value) + set(${out_var_option_name} "${unsupported_option}" PARENT_SCOPE) + set(unsupported_options_used TRUE) + break() + endif() + endforeach() + set(${out_var} "${unsupported_options_used}" PARENT_SCOPE) +endfunction() + +# When an unsupported option is used, show an error message and tell the user +# to run the perly script manually. +function(qt_ir_show_error_how_to_run_perl opt_file unsupported_option_name) + qt_ir_get_raw_args_from_optfile("${opt_file}" args) + string(REPLACE ";" " " args "${args}") + + set(perl_cmd "perl ./init-repository.pl ${args}") + + message(FATAL_ERROR + "Option '${unsupported_option_name}' is not implemented in the cmake " + "port of init-repository. Please let us know if this option is really " + "important for you at https://bugreports.qt.io/. Meanwhile, you can " + "still run the perl script directly. \n ${perl_cmd}") +endfunction() + +# Check whether help was requested. +function(qt_ir_is_help_requested out_var) + qt_ir_get_option_value(help value) + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +# Check whether the verbose option was used. +function(qt_ir_is_verbose out_var) + qt_ir_get_option_value(verbose value) + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +# Main logic of the script. +function(qt_ir_run_after_args_parsed) + qt_ir_is_help_requested(show_help) + if(show_help) + qt_ir_show_help() + return() + endif() + + set(working_directory "${CMAKE_CURRENT_SOURCE_DIR}") + + qt_ir_handle_if_already_initialized(should_exit "${working_directory}") + if(should_exit) + return() + endif() + + # This will be used by the module subset processing to determine whether we + # should re-initialize the previously initialized (existing) subset. + qt_ir_check_if_already_initialized_cmake_style(is_initialized + "${working_directory}" FORCE_QUIET) + set(previously_initialized_option "") + if(is_initialized) + set(previously_initialized_option PREVIOUSLY_INITIALIZED) + endif() + + + # Ge the name of the qt5 repo (tqtc- or not) and the base url for all other repos + qt_ir_get_qt5_repo_name_and_base_url( + OUT_VAR_QT5_REPO_NAME qt5_repo_name + OUT_VAR_BASE_URL base_url + WORKING_DIRECTORY "${working_directory}") + + qt_ir_get_already_initialized_submodules("${prefix}" + already_initialized_submodules + "${qt5_repo_name}" + "${working_directory}") + + # Get some additional options to pass down. + qt_ir_get_option_value(alternates alternates) + qt_ir_get_option_as_cmake_flag_option(branch "CHECKOUT_BRANCH" checkout_branch_option) + + # The prefix for the cmake-style 'dictionary' that will be used by various functions. + set(prefix "ir_top") + + # Initialize and clone the submodules + qt_ir_handle_init_submodules("${prefix}" + ALTERNATES "${alternates}" + ALREADY_INITIALIZED_SUBMODULES "${already_initialized_submodules}" + BASE_URL "${base_url}" + PARENT_REPO_BASE_GIT_PATH "${qt5_repo_name}" + PROCESS_SUBMODULES_FROM_COMMAND_LINE + WORKING_DIRECTORY "${working_directory}" + ${checkout_branch_option} + ${previously_initialized_option} + ) + + # Add gerrit remotes. + qt_ir_add_git_remotes("${qt5_repo_name}" "${working_directory}") + + # Install commit and other various hooks. + qt_ir_install_git_hooks( + PARENT_REPO_BASE_GIT_PATH "${qt5_repo_name}" + WORKING_DIRECTORY "${working_directory}" + ) + + # Mark the repo as being initialized. + qt_ir_set_is_initialized("${working_directory}") +endfunction() + +# Entrypoint of the init-repository script. +function(qt_ir_run_main_script) + qt_ir_set_known_command_line_options() + qt_ir_process_args_from_optfile("${OPTFILE}") + + qt_ir_check_if_unsupported_options_used( + unsupported_options_used option_name) + if(unsupported_options_used) + qt_ir_show_error_how_to_run_perl("${OPTFILE}" "${option_name}") + endif() + + qt_ir_run_after_args_parsed() +endfunction() diff --git a/cmake/QtIROptionsHelpers.cmake b/cmake/QtIROptionsHelpers.cmake new file mode 100644 index 00000000..877265be --- /dev/null +++ b/cmake/QtIROptionsHelpers.cmake @@ -0,0 +1,34 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Declare command line options known to init-repository. +macro(qt_ir_set_known_command_line_options) + # Implemented options + + # Note alternates is a qt specific option name, but it uses + # git submodule's --reference option underneath which also implies --shared. + # It essentially uses the git object storage of another repo, to avoid + # cloning the same objects and thus saving space. + qt_ir_commandline_option(alternates TYPE string) + + qt_ir_commandline_option(berlin TYPE boolean) + qt_ir_commandline_option(branch TYPE boolean) + qt_ir_commandline_option(codereview-username TYPE string) + qt_ir_commandline_option(copy-objects TYPE boolean) + qt_ir_commandline_option(fetch TYPE boolean DEFAULT_VALUE yes) + qt_ir_commandline_option(force SHORT_NAME f TYPE boolean) + qt_ir_commandline_option(force-hooks TYPE boolean) + qt_ir_commandline_option(help SHORT_NAME h TYPE boolean) + qt_ir_commandline_option(ignore-submodules TYPE boolean) + qt_ir_commandline_option(mirror TYPE string) + qt_ir_commandline_option(module-subset TYPE string) + qt_ir_commandline_option(optional-deps TYPE boolean DEFAULT_VALUE yes) + qt_ir_commandline_option(oslo TYPE boolean) + qt_ir_commandline_option(perl-identical-output TYPE boolean) + qt_ir_commandline_option(perl-init-check TYPE boolean) + qt_ir_commandline_option(quiet SHORT_NAME q TYPE boolean) + qt_ir_commandline_option(resolve-deps TYPE boolean DEFAULT_VALUE yes) + qt_ir_commandline_option(update TYPE boolean DEFAULT_VALUE yes) + qt_ir_commandline_option(verbose TYPE boolean) +endmacro() + diff --git a/cmake/QtIRParsingHelpers.cmake b/cmake/QtIRParsingHelpers.cmake new file mode 100644 index 00000000..d7d3f20e --- /dev/null +++ b/cmake/QtIRParsingHelpers.cmake @@ -0,0 +1,237 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Retrieves the contents of either .git/config or .gitmodules files for further parsing. +function(qt_ir_get_git_config_contents out_var) + set(options + READ_GITMODULES + READ_GIT_CONFIG + READ_GIT_CONFIG_LOCAL + ) + set(oneValueArgs + WORKING_DIRECTORY + ) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(arg_READ_GITMODULES) + set(args -f .gitmodules) + set(file_message ".gitmodules") + elseif(arg_READ_GIT_CONFIG) + set(args "") + set(file_message ".git/config") + elseif(arg_READ_GIT_CONFIG_LOCAL) + set(args "--local") + set(file_message ".local .git/config") + else() + message(FATAL_ERROR "qt_ir_get_git_config_contents: No option specified") + endif() + + qt_ir_get_working_directory_from_arg(working_directory) + + qt_ir_execute_process_and_log_and_handle_error( + FORCE_QUIET + COMMAND_ARGS git config --list ${args} + OUT_OUTPUT_VAR git_output + WORKING_DIRECTORY "${working_directory}" + ERROR_MESSAGE "Failed to get ${file_message} contents for parsing") + + string(STRIP "${git_output}" git_output) + set(${out_var} "${git_output}" PARENT_SCOPE) +endfunction() + +# Checks whether the given url has a scheme like https:// or is just a +# relative path. +function(qt_ir_has_url_scheme url out_var) + string(REGEX MATCH "^[a-z][a-z0-9+\-.]*://" has_url_scheme "${url}") + + if(has_url_scheme) + set(${out_var} TRUE PARENT_SCOPE) + else() + set(${out_var} FALSE PARENT_SCOPE) + endif() +endfunction() + +# Parses a key-value line from a .git/config or .gitmodules file +macro(qt_ir_parse_git_key_value) + string(REGEX REPLACE "^submodule\\.([^.=]+)\\.([^.=]+)=(.*)$" "\\1;\\2;\\3" + parsed_line "${line}") + + list(LENGTH parsed_line parsed_line_length) + set(submodule_name "") + set(key "") + set(value "") + if(parsed_line_length EQUAL 3) + list(GET parsed_line 0 submodule_name) + list(GET parsed_line 1 key) + list(GET parsed_line 2 value) + endif() +endmacro() + +# Parses a url line from a .gitmodules file +# e.g. line - 'submodule.qtbase.url=../qtbase.git' +# +# Arguments +# +# submodule_name +# submodule name, the key in 'submodule.${submodule_name}.url' +# e.g. 'qtbase' +# url_value +# the url where to clone a repo from +# in perl script it was called $base +# e.g. '../qtbase.git', 'https://code.qt.io/playground/qlitehtml.git' +# parent_repo_base_git_path +# the base git path of the parent of the submodule +# it is either a relative dir or a full url +# in the perl script it was called $my_repo_base, +# it was passed as first arg to git_clone_all_submodules, +# it was passed the value of $subbases{$module} when doing recursive submodule cloning +# e.g. 'qt5', 'tqtc-qt5', 'qtdeclarative.git', 'https://code.qt.io/playground/qlitehtml.git' +# +# Outputs +# +# ${out_var_prefix}_${submodule_name}_url +# just the value of ${url_value} +# ${out_var_prefix}_${submodule_name}_base_git_path +# the whole url if it has a scheme, otherwise it's the value of +# ${url_value} relative to ${parent_repo_base_git_path}, so all the ../ are collapsed +# e.g. 'qtdeclarative.git' +# 'https://code.qt.io/playground/qlitehtml.git', +macro(qt_ir_parse_git_url_key out_var_prefix submodule_name url_value parent_repo_base_git_path) + qt_ir_has_url_scheme("${url_value}" has_url_scheme) + if(NOT has_url_scheme) + set(base_git_path "${parent_repo_base_git_path}/${url_value}") + + # The exact code perl code was while ($base =~ s,(?!\.\./)[^/]+/\.\./,,g) {} + # That got rid of ../ and ../../ in the path, but it broke down + # when more than two ../ were present. + # We just use ABSOLUTE to resolve the path and get rid of all ../ + # Note the empty BASE_DIR is important, otherwise the path is relative to + # ${CMAKE_CURRENT_SOURCE_DIR}. + get_filename_component(base_git_path "${base_git_path}" ABSOLUTE BASE_DIR "") + else() + set(base_git_path "${url_value}") + endif() + + set(${out_var_prefix}_${submodule_name}_url "${url_value}" PARENT_SCOPE) + set(${out_var_prefix}_${submodule_name}_base_git_path "${base_git_path}" PARENT_SCOPE) +endmacro() + +# Parses a .git/config or .gitmodules file contents and sets variables for each submodule +# starting with ${out_var_prefix}_ +# These include: +# ${out_var_prefix}_${submodule_name}_path +# the path to the submodule relative to the parent repo +# ${out_var_prefix}_${submodule_name}_branch +# the branch that should be checked out when the branch option is used +# ${out_var_prefix}_${submodule_name}_url +# the url key as encountered in the config +# ${out_var_prefix}_${submodule_name}_base_git_path +# the git base path of the submodule, either a full url or a relative path +# ${out_var_prefix}_${submodule_name}_update +# the status of the submodule, can be 'none' +# ${out_var_prefix}_${submodule_name}_status +# the status of the submodule, can be 'essential', 'addon', etc +# ${out_var_prefix}_${submodule_name}_depends +# the list of submodules that this submodule depends on +# ${out_var_prefix}_${submodule_name}_recommends +# the list of submodules that this submodule recommends to be used with +# ${out_var_prefix}_submodules +# a list of all known submodule names encountered in the file +# ${out_var_prefix}_submodules_to_remove +# a list of all submodules to remove due to update == 'none' +# ${out_var_prefix}_statuses to +# a list of all known submodule statuses like 'essential', 'addon', etc +# ${out_var_prefix}_status_${status}_submodules +# a list of all submodules with the specific status +function(qt_ir_parse_git_config_file_contents out_var_prefix) + set(options + READ_GITMODULES + READ_GIT_CONFIG + READ_GIT_CONFIG_LOCAL + ) + set(oneValueArgs + PARENT_REPO_BASE_GIT_PATH + WORKING_DIRECTORY + ) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + qt_ir_get_working_directory_from_arg(working_directory) + + if(NOT arg_PARENT_REPO_BASE_GIT_PATH) + message(FATAL_ERROR + "qt_ir_parse_git_config_file_contents: No base PARENT_REPO_BASE_GIT_PATH specified") + endif() + set(parent_repo_base_git_path "${arg_PARENT_REPO_BASE_GIT_PATH}") + + if(arg_READ_GITMODULES) + set(read_git_config READ_GITMODULES) + elseif(arg_READ_GIT_CONFIG) + set(read_git_config READ_GIT_CONFIG) + elseif(arg_READ_GIT_CONFIG_LOCAL) + set(read_git_config READ_GIT_CONFIG_LOCAL) + else() + message(FATAL_ERROR + "qt_ir_parse_gitmodules_file_contents: No valid git config file specified") + endif() + + qt_ir_get_git_config_contents(contents + ${read_git_config} + WORKING_DIRECTORY "${working_directory}" + ) + string(REPLACE "\n" ";" lines "${contents}") + + set(known_submodules "") + set(statuses "") + set(submodules_to_remove "") + + foreach(line IN LISTS lines) + qt_ir_parse_git_key_value() + if(NOT submodule_name OR NOT key OR value STREQUAL "") + continue() + endif() + + list(APPEND known_submodules "${submodule_name}") + + if(key STREQUAL "path") + set(${out_var_prefix}_${submodule_name}_path "${value}" PARENT_SCOPE) + elseif(key STREQUAL "branch") + set(${out_var_prefix}_${submodule_name}_branch "${value}" PARENT_SCOPE) + elseif(key STREQUAL "url") + qt_ir_parse_git_url_key( + "${out_var_prefix}" "${submodule_name}" "${value}" "${parent_repo_base_git_path}") + elseif(key STREQUAL "update") + # Some repo submodules had a update = none key in their .gitmodules + # in which case we're supposed to skip initialzing those submodules, + # which the perl script did by adding -${submodule_name} to the subset. + # See qtdeclarative Change-Id: I633404f1c00d83dcbdca06a1d287623190323028 + set(${out_var_prefix}_${submodule_name}_update "${value}" PARENT_SCOPE) + if(value STREQUAL "none") + list(APPEND submodules_to_remove "-${submodule_name}") + endif() + elseif(key STREQUAL "status") + set(status_submodules "${${out_var_prefix}_status_${value}_submodules}") + list(APPEND status_submodules "${submodule_name}") + list(REMOVE_DUPLICATES status_submodules) + list(APPEND statuses "${value}") + + set(${out_var_prefix}_status_${value}_submodules "${status_submodules}") + set(${out_var_prefix}_status_${value}_submodules "${status_submodules}" PARENT_SCOPE) + set(${out_var_prefix}_${submodule_name}_status "${value}" PARENT_SCOPE) + elseif(key STREQUAL "depends") + string(REPLACE " " ";" value "${value}") + set(${out_var_prefix}_${submodule_name}_depends "${value}" PARENT_SCOPE) + elseif(key STREQUAL "recommends") + string(REPLACE " " ";" value "${value}") + set(${out_var_prefix}_${submodule_name}_recommends "${value}" PARENT_SCOPE) + endif() + endforeach() + + list(REMOVE_DUPLICATES known_submodules) + list(REMOVE_DUPLICATES submodules_to_remove) + list(REMOVE_DUPLICATES statuses) + set(${out_var_prefix}_submodules "${known_submodules}" PARENT_SCOPE) + set(${out_var_prefix}_submodules_to_remove "${submodules_to_remove}" PARENT_SCOPE) + set(${out_var_prefix}_statuses "${statuses}" PARENT_SCOPE) +endfunction() diff --git a/cmake/QtIRProcessHelpers.cmake b/cmake/QtIRProcessHelpers.cmake new file mode 100644 index 00000000..db7bf2cb --- /dev/null +++ b/cmake/QtIRProcessHelpers.cmake @@ -0,0 +1,165 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# A low-level execute_process wrapper that can be used to execute a single command +# while controlling the verbosity and error handling. +function(qt_ir_execute_process) + set(options + QUIET + ) + set(oneValueArgs + WORKING_DIRECTORY + OUT_RESULT_VAR + OUT_OUTPUT_VAR + OUT_ERROR_VAR + ) + set(multiValueArgs + COMMAND_ARGS + EP_EXTRA_ARGS + ) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT arg_COMMAND_ARGS) + message(FATAL_ERROR "Missing arguments to qt_ir_execute_process") + endif() + + if(arg_WORKING_DIRECTORY) + set(working_dir_value "${arg_WORKING_DIRECTORY}") + else() + set(working_dir_value ".") + endif() + set(working_dir WORKING_DIRECTORY "${working_dir_value}") + + set(result_variable "") + if(arg_OUT_RESULT_VAR) + set(result_variable RESULT_VARIABLE proc_result) + endif() + + set(swallow_output "") + if(arg_OUT_OUTPUT_VAR OR arg_QUIET) + list(APPEND swallow_output OUTPUT_VARIABLE proc_output) + endif() + if(arg_OUT_ERROR_VAR OR arg_QUIET) + list(APPEND swallow_output ERROR_VARIABLE proc_error) + endif() + if(NOT arg_QUIET) + set(working_dir_message "") + + qt_ir_is_verbose(verbose) + if(verbose) + set(working_dir_message " current working dir: ") + if(NOT working_dir_value STREQUAL ".") + string(APPEND working_dir_message "${working_dir_value}") + endif() + endif() + + string(REPLACE ";" " " command_args_string "${arg_COMMAND_ARGS}") + message("+ ${command_args_string}${working_dir_message}") + endif() + + execute_process( + COMMAND ${arg_COMMAND_ARGS} + ${working_dir} + ${result_variable} + ${swallow_output} + ${arg_EP_EXTRA_ARGS} + ) + + if(arg_OUT_RESULT_VAR) + set(${arg_OUT_RESULT_VAR} "${proc_result}" PARENT_SCOPE) + endif() + if(arg_OUT_OUTPUT_VAR) + set(${arg_OUT_OUTPUT_VAR} "${proc_output}" PARENT_SCOPE) + endif() + if(arg_OUT_ERROR_VAR) + set(${arg_OUT_ERROR_VAR} "${proc_error}" PARENT_SCOPE) + endif() +endfunction() + +# A higher level execute_process wrapper that can be used to execute a single command +# that is a bit more opinionated and expects options related to init-repository +# functionality. +# It handles queietness, error handling and logging. +# It also allows for slightly more compact syntax for calling processes. +function(qt_ir_execute_process_and_log_and_handle_error) + set(options + NO_HANDLE_ERROR + FORCE_VERBOSE + FORCE_QUIET + ) + set(oneValueArgs + WORKING_DIRECTORY + OUT_RESULT_VAR + OUT_OUTPUT_VAR + OUT_ERROR_VAR + ERROR_MESSAGE + ) + set(multiValueArgs + COMMAND_ARGS + EP_EXTRA_ARGS + ) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + qt_ir_get_option_value(quiet quiet) + set(quiet_option "") + if((quiet OR arg_FORCE_QUIET) AND NOT arg_FORCE_VERBOSE) + set(quiet_option "QUIET") + endif() + + set(working_dir "") + if(arg_WORKING_DIRECTORY) + set(working_dir WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}") + endif() + + set(extra_args "") + if(arg_EP_EXTRA_ARGS) + set(extra_args EP_EXTRA_ARGS "${arg_EP_EXTRA_ARGS}") + endif() + + set(out_output_var "") + if(arg_OUT_OUTPUT_VAR OR quiet) + set(out_output_var OUT_OUTPUT_VAR proc_output) + endif() + + set(out_error_var "") + if(arg_OUT_ERROR_VAR OR quiet) + set(out_error_var OUT_ERROR_VAR proc_error) + endif() + + qt_ir_execute_process( + ${quiet_option} + COMMAND_ARGS ${arg_COMMAND_ARGS} + OUT_RESULT_VAR proc_result + ${extra_args} + ${working_dir} + ${out_output_var} + ${out_error_var} + ) + + if(NOT proc_result EQUAL 0 AND NOT arg_NO_HANDLE_ERROR) + set(error_message "") + if(arg_ERROR_MESSAGE) + set(error_message "${arg_ERROR_MESSAGE}\n") + endif() + + string(REPLACE ";" " " cmd "${arg_COMMAND_ARGS}") + string(APPEND error_message "${cmd} exited with status: ${proc_result}\n") + if(proc_output) + string(APPEND error_message "stdout: ${proc_output}\n") + endif() + if(proc_error) + string(APPEND error_message "stderr: ${proc_error}\n") + endif() + message(FATAL_ERROR "${error_message}") + endif() + + if(arg_OUT_RESULT_VAR) + set(${arg_OUT_RESULT_VAR} "${proc_result}" PARENT_SCOPE) + endif() + if(arg_OUT_OUTPUT_VAR) + set(${arg_OUT_OUTPUT_VAR} "${proc_output}" PARENT_SCOPE) + endif() + if(arg_OUT_ERROR_VAR) + set(${arg_OUT_ERROR_VAR} "${proc_error}" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/QtIRScript.cmake b/cmake/QtIRScript.cmake new file mode 100644 index 00000000..50cf960c --- /dev/null +++ b/cmake/QtIRScript.cmake @@ -0,0 +1,17 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +# Sets up the include paths for all the helpers init-repository uses. +macro(qt_ir_setup_include_paths) + list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_CURRENT_LIST_DIR}/3rdparty/cmake" + ) + include(QtIRHelpers) +endmacro() + +qt_ir_setup_include_paths() +qt_ir_include_all_helpers() +qt_ir_run_main_script() diff --git a/cmake/QtTopLevelHelpers.cmake b/cmake/QtTopLevelHelpers.cmake index f8473d72..e84ce757 100644 --- a/cmake/QtTopLevelHelpers.cmake +++ b/cmake/QtTopLevelHelpers.cmake @@ -1,3 +1,6 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Populates $out_module_list with all subdirectories that have a CMakeLists.txt file function(qt_internal_find_modules out_module_list) set(module_list "") @@ -15,7 +18,7 @@ endfunction() # poor man's yaml parser, populating $out_dependencies with all dependencies # in the $depends_file # Each entry will be in the format dependency/sha1/required -function(qt_internal_parse_dependencies depends_file out_dependencies) +function(qt_internal_parse_dependencies_yaml depends_file out_dependencies) file(STRINGS "${depends_file}" lines) set(eof_marker "---EOF---") list(APPEND lines "${eof_marker}") @@ -48,7 +51,7 @@ function(qt_internal_parse_dependencies depends_file out_dependencies) endif() endforeach() message(DEBUG - "qt_internal_parse_dependencies for ${depends_file}\n dependencies: ${dependencies}") + "qt_internal_parse_dependencies_yaml for ${depends_file}\n dependencies: ${dependencies}") set(${out_dependencies} "${dependencies}" PARENT_SCOPE) endfunction() @@ -99,8 +102,22 @@ endfunction() # Keyword arguments: # # PARSED_DEPENDENCIES is a list of dependencies of module in the format that -# qt_internal_parse_dependencies returns. If this argument is not provided, dependencies.yaml of the -# module is parsed. +# qt_internal_parse_dependencies_yaml returns. +# If this argument is not provided, either a module's dependencies.yaml or .gitmodules file is +# used as the source of dependencies, depending on whether PARSE_GITMODULES option is enabled. +# +# PARSE_GITMODULES is a boolean that controls whether the .gitmodules or the dependencies.yaml +# file of the repo are used for extracting dependencies. Defaults to FALSE, so uses +# dependencies.yaml by default. +# +# EXCLUDE_OPTIONAL_DEPS is a boolean that controls whether optional dependencies are excluded from +# the final result. +# +# GITMODULES_PREFIX_VAR is the prefix of all the variables containing dependencies for the +# PARSE_GITMODULES mode. +# The function expects the following variables to be set in the parent scope +# ${arg_GITMODULES_PREFIX_VAR}_${submodule_name}_depends +# ${arg_GITMODULES_PREFIX_VAR}_${submodule_name}_recommends # # IN_RECURSION is an internal option that is set when the function is in recursion. # @@ -112,8 +129,9 @@ endfunction() # NORMALIZE_REPO_NAME_IF_NEEDED Will remove 'tqtc-' from the beginning of submodule dependencies # if a tqtc- named directory does not exist. function(qt_internal_resolve_module_dependencies module out_ordered out_revisions) - set(options IN_RECURSION NORMALIZE_REPO_NAME_IF_NEEDED) - set(oneValueArgs REVISION SKIPPED_VAR) + set(options IN_RECURSION NORMALIZE_REPO_NAME_IF_NEEDED PARSE_GITMODULES + EXCLUDE_OPTIONAL_DEPS) + set(oneValueArgs REVISION SKIPPED_VAR GITMODULES_PREFIX_VAR) set(multiValueArgs PARSED_DEPENDENCIES) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -141,10 +159,42 @@ function(qt_internal_resolve_module_dependencies module out_ordered out_revision if(DEFINED arg_PARSED_DEPENDENCIES) set(dependencies "${arg_PARSED_DEPENDENCIES}") else() - set(depends_file "${CMAKE_CURRENT_SOURCE_DIR}/${module}/dependencies.yaml") set(dependencies "") - if(EXISTS "${depends_file}") - qt_internal_parse_dependencies("${depends_file}" dependencies) + + if(NOT arg_PARSE_GITMODULES) + set(depends_file "${CMAKE_CURRENT_SOURCE_DIR}/${module}/dependencies.yaml") + if(EXISTS "${depends_file}") + qt_internal_parse_dependencies_yaml("${depends_file}" dependencies) + + if(arg_EXCLUDE_OPTIONAL_DEPS) + set(filtered_dependencies "") + foreach(dependency IN LISTS dependencies) + string(REPLACE "/" ";" dependency_split "${dependency}") + list(GET dependency_split 2 required) + if(required) + list(APPEND filtered_dependencies "${dependency}") + endif() + endforeach() + set(dependencies "${filtered_dependencies}") + endif() + endif() + else() + set(depends "${${arg_GITMODULES_PREFIX_VAR}_${dependency}_depends}") + foreach(dependency IN LISTS depends) + if(dependency) + # The HEAD value is not really used, but we need to add something. + list(APPEND dependencies "${dependency}/HEAD/TRUE") + endif() + endforeach() + + set(recommends "${${arg_GITMODULES_PREFIX_VAR}_${dependency}_recommends}") + if(NOT arg_EXCLUDE_OPTIONAL_DEPS) + foreach(dependency IN LISTS recommends) + if(dependency) + list(APPEND dependencies "${dependency}/HEAD/FALSE") + endif() + endforeach() + endif() endif() endif() @@ -171,11 +221,24 @@ function(qt_internal_resolve_module_dependencies module out_ordered out_revision set_property(GLOBAL APPEND PROPERTY QT_REQUIRED_DEPS_FOR_${module} ${dependency}) endif() + set(parse_gitmodules "") + if(arg_PARSE_GITMODULES) + set(parse_gitmodules "PARSE_GITMODULES") + endif() + + set(exclude_optional_deps "") + if(arg_EXCLUDE_OPTIONAL_DEPS) + set(exclude_optional_deps "EXCLUDE_OPTIONAL_DEPS") + endif() + qt_internal_resolve_module_dependencies(${dependency} dep_ordered dep_revisions REVISION "${revision}" SKIPPED_VAR skipped IN_RECURSION ${normalize_arg} + ${parse_gitmodules} + ${exclude_optional_deps} + GITMODULES_PREFIX_VAR ${arg_GITMODULES_PREFIX_VAR} ) if(NOT skipped) list(APPEND ordered ${dep_ordered}) @@ -197,12 +260,30 @@ endfunction() # Arguments: # modules is the initial list of repos. # out_all_ordered is the variable name where the result is stored. +# PARSE_GITMODULES and GITMODULES_PREFIX_VAR are keyowrd arguments that change the +# source of dependencies parsing from dependencies.yaml to .gitmodules. +# EXCLUDE_OPTIONAL_DEPS is a keyword argument that excludes optional dependencies from the result. +# See qt_internal_resolve_module_dependencies for details. # # See qt_internal_resolve_module_dependencies for side effects. function(qt_internal_sort_module_dependencies modules out_all_ordered) + set(options PARSE_GITMODULES EXCLUDE_OPTIONAL_DEPS) + set(oneValueArgs GITMODULES_PREFIX_VAR) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(parse_gitmodules "") + if(arg_PARSE_GITMODULES) + set(parse_gitmodules "PARSE_GITMODULES") + endif() + + set(exclude_optional_deps "") + if(arg_EXCLUDE_OPTIONAL_DEPS) + set(exclude_optional_deps "EXCLUDE_OPTIONAL_DEPS") + endif() # Create a fake repository "all_selected_repos" that has all repositories from the input as - # required dependency. The format must match what qt_internal_parse_dependencies produces. + # required dependency. The format must match what qt_internal_parse_dependencies_yaml produces. set(all_selected_repos_as_parsed_dependencies) foreach(module IN LISTS modules) list(APPEND all_selected_repos_as_parsed_dependencies "${module}/HEAD/FALSE") @@ -211,6 +292,9 @@ function(qt_internal_sort_module_dependencies modules out_all_ordered) qt_internal_resolve_module_dependencies(all_selected_repos ordered unused_revisions PARSED_DEPENDENCIES ${all_selected_repos_as_parsed_dependencies} NORMALIZE_REPO_NAME_IF_NEEDED + ${exclude_optional_deps} + ${parse_gitmodules} + GITMODULES_PREFIX_VAR ${arg_GITMODULES_PREFIX_VAR} ) # Drop "all_selected_repos" from the output. It depends on all selected repos, thus it must be diff --git a/cmake/QtWriteArgsFile.cmake b/cmake/QtWriteArgsFile.cmake new file mode 100644 index 00000000..336f8550 --- /dev/null +++ b/cmake/QtWriteArgsFile.cmake @@ -0,0 +1,92 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# This script writes its arguments to the file determined by OUT_FILE. +# Each argument appears on a separate line. +# This is used for writing the init-repository.opt file. +# +# This script takes the following arguments: +# IN_FILE: The input file. The whole command line as one string, or one argument per line. +# REDO_FILE: A file containing extra commands to be joined with IN_FILE. +# OUT_FILE: The output file. One argument per line. +# SKIP_ARGS: Number of arguments to skip from the front of the arguments list. +# IGNORE_ARGS: List of arguments to be ignored, i.e. that are not written. +# +# If the REDO_FILE is given, its parameters will be merged with IN_FILE parameters +# and be written into the OUT_FILE. + +cmake_minimum_required(VERSION 3.16) + +# Read arguments from IN_FILE and separate them. +file(READ "${IN_FILE}" raw_args) +# To catch cases where the path ends with an `\`, e.g., `-prefix "C:\Path\"` +string(REPLACE "\\\"" "\"" raw_args "${raw_args}") +string(REPLACE ";" "[[;]]" raw_args "${raw_args}") + +separate_arguments(args NATIVE_COMMAND "${raw_args}") + +string(REPLACE "\;" ";" args "${args}") +string(REPLACE "[[;]]" "\;" args "${args}") + +if(DEFINED REDO_FILE) + file(READ "${REDO_FILE}" raw_redo_args) + separate_arguments(redo_args NATIVE_COMMAND "${raw_redo_args}") + + if(args) + list(FIND args "--" args_ddash_loc) + list(FIND redo_args "--" redo_ddash_loc) + if("${redo_ddash_loc}" STREQUAL "-1") + if("${args_ddash_loc}" STREQUAL "-1") + list(LENGTH args args_ddash_loc) + endif() + # Avoid adding an empty line for an empty -redo + if(NOT "${redo_args}" STREQUAL "") + list(INSERT args ${args_ddash_loc} "${redo_args}") + endif() + else() + # Handling redo's configure options + list(SUBLIST redo_args 0 ${redo_ddash_loc} redo_config_args) + if(redo_config_args) + if("${args_ddash_loc}" STREQUAL "-1") + list(APPEND args "${redo_config_args}") + else() + list(INSERT args ${args_ddash_loc} "${redo_config_args}") + endif() + endif() + + # Handling redo's CMake options + list(LENGTH redo_args redo_args_len) + math(EXPR redo_ddash_loc "${redo_ddash_loc} + 1") + # Catch an unlikely case of -redo being called with an empty --, ie., `-redo --` + if(NOT ${redo_ddash_loc} STREQUAL ${redo_args_len}) + list(SUBLIST redo_args ${redo_ddash_loc} -1 redo_cmake_args) + endif() + + if(DEFINED redo_cmake_args) + if("${args_ddash_loc}" STREQUAL "-1") + list(APPEND args "--") + endif() + list(APPEND args "${redo_cmake_args}") + endif() + endif() + else() + list(APPEND args "${redo_args}") + endif() +endif() + +# Skip arguments if requested +if(DEFINED SKIP_ARGS) + foreach(i RANGE 1 ${SKIP_ARGS}) + list(POP_FRONT args) + endforeach() +endif() + +# Write config.opt +set(content "") +foreach(arg IN LISTS args) + if(NOT arg IN_LIST IGNORE_ARGS) + string(APPEND content "${arg}\n") + endif() +endforeach() + +file(WRITE "${OUT_FILE}" "${content}") diff --git a/init-repository b/init-repository new file mode 100755 index 00000000..8a4469bf --- /dev/null +++ b/init-repository @@ -0,0 +1,24 @@ +#!/bin/sh +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +set -eu + +script_dir_path=`dirname $0` +script_dir_path=`(cd "$script_dir_path"; /bin/pwd)` + +optfile=init-repository.opt +opttmpfile=init-repository.opt.in + +# Posix compatible way to truncate file +: > "$optfile" +: > "$opttmpfile" + +# For consistency, use QtWriteArgsFile.cmake to write the optfile like we do on Windows. +# We do the same with the configure script in qtbase. +for arg in "$@"; do echo \"$arg\" >> "$opttmpfile"; done + +cmake -DIN_FILE="${opttmpfile}" -DOUT_FILE="${optfile}" -P "${script_dir_path}/cmake/QtWriteArgsFile.cmake" + +cmake_script_path="$script_dir_path/cmake/QtIRScript.cmake" +exec cmake -DOPTFILE="${optfile}" -P "$cmake_script_path" diff --git a/init-repository.bat b/init-repository.bat new file mode 100644 index 00000000..b9daca2a --- /dev/null +++ b/init-repository.bat @@ -0,0 +1,23 @@ +:: Copyright (C) 2024 The Qt Company Ltd. +:: SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +@echo off +setlocal ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS +set script_dir_path=%~dp0 +set script_dir_path=%script_dir_path:~0,-1% + +set cmake_scripts_dir=%script_dir_path%\cmake +:: The '.' in 'echo.%*' ensures we don't print "echo is off" when no arguments are passed +:: https://devblogs.microsoft.com/oldnewthing/20170802-00/?p=96735 +:: The space before the '>' makes sure that when we have a digit at the end of the args, we +:: don't accidentally concatenate it with the '>' resulting in '0>' or '2>' which redirects into the +:: file from a stream different than stdout, leading to broken or empty content. +echo.%* >init-repository.opt.in + +call cmake -DIN_FILE=init-repository.opt.in -DOUT_FILE=init-repository.opt ^ + -P "%cmake_scripts_dir%\QtWriteArgsFile.cmake" +call cmake -DOPTFILE=init-repository.opt ^ + -P "%cmake_scripts_dir%\QtIRScript.cmake" + +del init-repository.opt.in +del init-repository.opt