Change the external projects approach for multi-abi builds

Instead of generating external projects that build the project tree
for each target, this creates a single project for each ABI that have
the common for all targets configure steps. Each executable target
then adds additional build step to each ABI-specific external project,
that builds and copies dependencies to the "main" project build tree.

To resolve dependencies from the build tree, when building multi-abi
apk instead of scanning the build directories of external projects for
dependencies, it makes sense to run androiddeployqt for each
ABI-specific external project to copy all necessary libraries. This
is done by adding --copy-dependencies-only flag to androiddeployqt
that only copies the apk dependencies, but avoids creating apk and all
the essential steps. The ABI-specific external project now handles the
deploying of the build artifacts to the end-point apk deployment
directory and the "main" project assembles the apk using collected
artifacts. The ABI-specific external project uses the
qt_internal_${target}_copy_apk_dependencies target to run androiddeployqt
with the introduced --copy-dependencies-only flag.

TODO: Build steps that build and copy the ABI-specific apk
dependencies are non-skipable and will run each time top-level build
is triggered. This behavior should be fixed by adding dependencies to
the generated by androiddeployqt DEPFILES for each ABI in the
top-level build.

Task-number: QTBUG-88841
Tash-number: QTBUG-94714
Change-Id: Id442a9fbd589f58b70f4204c5215645056b379a2
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Alexey Edelev 2021-11-15 18:54:42 +01:00
parent 0062f5a208
commit 87db26bdfe
3 changed files with 87 additions and 35 deletions

View File

@ -92,13 +92,14 @@ function(qt6_android_generate_deployment_settings target)
set(abi_records "")
get_target_property(qt_android_abis ${target} _qt_android_abis)
if(qt_android_abis)
foreach(abi IN LISTS qt_android_abis)
_qt_internal_get_android_abi_path(qt_abi_path ${abi})
file(TO_CMAKE_PATH "${qt_abi_path}" qt_android_install_dir_native)
list(APPEND abi_records "\"${abi}\": \"${qt_android_install_dir_native}\"")
endforeach()
if(NOT qt_android_abis)
set(qt_android_abis "")
endif()
foreach(abi IN LISTS qt_android_abis)
_qt_internal_get_android_abi_path(qt_abi_path ${abi})
file(TO_CMAKE_PATH "${qt_abi_path}" qt_android_install_dir_native)
list(APPEND abi_records "\"${abi}\": \"${qt_android_install_dir_native}\"")
endforeach()
# Required to build unit tests in developer build
if(QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX)
@ -336,14 +337,8 @@ function(qt6_android_add_apk_target target)
set(dep_file_name "${target}.d")
set(apk_final_file_path "${apk_final_dir}/${apk_file_name}")
set(dep_file_path "${apk_final_dir}/${dep_file_name}")
# Temporary location of the library target file. If the library is built as an external project
# inside multi-abi build the QT_ANDROID_ABI_TARGET_PATH variable will point to the ABI related
# folder in the top-level build directory.
set(copy_target_path "${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}")
if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT AND QT_ANDROID_ABI_TARGET_PATH)
set(copy_target_path "${QT_ANDROID_ABI_TARGET_PATH}")
endif()
set(target_file_copy_relative_path
"libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>")
set(extra_deps "")
@ -362,7 +357,7 @@ function(qt6_android_add_apk_target target)
DEPENDS ${target} ${extra_deps}
COMMAND ${CMAKE_COMMAND}
-E copy_if_different $<TARGET_FILE:${target}>
"${copy_target_path}/$<TARGET_FILE_NAME:${target}>"
"${apk_final_dir}/$<TARGET_FILE_NAME:${target}>"
COMMENT "Copying ${target} binary to apk folder"
)
@ -394,7 +389,7 @@ function(qt6_android_add_apk_target target)
add_custom_command(OUTPUT "${apk_final_file_path}"
COMMAND ${CMAKE_COMMAND}
-E copy "$<TARGET_FILE:${target}>"
"${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
"${apk_final_dir}/${target_file_copy_relative_path}"
COMMAND "${deployment_tool}"
--input "${deployment_file}"
--output "${apk_final_dir}"
@ -436,6 +431,34 @@ function(qt6_android_add_apk_target target)
COMMENT "Creating AAB for ${target}"
)
if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT)
# When building per-ABI external projects we only need to copy ABI-specific libraries and
# resources to the "main" ABI android build folder.
if("${QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR}" STREQUAL "")
message(FATAL_ERROR "QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR is not set when building"
" ABI specific external project. This should not happen and might mean an issue"
" in Qt. Please report a bug with CMake traces attached.")
endif()
# Assume that external project mirrors build structure of the top-level ABI project and
# replace the build root when specifying the output directory of androiddeployqt.
file(RELATIVE_PATH androiddeployqt_output_path "${CMAKE_BINARY_DIR}" "${apk_final_dir}")
set(androiddeployqt_output_path
"${QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR}/${androiddeployqt_output_path}")
add_custom_target(qt_internal_${target}_copy_apk_dependencies
DEPENDS ${target} ${extra_deps}
COMMAND ${CMAKE_COMMAND}
-E copy_if_different $<TARGET_FILE:${target}>
"${androiddeployqt_output_path}/${target_file_copy_relative_path}"
COMMAND ${deployment_tool}
--input ${deployment_file}
--output ${androiddeployqt_output_path}
--copy-dependencies-only
${extra_args}
COMMENT "Resolving ${CMAKE_ANDROID_ARCH_ABI} dependencies for the ${target} APK"
)
endif()
set_property(GLOBAL APPEND PROPERTY _qt_apk_targets ${target})
_qt_internal_collect_target_apk_dependencies_defer(${target})
endfunction()

