qt5/cmake/QtIRHelpers.cmake
Alexandru Croitor 2c9664ca33 CMake: Integrate init-repository with the configure script
Calling configure will now implicitly run init-repository when
appropriate. See further down below for what "appropriate" means.

All supported init-repository options can be passed to configure as
except for -mirror, -oslo, -berlin.

This includes useful options like -submodules, -no-resolve-deps and
-no-optional-deps.

When running configure on a qt5.git clone without any submodules
cloned, configure will exit with a helpful error message suggesting to
pass -init-submodules, so it automatically clones missing repositories.
This means cloning is opt-in, so that internet access is not done
implicitly.

The error message also suggests passing the -submodules option.
This will affect which submodules will be cloned by init-repository
and which submodules will be configured by configure.
In this case -submodules is effectively an alias of
init-repository's -module-subset for cloning purposes.

When calling configure a second time, without -init-submodules, on an
already configured repo, init-repository behavior is entirely skipped.

-submodules now accepts init-repository-style special values like
"essential", "addon", "all", "existing", "-deprecated" for the purpose
of cloning submodules. The values are then translated into actual repos
that should also be configured or skipped by configure.

The default subset of cloned submodules is currently the same one as
init-repository, "default", which clones 44 actively maintained
repositories as well as deprecated submodules.

If configure is called a second time WITH -init-submodules, it's the
same as calling init-repository --force to re-initialize submodules.
In this case passing something like
 --submodules existing,<additional-submodules>
might make sense to add or remove submodules.

As a drive-by this also fixes the bug where you couldn't pass a
  configure -- -DFOO=0
parameter to configure, because it got treated as '0>', redirecting
from a different stream than stdout, leading to empty content in the
file.

[ChangeLog][General][Build System] The configure script now implicitly
calls init-repository when appropriate and accepts init-repository
command line options.

Fixes: QTBUG-120030
Task-number: QTBUG-122622
Change-Id: Iedbfcbf0a87c8ee89e40d00b6377b68296a65a62
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
2024-02-28 06:24:02 +01:00

357 lines
14 KiB
CMake

