diff options
Diffstat (limited to 'cmake')
-rw-r--r-- | cmake/3rdparty/cmake/Copyright.txt | 136 | ||||
-rw-r--r-- | cmake/3rdparty/cmake/QtIRRunCMake.cmake | 349 | ||||
-rw-r--r-- | cmake/3rdparty/cmake/QtIRTestHelpers.cmake | 39 | ||||
-rw-r--r-- | cmake/3rdparty/cmake/qt_attribution.json | 16 | ||||
-rw-r--r-- | cmake/QtIRCommandLineHelpers.cmake | 404 | ||||
-rw-r--r-- | cmake/QtIRGitHelpers.cmake | 1151 | ||||
-rw-r--r-- | cmake/QtIRHelp.txt | 134 | ||||
-rw-r--r-- | cmake/QtIRHelpers.cmake | 367 | ||||
-rw-r--r-- | cmake/QtIROptionsHelpers.cmake | 48 | ||||
-rw-r--r-- | cmake/QtIRParsingHelpers.cmake | 237 | ||||
-rw-r--r-- | cmake/QtIRProcessHelpers.cmake | 165 | ||||
-rw-r--r-- | cmake/QtIRScript.cmake | 17 | ||||
-rw-r--r-- | cmake/QtSortModuleDependencies.cmake | 16 | ||||
-rw-r--r-- | cmake/QtSynchronizeRepo.cmake | 16 | ||||
-rw-r--r-- | cmake/QtTopLevelConfigureScript.cmake | 17 | ||||
-rw-r--r-- | cmake/QtTopLevelHelpers.cmake | 514 | ||||
-rw-r--r-- | cmake/QtWriteArgsFile.cmake | 92 |
17 files changed, 3646 insertions, 72 deletions
diff --git a/cmake/3rdparty/cmake/Copyright.txt b/cmake/3rdparty/cmake/Copyright.txt new file mode 100644 index 00000000..2074109b --- /dev/null +++ b/cmake/3rdparty/cmake/Copyright.txt @@ -0,0 +1,136 @@ +CMake - Cross Platform Makefile Generator +Copyright 2000-2024 Kitware, Inc. and Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Kitware, Inc. nor the names of Contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +The following individuals and institutions are among the Contributors: + +* Aaron C. Meadows <cmake@shadowguarddev.com> +* Adriaan de Groot <groot@kde.org> +* Aleksey Avdeev <solo@altlinux.ru> +* Alexander Neundorf <neundorf@kde.org> +* Alexander Smorkalov <alexander.smorkalov@itseez.com> +* Alexey Sokolov <sokolov@google.com> +* Alex Merry <alex.merry@kde.org> +* Alex Turbov <i.zaufi@gmail.com> +* Andreas Pakulat <apaku@gmx.de> +* Andreas Schneider <asn@cryptomilk.org> +* André Rigland Brodtkorb <Andre.Brodtkorb@ifi.uio.no> +* Axel Huebl, Helmholtz-Zentrum Dresden - Rossendorf +* Benjamin Eikel +* Bjoern Ricks <bjoern.ricks@gmail.com> +* Brad Hards <bradh@kde.org> +* Christopher Harvey +* Christoph Grüninger <foss@grueninger.de> +* Clement Creusot <creusot@cs.york.ac.uk> +* Daniel Blezek <blezek@gmail.com> +* Daniel Pfeifer <daniel@pfeifer-mail.de> +* Dawid Wróbel <me@dawidwrobel.com> +* Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> +* Eran Ifrah <eran.ifrah@gmail.com> +* Esben Mose Hansen, Ange Optimization ApS +* Geoffrey Viola <geoffrey.viola@asirobots.com> +* Google Inc +* Gregor Jasny +* Helio Chissini de Castro <helio@kde.org> +* Ilya Lavrenov <ilya.lavrenov@itseez.com> +* Insight Software Consortium <insightsoftwareconsortium.org> +* Intel Corporation <www.intel.com> +* Jan Woetzel +* Jordan Williams <jordan@jwillikers.com> +* Julien Schueller +* Kelly Thompson <kgt@lanl.gov> +* Konstantin Podsvirov <konstantin@podsvirov.pro> +* Laurent Montel <montel@kde.org> +* Mario Bensi <mbensi@ipsquad.net> +* Martin Gräßlin <mgraesslin@kde.org> +* Mathieu Malaterre <mathieu.malaterre@gmail.com> +* Matthaeus G. Chajdas +* Matthias Kretz <kretz@kde.org> +* Matthias Maennich <matthias@maennich.net> +* Michael Hirsch, Ph.D. <www.scivision.co> +* Michael Stürmer +* Miguel A. Figueroa-Villanueva +* Mike Durso <rbprogrammer@gmail.com> +* Mike Jackson +* Mike McQuaid <mike@mikemcquaid.com> +* Nicolas Bock <nicolasbock@gmail.com> +* Nicolas Despres <nicolas.despres@gmail.com> +* Nikita Krupen'ko <krnekit@gmail.com> +* NVIDIA Corporation <www.nvidia.com> +* OpenGamma Ltd. <opengamma.com> +* Patrick Stotko <stotko@cs.uni-bonn.de> +* Per Øyvind Karlsen <peroyvind@mandriva.org> +* Peter Collingbourne <peter@pcc.me.uk> +* Petr Gotthard <gotthard@honeywell.com> +* Philip Lowman <philip@yhbt.com> +* Philippe Proulx <pproulx@efficios.com> +* Raffi Enficiaud, Max Planck Society +* Raumfeld <raumfeld.com> +* Roger Leigh <rleigh@codelibre.net> +* Rolf Eike Beer <eike@sf-mail.de> +* Roman Donchenko <roman.donchenko@itseez.com> +* Roman Kharitonov <roman.kharitonov@itseez.com> +* Ruslan Baratov +* Sebastian Holtermann <sebholt@xwmw.org> +* Stephen Kelly <steveire@gmail.com> +* Sylvain Joubert <joubert.sy@gmail.com> +* The Qt Company Ltd. +* Thomas Sondergaard <ts@medical-insight.com> +* Tobias Hunger <tobias.hunger@qt.io> +* Todd Gamblin <tgamblin@llnl.gov> +* Tristan Carel +* University of Dundee +* Vadim Zhukov +* Will Dicharry <wdicharry@stellarscience.com> + +See version control history for details of individual contributions. + +The above copyright and license notice applies to distributions of +CMake in source and binary form. Third-party software packages supplied +with CMake under compatible licenses provide their own copyright notices +documented in corresponding subdirectories or source files. + +------------------------------------------------------------------------------ + +CMake was initially developed by Kitware with the following sponsorship: + + * National Library of Medicine at the National Institutes of Health + as part of the Insight Segmentation and Registration Toolkit (ITK). + + * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel + Visualization Initiative. + + * National Alliance for Medical Image Computing (NAMIC) is funded by the + National Institutes of Health through the NIH Roadmap for Medical Research, + Grant U54 EB005149. + + * Kitware, Inc. diff --git a/cmake/3rdparty/cmake/QtIRRunCMake.cmake b/cmake/3rdparty/cmake/QtIRRunCMake.cmake new file mode 100644 index 00000000..dd6a10de --- /dev/null +++ b/cmake/3rdparty/cmake/QtIRRunCMake.cmake @@ -0,0 +1,349 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. +# +# Original file location was Tests/RunCMake/RunCMake.cmake + +foreach( + arg + IN ITEMS + RunCMake_GENERATOR + RunCMake_SOURCE_DIR + RunCMake_BINARY_DIR + ) + if(NOT DEFINED ${arg}) + message(FATAL_ERROR "${arg} not given!") + endif() +endforeach() + +function(run_cmake test) + if(DEFINED ENV{RunCMake_TEST_FILTER}) + set(test_and_variant "${test}${RunCMake_TEST_VARIANT_DESCRIPTION}") + if(NOT test_and_variant MATCHES "$ENV{RunCMake_TEST_FILTER}") + return() + endif() + unset(test_and_variant) + endif() + + set(top_src "${RunCMake_SOURCE_DIR}") + set(top_bin "${RunCMake_BINARY_DIR}") + if(EXISTS ${top_src}/${test}-result.txt) + file(READ ${top_src}/${test}-result.txt expect_result) + string(REGEX REPLACE "\n+$" "" expect_result "${expect_result}") + elseif(DEFINED RunCMake_TEST_EXPECT_RESULT) + set(expect_result "${RunCMake_TEST_EXPECT_RESULT}") + else() + set(expect_result 0) + endif() + + string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} platform_name) + #remove all additional bits from cygwin/msys name + if(platform_name MATCHES cygwin) + set(platform_name cygwin) + endif() + if(platform_name MATCHES msys) + set(platform_name msys) + endif() + + foreach(o IN ITEMS stdout stderr config) + if(RunCMake-${o}-file AND EXISTS ${top_src}/${RunCMake-${o}-file}) + file(READ ${top_src}/${RunCMake-${o}-file} expect_${o}) + string(REGEX REPLACE "\n+$" "" expect_${o} "${expect_${o}}") + elseif(EXISTS ${top_src}/${test}-${o}-${platform_name}.txt) + file(READ ${top_src}/${test}-${o}-${platform_name}.txt expect_${o}) + string(REGEX REPLACE "\n+$" "" expect_${o} "${expect_${o}}") + elseif(EXISTS ${top_src}/${test}-${o}.txt) + file(READ ${top_src}/${test}-${o}.txt expect_${o}) + string(REGEX REPLACE "\n+$" "" expect_${o} "${expect_${o}}") + elseif(DEFINED RunCMake_TEST_EXPECT_${o}) + string(REGEX REPLACE "\n+$" "" expect_${o} "${RunCMake_TEST_EXPECT_${o}}") + else() + unset(expect_${o}) + endif() + endforeach() + foreach(o IN ITEMS stdout stderr config) + if(DEFINED RunCMake_TEST_NOT_EXPECT_${o}) + string(REGEX REPLACE "\n+$" "" not_expect_${o} "${RunCMake_TEST_NOT_EXPECT_${o}}") + endif() + endforeach() + if (NOT expect_stderr) + if (NOT RunCMake_DEFAULT_stderr) + set(RunCMake_DEFAULT_stderr "^$") + endif() + set(expect_stderr ${RunCMake_DEFAULT_stderr}) + endif() + + if (NOT RunCMake_TEST_SOURCE_DIR) + set(RunCMake_TEST_SOURCE_DIR "${top_src}") + endif() + if(NOT RunCMake_TEST_BINARY_DIR) + set(RunCMake_TEST_BINARY_DIR "${top_bin}/${test}-build") + endif() + if(NOT RunCMake_TEST_NO_CLEAN) + file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") + endif() + file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + if(RunCMake-prep-file AND EXISTS ${top_src}/${RunCMake-prep-file}) + include(${top_src}/${RunCMake-prep-file}) + else() + include(${top_src}/${test}-prep.cmake OPTIONAL) + endif() + if(RunCMake_TEST_OUTPUT_MERGE) + set(actual_stderr_var actual_stdout) + set(actual_stderr "") + else() + set(actual_stderr_var actual_stderr) + endif() + if(DEFINED RunCMake_TEST_TIMEOUT) + set(maybe_timeout TIMEOUT ${RunCMake_TEST_TIMEOUT}) + else() + set(maybe_timeout "") + endif() + if(RunCMake-stdin-file AND EXISTS ${top_src}/${RunCMake-stdin-file}) + set(maybe_input_file INPUT_FILE ${top_src}/${RunCMake-stdin-file}) + elseif(EXISTS ${top_src}/${test}-stdin.txt) + set(maybe_input_file INPUT_FILE ${top_src}/${test}-stdin.txt) + else() + set(maybe_input_file "") + endif() + if(NOT RunCMake_TEST_COMMAND) + if(NOT DEFINED RunCMake_TEST_OPTIONS) + set(RunCMake_TEST_OPTIONS "") + endif() + if(APPLE) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0025=NEW) + endif() + if(RunCMake_TEST_LCC AND NOT RunCMake_TEST_NO_CMP0129) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0129=NEW) + endif() + if(RunCMake_MAKE_PROGRAM) + list(APPEND RunCMake_TEST_OPTIONS "-DCMAKE_MAKE_PROGRAM=${RunCMake_MAKE_PROGRAM}") + endif() + set(RunCMake_TEST_COMMAND ${CMAKE_COMMAND}) + if(NOT RunCMake_TEST_NO_SOURCE_DIR) + list(APPEND RunCMake_TEST_COMMAND "${RunCMake_TEST_SOURCE_DIR}") + endif() + list(APPEND RunCMake_TEST_COMMAND -G "${RunCMake_GENERATOR}") + if(RunCMake_GENERATOR_PLATFORM) + list(APPEND RunCMake_TEST_COMMAND -A "${RunCMake_GENERATOR_PLATFORM}") + endif() + if(RunCMake_GENERATOR_TOOLSET) + list(APPEND RunCMake_TEST_COMMAND -T "${RunCMake_GENERATOR_TOOLSET}") + endif() + if(RunCMake_GENERATOR_INSTANCE) + list(APPEND RunCMake_TEST_COMMAND "-DCMAKE_GENERATOR_INSTANCE=${RunCMake_GENERATOR_INSTANCE}") + endif() + list(APPEND RunCMake_TEST_COMMAND + -DRunCMake_TEST=${test} + --no-warn-unused-cli + ) + else() + set(RunCMake_TEST_OPTIONS "") + endif() + if(NOT DEFINED RunCMake_TEST_RAW_ARGS) + set(RunCMake_TEST_RAW_ARGS "") + endif() + if(NOT RunCMake_TEST_COMMAND_WORKING_DIRECTORY) + set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + endif() + string(CONCAT _code [[execute_process( + COMMAND ${RunCMake_TEST_COMMAND} + ${RunCMake_TEST_OPTIONS} + ]] "${RunCMake_TEST_RAW_ARGS}\n" [[ + WORKING_DIRECTORY "${RunCMake_TEST_COMMAND_WORKING_DIRECTORY}" + OUTPUT_VARIABLE actual_stdout + ERROR_VARIABLE ${actual_stderr_var} + RESULT_VARIABLE actual_result + ENCODING UTF8 + ${maybe_timeout} + ${maybe_input_file} + )]]) + if(DEFINED ENV{PWD}) + set(old_pwd "$ENV{PWD}") + else() + set(old_pwd) + endif() + # Emulate a shell using this directory. + set(ENV{PWD} "${RunCMake_TEST_COMMAND_WORKING_DIRECTORY}") + cmake_language(EVAL CODE "${_code}") + if(DEFINED old_pwd) + set(ENV{PWD} "${old_pwd}") + else() + set(ENV{PWD}) + endif() + set(msg "") + if(NOT "${actual_result}" MATCHES "${expect_result}") + string(APPEND msg "Result is [${actual_result}], not [${expect_result}].\n") + endif() + set(config_file "${RunCMake_TEST_COMMAND_WORKING_DIRECTORY}/CMakeFiles/CMakeConfigureLog.yaml") + if(EXISTS "${config_file}") + file(READ "${config_file}" actual_config) + else() + set(actual_config "") + endif() + + # Special case: remove ninja no-op line from stderr, but not stdout. + # Test cases that look for it should use RunCMake_TEST_OUTPUT_MERGE. + string(REGEX REPLACE "(^|\r?\n)ninja: no work to do\\.\r?\n" "\\1" actual_stderr "${actual_stderr}") + + # Remove incidental content from both stdout and stderr. + string(CONCAT ignore_line_regex + "(^|\n)((==[0-9]+==" + "|BullseyeCoverage" + "|[a-z]+\\([0-9]+\\) malloc:" + "|clang[^:]*: warning: the object size sanitizer has no effect at -O0, but is explicitly enabled:" + "|flang-new: warning: argument unused during compilation: .-flang-experimental-exec." + "|icp?x: remark: Note that use of .-g. without any optimization-level option will turn off most compiler optimizations" + "|ifx: remark #10440: Note that use of a debug option without any optimization-level option will turnoff most compiler optimizations" + "|lld-link: warning: procedure symbol record for .* refers to PDB item index [0-9A-Fa-fx]+ which is not a valid function ID record" + "|Error kstat returned" + "|Hit xcodebuild bug" + "|Recompacting log\\.\\.\\." + + "|LICENSE WARNING:" + "|Your license to use PGI[^\n]*expired" + "|Please obtain a new version at" + "|contact PGI Sales at" + "|ic(p?c|l): remark #10441: The Intel\\(R\\) C\\+\\+ Compiler Classic \\(ICC\\) is deprecated" + + "|[^\n]*install_name_tool: warning: changes being made to the file will invalidate the code signature in:" + "|[^\n]*(createItemModels|_NSMainThread|Please file a bug at)" + "|[^\n]*xcodebuild[^\n]*DVTAssertions: Warning" + "|[^\n]*xcodebuild[^\n]*DVTCoreDeviceEnabledState: DVTCoreDeviceEnabledState_Disabled set via user default" + "|[^\n]*xcodebuild[^\n]*DVTPlugInManager" + "|[^\n]*xcodebuild[^\n]*DVTSDK: Warning: SDK path collision for path" + "|[^\n]*xcodebuild[^\n]*Requested but did not find extension point with identifier" + "|[^\n]*xcodebuild[^\n]*nil host used in call to allows.*HTTPSCertificateForHost" + "|[^\n]*xcodebuild[^\n]*warning: file type[^\n]*is based on missing file type" + "|[^\n]*objc[^\n]*: Class [^\n]* One of the two will be used. Which one is undefined." + "|[^\n]*is a member of multiple groups" + "|[^\n]*offset in archive not a multiple of 8" + "|[^\n]*from Time Machine by path" + "|[^\n]*Bullseye Testing Technology" + ${RunCMake_TEST_EXTRA_IGNORE_LINE_REGEX} + ")[^\n]*\n)+" + ) + if(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION) + string(REGEX REPLACE [[ +^CMake Deprecation Warning at [^ +]*CMakeLists.txt:1 \(cmake_minimum_required\): + Compatibility with CMake < 3\.5 will be removed from a future version of + CMake. + + Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell + CMake that the project does not need compatibility with older versions\. ++ +]] "" actual_stderr "${actual_stderr}") + endif() + foreach(o IN ITEMS stdout stderr config) + string(REGEX REPLACE "\r\n" "\n" actual_${o} "${actual_${o}}") + string(REGEX REPLACE "${ignore_line_regex}" "\\1" actual_${o} "${actual_${o}}") + string(REGEX REPLACE "\n+$" "" actual_${o} "${actual_${o}}") + if(DEFINED expect_${o}) + if(NOT "${actual_${o}}" MATCHES "${expect_${o}}") + string(APPEND msg "${o} does not match that expected.\n") + endif() + endif() + if(DEFINED not_expect_${o}) + if("${actual_${o}}" MATCHES "${not_expect_${o}}") + string(APPEND msg "${o} matches that not expected.\n") + endif() + endif() + endforeach() + unset(RunCMake_TEST_FAILED) + if(RunCMake-check-file AND EXISTS ${top_src}/${RunCMake-check-file}) + include(${top_src}/${RunCMake-check-file}) + else() + include(${top_src}/${test}-check.cmake OPTIONAL) + endif() + if(RunCMake_TEST_FAILED) + set(msg "${RunCMake_TEST_FAILED}\n${msg}") + endif() + if(msg) + string(REPLACE ";" "\" \"" command "\"${RunCMake_TEST_COMMAND}\"") + if(RunCMake_TEST_OPTIONS) + string(REPLACE ";" "\" \"" options "\"${RunCMake_TEST_OPTIONS}\"") + string(APPEND command " ${options}") + endif() + if(RunCMake_TEST_RAW_ARGS) + string(APPEND command " ${RunCMake_TEST_RAW_ARGS}") + endif() + string(APPEND msg "Command was:\n command> ${command}\n") + endif() + if(msg) + foreach(o IN ITEMS stdout stderr config) + if(DEFINED expect_${o}) + string(REGEX REPLACE "\n" "\n expect-${o}> " expect_${o} " expect-${o}> ${expect_${o}}") + string(APPEND msg "Expected ${o} to match:\n${expect_${o}}\n") + endif() + if(NOT o STREQUAL "config" OR DEFINED expect_${o}) + string(REGEX REPLACE "\n" "\n actual-${o}> " actual_${o} " actual-${o}> ${actual_${o}}") + string(APPEND msg "Actual ${o}:\n${actual_${o}}\n") + endif() + endforeach() + message(SEND_ERROR "${test}${RunCMake_TEST_VARIANT_DESCRIPTION} - FAILED:\n${msg}") + else() + message(STATUS "${test}${RunCMake_TEST_VARIANT_DESCRIPTION} - PASSED") + endif() +endfunction() + +function(run_cmake_command test) + set(RunCMake_TEST_COMMAND "${ARGN}") + run_cmake(${test}) +endfunction() + +function(run_cmake_script test) + set(RunCMake_TEST_COMMAND ${CMAKE_COMMAND} ${ARGN} -P ${RunCMake_SOURCE_DIR}/${test}.cmake) + run_cmake(${test}) +endfunction() + +function(run_cmake_with_options test) + set(RunCMake_TEST_OPTIONS "${ARGN}") + run_cmake(${test}) +endfunction() + +function(run_cmake_with_raw_args test args) + set(RunCMake_TEST_RAW_ARGS "${args}") + run_cmake(${test}) +endfunction() + +function(ensure_files_match expected_file actual_file) + if(NOT EXISTS "${expected_file}") + message(FATAL_ERROR "Expected file does not exist:\n ${expected_file}") + endif() + if(NOT EXISTS "${actual_file}") + message(FATAL_ERROR "Actual file does not exist:\n ${actual_file}") + endif() + file(READ "${expected_file}" expected_file_content) + file(READ "${actual_file}" actual_file_content) + if(NOT "${expected_file_content}" STREQUAL "${actual_file_content}") + message(FATAL_ERROR "Actual file content does not match expected:\n + \n + expected file: ${expected_file}\n + expected content:\n + ${expected_file_content}\n + \n + actual file: ${actual_file}\n + actual content:\n + ${actual_file_content}\n + ") + endif() +endfunction() + +# Get the user id on unix if possible. +function(get_unix_uid var) + set("${var}" "" PARENT_SCOPE) + if(UNIX) + set(ID "id") + if(CMAKE_SYSTEM_NAME STREQUAL "SunOS" AND EXISTS "/usr/xpg4/bin/id") + set (ID "/usr/xpg4/bin/id") + endif() + execute_process(COMMAND ${ID} -u $ENV{USER} OUTPUT_VARIABLE uid ERROR_QUIET + RESULT_VARIABLE status OUTPUT_STRIP_TRAILING_WHITESPACE) + if(status EQUAL 0) + set("${var}" "${uid}" PARENT_SCOPE) + endif() + endif() +endfunction() + +# Protect RunCMake tests from calling environment. +unset(ENV{MAKEFLAGS}) diff --git a/cmake/3rdparty/cmake/QtIRTestHelpers.cmake b/cmake/3rdparty/cmake/QtIRTestHelpers.cmake new file mode 100644 index 00000000..804e20f0 --- /dev/null +++ b/cmake/3rdparty/cmake/QtIRTestHelpers.cmake @@ -0,0 +1,39 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. +# +# Original file location was Tests/RunCMake/CMakeLists.txt + +macro(add_RunCMake_test test) + set(TEST_ARGS ${ARGN}) + if ("${ARGV1}" STREQUAL "TEST_DIR") + if ("${ARGV2}" STREQUAL "") + message(FATAL_ERROR "Invalid args") + endif() + set(Test_Dir ${ARGV2}) + list(REMOVE_AT TEST_ARGS 0) + list(REMOVE_AT TEST_ARGS 0) + else() + set(Test_Dir ${test}) + endif() + if(CMAKE_C_COMPILER_ID STREQUAL "LCC") + list(APPEND TEST_ARGS -DRunCMake_TEST_LCC=1) + endif() + add_test(NAME RunCMake.${test} COMMAND ${CMAKE_CMAKE_COMMAND} + -DCMAKE_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR} + -DRunCMake_GENERATOR_IS_MULTI_CONFIG=${_isMultiConfig} + -DRunCMake_GENERATOR=${CMAKE_GENERATOR} + -DRunCMake_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE} + -DRunCMake_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} + -DRunCMake_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET} + -DRunCMake_MAKE_PROGRAM=${CMake_TEST_EXPLICIT_MAKE_PROGRAM} + -DRunCMake_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${Test_Dir} + -DRunCMake_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/${test} + ${${test}_ARGS} + ${TEST_ARGS} + -P "${CMAKE_CURRENT_SOURCE_DIR}/${Test_Dir}/RunCMakeTest.cmake" + ) + set_tests_properties("RunCMake.${test}" PROPERTIES LABELS "CMake;run") + if(${test} MATCHES ^CMP) + set_property(TEST "RunCMake.${test}" APPEND PROPERTY LABELS "policy") + endif() +endmacro() diff --git a/cmake/3rdparty/cmake/qt_attribution.json b/cmake/3rdparty/cmake/qt_attribution.json new file mode 100644 index 00000000..8ba8a1e7 --- /dev/null +++ b/cmake/3rdparty/cmake/qt_attribution.json @@ -0,0 +1,16 @@ +{ + "Id": "cmake-test-modules", + "Name": "cmake-test-modules", + "QDocModule": "qtcore", + "QtUsage": "Used as part of the build system.", + "QtParts" : [ "tests" ], + + "Description": "CMake helpers for running CMake tests.", + "Homepage": "https://cmake.org/", + "Version": "3.29.0", + + "License": "BSD 3-Clause \"New\" or \"Revised\" License", + "LicenseId": "BSD-3-Clause", + "LicenseFile": "Copyright.txt", + "Copyright": "Copyright © 2000-2024 Kitware, Inc. and Contributors" +} diff --git a/cmake/QtIRCommandLineHelpers.cmake b/cmake/QtIRCommandLineHelpers.cmake new file mode 100644 index 00000000..465a994b --- /dev/null +++ b/cmake/QtIRCommandLineHelpers.cmake @@ -0,0 +1,404 @@ +# 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. +# +# Handles the following styles of CLI arguments: +# --no-foo / --disable-foo +# -no-foo / -disable-foo +# --foo=<values> +# --foo <values> +# -foo <values> +# --foo +# -foo +# --f +# -f +# +# Currently handles the following types of CLI arguments: +# string +# boolean +# void +# +# IGNORE_UNKNOWN_ARGS tells the function not to fail if it encounters an unknown +# option, and instead append it to a global list of unknown options. +# It is 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() + if(NOT arg_IGNORE_UNKNOWN_ARGS) + qt_ir_add_error("Invalid command line parameter '${arg}'.") + else() + message(DEBUG "Unknown command line parameter '${arg}'. Collecting.") + qt_ir_append_unknown_args("${arg}") + continue() + endif() + 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}'. Collecting.") + qt_ir_append_unknown_args("${arg}") + 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() + +# Adds to the unknown command line args. +function(qt_ir_append_unknown_args args) + set_property(GLOBAL APPEND PROPERTY _qt_ir_unknown_args ${args}) +endfunction() + +# Gets the unhandled command line args. +function(qt_ir_get_unknown_args out_var) + get_property(args GLOBAL PROPERTY _qt_ir_unknown_args) + set(${out_var} "${args}" PARENT_SCOPE) +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..288f4ac7 --- /dev/null +++ b/cmake/QtIRGitHelpers.cmake @@ -0,0 +1,1151 @@ +# 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 the are no previously initialized submodules, and 'existing' is specified, default + # to 'default'. This handles the case when someone runs git submodule deinit --all --force, + # where git initrepository.initialized config key is still true, and then runs + # configure -init-submodules. Without defaulting to default, we would end up with an empty + # subset and configure would fail. + if(NOT module_subset) + if(arg_PREVIOUSLY_INITIALIZED) + if(arg_ALREADY_INITIALIZED_SUBMODULES) + set(module_subset "existing") + else() + message(DEBUG "No previously initialized submodules detected even though " + "'existing' was specified, defaulting to 'default'") + set(module_subset "default") + endif() + 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) + qt_ir_is_called_from_configure(is_called_from_configure) + + if(is_initialized) + if(NOT force) + set(should_exit TRUE) + if(NOT quiet AND NOT is_called_from_configure) + 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 + TOP_LEVEL_SRC_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}") + + if(NOT arg_TOP_LEVEL_SRC_PATH) + message(FATAL_ERROR "qt_ir_install_git_hooks: No TOP_LEVEL_SRC_PATH specified") + endif() + set(top_level_src_path "${arg_TOP_LEVEL_SRC_PATH}") + + set(hooks_dir "${top_level_src_path}/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() + +# Saves the list of top-level submodules that should be included and excluded. +# Will be used to pass these values to the top-level configure script. +function(qt_ir_set_top_level_submodules included_submodules excluded_submodules) + set_property(GLOBAL PROPERTY _qt_ir_top_level_included_submodules "${included_submodules}") + set_property(GLOBAL PROPERTY _qt_ir_top_level_excluded_submodules "${excluded_submodules}") +endfunction() + +# Gets the list of top-level submodules that should be included and excluded. +function(qt_ir_get_top_level_submodules out_included_submodules out_excluded_submodules) + get_property(included GLOBAL PROPERTY _qt_ir_top_level_included_submodules) + get_property(excluded GLOBAL PROPERTY _qt_ir_top_level_excluded_submodules) + + set(${out_included_submodules} "${included}" PARENT_SCOPE) + set(${out_excluded_submodules} "${excluded}" PARENT_SCOPE) +endfunction() + +# Parses the .gitmodules file and proceses the submodules based on the module-subset option +# or the given SUBMODULES argument. +# Also adds dependencies if requested. +# +# This is a macro because we want the variables set by +# qt_ir_parse_gitmodules_file_contents to be available in the calling scope, because it's +# essentially setting a dictionarty, and we don't want to propagate all the variables manually. +macro(qt_ir_get_submodules prefix out_var_submodules) + set(options + PREVIOUSLY_INITIALIZED + PROCESS_SUBMODULES_FROM_COMMAND_LINE + ) + set(oneValueArgs + PARENT_REPO_BASE_GIT_PATH + WORKING_DIRECTORY + ) + set(multiValueArgs + ALREADY_INITIALIZED_SUBMODULES + SUBMODULES + ) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + qt_ir_get_working_directory_from_arg(working_directory) + + # Parse the .gitmodules content here, so the parsed data is available downstream + # in other functions and recursive calls of the same function. + qt_ir_parse_git_config_file_contents("${prefix}" + READ_GITMODULES + PARENT_REPO_BASE_GIT_PATH "${arg_PARENT_REPO_BASE_GIT_PATH}" + WORKING_DIRECTORY "${working_directory}" + ) + + qt_ir_get_option_value(perl-identical-output perl_identical_output_for_tests) + set(extra_implict_submodules "") + + # Get which modules should be initialized, based on the module-subset option. + if(arg_PROCESS_SUBMODULES_FROM_COMMAND_LINE) + qt_ir_get_option_value(module-subset initial_module_subset) + + # Implicitly add qtrepotools, so we can install git hooks and don't get the + # missing qtrepotools warning. + if(NOT perl_identical_output_for_tests) + list(APPEND extra_implict_submodules "qtrepotools") + qt_ir_is_verbose(verbose) + if(verbose) + message("Implicitly adding qtrepotools to the list of submodules " + "to initialize for access to git commit hooks, etc. " + "(use --module-subset=<values>,-qtrepotools to exclude it)") + endif() + endif() + + if(NOT perl_identical_output_for_tests) + message(DEBUG "module-subset from command line: ${initial_module_subset}") + endif() + elseif(arg_SUBMODULES) + set(initial_module_subset "${arg_SUBMODULES}") + if(NOT perl_identical_output_for_tests) + message(DEBUG "module-subset from args: ${initial_module_subset}") + endif() + else() + message(FATAL_ERROR "No submodules specified") + endif() + + qt_ir_get_cmake_flag(PREVIOUSLY_INITIALIZED previously_initialized_opt) + qt_ir_process_module_subset_values("${prefix}" + ${previously_initialized_opt} + ${perl_identical_output_opt} + ALREADY_INITIALIZED_SUBMODULES ${arg_ALREADY_INITIALIZED_SUBMODULES} + EXTRA_IMPLICIT_SUBMODULES ${extra_implict_submodules} + MODULE_SUBSET "${initial_module_subset}" + OUT_VAR_INCLUDE processed_module_subset + OUT_VAR_EXCLUDE modules_to_exclude + ) + if(NOT perl_identical_output_for_tests) + message(DEBUG "Processed module subset: ${processed_module_subset}") + endif() + + # We only resolve dependencies for the top-level call, not for recursive calls. + if(arg_PROCESS_SUBMODULES_FROM_COMMAND_LINE) + # Resolve which submodules should be initialized, including dependencies. + qt_ir_get_module_subset_including_deps("${prefix}" + submodules_with_deps "${processed_module_subset}") + + # Then remove any explicitly specified submodules. + set(submodules_with_deps_and_excluded "${submodules_with_deps}") + if(modules_to_exclude) + list(REMOVE_ITEM submodules_with_deps_and_excluded ${modules_to_exclude}) + endif() + + 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}") + qt_ir_set_top_level_submodules("${submodules}" "${modules_to_exclude}") + 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..9d6f5749 --- /dev/null +++ b/cmake/QtIRHelp.txt @@ -0,0 +1,134 @@ +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=<module1>,<module2>... / -submodules <module1>,<module2>... + 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". + For compatibility with qt's configure script, -submodules is an alias + of --module-subset. Note the difference in dashes and the equal sign. + + --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 <Gerrit/JIRA 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 <path to other Qt5 repo> + 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 <url-base> + Uses <url-base> 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..9f372932 --- /dev/null +++ b/cmake/QtIRHelpers.cmake @@ -0,0 +1,367 @@ +# 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) + cmake_parse_arguments(arg "ALREADY_INITIALIZED" "" "" ${ARGV}) + + # 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) + if(NOT include_submodules AND arg_ALREADY_INITIALIZED) + set(include_submodules "${submodules}") + endif() + + # 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() + + # Pass --help if it was requested. + qt_ir_is_help_requested(show_help) + if(show_help) + list(APPEND filtered_args "-help") + 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() diff --git a/cmake/QtIROptionsHelpers.cmake b/cmake/QtIROptionsHelpers.cmake new file mode 100644 index 00000000..d87c7463 --- /dev/null +++ b/cmake/QtIROptionsHelpers.cmake @@ -0,0 +1,48 @@ +# 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) + + # These are used when init-repository is called from configure. + qt_ir_commandline_option(from-configure TYPE boolean) + # Implies force. + qt_ir_commandline_option(init-submodules TYPE boolean) + # We alias qtbase's submodules option to init-repository module-subset. + qt_ir_commandline_option(submodules ALIAS module-subset TYPE string) + + set_property(GLOBAL PROPERTY _qt_ir_known_command_line_options "${commandline_known_options}") +endmacro() + +# Gets list of known command line options. +function(qt_ir_get_known_command_line_options out_var) + get_property(values GLOBAL PROPERTY _qt_ir_known_command_line_options) + set(${out_var} "${values}" PARENT_SCOPE) +endfunction() 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..fc5ffba9 --- /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("${CMAKE_CURRENT_SOURCE_DIR}" exit_reason) diff --git a/cmake/QtSortModuleDependencies.cmake b/cmake/QtSortModuleDependencies.cmake new file mode 100644 index 00000000..2f8cdb59 --- /dev/null +++ b/cmake/QtSortModuleDependencies.cmake @@ -0,0 +1,16 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# The script produces the list of qt submodules that are required to build the submodules listed +# in the QT_BUILD_SUBMODULES variable. The resulting list preserves the required build order. +# Usage: +# cmake [-DQT_BUILD_SUBMODULES="<repo;..>"] [-BUILD_<repo>=<TRUE|FALSE>] \ +# -P <path/to>/qt6/cmake/QtSortModuleDependencies.cmake +cmake_minimum_required(VERSION 3.16) + +include(${CMAKE_CURRENT_LIST_DIR}/QtTopLevelHelpers.cmake) + +qt_internal_collect_modules_only(result "${QT_BUILD_SUBMODULES}") + +list(JOIN result " " result) +message("${result}") diff --git a/cmake/QtSynchronizeRepo.cmake b/cmake/QtSynchronizeRepo.cmake index 522ea76e..eabd5c7c 100644 --- a/cmake/QtSynchronizeRepo.cmake +++ b/cmake/QtSynchronizeRepo.cmake @@ -1,3 +1,15 @@ -include(cmake/QtTopLevelHelpers.cmake) +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# This script is to be called (ideally from a git-sync-to alias script): +# cmake -DSYNC_TO_MODULE="$1" -DSYNC_TO_BRANCH="$2" -P cmake/QtSynchronizeRepo.cmake +# Or as follows (ideally from a git-qt-foreach alias script): +# cmake -DQT_FOREACH=TRUE "-DARGS=$*" -P cmake/QtSynchronizeRepo.cmake -qt_internal_sync_to(${SYNC_TO_MODULE} ${SYNC_TO_BRANCH}) +cmake_policy(VERSION 3.16) +include(cmake/QtTopLevelHelpers.cmake) +if(QT_FOREACH) + qt_internal_foreach_repo_run(ARGS ${ARGS}) +else() + qt_internal_sync_to(${SYNC_TO_MODULE} ${SYNC_TO_BRANCH}) +endif() diff --git a/cmake/QtTopLevelConfigureScript.cmake b/cmake/QtTopLevelConfigureScript.cmake new file mode 100644 index 00000000..304bf7b7 --- /dev/null +++ b/cmake/QtTopLevelConfigureScript.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 configure uses. +macro(qt_tl_setup_include_paths) + list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_CURRENT_LIST_DIR}/3rdparty/cmake" + ) + include(QtTopLevelHelpers) +endmacro() + +qt_tl_setup_include_paths() +qt_tl_include_all_helpers() +qt_tl_run_main_script() diff --git a/cmake/QtTopLevelHelpers.cmake b/cmake/QtTopLevelHelpers.cmake index 2d06a8fd..7fe21e4f 100644 --- a/cmake/QtTopLevelHelpers.cmake +++ b/cmake/QtTopLevelHelpers.cmake @@ -1,3 +1,62 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +macro(qt_tl_include_all_helpers) + include(QtIRHelpers) + qt_ir_include_all_helpers() +endmacro() + +function(qt_tl_run_toplevel_configure top_level_src_path) + cmake_parse_arguments(arg "ALREADY_INITIALIZED" "" "" ${ARGV}) + + qt_ir_get_cmake_flag(ALREADY_INITIALIZED arg_ALREADY_INITIALIZED) + + # Filter out init-repository specific arguments before passing them to + # configure. + qt_ir_get_args_from_optfile_configure_filtered("${OPTFILE}" configure_args + ${arg_ALREADY_INITIALIZED}) + # Get the path to the qtbase configure script. + set(qtbase_dir_name "qtbase") + set(configure_path "${top_level_src_path}/${qtbase_dir_name}/configure") + if(CMAKE_HOST_WIN32) + string(APPEND configure_path ".bat") + endif() + + if(NOT EXISTS "${configure_path}") + message(FATAL_ERROR + "The required qtbase/configure script was not found: ${configure_path}\n" + "Try re-running configure with --init-submodules") + endif() + + # Make a build directory for qtbase in the current build directory. + set(qtbase_build_dir "${CMAKE_CURRENT_BINARY_DIR}/${qtbase_dir_name}") + file(MAKE_DIRECTORY "${qtbase_build_dir}") + + qt_ir_execute_process_and_log_and_handle_error( + COMMAND_ARGS "${configure_path}" -top-level ${configure_args} + WORKING_DIRECTORY "${qtbase_build_dir}" + FORCE_VERBOSE + ) +endfunction() + +function(qt_tl_run_main_script) + if(NOT TOP_LEVEL_SRC_PATH) + message(FATAL_ERROR "Assertion: configure TOP_LEVEL_SRC_PATH is not set") + endif() + + # Tell init-repository it is called from configure. + qt_ir_set_option_value(from-configure TRUE) + + # Run init-repository in-process. + qt_ir_run_main_script("${TOP_LEVEL_SRC_PATH}" exit_reason) + if(exit_reason AND NOT exit_reason STREQUAL "ALREADY_INITIALIZED") + return() + endif() + + # Then run configure out-of-process. + qt_tl_run_toplevel_configure("${TOP_LEVEL_SRC_PATH}" ${exit_reason}) +endfunction() + # Populates $out_module_list with all subdirectories that have a CMakeLists.txt file function(qt_internal_find_modules out_module_list) set(module_list "") @@ -14,8 +73,8 @@ 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 -function(qt_internal_parse_dependencies depends_file out_dependencies) +# Each entry will be in the format dependency/sha1/required +function(qt_internal_parse_dependencies_yaml depends_file out_dependencies) file(STRINGS "${depends_file}" lines) set(eof_marker "---EOF---") list(APPEND lines "${eof_marker}") @@ -47,91 +106,276 @@ function(qt_internal_parse_dependencies depends_file out_dependencies) string(TOUPPER "${CMAKE_MATCH_1}" required) endif() endforeach() - message(DEBUG "qt_internal_parse_dependencies for ${depends_file}: ${dependencies} ${revisions}") + message(DEBUG + "qt_internal_parse_dependencies_yaml for ${depends_file}\n dependencies: ${dependencies}") set(${out_dependencies} "${dependencies}" PARENT_SCOPE) endfunction() -# Load $module and populate $out_ordered with the submodules based on their dependencies -# $ordered carries already sorted dependencies; $out_has_dependencies is left empty -# if there are no dependencies, otherwise set to 1; Save list of dependencies for $module into -# $out_module_dependencies. List may contain duplicates, since function checks max depth -# dependencies. -# Function calls itself recursively if a dependency is found that is not yet in $ordered. -function(qt_internal_add_module_dependencies module ordered out_ordered out_has_dependencies - out_module_dependencies out_revisions) - set(depends_file "${CMAKE_CURRENT_SOURCE_DIR}/${module}/dependencies.yaml") - if(NOT EXISTS "${depends_file}") - set(${out_has_dependencies} "" PARENT_SCOPE) +# Helper macro for qt_internal_resolve_module_dependencies. +macro(qt_internal_resolve_module_dependencies_set_skipped value) + if(DEFINED arg_SKIPPED_VAR) + set(${arg_SKIPPED_VAR} ${value} PARENT_SCOPE) + endif() +endmacro() + +# Strips tqtc- prefix from a repo name. +function(qt_internal_normalize_repo_name repo_name out_var) + string(REGEX REPLACE "^tqtc-" "" normalized "${repo_name}") + set(${out_var} "${normalized}" PARENT_SCOPE) +endfunction() + +# Checks if a directory with the given repo name exists in the current +# source / working directory. If it doesn't, it strips the tqtc- prefix. +function(qt_internal_use_normalized_repo_name_if_needed repo_name out_var) + set(base_dir "${CMAKE_CURRENT_SOURCE_DIR}") + set(repo_dir "${base_dir}/${repo_name}") + if(NOT IS_DIRECTORY "${repo_dir}") + qt_internal_normalize_repo_name("${repo_name}" repo_name) + endif() + set(${out_var} "${repo_name}" PARENT_SCOPE) +endfunction() + + +# Resolve the dependencies of the given module. +# "Module" in the sense of Qt repository. +# +# Side effects: Sets the global properties QT_DEPS_FOR_${module} and QT_REQUIRED_DEPS_FOR_${module} +# with the direct (required) dependencies of module. +# +# +# Positional arguments: +# +# module is the Qt repository. +# +# out_ordered is where the result is stored. This is a list of all dependencies, including +# transitive ones, in topologically sorted order. Note that ${module} itself is also part of +# out_ordered. +# +# out_revisions is a list of git commit IDs for each of the dependencies in ${out_ordered}. This +# list has the same length as ${out_ordered}. +# +# +# Keyword arguments: +# +# PARSED_DEPENDENCIES is a list of dependencies of module in the format that +# 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. +# +# REVISION is an internal value with the git commit ID that belongs to ${module}. +# +# SKIPPED_VAR is an output variable name that is set to TRUE if the module was skipped, to FALSE +# otherwise. +# +# NORMALIZE_REPO_NAME_IF_NEEDED Will remove 'tqtc-' from the beginning of submodule dependencies +# if a tqtc- named directory does not exist. +# +# SKIP_MODULES Modules that should be skipped from evaluation completely. +function(qt_internal_resolve_module_dependencies module out_ordered out_revisions) + 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 SKIP_MODULES) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Clear the property that stores the repositories we've already seen. + if(NOT arg_IN_RECURSION) + set_property(GLOBAL PROPERTY _qt_internal_seen_repos) + endif() + + # Bail out if we've seen the module already or it was skipped explicitly from command line. + qt_internal_resolve_module_dependencies_set_skipped(FALSE) + get_property(seen GLOBAL PROPERTY _qt_internal_seen_repos) + if(module IN_LIST seen OR module IN_LIST arg_SKIP_MODULES) + qt_internal_resolve_module_dependencies_set_skipped(TRUE) return() endif() - set(${out_has_dependencies} "1" PARENT_SCOPE) - set(dependencies "") - qt_internal_parse_dependencies("${depends_file}" dependencies) - # module hasn't been seen yet, append it - list(FIND ordered "${module}" pindex) - if (pindex EQUAL -1) - list(LENGTH ordered pindex) - list(APPEND ordered "${module}") - list(APPEND revisions "HEAD") + + set_property(GLOBAL APPEND PROPERTY _qt_internal_seen_repos ${module}) + + # Set a default REVISION. + if("${arg_REVISION}" STREQUAL "") + set(arg_REVISION HEAD) endif() - set(modules_dependencies "") + + # Retrieve the dependencies. + if(DEFINED arg_PARSED_DEPENDENCIES) + set(dependencies "${arg_PARSED_DEPENDENCIES}") + else() + set(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() + + # Traverse the dependencies. + set(ordered) + set(revisions) foreach(dependency IN LISTS dependencies) if(dependency MATCHES "(.*)/([^/]+)/([^/]+)") set(dependency "${CMAKE_MATCH_1}") set(revision "${CMAKE_MATCH_2}") set(required "${CMAKE_MATCH_3}") - if(required) - set_property(GLOBAL APPEND PROPERTY QT_REQUIRED_DEPS_FOR_${module} ${dependency}) - endif() else() message(FATAL_ERROR "Internal Error: wrong dependency format ${dependency}") endif() - list(APPEND modules_dependencies "${dependency}") - list(FIND ordered "${dependency}" dindex) - if (dindex EQUAL -1) - # dependency hasnt' been seen yet - load it - list(INSERT ordered ${pindex} "${dependency}") - list(INSERT revisions ${pindex} "${revision}") - qt_internal_add_module_dependencies(${dependency} "${ordered}" ordered has_dependency - "${out_module_dependencies}" revisions) - elseif(dindex GREATER pindex) - # otherwise, make sure it is before module - list(REMOVE_AT ordered ${dindex}) - list(REMOVE_AT revisions ${dindex}) - list(INSERT ordered ${pindex} "${dependency}") - list(INSERT revisions ${pindex} "${revision}") + + set(normalize_arg "") + if(arg_NORMALIZE_REPO_NAME_IF_NEEDED) + qt_internal_use_normalized_repo_name_if_needed("${dependency}" dependency) + set(normalize_arg "NORMALIZE_REPO_NAME_IF_NEEDED") + endif() + + set_property(GLOBAL APPEND PROPERTY QT_DEPS_FOR_${module} ${dependency}) + if(required) + 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() + + set(extra_options "") + if(arg_SKIP_MODULES) + list(APPEND extra_options SKIP_MODULES ${arg_SKIP_MODULES}) + 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} + ${extra_options} + ) + if(NOT skipped) + list(APPEND ordered ${dep_ordered}) + list(APPEND revisions ${dep_revisions}) endif() endforeach() + + list(APPEND ordered ${module}) + list(APPEND revisions ${arg_REVISION}) set(${out_ordered} "${ordered}" PARENT_SCOPE) - set(${out_module_dependencies} ${${out_module_dependencies}} ${modules_dependencies} PARENT_SCOPE) set(${out_revisions} "${revisions}" PARENT_SCOPE) endfunction() -# populates $out_all_ordered with the sequence of the modules that need -# to be built in order to build $modules; dependencies for each module are populated -# in variables with specified in $dependencies_map_prefix prefix -function(qt_internal_sort_module_dependencies modules out_all_ordered dependencies_map_prefix) - set(ordered "") +# Resolves the dependencies of the given modules. +# "Module" is here used in the sense of Qt repository. +# +# Returns all dependencies, including transitive ones, in topologically sorted order. +# +# 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. +# +# SKIP_MODULES Modules that should be skipped from evaluation completely. +# +# 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 SKIP_MODULES) + 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_yaml produces. + set(all_selected_repos_as_parsed_dependencies) foreach(module IN LISTS modules) - set(out_ordered "") - if(NOT dependencies_map_prefix) - message(FATAL_ERROR "dependencies_map_prefix is not provided") - endif() - set(module_dependencies_list_var_name "${dependencies_map_prefix}${module}") - qt_internal_add_module_dependencies(${module} "${ordered}" out_ordered module_depends - "${module_dependencies_list_var_name}" revisions) - set(${module_dependencies_list_var_name} - "${${module_dependencies_list_var_name}}" PARENT_SCOPE) - if(NOT module_depends) - list(APPEND no_dependencies "${module}") - else() - set(ordered "${out_ordered}") - endif() + list(APPEND all_selected_repos_as_parsed_dependencies "${module}/HEAD/FALSE") endforeach() - if (no_dependencies) - list(APPEND ordered "${no_dependencies}") + + set(extra_args "") + if(arg_SKIP_MODULES) + set(extra_args SKIP_MODULES ${arg_SKIP_MODULES}) endif() - message(DEBUG "qt_internal_parse_dependencies sorted ${modules}: ${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} + ${extra_args} + ) + + # Drop "all_selected_repos" from the output. It depends on all selected repos, thus it must be + # the last element in the topologically sorted list. + list(REMOVE_AT ordered -1) + + message(DEBUG + "qt_internal_sort_module_dependencies + input modules: ${modules}\n topo-sorted: ${ordered}") set(${out_all_ordered} "${ordered}" PARENT_SCOPE) endfunction() @@ -268,6 +512,17 @@ function(qt_internal_sync_to module) endif() qt_internal_checkout("${module}" "${revision}") + qt_internal_resolve_module_dependencies(${module} initial_dependencies initial_revisions) + if(initial_dependencies) + foreach(dependency ${initial_dependencies}) + if(dependency MATCHES "^tqtc-") + message(WARNING + "Handling of tqtc- repos will likely fail. Fixing this is non-trivial.") + break() + endif() + endforeach() + endif() + set(revision "") set(checkedout "1") # Load all dependencies for $module, then iterate over the dependencies in reverse order, @@ -275,19 +530,16 @@ function(qt_internal_sync_to module) # Repeat everything (we need to reload dependencies after each checkout) until no more checkouts # are done. while(${checkedout}) - set(dependencies "") - set(revisions "") - set(prefix "") - qt_internal_add_module_dependencies(${module} "${dependencies}" dependencies has_dependencies prefix revisions) + qt_internal_resolve_module_dependencies(${module} dependencies revisions) message(DEBUG "${module} dependencies: ${dependencies}") message(DEBUG "${module} revisions : ${revisions}") - if (NOT has_dependencies) + list(LENGTH dependencies count) + if (count EQUAL "0") message(NOTICE "Module ${module} has no dependencies") return() endif() - list(LENGTH dependencies count) math(EXPR count "${count} - 1") set(checkedout 0) foreach(i RANGE ${count} 0 -1 ) @@ -325,3 +577,125 @@ function(qt_internal_sync_to module) endforeach() endwhile() endfunction() + +# Runs user specified command for all qt repositories in qt directory. +# Similar to git submodule foreach, except without relying on .gitmodules existing. +# Useful for worktree checkouts. +function(qt_internal_foreach_repo_run) + cmake_parse_arguments(PARSE_ARGV 0 arg + "" + "" + "ARGS" + ) + if(NOT arg_ARGS) + message(FATAL_ERROR "No arguments specified to qt_internal_foreach_repo_run") + endif() + separate_arguments(args NATIVE_COMMAND "${arg_ARGS}") + + # Find the qt repos + qt_internal_find_modules(modules) + + # Hack to support color output on unix systems + # https://stackoverflow.com/questions/18968979/how-to-make-colorized-message-with-cmake + execute_process(COMMAND + /usr/bin/tty + OUTPUT_VARIABLE tty_name + RESULT_VARIABLE tty_exit_code + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + set(color_supported FALSE) + set(output_goes_where "") + if(NOT tty_exit_CODE AND tty_name) + set(color_supported TRUE) + set(output_goes_where "OUTPUT_FILE" "${tty_name}") + endif() + + # Count successes and failures. + set(count_success "0") + set(count_failure "0") + + # Show colored error markers. + set(color "--normal") + if(color_supported) + set(color "--red") + endif() + + foreach(module IN LISTS modules) + message("Entering '${module}'") + execute_process( + COMMAND ${args} + WORKING_DIRECTORY "${module}" + ${output_goes_where} + RESULT_VARIABLE cmd_result + ) + if(cmd_result) + math(EXPR count_failure "${count_failure}+1") + # cmake_echo_color is undocumented, but lets us output colors and control newlines. + execute_process( + COMMAND + ${CMAKE_COMMAND} -E env CLICOLOR_FORCE=1 + ${CMAKE_COMMAND} -E cmake_echo_color "${color}" + "Process execution failed here ^^^^^^^^^^^^^^^^^^^^" + ) + else() + math(EXPR count_success "${count_success}+1") + endif() + endforeach() + + # Show summary with colors. + set(color "--normal") + if(count_failure AND color_supported) + set(color "--red") + endif() + + message("\nSummary\n=======\n") + execute_process( + COMMAND + ${CMAKE_COMMAND} -E cmake_echo_color --normal --no-newline "Failures: " + ) + execute_process( + COMMAND + ${CMAKE_COMMAND} -E env CLICOLOR_FORCE=1 + ${CMAKE_COMMAND} -E cmake_echo_color "${color}" "${count_failure}" + ) + message("Successes: ${count_success}") +endfunction() + +# The function collects repos and dependencies that are required to build +# repos listed in ARGN. If the BUILD_<repo> is defined the 'repo' will be +# excluded from the list. +function(qt_internal_collect_modules_only out_repos) + set(initial_modules "${ARGN}") + get_filename_component(qt5_repo_dir "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) + + # Overriding CMAKE_CURRENT_SOURCE_DIR is ugly but works + set(CMAKE_CURRENT_SOURCE_DIR "${qt5_repo_dir}") + if(NOT initial_modules) + qt_internal_find_modules(initial_modules) + endif() + + qt_internal_sort_module_dependencies("${initial_modules}" ${out_repos}) + foreach(module IN LISTS ${out_repos}) + # Check for unmet dependencies + if(DEFINED BUILD_${module} AND NOT BUILD_${module}) + list(REMOVE_ITEM ${out_repos} ${module}) + continue() + endif() + get_property(required_deps GLOBAL PROPERTY QT_REQUIRED_DEPS_FOR_${module}) + get_property(dependencies GLOBAL PROPERTY QT_DEPS_FOR_${module}) + foreach(dep IN LISTS dependencies) + set(required FALSE) + if(dep IN_LIST required_deps) + set(required TRUE) + endif() + if(required AND DEFINED BUILD_${dep} AND NOT BUILD_${dep}) + set(BUILD_${module} FALSE) + list(REMOVE_ITEM ${out_repos} ${module}) + break() + endif() + endforeach() + endforeach() + + set(${out_repos} "${${out_repos}}" PARENT_SCOPE) +endfunction() 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}") |