View File

@ -597,25 +597,42 @@ function(_qt_internal_create_executable target)
else()
set(config_arg "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif()
_qt_internal_get_android_abi_path(qt_abi_path ${abi})
set(qt_abi_toolchain_path
"${qt_abi_path}/lib/cmake/${QT_CMAKE_EXPORT_NAMESPACE}/qt.toolchain.cmake")
set(abi_copy_target_path "${CMAKE_CURRENT_BINARY_DIR}/android-build/libs/${abi}")
ExternalProject_Add("${target}_${abi}"
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${target}_${abi}"
CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${qt_abi_toolchain_path}"
"-DQT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT=ON"
"-DQT_ANDROID_ABI_TARGET_PATH=${abi_copy_target_path}"
"${config_arg}"
STEP_TARGETS build
EXCLUDE_FROM_ALL TRUE
BUILD_COMMAND "${CMAKE_COMMAND}"
"--build" "${CMAKE_CURRENT_BINARY_DIR}/${target}_${abi}"
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_get_android_abi_path(qt_abi_path ${abi})
set(qt_abi_toolchain_path
"${qt_abi_path}/lib/cmake/${QT_CMAKE_EXPORT_NAMESPACE}/qt.toolchain.cmake")
ExternalProject_Add("qt_internal_android_${abi}"
SOURCE_DIR "${CMAKE_SOURCE_DIR}"
BINARY_DIR "${android_abi_build_dir}"
CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${qt_abi_toolchain_path}"
"-DQT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT=ON"
"-DQT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR=${CMAKE_BINARY_DIR}"
"${config_arg}"
EXCLUDE_FROM_ALL TRUE
STETPS_TARGETS
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
COMMAND "${CMAKE_COMMAND}"
"--build" "${android_abi_build_dir}"
"--config" "$<CONFIG>"
"--target" "${target}_prepare_apk_dir"
"--target" "qt_internal_${target}_copy_apk_dependencies"
)
add_dependencies(${target} "${target}_${abi}-build")
ExternalProject_Add_StepTargets("qt_internal_android_${abi}"
"${target}_build")
add_dependencies(${target} "qt_internal_android_${abi}-${target}_build")
endforeach()
if(missing_qt_abi_toolchains)

View File

@ -135,6 +135,7 @@ struct Options
bool build;
bool auxMode;
bool noRccBundleCleanup = false;
bool copyDependenciesOnly = false;
QElapsedTimer timer;
// External tools
@ -563,6 +564,9 @@ Options parseOptions()
} else if (argument.compare(QLatin1String("--no-rcc-bundle-cleanup"),
Qt::CaseInsensitive) == 0) {
options.noRccBundleCleanup = true;
} else if (argument.compare(QLatin1String("--copy-dependencies-only"),
Qt::CaseInsensitive) == 0) {
options.copyDependenciesOnly = true;
}
}
@ -687,6 +691,10 @@ void printHelp()
" the resource bundle content, but it should not be used when deploying\n"
" a project, since it litters the 'assets' directory.\n"
"\n"
" --copy-dependencies-only: resolve application dependencies and stop\n"
" deploying process after all libraries and resources that the\n"
" application depends on have been copied.\n"
"\n"
" --help: Displays this information.\n",
qPrintable(QCoreApplication::arguments().at(0))
);
@ -2347,7 +2355,7 @@ bool copyQtFiles(Options *options)
if (options->verbose) {
switch (options->deploymentMechanism) {
case Options::Bundled:
fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies.size()));
fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies[options->currentArchitecture].size()));
break;
};
}
@ -3163,7 +3171,7 @@ int main(int argc, char *argv[])
options.setCurrentQtArchitecture(it.key(), it.value().qtInstallDirectory);
// All architectures have a copy of the gradle files but only one set needs to be copied.
if (!androidTemplatetCopied && options.build && !options.auxMode) {
if (!androidTemplatetCopied && options.build && !options.auxMode && !options.copyDependenciesOnly) {
cleanAndroidFiles(options);
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
@ -3218,6 +3226,10 @@ int main(int argc, char *argv[])
fprintf(stdout, "[TIMING] %lld ns: Bundled Qt libs\n", options.timer.nsecsElapsed());
}
if (options.copyDependenciesOnly) {
return 0;
}
if (!createRcc(options))
return CannotCreateRcc;