Android: Add facilities to build AAR packages

This adds new aar target, as we do with apk targets, and an option
to androiddeployqt, --build-aar, which builds an AAR instead of APK.

AndroidManifest.xml of an AAR does not include application or activity
nodes, so we add an AAR specific manifest. Also the plugin type in
build.gradle will be com.android.library when choosing to build an
AAR.

Task-number: QTBUG-116955
Task-number: QTBUG-65567
Change-Id: Id33ac236f44038a8411ebfecdcc4e404c976e277
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Soheil Armin 2024-03-27 01:26:22 +02:00 committed by Tinja Paavoseppä
parent e59f310061
commit 79f3fe7040
5 changed files with 124 additions and 2 deletions

View File

@ -8,5 +8,6 @@ if (ANDROID)
add_subdirectory(jar)
add_subdirectory(java)
add_subdirectory(templates)
add_subdirectory(templates_aar)
endif()

View File

@ -0,0 +1,9 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.qtproject.example"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:versionName="-- %%INSERT_VERSION_NAME%% --">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
</manifest>

View File

@ -0,0 +1,24 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Android aar specific template files
set(templates_aar_files
"${CMAKE_CURRENT_SOURCE_DIR}/AndroidManifest.xml")
add_custom_target(Qt${QtBase_VERSION_MAJOR}AndroidAarTemplates
SOURCES
${templates_aar_files}
)
qt_path_join(destination ${QT_INSTALL_DIR} ${INSTALL_DATADIR} "src/android/templates_aar")
qt_copy_or_install(FILES ${templates_aar_files}
DESTINATION "${destination}")
if(NOT QT_WILL_INSTALL)
qt_internal_copy_at_build_time(TARGET Qt${QtBase_VERSION_MAJOR}AndroidAarTemplates
FILES ${templates_aar_files}
DESTINATION ${destination}
)
endif()

View File

@ -381,6 +381,9 @@ function(qt6_android_add_apk_target target)
if(TARGET aab)
add_dependencies(aab ${target}_make_aab)
endif()
if(TARGET aar)
add_dependencies(aar ${target}_make_aar)
endif()
if(TARGET apk)
add_dependencies(apk ${target}_make_apk)
_qt_internal_create_global_apk_all_target_if_needed()
@ -418,8 +421,10 @@ function(qt6_android_add_apk_target target)
endif()
set(apk_file_name "${target}.apk")
set(aar_file_name "${target}.aar")
set(dep_file_name "${target}.d")
set(apk_final_file_path "${apk_final_dir}/${apk_file_name}")
set(aar_final_file_path "${apk_final_dir}/${aar_file_name}")
set(dep_file_path "${apk_final_dir}/${dep_file_name}")
set(target_file_copy_relative_path
"libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>")
@ -523,10 +528,33 @@ function(qt6_android_add_apk_target target)
VERBATIM
${uses_terminal}
)
# Add custom command that creates the aar and triggers rebuild if files listed in
# ${dep_file_path} are changed.
add_custom_command(OUTPUT "${aar_final_file_path}"
COMMAND ${CMAKE_COMMAND}
-E copy "$<TARGET_FILE:${target}>"
"${apk_final_dir}/${target_file_copy_relative_path}"
COMMAND "${deployment_tool}"
--input "${deployment_file}"
--output "${apk_final_dir}"
--apk "${aar_final_file_path}"
--depfile "${dep_file_path}"
--builddir "${relative_to_dir}"
--build-aar
${extra_args}
COMMENT "Creating AAR for ${target}"
DEPENDS "${target}" "${deployment_file}" ${extra_deps}
DEPFILE "${dep_file_path}"
VERBATIM
${uses_terminal}
)
cmake_policy(POP)
# Create a ${target}_make_apk target to trigger the apk build.
add_custom_target(${target}_make_apk DEPENDS "${apk_final_file_path}")
# Create a ${target}_make_aar target to trigger the aar build.
add_custom_target(${target}_make_aar DEPENDS "${aar_final_file_path}")
else()
add_custom_target(${target}_make_apk
DEPENDS ${target}_prepare_apk_dir
@ -540,6 +568,19 @@ function(qt6_android_add_apk_target target)
VERBATIM
${uses_terminal}
)
add_custom_target(${target}_make_aar
DEPENDS ${target}_prepare_apk_dir
COMMAND ${deployment_tool}
--input ${deployment_file}
--output ${apk_final_dir}
--apk ${aar_final_file_path}
--build-aar
${extra_args}
COMMENT "Creating AAR for ${target}"
VERBATIM
${uses_terminal}
)
endif()
# Add target triggering AAB creation. Since the _make_aab target is not added to the ALL
@ -638,6 +679,11 @@ function(_qt_internal_create_global_android_targets)
# It will trigger building all the apk build targets that are added as part of the project.
# Allow opting out.
_qt_internal_create_global_android_targets_impl(aab)
# Create a top-level "aar" target for convenience, so that users can call 'ninja aar'.
# It will trigger building all the aar build targets that are added as part of the project.
# Allow opting out.
_qt_internal_create_global_android_targets_impl(aar)
endfunction()
# The function collects all known non-imported shared libraries that are created in the build tree.

