diff --git a/doc/global/externalsites/external-resources.qdoc b/doc/global/externalsites/external-resources.qdoc index 272c73b3b01..0dcea08a53a 100644 --- a/doc/global/externalsites/external-resources.qdoc +++ b/doc/global/externalsites/external-resources.qdoc @@ -32,6 +32,10 @@ \externalpage https://source.android.com/devices/tech/debug/tagged-pointers \title Android: Tagged Pointers */ +/*! + \externalpage https://developer.android.com/build/configure-app-module + \title Android: Configure the app module +*/ /*! \externalpage https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl \title iOS: canOpenURL: diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 96b24ee680e..3e96130b85a 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -216,6 +216,10 @@ function(qt6_android_generate_deployment_settings target) _qt_internal_add_android_deployment_property(file_contents "android-package-source-directory" ${target} "_qt_android_native_package_source_dir") + # version code + _qt_internal_add_android_deployment_property(file_contents "android-package-name" + ${target} "QT_ANDROID_PACKAGE_NAME") + # version code _qt_internal_add_android_deployment_property(file_contents "android-version-code" ${target} "QT_ANDROID_VERSION_CODE") diff --git a/src/corelib/doc/src/cmake/cmake-properties.qdoc b/src/corelib/doc/src/cmake/cmake-properties.qdoc index 8fe2b0e88fd..3740b29612a 100644 --- a/src/corelib/doc/src/cmake/cmake-properties.qdoc +++ b/src/corelib/doc/src/cmake/cmake-properties.qdoc @@ -244,6 +244,55 @@ CMake will attempt to use the latest installed version. \sa{qt6_android_generate_deployment_settings}{qt_android_generate_deployment_settings()} */ +/*! +\page cmake-target-property-qt-android-package-name.html +\ingroup cmake-properties-qtcore +\ingroup cmake-target-properties-qtcore + +\title QT_ANDROID_PACKAGE_NAME +\target cmake-target-property-QT_ANDROID_PACKAGE_NAME + +\summary {The app's package name.} + +\cmakepropertysince 6.8 +\preliminarycmakeproperty +\cmakepropertyandroidonly + +Specifies the app's package name. This is usually a unique dot separated +name for the app, that will be used to identify the app on devices or in +the Play Store. For example, "org.qtproject.example.gallery". + +The package name set by this property is passed to the \c build.gradle file +as a \c namespace property, instead of \c AndroidManifest.xml, since the +latter is deprecated since Android Gradle Plugin 7.4. + +The package name considers some words or characters as illegal and the build +will clean such names if any is encountered. An underscore (\c _) either replaces +illegal characters or is appended to illegal words. + +\list + \li Allowed characters: alphanumeric, an underscore or a dot [a-zA-Z0-9_.]. + \li Illegal words: abstract, continue, for, new, switch, assert, default, + if, package, synchronized, boolean, do, goto, private, this, break, + double, implements, protected, throw, byte, else, import, public, + throws, case, enum, instanceof, return, transient, catch, extends, + int, short, try, char, final, interface, static, void, class, finally, + long, strictfp, volatile, const, float, native, super, while. +\endlist + +The default package name for Qt for Android apps is \c org.qtproject.example.. + +\note Setting the package name manually in \c build.gradle (via +\c namespace property) takes precedence over \c AndroidManifest.xml +(via \c package attribute), and the latter also takes precedence over +this property. + +For more information, see Android's +\l{Android: Configure the app module}{configure the app module}. + +\sa{qt6_android_generate_deployment_settings}{qt_android_generate_deployment_settings()} +*/ + /*! \page cmake-target-property-qt-android-version-code.html \ingroup cmake-properties-qtcore diff --git a/src/corelib/doc/src/cmake/qt_android_generate_deployment_settings.qdoc b/src/corelib/doc/src/cmake/qt_android_generate_deployment_settings.qdoc index 5a7bbdc33f5..daa3680070a 100644 --- a/src/corelib/doc/src/cmake/qt_android_generate_deployment_settings.qdoc +++ b/src/corelib/doc/src/cmake/qt_android_generate_deployment_settings.qdoc @@ -63,6 +63,7 @@ how to accomplish this. \li \l{cmake-target-property-QT_ANDROID_MIN_SDK_VERSION}{QT_ANDROID_MIN_SDK_VERSION} \li \l{cmake-target-property-QT_ANDROID_PACKAGE_SOURCE_DIR}{QT_ANDROID_PACKAGE_SOURCE_DIR} \li \l{cmake-target-property-QT_ANDROID_TARGET_SDK_VERSION}{QT_ANDROID_TARGET_SDK_VERSION} +\li \l{cmake-target-property-QT_ANDROID_PACKAGE_NAME}{QT_ANDROID_PACKAGE_NAME} \li \l{cmake-target-property-QT_ANDROID_VERSION_NAME}{QT_ANDROID_VERSION_NAME} \li \l{cmake-target-property-QT_ANDROID_VERSION_CODE}{QT_ANDROID_VERSION_CODE} \li \l{cmake-target-property-QT_QML_IMPORT_PATH}{QT_QML_IMPORT_PATH} diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index 4ff57373810..da5c2c8c775 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -802,18 +802,25 @@ GradleBuildConfigs gradleBuildConfigs(const QString &path) return configs; } -QString cleanPackageName(QString packageName) +QString cleanPackageName(QString packageName, bool *cleaned = nullptr) { auto isLegalChar = [] (QChar c) -> bool { ushort ch = c.unicode(); return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || - ch == '.'; + ch == '.' || ch == '_'; }; + + if (cleaned) + *cleaned = false; + for (QChar &c : packageName) { - if (!isLegalChar(c)) + if (!isLegalChar(c)) { c = u'_'; + if (cleaned) + *cleaned = true; + } } static QStringList keywords; @@ -848,12 +855,16 @@ QString cleanPackageName(QString packageName) QChar c = word[0]; if ((c >= u'0' && c <= u'9') || c == u'_') { packageName.insert(index + 1, u'a'); + if (cleaned) + *cleaned = true; index = next + 1; continue; } } if (keywords.contains(word)) { packageName.insert(next, "_"_L1); + if (cleaned) + *cleaned = true; index = next + 1; } else { index = next; @@ -885,26 +896,29 @@ QString detectLatestAndroidPlatform(const QString &sdkPath) QString extractPackageName(Options *options) { - QString packageName; + { + const QString gradleBuildFile = options->androidSourceDirectory + "/build.gradle"_L1; + QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace; + + if (!packageName.isEmpty() && packageName != "androidPackageName"_L1) + return packageName; + } + QFile androidManifestXml(options->androidSourceDirectory + "/AndroidManifest.xml"_L1); if (androidManifestXml.open(QIODevice::ReadOnly)) { QXmlStreamReader reader(&androidManifestXml); while (!reader.atEnd()) { reader.readNext(); - if (reader.isStartElement() && reader.name() == "manifest"_L1) - packageName = reader.attributes().value("package"_L1).toString(); + if (reader.isStartElement() && reader.name() == "manifest"_L1) { + QString packageName = reader.attributes().value("package"_L1).toString(); + if (!packageName.isEmpty() && packageName != "org.qtproject.example"_L1) + return packageName; + break; + } } } - if (packageName.isEmpty()) { - const QString gradleBuildFile = options->androidSourceDirectory + "/build.gradle"_L1; - packageName = gradleBuildConfigs(gradleBuildFile).appNamespace; - } - - if (packageName.isEmpty() || packageName == "androidPackageName"_L1) - packageName = "org.qtproject.example.%1"_L1.arg(options->applicationBinary); - - return cleanPackageName(packageName); + return QString(); } bool parseCmakeBoolean(const QJsonValue &value) @@ -1301,6 +1315,24 @@ bool readInputFile(Options *options) } } + { + const QJsonValue androidPackageName = jsonObject.value("android-package-name"_L1); + const QString extractedPackageName = extractPackageName(options); + if (!extractedPackageName.isEmpty()) + options->packageName = extractedPackageName; + else if (!androidPackageName.isUndefined()) + options->packageName = androidPackageName.toString(); + else + options->packageName = "org.qtproject.example.%1"_L1.arg(options->applicationBinary); + + bool cleaned; + options->packageName = cleanPackageName(options->packageName, &cleaned); + if (cleaned) { + fprintf(stderr, "Warning: Package name contained illegal characters and was cleaned " + "to \"%s\"\n", qPrintable(options->packageName)); + } + } + { using ItFlag = QDirListing::IteratorFlag; const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1); @@ -1358,7 +1390,6 @@ bool readInputFile(Options *options) options->isZstdCompressionEnabled = zstdCompressionFlag.toBool(); } } - options->packageName = extractPackageName(options); return true; } diff --git a/tests/auto/other/android_deployment_settings/CMakeLists.txt b/tests/auto/other/android_deployment_settings/CMakeLists.txt index f2ba1353282..9ef457189a6 100644 --- a/tests/auto/other/android_deployment_settings/CMakeLists.txt +++ b/tests/auto/other/android_deployment_settings/CMakeLists.txt @@ -30,6 +30,7 @@ set_target_properties(${target} PROPERTIES QT_ANDROID_SDK_BUILD_TOOLS_REVISION "23.0.2" QT_ANDROID_MIN_SDK_VERSION "1" QT_ANDROID_TARGET_SDK_VERSION "2" + QT_ANDROID_PACKAGE_NAME "org.qtproject.android_deployment_settings_test" QT_ANDROID_DEPLOYMENT_DEPENDENCIES "dep1.so;dep2.so;dep3.so" QT_ANDROID_DEPLOYMENT_SETTINGS_FILE "attempt_to_rewrite.json" QT_ANDROID_EXTRA_LIBS @@ -53,6 +54,7 @@ set_target_properties(${target} PROPERTIES QT_ANDROID_SDK_BUILD_TOOLS_REVISION "23.0.2" QT_ANDROID_MIN_SDK_VERSION "1" QT_ANDROID_TARGET_SDK_VERSION "2" + QT_ANDROID_PACKAGE_NAME "org.qtproject.android_deployment_settings_test" QT_ANDROID_DEPLOYMENT_DEPENDENCIES "dep1.so;dep2.so;dep3.so" QT_ANDROID_EXTRA_LIBS "some/path/to/lib1.so;some/path\\to/lib2.so;some\\path\\to\\lib3.so;some/path/to/lib4.so" diff --git a/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp b/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp index d68d08b58eb..f8428aaf43c 100644 --- a/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp +++ b/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp @@ -76,6 +76,8 @@ void tst_android_deployment_settings::DeploymentSettings_data() << "1"; QTest::newRow("android-target-sdk-version") << "android-target-sdk-version" << "2"; + QTest::newRow("android-package-name") << "android-package-name" + << "org.qtproject.android_deployment_settings_test"; } void tst_android_deployment_settings::DeploymentSettings()