Ensure that per-ABI builds are sequential

The current approach doesn't work correctly because external project
steps that we run on per-ABI build directories look as
'cmake --build <abi/build/dir> --target <target_name>'. When the
project has a single APK this works perfectly fine. But when building
more than one apk this leads to the simultanous build in the same
'abi/build/dir' which causes undefined behavior and concurrent access
to the build artifacts. This is especially sensible when APK targets
have the common dependencies.

The solution is split for two usecases:
- Ninja-like generators, that support job pools.
- Other generator.

For Ninja-like generators we now create job pools per-ABI with job number 1,
this convinces ninja to run only one 'cmake --build' command in single ABI
scope.

For other generators the solution is not that good. We create the dependency
chain between all APK targets and this leads to the build of the unwanted
dependencies. For example if project has apkA and apkB targets, then
apkB_x86 will depend on apkA_x86(assuming x86 is not the main ABI). This is
the only way we may ensure that 'cmake --build' commands won't be running
simultanously.

Fixes: QTBUG-122838
Pick-to: 6.7
Change-Id: I6f48fae57047a29129836168c28e14cde4eaa958
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Alexey Edelev 2024-03-12 15:15:06 +01:00
parent a92c195dd4
commit 31a91d2e93

View File

@ -576,7 +576,7 @@ function(qt6_android_add_apk_target target)
"$<TARGET_FILE:${target}>"
"${androiddeployqt_output_path}/${target_file_copy_relative_path}"
)
if(has_depfile_support AND FALSE) # TODO: It breaks multi-abi builds. See QTBUG-122838
if(has_depfile_support)
set(deploy_android_deps_dir "${apk_final_dir}/${target}_deploy_android")
set(timestamp_file "${deploy_android_deps_dir}/timestamp")
set(dep_file "${deploy_android_deps_dir}/${target}.d")
@ -1183,6 +1183,91 @@ function(_qt_internal_collect_default_android_abis)
)
endfunction()
# Returns a path to the timestamp file for the specific step of the multi-ABI Android project
function(_qt_internal_get_android_abi_step_stampfile out project abi step)
get_target_property(build_dir ${project} _qt_android_build_directory)
get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi)
set(${out} "${build_dir}/$<CONFIG>/${project}_${step}_stamp" PARENT_SCOPE)
else()
set(${out} "${build_dir}/${project}_${step}_stamp" PARENT_SCOPE)
endif()
endfunction()
# Creates the multi-ABI Android projects and assigns the JOB_POOL to them if it's possible
function(_qt_internal_add_android_abi_project project abi)
add_custom_target(${project})
set(build_dir "${CMAKE_BINARY_DIR}/android_abi_builds/${abi}")
set_target_properties(${project} PROPERTIES
_qt_android_build_directory "${build_dir}"
)
file(MAKE_DIRECTORY "${build_dir}")
if(CMAKE_GENERATOR MATCHES "^Ninja")
set_property(GLOBAL APPEND PROPERTY JOB_POOLS _qt_android_${project}_pool=1)
endif()
endfunction()
# Adds the custom build step to the multi-ABI Android project
function(_qt_internal_add_android_abi_step project abi step)
cmake_parse_arguments(arg "" "" "COMMAND;DEPENDS" ${ARGV})
if(NOT arg_COMMAND)
message(FATAL_ERROR "COMMAND is not set for ${project} step ${step} Android ABI ${abi}.")
endif()
set(dep_stamps "")
foreach(dep ${arg_DEPENDS})
_qt_internal_get_android_abi_step_stampfile(stamp ${project} ${abi} ${dep})
list(APPEND dep_stamps "${stamp}")
endforeach()
get_target_property(build_dir ${project} _qt_android_build_directory)
if(CMAKE_GENERATOR MATCHES "^Ninja")
set(add_to_pool JOB_POOL _qt_android_${project}_pool)
else()
set(add_to_pool "")
endif()
_qt_internal_get_android_abi_step_stampfile(stamp ${project} ${abi} ${step})
add_custom_command(OUTPUT "${stamp}"
COMMAND ${arg_COMMAND}
COMMAND "${CMAKE_COMMAND}" -E touch "${stamp}"
${add_to_pool}
DEPENDS
${dep_stamps}
WORKING_DIRECTORY
"${build_dir}"
VERBATIM
)
add_custom_target("${project}_${step}" DEPENDS "${stamp}")
get_target_property(known_steps ${project} _qt_android_abi_steps)
if(NOT CMAKE_GENERATOR MATCHES "^Ninja")
if(NOT QT_NO_WARN_ANDROID_MULTI_ABI_GENERATOR)
get_property(is_warned GLOBAL PROPERTY _qt_internal_warn_android_multi_abi_generator)
if(NOT is_warned)
set_property(GLOBAL PROPERTY _qt_internal_warn_android_multi_abi_generator TRUE)
message(WARNING "Building Multi-ABI Qt projects with the '${CMAKE_GENERATOR}'"
" generator has limitations. All targets from non-main ABI will be built"
" unconditionally. Please use the 'Ninja' or 'Ninja Multi-config' generators"
" with ninja build instead. Set QT_NO_WARN_ANDROID_MULTI_ABI_GENERATOR to"
" 'TRUE' to suppress this warning."
)
endif()
endif()
if(known_steps)
list(GET known_steps 0 first)
add_dependencies(${first} ${project}_${step})
endif()
endif()
list(PREPEND known_steps ${project}_${step})
set_target_properties(${project} PROPERTIES _qt_android_abi_steps "${known_steps}")
endfunction()
# The function configures external projects for ABIs that target packages need to build with.
# Each target adds build step to the external project that is linked to the
# qt_internal_android_${abi}-${target}_build target in the primary ABI build tree.
@ -1292,7 +1377,6 @@ function(_qt_internal_configure_android_multiabi_target target)
set(previous_copy_apk_dependencies_target ${target})
# Create external projects for each android ABI except the main one.
list(REMOVE_ITEM android_abis "${CMAKE_ANDROID_ARCH_ABI}")
include(ExternalProject)
foreach(abi IN ITEMS ${android_abis})
if(NOT "${abi}" IN_LIST QT_DEFAULT_ANDROID_ABIS)
list(APPEND missing_qt_abi_toolchains ${abi})
@ -1300,16 +1384,17 @@ function(_qt_internal_configure_android_multiabi_target target)
continue()
endif()
set(android_abi_build_dir "${CMAKE_BINARY_DIR}/android_abi_builds/${abi}")
get_property(abi_external_projects GLOBAL
PROPERTY _qt_internal_abi_external_projects)
if(NOT abi_external_projects
OR NOT "qt_internal_android_${abi}" IN_LIST abi_external_projects)
_qt_internal_add_android_abi_project(qt_internal_android_${abi} ${abi})
get_target_property(android_abi_build_dir qt_internal_android_${abi}
_qt_android_build_directory)
_qt_internal_get_android_abi_toolchain_path(qt_abi_toolchain_path ${abi})
ExternalProject_Add("qt_internal_android_${abi}"
SOURCE_DIR "${CMAKE_SOURCE_DIR}"
BINARY_DIR "${android_abi_build_dir}"
CONFIGURE_COMMAND
_qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi} configure
COMMAND
"${CMAKE_COMMAND}"
"-G${CMAKE_GENERATOR}"
"-DCMAKE_TOOLCHAIN_FILE=${qt_abi_toolchain_path}"
@ -1321,44 +1406,36 @@ function(_qt_internal_configure_android_multiabi_target target)
"${user_cmake_args}"
"-B" "${android_abi_build_dir}"
"-S" "${CMAKE_SOURCE_DIR}"
EXCLUDE_FROM_ALL TRUE
BUILD_COMMAND "" # avoid top-level build of external project
)
set_property(GLOBAL APPEND PROPERTY
_qt_internal_abi_external_projects "qt_internal_android_${abi}")
endif()
ExternalProject_Add_Step("qt_internal_android_${abi}"
"${target}_build"
DEPENDEES configure
# TODO: Remove this when the step will depend on DEPFILE generated by
# androiddeployqt for the ${target}.
ALWAYS TRUE
EXCLUDE_FROM_MAIN TRUE
COMMAND "${CMAKE_COMMAND}"
"--build" "${android_abi_build_dir}"
"--config" "$<CONFIG>"
"--target" "${target}"
)
ExternalProject_Add_StepTargets("qt_internal_android_${abi}"
"${target}_build")
add_dependencies(${target} "qt_internal_android_${abi}-${target}_build")
ExternalProject_Add_Step("qt_internal_android_${abi}"
"${target}_copy_apk_dependencies"
DEPENDEES "${target}_build"
# TODO: Remove this when the step will depend on DEPFILE generated by
# androiddeployqt for the ${target}.
ALWAYS TRUE
EXCLUDE_FROM_MAIN TRUE
COMMAND "${CMAKE_COMMAND}"
"--build" "${android_abi_build_dir}"
"--config" "$<CONFIG>"
"--target" "qt_internal_${target}_copy_apk_dependencies"
get_target_property(android_abi_build_dir qt_internal_android_${abi}
_qt_android_build_directory)
_qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi} ${target}_build
DEPENDS
configure
COMMAND
"${CMAKE_COMMAND}"
--build "${android_abi_build_dir}"
--config $<CONFIG>
--target ${target}
)
add_dependencies(${target} "qt_internal_android_${abi}_${target}_build")
_qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi}
${target}_copy_apk_dependencies
DEPENDS
${target}_build
COMMAND
"${CMAKE_COMMAND}"
--build "${android_abi_build_dir}"
--config $<CONFIG>
--target qt_internal_${target}_copy_apk_dependencies
)
ExternalProject_Add_StepTargets("qt_internal_android_${abi}"
"${target}_copy_apk_dependencies")
set(external_project_copy_target
"qt_internal_android_${abi}-${target}_copy_apk_dependencies")
"qt_internal_android_${abi}_${target}_copy_apk_dependencies")
# Need to build dependency chain between the
# qt_internal_android_${abi}-${target}_copy_apk_dependencies targets for all ABI's, to