summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexey Edelev <alexey.edelev@qt.io>2025-07-01 18:34:01 +0200
committerAlexey Edelev <alexey.edelev@qt.io>2025-07-24 17:33:35 +0200
commite34914d5bfcecac4c2e242d4653391a62d7fbc81 (patch)
tree5ea2f0fc8ed044d17b76601c28fddb4043c03daa
parentb8f6af3b43890156cb200d6ad4a15e8e3c9ef1d5 (diff)
Add signing support for modern android bundles
Use gradle based signing mechanism for the modern Android bundles. From the interface perspective signing remains unchanged: QT_ANDROID_SIGN_<AAB|APK> flags control if the package should be signed. The credentials are taken from the QT_ANDROID_KEYSTORE_* environment variables. Signing is done in separate CMake script to avoid storing passwords inside build scripts. Scripts reads passwords from the environment when the respective signing rule is running. Change-Id: Id1097b2b6d011a63c58e5a441c5360a1a5d97e8f Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io> Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
-rw-r--r--cmake/QtAndroidSignPackage.cmake98
-rw-r--r--cmake/QtBuildHelpers.cmake1
-rw-r--r--src/corelib/Qt6AndroidGradleHelpers.cmake85
-rw-r--r--tests/auto/cmake/CMakeLists.txt35
-rw-r--r--tests/auto/cmake/test_android_signing/CMakeLists.txt16
5 files changed, 207 insertions, 28 deletions
diff --git a/cmake/QtAndroidSignPackage.cmake b/cmake/QtAndroidSignPackage.cmake
new file mode 100644
index 00000000000..c4afa7dadcf
--- /dev/null
+++ b/cmake/QtAndroidSignPackage.cmake
@@ -0,0 +1,98 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# The script allows signing of Android packages using either jarsigner or
+# apksigner program.
+#
+# Usage:
+# cmake -DPROGRAM=/path/to/signer [-DZIPALIGN_PATH=/path/to/zipalign]
+# -DUNSIGNED_PACKAGE=path/to/input -DSIGNED_PACKAGE=path/to/output
+# -P QtAndroidSignPackage.cmake
+
+if(NOT DEFINED PROGRAM)
+ message(FATAL_ERROR "PROGRAM variable is not defined. Please"
+ " specify the path to the apksigner or jarsigner program.")
+endif()
+
+if(NOT DEFINED UNSIGNED_PACKAGE)
+ message(FATAL_ERROR "UNSIGNED_PACKAGE variable is not defined. Please"
+ " specify the path to the input file.")
+endif()
+
+if(NOT DEFINED SIGNED_PACKAGE)
+ message(FATAL_ERROR "SIGNED_PACKAGE variable is not defined. Please"
+ " specify the path to the output file.")
+endif()
+
+get_filename_component(program_name "${PROGRAM}" NAME_WE)
+get_filename_component(type "${UNSIGNED_PACKAGE}" LAST_EXT)
+if(type STREQUAL ".aab")
+ if(NOT program_name STREQUAL "jarsigner")
+ message(WARNING "Unexpected signer program for .aab files: ${program_name}. "
+ "Expected 'jarsigner'.")
+ endif()
+
+ set(digestalg "SHA-256")
+ set(sigalg "SHA256withRSA")
+
+ if(DEFINED ENV{QT_ANDROID_KEYSTORE_KEY_PASS})
+ set(keypass_arg -keypass "$ENV{QT_ANDROID_KEYSTORE_KEY_PASS}")
+ else()
+ set(keypass_arg "")
+ endif()
+
+ if(DEFINED ENV{QT_ANDROID_KEYSTORE_STORE_PASS})
+ set(storepass_arg -storepass "$ENV{QT_ANDROID_KEYSTORE_STORE_PASS}")
+ else()
+ set(storepass_arg "")
+ endif()
+
+ execute_process(
+ COMMAND ${PROGRAM}
+ -sigalg ${sigalg} -digestalg ${digestalg}
+ -keystore "$ENV{QT_ANDROID_KEYSTORE_PATH}"
+ ${storepass_arg}
+ ${keypass_arg}
+ -signedjar "${SIGNED_PACKAGE}"
+ "${UNSIGNED_PACKAGE}"
+ "$ENV{QT_ANDROID_KEYSTORE_ALIAS}"
+ RESULT_VARIABLE sign_result
+ OUTPUT_VARIABLE sign_output
+ ERROR_VARIABLE sign_error
+ )
+
+ if(NOT sign_result EQUAL 0)
+ message(FATAL_ERROR "Unable to sign ${UNSIGNED_PACKAGE}: ${sign_output} ${sign_error}")
+ endif()
+elseif(type STREQUAL ".apk")
+ if(NOT DEFINED ZIPALIGN_PATH)
+ message(FATAL_ERROR "ZIPALIGN_PATH variable is not defined. Please"
+ " specify the path to the zipalign program.")
+ endif()
+
+ if(NOT program_name STREQUAL "apksigner")
+ message(WARNING "Unexpected signer program for .apk files: ${program_name}. "
+ "Expected 'apksigner'.")
+ endif()
+
+ execute_process(
+ COMMAND "${ZIPALIGN_PATH}" -c 4 "${UNSIGNED_PACKAGE}")
+
+ execute_process(
+ COMMAND "${PROGRAM}" sign
+ --ks "$ENV{QT_ANDROID_KEYSTORE_PATH}"
+ --ks-pass "pass:$ENV{QT_ANDROID_KEYSTORE_STORE_PASS}"
+ --key-pass "pass:$ENV{QT_ANDROID_KEYSTORE_KEY_PASS}"
+ --ks-key-alias "$ENV{QT_ANDROID_KEYSTORE_ALIAS}"
+ --out "${SIGNED_PACKAGE}" "${UNSIGNED_PACKAGE}"
+ RESULT_VARIABLE sign_result
+ OUTPUT_VARIABLE sign_output
+ ERROR_VARIABLE sign_error
+ )
+
+ if(NOT sign_result EQUAL 0)
+ message(FATAL_ERROR "Unable to sign ${UNSIGNED_PACKAGE}: ${sign_output} ${sign_error}")
+ endif()
+else()
+ message(FATAL_ERROR "Unsupported file type: ${type}. Only .apk and .aab files are supported.")
+endif()
diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake
index df0aa33cda2..b6132ddae7e 100644
--- a/cmake/QtBuildHelpers.cmake
+++ b/cmake/QtBuildHelpers.cmake
@@ -320,6 +320,7 @@ endfunction()
# The files are expected to exist under the qtbase/cmake sub-directory.
function(qt_internal_get_qt_build_public_files_to_install out_var)
set(${out_var}
+ QtAndroidSignPackage.cmake
QtCopyFileIfDifferent.cmake
QtInitProject.cmake
QtPublicCMakeEarlyPolicyHelpers.cmake
diff --git a/src/corelib/Qt6AndroidGradleHelpers.cmake b/src/corelib/Qt6AndroidGradleHelpers.cmake
index 933ecce2950..f71bac4e08a 100644
--- a/src/corelib/Qt6AndroidGradleHelpers.cmake
+++ b/src/corelib/Qt6AndroidGradleHelpers.cmake
@@ -263,6 +263,21 @@ function(_qt_internal_android_prepare_gradle_build target)
_qt_internal_create_global_apk_all_target_if_needed()
endfunction()
+# Returns the path to the output package file for the target.
+function(_qt_internal_android_get_output_package_name out_var target)
+ _qt_internal_android_package_path(package_build_dir ${target} ${type})
+ _qt_internal_android_get_deployment_type_option(deployment_type_suffix "release" "debug")
+ if(NOT deployment_type_suffix STREQUAL "release" AND type STREQUAL "apk")
+ set(extra_suffix "-unsigned")
+ else()
+ set(extra_suffix "")
+ endif()
+
+ set(output_dir "${package_build_dir}/${deployment_type_suffix}")
+ set(${out_var} "${output_dir}/app-${deployment_type_suffix}${extra_suffix}.${type}"
+ PARENT_SCOPE)
+endfunction()
+
# Adds the modern gradle build targets.
# These targets use the settings.gradle based build directory structure.
function(_qt_internal_android_add_gradle_build target type)
@@ -280,11 +295,7 @@ function(_qt_internal_android_add_gradle_build target type)
set(package_file_path "${android_build_dir}/${target}.${type}")
- _qt_internal_android_package_path(package_build_dir ${target} ${type})
- _qt_internal_android_get_deployment_type_option(deployment_type_suffix
- "release" "debug")
- set(package_build_file_path
- "${package_build_dir}/${deployment_type_suffix}/app-${deployment_type_suffix}.${type}")
+ _qt_internal_android_get_output_package_name(package_build_file_path ${target} ${type})
set(extra_deps "")
if(TARGET ${target}_copy_feature_names)
@@ -314,7 +325,69 @@ function(_qt_internal_android_add_gradle_build target type)
VERBATIM
)
- add_custom_target(${target}_make_${type} DEPENDS "${package_file_path}")
+ _qt_internal_android_sign_package(signed_package ${target} ${type})
+
+ add_custom_target(${target}_make_${type} DEPENDS "${package_file_path}" "${signed_package}")
+endfunction()
+
+function(_qt_internal_android_sign_package out_file target type)
+ string(TOUPPER "${type}" type_upper)
+ if(NOT QT_ANDROID_SIGN_${type_upper})
+ set(${out_file} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(type STREQUAL "aab")
+ find_program(jarsigner NAMES jarsigner)
+ if(NOT jarsigner)
+ message(FATAL_ERROR "jarsigner is not found. Please install"
+ " a JDK version to sign '${target}'.")
+ endif()
+
+ set(program ${jarsigner})
+ set(extra_args "")
+ elseif(type STREQUAL "apk")
+ _qt_internal_android_get_target_sdk_build_tools_revision(build_tools_version ${target})
+ if(CMAKE_HOST_WIN32)
+ set(suffix ".bat")
+ else()
+ set(suffix "")
+ endif()
+ set(build_tools_base_path "${ANDROID_SDK_ROOT}/build-tools/${build_tools_version}")
+ set(program "${build_tools_base_path}/apksigner${suffix}")
+ set(extra_args "-DZIPALIGN_PATH=${build_tools_base_path}/build-tools/zipalign${suffix}")
+ endif()
+
+ _qt_internal_android_get_output_package_name(package_build_file_path ${target} ${type})
+
+ _qt_internal_android_package_path(package_build_dir ${target} ${type})
+ _qt_internal_android_get_deployment_type_option(deployment_type_suffix
+ "release" "debug")
+ set(base_output_path "${package_build_dir}/${deployment_type_suffix}")
+ set(package_build_file_path_signed
+ "${base_output_path}/app-${deployment_type_suffix}-signed.${type}")
+
+ set(package_file_path "${android_build_dir}/${target}-signed.${type}")
+
+ set(sign_package_script "${_qt_6_config_cmake_dir}/QtAndroidSignPackage.cmake")
+ add_custom_command(OUTPUT "${package_file_path}"
+ BYPRODUCTS "${package_build_file_path_signed}"
+ COMMAND
+ ${CMAKE_COMMAND} "-DPROGRAM=${program}" "-DUNSIGNED_PACKAGE=${package_build_file_path}"
+ "-DSIGNED_PACKAGE=${package_build_file_path_signed}" ${extra_args}
+ -P "${sign_package_script}"
+ COMMAND
+ ${CMAKE_COMMAND} -E copy_if_different
+ "${package_build_file_path_signed}" "${package_file_path}"
+ DEPENDS
+ ${package_build_file_path}
+ ${sign_package_script}
+ WORKING_DIRECTORY
+ "${android_build_dir}"
+ VERBATIM
+ )
+
+ set(${out_file} "${package_file_path}" PARENT_SCOPE)
endfunction()
# Returns the path to the android executable package either apk or aab.
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt
index a228cb1a5a0..056bd6acfdf 100644
--- a/tests/auto/cmake/CMakeLists.txt
+++ b/tests/auto/cmake/CMakeLists.txt
@@ -147,25 +147,22 @@ if(ANDROID)
"QT_ANDROID_KEYSTORE_KEY_PASS=qttest"
)
- # Signing unimplemented for the modern Android bundles
- if(NOT QT_USE_ANDROID_MODERN_BUNDLE)
- foreach(package_type aab apk)
- string(TOUPPER "${package_type}" package_type_upper)
- set(test_name "test_android_signing_${package_type}")
- _qt_internal_test_expect_pass(test_android_signing
- TESTNAME ${test_name}
- BUILD_OPTIONS ${common_android_vars} -DQT_ANDROID_SIGN_${package_type_upper}=ON
- BUILD_DIR ${test_name}
- BUILD_TARGET test_android_signing_make_${package_type}
- BINARY ${CMAKE_CTEST_COMMAND}
- BINARY_ARGS -V
- )
- set_property(TEST ${test_name} APPEND PROPERTY
- ENVIRONMENT ${signing_test_common_environment}
- "QT_ANDROID_KEYSTORE_PATH=${CMAKE_CURRENT_BINARY_DIR}/${test_name}/qttest.jks"
- )
- endforeach()
- endif()
+ foreach(package_type aab apk)
+ string(TOUPPER "${package_type}" package_type_upper)
+ set(test_name "test_android_signing_${package_type}")
+ _qt_internal_test_expect_pass(test_android_signing
+ TESTNAME ${test_name}
+ BUILD_OPTIONS ${common_android_vars} -DQT_ANDROID_SIGN_${package_type_upper}=ON
+ BUILD_DIR ${test_name}
+ BUILD_TARGET test_android_signing_make_${package_type}
+ BINARY ${CMAKE_CTEST_COMMAND}
+ BINARY_ARGS -V
+ )
+ set_property(TEST ${test_name} APPEND PROPERTY
+ ENVIRONMENT ${signing_test_common_environment}
+ "QT_ANDROID_KEYSTORE_PATH=${CMAKE_CURRENT_BINARY_DIR}/${test_name}/qttest.jks"
+ )
+ endforeach()
# Test only multi-abi specific functionality when QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS is
# ON.
diff --git a/tests/auto/cmake/test_android_signing/CMakeLists.txt b/tests/auto/cmake/test_android_signing/CMakeLists.txt
index 50bdf85041b..4df64873360 100644
--- a/tests/auto/cmake/test_android_signing/CMakeLists.txt
+++ b/tests/auto/cmake/test_android_signing/CMakeLists.txt
@@ -45,13 +45,23 @@ target_link_libraries(test_android_signing PRIVATE
_qt_internal_android_get_target_deployment_dir(android_build test_android_signing)
-get_filename_component(build_dir_name ${android_build} NAME)
+if(QT_USE_ANDROID_MODERN_BUNDLE)
+ set(output_package_basename "app-release-signed")
+else()
+ get_filename_component(build_dir_name ${android_build} NAME)
+ if(QT_ANDROID_SIGN_AAB)
+ set(output_package_basename "${build_dir_name}-release")
+ else()
+ set(output_package_basename "${build_dir_name}-release-signed")
+ endif()
+endif()
+
if(QT_ANDROID_SIGN_AAB)
set(expected_output
- "${android_build}/build/outputs/bundle/release/${build_dir_name}-release.aab")
+ "${android_build}/build/outputs/bundle/release/${output_package_basename}.aab")
else()
set(expected_output
- "${android_build}/build/outputs/apk/release/${build_dir_name}-release-signed.apk")
+ "${android_build}/build/outputs/apk/release/${output_package_basename}.apk")
endif()
add_test(NAME test_android_signing_verify_signature