summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexey Edelev <alexey.edelev@qt.io>2021-08-17 16:25:03 +0200
committerAlexey Edelev <alexey.edelev@qt.io>2021-09-09 21:49:37 +0200
commitd20f4ae706559fb7de8db9dd4845f7ce3936061a (patch)
treecd2729521d7aaf72be595de5be0be35f2473b7a5
parent2b8a6d026d2a9fed48847d9759a1b07ec34418a1 (diff)
Support deploying of libraries from a build tree when building android apk
If the project consists of an executable and multiple libraries that are linked to the executable, currently you need to specify them manually using QT_ANDROID_EXTRA_LIBS target property. This automates deploying of the libraries that are a part of the project build tree. _qt_internal_collect_target_apk_dependencies collects all the known non-imported shared libraries from the project build tree. When running androiddeployqt we specify extra library directories that point to the collected library locations in build tree, to help androiddeployqt resolve shared libraries that are build as a part of the project. The described procedure is running automatically if CMake version is greater than or equal to 3.18 is. For the CMake versions less than 3.18 users need to call a new public qt_finalize_project function at the end of project's top-level CMakeLists.txt Task-number: QTBUG-94714 Change-Id: I400ca4e49e940cfc25ae90d65372e79825bee55a Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io> Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake131
-rw-r--r--src/corelib/Qt6CoreMacros.cmake21
-rw-r--r--src/tools/androiddeployqt/main.cpp22
3 files changed, 166 insertions, 8 deletions
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index 3640624aa3..144a082c6f 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -19,6 +19,15 @@ endfunction()
# Generate the deployment settings json file for a cmake target.
function(qt6_android_generate_deployment_settings target)
+ # Avoid calling the function twice
+ get_target_property(is_called ${target} _qt_is_android_generate_deployment_settings_called)
+ if(is_called)
+ return()
+ endif()
+ set_target_properties(${target} PROPERTIES
+ _qt_is_android_generate_deployment_settings_called TRUE
+ )
+
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
if (NOT TARGET ${target})
message(SEND_ERROR "${target} is not a cmake target")
@@ -140,12 +149,12 @@ function(qt6_android_generate_deployment_settings target)
endif()
# package source dir
- get_target_property(android_package_source_dir ${target} QT_ANDROID_PACKAGE_SOURCE_DIR)
- if (android_package_source_dir)
- file(TO_CMAKE_PATH "${android_package_source_dir}" android_package_source_dir_native)
- string(APPEND file_contents
- " \"android-package-source-directory\": \"${android_package_source_dir_native}\",\n")
- endif()
+ set(android_package_source_dir_genex
+ "$<TARGET_PROPERTY:${target},QT_ANDROID_PACKAGE_SOURCE_DIR>")
+ string(APPEND file_contents
+ "$<$<BOOL:${android_package_source_dir_genex}>:"
+ " \"android-package-source-directory\": \"${android_package_source_dir_genex}\"\,\n"
+ ">")
# version code
get_target_property(android_version_code ${target} QT_ANDROID_VERSION_CODE)
@@ -235,13 +244,34 @@ function(qt6_android_generate_deployment_settings target)
foreach(prefix IN LISTS CMAKE_FIND_ROOT_PATH)
if (NOT "${prefix}" STREQUAL "${qt_android_install_dir_native}"
AND NOT "${prefix}" STREQUAL "${android_ndk_root_native}")
- list(APPEND extra_prefix_list \"${prefix}\")
+ list(APPEND extra_prefix_list "\"${prefix}\"")
endif()
endforeach()
string (REPLACE ";" "," extra_prefix_list "${extra_prefix_list}")
string(APPEND file_contents
" \"extraPrefixDirs\" : [ ${extra_prefix_list} ],\n")
+ # Extra library paths that could be used as a dependency lookup path by androiddeployqt.
+ #
+ # Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder
+ # when looking for dependencies.
+ set(extra_library_dirs_property_genex
+ "$<TARGET_PROPERTY:${target},_qt_android_extra_library_dirs>"
+ )
+ set(extra_library_dirs_add_quote_genex
+ "$<$<BOOL:${extra_library_dirs_property_genex}>:\">"
+ )
+ string(JOIN "" extra_library_dirs
+ "${extra_library_dirs_add_quote_genex}"
+ "$<JOIN:"
+ "$<GENEX_EVAL:${extra_library_dirs_property_genex}>,"
+ "\",\""
+ ">"
+ "${extra_library_dirs_add_quote_genex}"
+ )
+ string(APPEND file_contents
+ " \"extraLibraryDirs\" : [ ${extra_library_dirs} ],\n")
+
if(QT_FEATURE_zstd)
set(is_zstd_enabled "true")
else()
@@ -261,7 +291,7 @@ function(qt6_android_generate_deployment_settings target)
# content end
string(APPEND file_contents "}\n")
- file(WRITE ${deploy_file} ${file_contents})
+ file(GENERATE OUTPUT ${deploy_file} CONTENT ${file_contents})
set_target_properties(${target}
PROPERTIES
@@ -358,6 +388,8 @@ function(qt6_android_add_apk_target target)
COMMENT "Creating APK for ${target}"
)
endif()
+ set_property(GLOBAL APPEND PROPERTY _qt_apk_targets ${target})
+ _qt_internal_collect_target_apk_dependencies_defer(${target})
endfunction()
function(_qt_internal_create_global_apk_target)
@@ -371,6 +403,89 @@ function(_qt_internal_create_global_apk_target)
endif()
endfunction()
+# The function collects all known non-imported shared libraries that are created in the build tree.
+# It uses the CMake DEFER CALL feature if the CMAKE_VERSION is greater
+# than or equal to 3.18.
+# Note: Users that use cmake version less that 3.18 need to call qt_finalize_project
+# in the end of a project's top-level CMakeLists.txt.
+function(_qt_internal_collect_target_apk_dependencies_defer target)
+ # User opted-out the functionality
+ if(QT_NO_COLLECT_BUILD_TREE_APK_DEPS)
+ return()
+ endif()
+
+ get_property(is_called GLOBAL PROPERTY _qt_is_collect_target_apk_dependencies_defer_called)
+ if(is_called) # Already scheduled
+ return()
+ endif()
+ set_property(GLOBAL PROPERTY _qt_is_collect_target_apk_dependencies_defer_called TRUE)
+
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ cmake_language(EVAL CODE "cmake_language(DEFER DIRECTORY \"${CMAKE_SOURCE_DIR}\"
+ CALL _qt_internal_collect_target_apk_dependencies ${target})")
+ else()
+ # User don't want to see the warning
+ if(NOT QT_NO_WARN_BUILD_TREE_APK_DEPS)
+ message(WARNING "CMake version you use is less than 3.18. APK dependencies, that are a"
+ " part of the project tree, might not be collected correctly."
+ " Please call qt_finalize_project in the end of a project's top-level"
+ " CMakeLists.txt file to make sure that all the APK dependencies are"
+ " collected correctly."
+ " You can pass -DQT_NO_WARN_BUILD_TREE_APK_DEPS=ON when configuring the project"
+ " to silence the warning.")
+ endif()
+ endif()
+endfunction()
+
+# The function collects shared libraries from the build system tree, that might be dependencies for
+# the main apk targets.
+function(_qt_internal_collect_target_apk_dependencies target)
+ # User opted-out the functionality
+ if(QT_NO_COLLECT_BUILD_TREE_APK_DEPS)
+ return()
+ endif()
+
+ get_property(is_called GLOBAL PROPERTY _qt_is_collect_target_apk_dependencies_called)
+ if(is_called)
+ return()
+ endif()
+ set_property(GLOBAL PROPERTY _qt_is_collect_target_apk_dependencies_called TRUE)
+
+ get_property(apk_targets GLOBAL PROPERTY _qt_apk_targets)
+
+ _qt_internal_collect_buildsystem_shared_libraries(libs "${CMAKE_SOURCE_DIR}")
+
+ foreach(lib IN LISTS libs)
+ if(NOT lib IN_LIST apk_targets)
+ list(APPEND extra_prefix_dirs "$<TARGET_FILE_DIR:${lib}>")
+ endif()
+ endforeach()
+
+ set_target_properties(${target} PROPERTIES _qt_android_extra_library_dirs "${extra_prefix_dirs}")
+endfunction()
+
+# The function recursively goes through the project subfolders and collects targets that supposed to
+# be shared libraries of any kind.
+function(_qt_internal_collect_buildsystem_shared_libraries out_var subdir)
+ set(result "")
+ get_directory_property(buildsystem_targets DIRECTORY ${subdir} BUILDSYSTEM_TARGETS)
+ foreach(buildsystem_target IN LISTS buildsystem_targets)
+ if(buildsystem_target AND TARGET ${buildsystem_target})
+ get_target_property(target_type ${buildsystem_target} TYPE)
+ if(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY")
+ list(APPEND result ${buildsystem_target})
+ endif()
+ endif()
+ endforeach()
+
+ get_directory_property(subdirs DIRECTORY "${subdir}" SUBDIRECTORIES)
+ foreach(dir IN LISTS subdirs)
+ _qt_internal_collect_buildsystem_shared_libraries(result_inner "${dir}")
+ endforeach()
+ list(APPEND result ${result_inner})
+ set(${out_var} "${result}" PARENT_SCOPE)
+endfunction()
+
# This function allows deciding whether apks should be built as part of the ALL target at first
# add_executable call point, rather than when the 'apk' target is created as part of the
# find_package(Core) call.
diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake
index acfd196417..d5cffee105 100644
--- a/src/corelib/Qt6CoreMacros.cmake
+++ b/src/corelib/Qt6CoreMacros.cmake
@@ -2238,7 +2238,28 @@ function(qt6_disable_unicode_defines target)
set_target_properties(${target} PROPERTIES QT_NO_UNICODE_DEFINES TRUE)
endfunction()
+# Finalizer function for the top-level user projects.
+#
+# This function is currently in Technical Preview.
+# Its signature and behavior might change.
+function(qt6_finalize_project)
+ if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
+ message("qt6_finalize_project is called not in the top-level CMakeLists.txt.")
+ endif()
+ if(ANDROID)
+ _qt_internal_collect_target_apk_dependencies()
+ endif()
+endfunction()
+
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_finalize_project)
+ if(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
+ qt6_finalize_project()
+ else()
+ message(FATAL_ERROR "qt_finalize_project() is only available in Qt 6.")
+ endif()
+ endfunction()
+
function(qt_disable_unicode_defines)
qt6_disable_unicode_defines(${ARGV})
endfunction()
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index bc280dc107..75db7bab25 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -166,6 +166,9 @@ struct Options
// Build paths
QString qtInstallDirectory;
std::vector<QString> extraPrefixDirs;
+ // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder
+ // when looking for dependencies.
+ std::vector<QString> extraLibraryDirs;
QString androidSourceDirectory;
QString outputDirectory;
QString inputFileName;
@@ -922,6 +925,14 @@ bool readInputFile(Options *options)
}
{
+ const auto extraLibraryDirs = jsonObject.value(QLatin1String("extraLibraryDirs")).toArray();
+ options->extraLibraryDirs.reserve(extraLibraryDirs.size());
+ for (const QJsonValue path : extraLibraryDirs) {
+ options->extraLibraryDirs.push_back(path.toString());
+ }
+ }
+
+ {
const QJsonValue androidSourcesDirectory = jsonObject.value(QLatin1String("android-package-source-directory"));
if (!androidSourcesDirectory.isUndefined())
options->androidSourceDirectory = androidSourcesDirectory.toString();
@@ -1607,6 +1618,17 @@ bool updateAndroidFiles(Options &options)
static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
{
+ // Use extraLibraryDirs as the extra library lookup folder if it is expected to find a file in
+ // any $prefix/lib folder.
+ // Library directories from a build tree(extraLibraryDirs) have the higher priority.
+ if (relativeFileName.startsWith(QLatin1String("lib/"))) {
+ for (const auto &dir : options->extraLibraryDirs) {
+ const QString path = dir + QLatin1Char('/') + relativeFileName.mid(sizeof("lib/") - 1);
+ if (QFile::exists(path))
+ return path;
+ }
+ }
+
for (const auto &prefix : options->extraPrefixDirs) {
const QString path = prefix + QLatin1Char('/') + relativeFileName;
if (QFile::exists(path))