Introduce qt_add_android_permission CMake function
qt_add_android_permission function can be used to set Android permissions on target executable. This allows setting new permissions, or overriding permissions set by Qt modules, without needing to supply a manual application AndroidManifest.xml. The change consists of: - New public CMake function for setting the permissions on the target + documentation - Writing these application permissions into the deployment settings json file - Reading and handling these permissions at androiddeployqt side - Moving some pre-existing permission functionality from QtAndroidHelpers.cmake to Qt6AndroidMacros.cmake so that they can be reused also in the context of application CMakeLists.txt processing - Documentation update for Android permission handling In future this same mechanism can be extended for Android features. [ChangeLog][CMake] Added qt_add_android_permission function for setting Android permissions from application CMake Fixes: QTBUG-128280 Change-Id: Ia22951fb435598be00b5da5eae11b9f35f704795 Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io> Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
parent
3af20bd8eb
commit
c76c888556
@ -89,40 +89,7 @@ macro(qt_internal_setup_android_target_properties)
|
||||
endmacro()
|
||||
|
||||
function(qt_internal_add_android_permission target)
|
||||
cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN})
|
||||
|
||||
if(NOT target)
|
||||
message(FATAL_ERROR "Target for adding Android permission cannot be empty (${arg_NAME})")
|
||||
endif()
|
||||
|
||||
if(NOT arg_NAME)
|
||||
message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})")
|
||||
endif()
|
||||
|
||||
set(permission_entry "${arg_NAME}")
|
||||
|
||||
if(arg_ATTRIBUTES)
|
||||
# Permission with additional attributes
|
||||
list(LENGTH arg_ATTRIBUTES attributes_len)
|
||||
math(EXPR attributes_modulus "${attributes_len} % 2")
|
||||
if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0))
|
||||
message(FATAL_ERROR "Android permission attributes must be name-value pairs (${arg_NAME})")
|
||||
endif()
|
||||
# Combine name-value pairs
|
||||
set(index 0)
|
||||
set(attributes "")
|
||||
while(index LESS attributes_len)
|
||||
list(GET arg_ATTRIBUTES ${index} name)
|
||||
math(EXPR index "${index} + 1")
|
||||
list(GET arg_ATTRIBUTES ${index} value)
|
||||
string(APPEND attributes " android:${name}=\"${value}\"")
|
||||
math(EXPR index "${index} + 1")
|
||||
endwhile()
|
||||
set(permission_entry "${permission_entry}\;${attributes}")
|
||||
endif()
|
||||
|
||||
# Append the permission to the target's property
|
||||
set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}")
|
||||
_qt_internal_add_android_permission(${ARGV})
|
||||
endfunction()
|
||||
|
||||
|
||||
@ -233,9 +200,9 @@ function(qt_internal_android_dependencies_content target file_content_out)
|
||||
elseif(permission_len EQUAL 2)
|
||||
list(GET permission 0 name)
|
||||
list(GET permission 1 extras)
|
||||
string(APPEND file_contents "<permission name=\"${name}\" extras=\'${extras}\'/>\n")
|
||||
string(APPEND file_contents "<permission name=\"${name}\" extras=\"${extras}\"/>\n")
|
||||
else()
|
||||
message(FATAL_ERROR "Unexpected permission: " "${permission}" "${permission_len}")
|
||||
message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
@ -258,10 +258,14 @@ Since Qt 6.9, it is possible to override the default permissions set
|
||||
by Qt modules. This is useful if you need to define the same permissions
|
||||
as used by a Qt module, but with additional or different attributes.
|
||||
|
||||
To achieve this, you can manually define these permissions in the Android
|
||||
manifest file, along with the \c {<!-- %%INSERT_PERMISSIONS -->} placeholder.
|
||||
Manually defined permissions take precedence over the same permissions added
|
||||
by Qt modules, avoiding duplication.
|
||||
There are two ways to achieve this. First way is to use
|
||||
\l {qt_add_android_permission} CMake function in the application's
|
||||
\c {CMakeLists.txt}. Permissions defined this way take precedence over
|
||||
the same permissions defined by Qt modules, avoiding duplication.
|
||||
|
||||
Second way is to manually define these permissions in the Android
|
||||
manifest file. Permissions defined this way take precedence over permissions
|
||||
set by Qt modules, or set with \l {qt_add_android_permission}.
|
||||
|
||||
\section2 Style Extraction
|
||||
|
||||
|
@ -50,6 +50,43 @@ function(_qt_internal_add_tool_to_android_deployment_settings out_var tool json_
|
||||
set(${out_var} "${${out_var}}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Generates a JSON array of permissions that the 'target' may have,
|
||||
# returns an empty JSON array if no permissions were found.
|
||||
function(_qt_internal_generate_android_permissions_json out_result target)
|
||||
|
||||
set(${out_result} "[]" PARENT_SCOPE)
|
||||
|
||||
if(NOT TARGET ${target})
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_target_property(permissions ${target} QT_ANDROID_PERMISSIONS)
|
||||
if(NOT permissions)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(result "[")
|
||||
set(json_objects "")
|
||||
foreach(permission IN LISTS permissions)
|
||||
# Check if the permission has also extra attributes in addition to the permission name
|
||||
list(LENGTH permission permission_len)
|
||||
if(permission_len EQUAL 1)
|
||||
list(APPEND json_objects "{ \"name\": \"${permission}\" }")
|
||||
elseif(permission_len EQUAL 2)
|
||||
list(GET permission 0 name)
|
||||
list(GET permission 1 extras)
|
||||
list(APPEND json_objects "{ \"name\": \"${name}\", \"extras\": \"${extras}\" }")
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Join all JSON objects with a comma. This also avoids trailing commas JSON doesn't accept
|
||||
string(JOIN ",\n " joined_json_objects ${json_objects})
|
||||
string(APPEND result "\n ${joined_json_objects}\n ]")
|
||||
set(${out_result} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Generate the deployment settings json file for a cmake target.
|
||||
function(qt6_android_generate_deployment_settings target)
|
||||
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
|
||||
@ -257,6 +294,9 @@ function(qt6_android_generate_deployment_settings target)
|
||||
__qt_internal_collect_plugin_library_files("${target}" "${plugin_targets}" plugin_targets)
|
||||
string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n")
|
||||
|
||||
_qt_internal_generate_android_permissions_json(permissions_json_array "${target}")
|
||||
string(APPEND file_contents " \"permissions\": ${permissions_json_array},\n")
|
||||
|
||||
# App binary
|
||||
string(APPEND file_contents
|
||||
" \"application-binary\": \"${target_output_name}\",\n")
|
||||
@ -345,6 +385,54 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
function(_qt_internal_add_android_permission target)
|
||||
if(NOT TARGET ${target})
|
||||
message(FATAL_ERROR "Empty or invalid target for adding Android permission: (${target})")
|
||||
endif()
|
||||
|
||||
cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN})
|
||||
|
||||
if(NOT arg_NAME)
|
||||
message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})")
|
||||
endif()
|
||||
|
||||
set(permission_entry "${arg_NAME}")
|
||||
|
||||
if(arg_ATTRIBUTES)
|
||||
# Permission with additional attributes
|
||||
list(LENGTH arg_ATTRIBUTES attributes_len)
|
||||
math(EXPR attributes_modulus "${attributes_len} % 2")
|
||||
if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0))
|
||||
message(FATAL_ERROR "Android permission: ${arg_NAME} attributes: ${arg_ATTRIBUTES} must"
|
||||
" be name-value pairs (for example: minSdkVersion 30)")
|
||||
endif()
|
||||
# Combine name-value pairs
|
||||
set(index 0)
|
||||
set(attributes "")
|
||||
while(index LESS attributes_len)
|
||||
list(GET arg_ATTRIBUTES ${index} name)
|
||||
math(EXPR index "${index} + 1")
|
||||
list(GET arg_ATTRIBUTES ${index} value)
|
||||
string(APPEND attributes "android:${name}=\'${value}\' ")
|
||||
math(EXPR index "${index} + 1")
|
||||
endwhile()
|
||||
set(permission_entry "${permission_entry}\;${attributes}")
|
||||
endif()
|
||||
|
||||
# Append the permission to the target's property
|
||||
set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}")
|
||||
endfunction()
|
||||
|
||||
function(qt6_add_android_permission target)
|
||||
_qt_internal_add_android_permission(${ARGV})
|
||||
endfunction()
|
||||
|
||||
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
|
||||
function(qt_add_android_permission target)
|
||||
qt6_add_android_permission(${ARGV})
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
function(qt6_android_apply_arch_suffix target)
|
||||
get_target_property(called_from_qt_impl
|
||||
${target} _qt_android_apply_arch_suffix_called_from_qt_impl)
|
||||
|
@ -92,6 +92,21 @@ qt_android_generate_deployment_settings(myapp)
|
||||
qt_android_add_apk_target(myapp)
|
||||
#! [qt_android_deploy_basic]
|
||||
|
||||
#! [qt_add_android_permission]
|
||||
qt_add_executable(myapp
|
||||
// ...
|
||||
)
|
||||
qt_add_android_permission(myapp
|
||||
NAME android.permission.BLUETOOTH_SCAN
|
||||
ATTRIBUTES
|
||||
minSdkVersion 31
|
||||
usesPermissionFlags neverForLocation
|
||||
)
|
||||
qt_add_android_permission(myapp
|
||||
NAME android.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
#! [qt_add_android_permission]
|
||||
|
||||
#! [qt_finalize_project_manual]
|
||||
cmake_minimum_required(VERSIONS 3.16)
|
||||
|
||||
|
38
src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
Normal file
38
src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\page qt-add-android-permission.html
|
||||
\ingroup cmake-commands-qtcore
|
||||
|
||||
\title qt_add_android_permission
|
||||
\keyword qt6_add_android_permission
|
||||
|
||||
\summary {Adds an Android permission to the target executable.}
|
||||
|
||||
\include cmake-find-package-core.qdocinc
|
||||
|
||||
\cmakecommandsince 6.9
|
||||
|
||||
\section1 Synopsis
|
||||
|
||||
\badcode
|
||||
qt_add_android_permission(target NAME <permission-name> [ATTRIBUTES <name1> <value1> ...])
|
||||
\endcode
|
||||
|
||||
\versionlessCMakeCommandsNote qt6_add_android_permission()
|
||||
|
||||
\section1 Description
|
||||
|
||||
The command adds an Android permission to the \c {target} executable.
|
||||
This can be used to define additional permissions, or overriding
|
||||
the default permissions set by Qt modules.
|
||||
|
||||
For further information on defining Android permissions,
|
||||
see \l {Qt Permissions and Features}.
|
||||
|
||||
\section1 Example
|
||||
|
||||
\snippet cmake-macros/examples.cmake qt_add_android_permission
|
||||
|
||||
*/
|
@ -242,7 +242,8 @@ struct Options
|
||||
|
||||
// Per package collected information
|
||||
// permissions 'name' => 'optional additional attributes'
|
||||
QMap<QString, QString> permissions;
|
||||
QMap<QString, QString> modulePermissions;
|
||||
QMap<QString, QString> applicationPermissions;
|
||||
QStringList features;
|
||||
|
||||
// Override qml import scanner path
|
||||
@ -1457,6 +1458,21 @@ bool readInputFile(Options *options)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QJsonArray permissions = jsonObject.value("permissions"_L1).toArray();
|
||||
if (!permissions.isEmpty()) {
|
||||
for (const QJsonValue &value : permissions) {
|
||||
if (value.isObject()) {
|
||||
QJsonObject permissionObj = value.toObject();
|
||||
QString name = permissionObj.value("name"_L1).toString();
|
||||
QString extras;
|
||||
if (permissionObj.contains("extras"_L1))
|
||||
extras = permissionObj.value("extras"_L1).toString().trimmed();
|
||||
options->applicationPermissions.insert(name, extras);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1896,14 +1912,23 @@ bool updateAndroidManifest(Options &options)
|
||||
QXmlStreamReader reader(&androidManifestXml);
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (reader.isStartElement() && reader.name() == "uses-permission"_L1)
|
||||
options.permissions.remove(QString(reader.attributes().value("android:name"_L1)));
|
||||
if (reader.isStartElement() && reader.name() == "uses-permission"_L1) {
|
||||
options.modulePermissions.remove(
|
||||
QString(reader.attributes().value("android:name"_L1)));
|
||||
options.applicationPermissions.remove(
|
||||
QString(reader.attributes().value("android:name"_L1)));
|
||||
}
|
||||
}
|
||||
androidManifestXml.close();
|
||||
}
|
||||
|
||||
// Application may define permissions in its CMakeLists.txt, give them the priority
|
||||
QMap<QString, QString> resolvedPermissions = options.modulePermissions;
|
||||
for (auto [name, extras] : options.applicationPermissions.asKeyValueRange())
|
||||
resolvedPermissions.insert(name, extras);
|
||||
|
||||
QString permissions;
|
||||
for (auto [name, extras] : options.permissions.asKeyValueRange())
|
||||
for (auto [name, extras] : resolvedPermissions.asKeyValueRange())
|
||||
permissions += " <uses-permission android:name=\"%1\" %2 />\n"_L1.arg(name).arg(extras);
|
||||
replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
|
||||
|
||||
@ -2172,9 +2197,9 @@ bool readAndroidDependencyXml(Options *options,
|
||||
QString extras = reader.attributes().value("extras"_L1).toString();
|
||||
// With duplicate permissions prioritize the one without any attributes,
|
||||
// as that is likely the most permissive
|
||||
if (!options->permissions.contains(name)
|
||||
|| !options->permissions.value(name).isEmpty()) {
|
||||
options->permissions.insert(name, extras);
|
||||
if (!options->modulePermissions.contains(name)
|
||||
|| !options->modulePermissions.value(name).isEmpty()) {
|
||||
options->modulePermissions.insert(name, extras);
|
||||
}
|
||||
} else if (reader.name() == "feature"_L1) {
|
||||
QString name = reader.attributes().value("name"_L1).toString();
|
||||
|
@ -18,6 +18,13 @@ function(tst_generate_android_deployment_setting target)
|
||||
qt6_android_generate_deployment_settings(${target})
|
||||
endfunction()
|
||||
|
||||
function(tst_add_android_permissions target)
|
||||
qt6_add_android_permission(${target} NAME PERMISSION_WITH_ATTRIBUTES
|
||||
ATTRIBUTES
|
||||
minSdkVersion 32 maxSdkVersion 34)
|
||||
qt6_add_android_permission(${target} NAME PERMISSION_WITHOUT_ATTRIBUTES)
|
||||
endfunction()
|
||||
|
||||
qt6_policy(SET QTP0002 NEW)
|
||||
|
||||
set(target tst_android_deployment_settings_new)
|
||||
@ -46,6 +53,7 @@ set_target_properties(${target} PROPERTIES
|
||||
# qt6_android_generate_deployment_settings
|
||||
QT_ANDROID_DEPLOYMENT_SETTINGS_FILE "custom_deployment_settings.json"
|
||||
)
|
||||
tst_add_android_permissions(${target})
|
||||
tst_generate_android_deployment_setting(${target})
|
||||
|
||||
qt6_policy(SET QTP0002 OLD)
|
||||
@ -67,6 +75,7 @@ set_target_properties(${target} PROPERTIES
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR "path\\to/source\\dir"
|
||||
QT_ANDROID_SYSTEM_LIBS_PREFIX "myLibPrefix"
|
||||
)
|
||||
tst_add_android_permissions(${target})
|
||||
tst_generate_android_deployment_setting(${target})
|
||||
|
||||
get_target_property(new_settings
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QFile>
|
||||
@ -82,13 +83,29 @@ void tst_android_deployment_settings::DeploymentSettings_data()
|
||||
<< "org.qtproject.android_deployment_settings_test";
|
||||
QTest::newRow("android-app-name") << "android-app-name"
|
||||
<< "Android Deployment Settings Test";
|
||||
QTest::newRow("permissions") << "permissions"
|
||||
<< "[{\"name\":\"PERMISSION_WITH_ATTRIBUTES\","
|
||||
"\"extras\":\"android:minSdkVersion='32' android:maxSdkVersion='34' \"},"
|
||||
"{\"name\":\"PERMISSION_WITHOUT_ATTRIBUTES\"}]";
|
||||
}
|
||||
|
||||
void tst_android_deployment_settings::DeploymentSettings()
|
||||
{
|
||||
QFETCH(QString, key);
|
||||
QFETCH(QString, value);
|
||||
QCOMPARE(jsonDoc[key].toString(), value);
|
||||
QJsonValue keyValue = jsonDoc[key];
|
||||
if (keyValue.type() == QJsonValue::Type::String) {
|
||||
QCOMPARE(keyValue.toString(), value);
|
||||
} else if (keyValue.type() == QJsonValue::Type::Array) {
|
||||
QJsonParseError parseError;
|
||||
// For robustness (field order, whitespaces etc.) make comparison between QJsonDocuments
|
||||
QJsonDocument expectedDoc = QJsonDocument::fromJson(value.toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
qFatal("Failed to parse expected JSON array: %s", qPrintable(parseError.errorString()));
|
||||
QCOMPARE(QJsonDocument(keyValue.toArray()), expectedDoc);
|
||||
} else {
|
||||
qFatal("Unhandled JSON type: %i", keyValue.type());
|
||||
}
|
||||
}
|
||||
|
||||
void tst_android_deployment_settings::QtPaths_data()
|
||||
|
Loading…
x
Reference in New Issue
Block a user