aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoerg Bornemann <joerg.bornemann@qt.io>2022-01-21 21:55:08 +0100
committerAlexandru Croitor <alexandru.croitor@qt.io>2022-04-29 16:22:12 +0200
commit0ec83c2903a92e91eb566046c1355e57a71eca2b (patch)
treed903199c15bda2e1ac951b5adb8635f3e86a47d1
parentd7133e4de08dcf381970a994af29aa5c2f174d69 (diff)
CMake: Rework inter-repository dependency resolution
In certain cases the dependency resolution between Qt repositories resulted in a wrong order, for example qtsvg being handled before qtbase. See the linked task for an example. Replace the existing algorithm with a post-order traversal of the dependency graph to produce a topological ordering. Rename qt_internal_add_module_dependencies to qt_internal_resolve_module_dependencies and remove unnecessary positional arguments. Use keyword arguments for a nicer API. Raise the cmake_policy in QtSynchronizeRepo.cmake to avoid policy warnings we now would get due to the use of IN_LIST. Fixes: QTBUG-98268 Change-Id: I1425fd9c802fa71ae42549ceb14bcfc4c0a62078 Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io> (cherry picked from commit 8a94d1e2946644253cf2979c35636cbfb850422d) Reviewed-by: Jörg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--CMakeLists.txt7
-rw-r--r--cmake/QtSynchronizeRepo.cmake4
-rw-r--r--cmake/QtTopLevelHelpers.cmake188
3 files changed, 127 insertions, 72 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a0f4d76c..b1afb2f5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,16 +52,15 @@ if(NOT QT_BUILD_SUBMODULES)
endif()
set(QT_BUILD_SUBMODULES "${QT_BUILD_SUBMODULES}" CACHE STRING "Submodules to build")
-set(qt_module_dependency_map_prefix "__qt_module_dependencies_")
-qt_internal_sort_module_dependencies("${QT_BUILD_SUBMODULES}" QT_BUILD_SUBMODULES
- "${qt_module_dependency_map_prefix}")
+qt_internal_sort_module_dependencies("${QT_BUILD_SUBMODULES}" QT_BUILD_SUBMODULES)
foreach(module IN LISTS QT_BUILD_SUBMODULES)
# Check for unmet dependencies
if(NOT DEFINED BUILD_${module} OR BUILD_${module})
message(NOTICE "Checking dependencies of '${module}'")
get_property(required_deps GLOBAL PROPERTY QT_REQUIRED_DEPS_FOR_${module})
- foreach(dep IN LISTS "${qt_module_dependency_map_prefix}${module}")
+ get_property(dependencies GLOBAL PROPERTY QT_DEPS_FOR_${module})
+ foreach(dep IN LISTS dependencies)
if (dep STREQUAL "qtbase")
# Always available skip
continue()
diff --git a/cmake/QtSynchronizeRepo.cmake b/cmake/QtSynchronizeRepo.cmake
index 522ea76e..5b379d0b 100644
--- a/cmake/QtSynchronizeRepo.cmake
+++ b/cmake/QtSynchronizeRepo.cmake
@@ -1,3 +1,7 @@
+# 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
+
+cmake_policy(VERSION 3.16)
include(cmake/QtTopLevelHelpers.cmake)
qt_internal_sync_to(${SYNC_TO_MODULE} ${SYNC_TO_BRANCH})
diff --git a/cmake/QtTopLevelHelpers.cmake b/cmake/QtTopLevelHelpers.cmake
index 2d06a8fd..bd511cdd 100644
--- a/cmake/QtTopLevelHelpers.cmake
+++ b/cmake/QtTopLevelHelpers.cmake
@@ -14,7 +14,7 @@ endfunction()
# poor man's yaml parser, populating $out_dependencies with all dependencies
# in the $depends_file
-# Each entry will be in the format dependency/sha1
+# Each entry will be in the format dependency/sha1/required
function(qt_internal_parse_dependencies depends_file out_dependencies)
file(STRINGS "${depends_file}" lines)
set(eof_marker "---EOF---")
@@ -51,86 +51,141 @@ function(qt_internal_parse_dependencies depends_file out_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()
+
+# 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.
+#
+# 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 returns. If this argument is not provided, dependencies.yaml of the
+# module is parsed.
+#
+# 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.
+function(qt_internal_resolve_module_dependencies module out_ordered out_revisions)
+ set(options IN_RECURSION)
+ set(oneValueArgs REVISION SKIPPED_VAR)
+ set(multiValueArgs PARSED_DEPENDENCIES)
+ 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.
+ qt_internal_resolve_module_dependencies_set_skipped(FALSE)
+ get_property(seen GLOBAL PROPERTY _qt_internal_seen_repos)
+ if(module IN_LIST seen)
+ 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(depends_file "${CMAKE_CURRENT_SOURCE_DIR}/${module}/dependencies.yaml")
+ if(NOT EXISTS "${depends_file}")
+ qt_internal_resolve_module_dependencies_set_skipped(TRUE)
+ return()
+ endif()
+ set(dependencies "")
+ qt_internal_parse_dependencies("${depends_file}" dependencies)
+ 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_property(GLOBAL APPEND PROPERTY QT_DEPS_FOR_${module} ${dependency})
+ if(required)
+ set_property(GLOBAL APPEND PROPERTY QT_REQUIRED_DEPS_FOR_${module} ${dependency})
+ endif()
+
+ qt_internal_resolve_module_dependencies(${dependency} dep_ordered dep_revisions
+ REVISION "${revision}"
+ SKIPPED_VAR skipped
+ IN_RECURSION)
+ 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.
+#
+# See qt_internal_resolve_module_dependencies for side effects.
+function(qt_internal_sort_module_dependencies modules out_all_ordered)
+
+ # Create a fake repository "all_selected_repos" that has all repositories from the input as
+ # required dependency. The format must match what qt_internal_parse_dependencies produces.
+ 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}")
- endif()
+
+ qt_internal_resolve_module_dependencies(all_selected_repos ordered unused_revisions
+ PARSED_DEPENDENCIES ${all_selected_repos_as_parsed_dependencies})
+
+ # 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_parse_dependencies sorted ${modules}: ${ordered}")
set(${out_all_ordered} "${ordered}" PARENT_SCOPE)
endfunction()
@@ -275,19 +330,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 )