View File

@ -105,6 +105,7 @@ struct Options
, installApk(false)
, uninstallApk(false)
, qmlImportScannerBinaryPath()
, buildAar(false)
{}
enum DeploymentMechanism
@ -240,6 +241,7 @@ struct Options
// Override qml import scanner path
QString qmlImportScannerBinaryPath;
bool qmlSkipImportScanning = false;
bool buildAar;
};
static const QHash<QByteArray, QByteArray> elfArchitectures = {
@ -526,6 +528,8 @@ Options parseOptions()
options.protectedAuthenticationPath = true;
} else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
options.auxMode = true;
} else if (argument.compare("--build-aar"_L1, Qt::CaseInsensitive) == 0) {
options.buildAar = true;
} else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
} else if (argument.compare("--no-rcc-bundle-cleanup"_L1,
@ -537,6 +541,23 @@ Options parseOptions()
}
}
if (options.buildAar) {
if (options.installApk || options.uninstallApk) {
fprintf(stderr, "Warning: Skipping %s, AAR packages are not installable.\n",
options.uninstallApk ? "--reinstall" : "--install");
options.installApk = false;
options.uninstallApk = false;
}
if (options.buildAAB) {
fprintf(stderr, "Warning: Skipping -aab as --build-aar is present.\n");
options.buildAAB = false;
}
if (!options.keyStore.isEmpty()) {
fprintf(stderr, "Warning: Skipping --sign, signing AAR packages is not supported.\n");
options.keyStore.clear();
}
}
if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
options.helpRequested = true;
@ -644,6 +665,9 @@ Optional arguments:
--apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
--build-aar: Build an AAR package. This option skips --aab, --install,
--reinstall, and --sign options if they are provided.
--qml-importscanner-binary <path/to/qmlimportscanner>: Override the
default qmlimportscanner binary path. By default the
qmlimportscanner binary is located using the Qt directory
@ -1434,6 +1458,9 @@ bool copyAndroidTemplate(const Options &options)
if (!copyAndroidTemplate(options, "/src/android/templates"_L1))
return false;
if (options.buildAar)
return copyAndroidTemplate(options, "/src/android/templates_aar"_L1);
return true;
}
@ -2850,7 +2877,9 @@ bool buildAndroidProject(const Options &options)
abiList.append(it.key());
}
gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
gradleProperties["qtGradlePluginType"] = "com.android.application";
gradleProperties["qtGradlePluginType"] = options.buildAar
? "com.android.library"
: "com.android.application";
if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
return false;
@ -2938,6 +2967,7 @@ bool uninstallApk(const Options &options)
enum PackageType {
AAB,
AAR,
UnsignedAPK,
SignedAPK
};
@ -2945,7 +2975,14 @@ enum PackageType {
QString packagePath(const Options &options, PackageType pt)
{
QString path(options.outputDirectory);
path += "/build/outputs/%1/"_L1.arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle"));
// The package type is always AAR if option.buildAar has been set
if (options.buildAar)
pt = AAR;
static QHash<PackageType, QString> packageTypeToPath{ { AAB, QStringLiteral("bundle") },
{ AAR, QStringLiteral("aar") },
{ UnsignedAPK, QStringLiteral("apk") },
{ SignedAPK, QStringLiteral("apk") } };
path += "/build/outputs/%1/"_L1.arg(packageTypeToPath[pt]);
QString buildType(options.releasePackage ? "release/"_L1 : "debug/"_L1);
if (QDir(path + buildType).exists())
path += buildType;
@ -2956,6 +2993,9 @@ QString packagePath(const Options &options, PackageType pt)
if (pt == UnsignedAPK)
path += "un"_L1;
path += "signed.apk"_L1;
} else if (pt == AAR){
path.chop(1);
path += ".aar"_L1;
} else {
path.chop(1);
path += ".aab"_L1;
@ -2966,6 +3006,8 @@ QString packagePath(const Options &options, PackageType pt)
if (pt == SignedAPK)
path += "-signed"_L1;
path += ".apk"_L1;
} else if (pt == AAR){
path += ".aar"_L1;
} else {
path += ".aab"_L1;
}