# 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(QtIRTestHelpers)
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()
# There are some init-repository options that we do not want to allow when called from
# configure. Make sure we error out when they are set by the user.
function(qt_ir_validate_options_for_configure)
set(disallowed_options
# Disallow mirror options, because users should set up a proper git mirror manually,
# not via configure.
mirror
oslo
berlin
)
foreach(disallowed_option IN LISTS disallowed_options)
qt_ir_get_option_value(${disallowed_option} value)
if(value)
set(msg
"Initialization option '${disallowed_option}' is not supported by configure. "
"If you think this option should be supported, please let us know at "
"https://bugreports.qt.io/"
)
message(FATAL_ERROR ${msg})
endif()
endforeach()
endfunction()
# Handle the case when init-repository is called from the configure script.
function(qt_ir_handle_called_from_configure top_level_src_path out_var_exit_reason)
# Nothing special to do if we're not called from configure.
qt_ir_is_called_from_configure(is_called_from_configure)
if(NOT is_called_from_configure)
set(${out_var_exit_reason} FALSE PARENT_SCOPE)
return()
endif()
# Check whether qtbase was cloned, if not, tell the user how to initialize
# the repos as part of the configure script.
qt_ir_get_option_value(init-submodules init_submodules)
set(configure_script "${top_level_src_path}/qtbase/configure")
if(NOT EXISTS "${configure_script}" AND NOT init_submodules)
set(msg "Oops. It looks like you didn't initialize any submodules yet.\nCall configure "
"with the -init-submodules option to automatically clone a default set of "
"submodules before configuring Qt.\nYou can also pass "
"-submodules submodule2,submodule3 to clone a particular set of submodules "
"and their dependencies. See ./init-repository --help for more information on values "
"accepted by --module-subset (which gets its values from -submodules).")
message(${msg})
set(${out_var_exit_reason} NEED_INIT_SUBMODULES PARENT_SCOPE)
return()
endif()
# Don't do init-repository things when called from configure, qtbase exists and the
# -init-submodules option is not passed. We assume the repo was already
# initialized.
if(NOT init_submodules)
set(${out_var_exit_reason} ALREADY_INITIALIZED PARENT_SCOPE)
return()
endif()
qt_ir_validate_options_for_configure()
# -init_submodules implies --force
qt_ir_set_option_value(force TRUE)
set(${out_var_exit_reason} FALSE PARENT_SCOPE)
endfunction()
# Returns a list of command line arguments with the init-repository specific
# options removed, which are not recognized by configure.
# It also handles -submodules values like 'essential', 'existing' and '-qtsvg' and transforms them
# into the final list of submodules to be included and excluded, which are then translated
# to configure -submodules and -skip options.
function(qt_ir_get_args_from_optfile_configure_filtered optfile_path out_var)
# Get args unknown to init-repository, and pass them to configure as-is.
qt_ir_get_unknown_args(unknown_args)
set(filtered_args ${unknown_args})
set(extra_configure_args "")
set(extra_cmake_args "")
# If the -submodules or --module-subset options were specified, transform
# the values into something configure understands and pass them to configure.
qt_ir_get_option_value(module-subset submodules)
if(submodules)
qt_ir_get_top_level_submodules(include_submodules exclude_submodules)
# qtrepotools is always implicitly cloned, but it doesn't actually
# have a CMakeLists.txt, so remove it.
list(REMOVE_ITEM include_submodules "qtrepotools")
# Make sure to explicitly pass -DBUILD_<module>=ON, in case they were
# skipped before, otherwise configure might fail.
if(include_submodules)
set(explicit_build_submodules "${include_submodules}")
list(TRANSFORM explicit_build_submodules PREPEND "-DBUILD_")
list(TRANSFORM explicit_build_submodules APPEND "=ON")
list(APPEND extra_cmake_args ${explicit_build_submodules})
endif()
list(JOIN include_submodules "," include_submodules)
list(JOIN exclude_submodules "," exclude_submodules)
# Handle case when the -skip argument is already passed.
# In that case read the passed values, merge with new ones,
# remove both the -skip and its values, and re-add it later.
list(FIND filtered_args "-skip" skip_index)
if(exclude_submodules AND skip_index GREATER -1)
list(LENGTH filtered_args filtered_args_length)
math(EXPR skip_args_index "${skip_index} + 1")
if(skip_args_index LESS filtered_args_length)
list(GET filtered_args "${skip_args_index}" skip_args)
string(REPLACE "," ";" skip_args "${skip_args}")
list(APPEND skip_args ${exclude_submodules})
list(REMOVE_DUPLICATES skip_args)
list(JOIN skip_args "," exclude_submodules)
list(REMOVE_AT filtered_args "${skip_args_index}")
list(REMOVE_AT filtered_args "${skip_index}")
endif()
endif()
# Handle case when only '-submodules existing' is passed and the
# subset ends up empty.
if(include_submodules)
list(APPEND extra_configure_args "-submodules" "${include_submodules}")
endif()
if(exclude_submodules)
list(APPEND extra_configure_args "-skip" "${exclude_submodules}")
endif()
endif()
# Insert the extra arguments into the proper positions before and after '--'.
list(FIND filtered_args "--" cmake_args_index)
# -- is not found
if(cmake_args_index EQUAL -1)
# Append extra configure args if present
if(extra_configure_args)
list(APPEND filtered_args ${extra_configure_args})
endif()
# Append extra cmake args if present, but make sure to add -- first at the end
if(extra_cmake_args)
list(APPEND filtered_args "--")
list(APPEND filtered_args ${extra_cmake_args})
endif()
else()
# -- is found, that means we probably have cmake args
# Insert extra configure args if present, before the -- index.
if(extra_configure_args)
list(INSERT filtered_args "${cmake_args_index}" ${extra_configure_args})
endif()
# Find the -- index again, because it might have moved
list(FIND filtered_args "--" cmake_args_index)
# Compute the index of the argument after the --.
math(EXPR cmake_args_index "${cmake_args_index} + 1")
# Insert extra cmake args if present, after the -- index.
if(extra_cmake_args)
list(INSERT filtered_args "${cmake_args_index}" ${extra_cmake_args})
endif()
endif()
set(${out_var} "${filtered_args}" PARENT_SCOPE)
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()
function(qt_ir_is_called_from_configure out_var)
qt_ir_get_option_value(from-configure value)
set(${out_var} "${value}" PARENT_SCOPE)
endfunction()
# Main logic of the script.
function(qt_ir_run_after_args_parsed top_level_src_path out_var_exit_reason)
set(${out_var_exit_reason} FALSE PARENT_SCOPE)
qt_ir_is_called_from_configure(is_called_from_configure)
qt_ir_is_help_requested(show_help)
if(show_help AND NOT is_called_from_configure)
qt_ir_show_help()
set(${out_var_exit_reason} SHOWED_HELP PARENT_SCOPE)
return()
endif()
set(working_directory "${top_level_src_path}")
qt_ir_handle_if_already_initialized(already_initialized "${working_directory}")
if(already_initialized)
set(${out_var_exit_reason} ALREADY_INITIALIZED PARENT_SCOPE)
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}"
TOP_LEVEL_SRC_PATH "${top_level_src_path}"
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 top_level_src_path out_var_exit_reason)
set(${out_var_exit_reason} FALSE PARENT_SCOPE)
# Windows passes backslash paths.
file(TO_CMAKE_PATH "${top_level_src_path}" top_level_src_path)
qt_ir_set_known_command_line_options()
# If called from configure, there might be arguments that init-repository doesn't know about
# because they are meant for configure. In that case ignore unknown arguments.
qt_ir_get_option_value(from-configure from_configure)
if(from_configure)
set(ignore_unknown_args "IGNORE_UNKNOWN_ARGS")
else()
set(ignore_unknown_args "")
endif()
qt_ir_process_args_from_optfile("${OPTFILE}" "${ignore_unknown_args}")
qt_ir_handle_called_from_configure("${top_level_src_path}" exit_reason)
if(exit_reason)
set(${out_var_exit_reason} "${exit_reason}" PARENT_SCOPE)
return()
endif()
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("${top_level_src_path}" exit_reason)
set(${out_var_exit_reason} "${exit_reason}" PARENT_SCOPE)
# TODO: Consider using cmake_language(EXIT <exit-code>) when cmake 3.29 is released.
endfunction()