From 87db26bdfe56e9445c03623eb962f569b2570b92 Mon Sep 17 00:00:00 2001 From: Alexey Edelev Date: Mon, 15 Nov 2021 18:54:42 +0100 Subject: [PATCH] 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 --- src/corelib/Qt6AndroidMacros.cmake | 55 +++++++++++++++++++++--------- src/corelib/Qt6CoreMacros.cmake | 51 ++++++++++++++++++--------- src/tools/androiddeployqt/main.cpp | 16 +++++++-- 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 52a5deca8c..447acbfc60 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -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}/$") 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 $ - "${copy_target_path}/$" + "${apk_final_dir}/$" 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 "$" - "${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$" + "${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 $ + "${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() diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index b3687d7bfa..c4d0b35120 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -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" "$" - "--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) diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index 766af0ec31..4e54c41801 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -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;