388 QStringList arguments = QCoreApplication::arguments();
389 for (
int i=0; i<arguments.size(); ++i) {
390 const QString &argument = arguments.at(i);
391 if (argument.compare(
"--output"_L1, Qt::CaseInsensitive) == 0) {
392 if (i + 1 == arguments.size())
395 options.outputDirectory = arguments.at(++i).trimmed();
396 }
else if (argument.compare(
"--input"_L1, Qt::CaseInsensitive) == 0) {
397 if (i + 1 == arguments.size())
400 options.inputFileName = arguments.at(++i);
401 }
else if (argument.compare(
"--aab"_L1, Qt::CaseInsensitive) == 0) {
404 }
else if (!options
.buildAAB && argument.compare(
"--no-build"_L1, Qt::CaseInsensitive) == 0) {
406 }
else if (argument.compare(
"--install"_L1, Qt::CaseInsensitive) == 0) {
409 }
else if (argument.compare(
"--reinstall"_L1, Qt::CaseInsensitive) == 0) {
412 }
else if (argument.compare(
"--android-platform"_L1, Qt::CaseInsensitive) == 0) {
413 if (i + 1 == arguments.size())
416 options.androidPlatform = arguments.at(++i);
417 }
else if (argument.compare(
"--help"_L1, Qt::CaseInsensitive) == 0) {
419 }
else if (argument.compare(
"--verbose"_L1, Qt::CaseInsensitive) == 0) {
421 }
else if (argument.compare(
"--deployment"_L1, Qt::CaseInsensitive) == 0) {
422 if (i + 1 == arguments.size()) {
425 QString deploymentMechanism = arguments.at(++i);
426 if (deploymentMechanism.compare(
"bundled"_L1, Qt::CaseInsensitive) == 0) {
428 }
else if (deploymentMechanism.compare(
"unbundled"_L1,
429 Qt::CaseInsensitive) == 0) {
432 fprintf(stderr,
"Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
436 }
else if (argument.compare(
"--device"_L1, Qt::CaseInsensitive) == 0) {
437 if (i + 1 == arguments.size())
440 options.installLocation = arguments.at(++i);
441 }
else if (argument.compare(
"--release"_L1, Qt::CaseInsensitive) == 0) {
443 }
else if (argument.compare(
"--jdk"_L1, Qt::CaseInsensitive) == 0) {
444 if (i + 1 == arguments.size())
447 options.jdkPath = arguments.at(++i);
448 }
else if (argument.compare(
"--apk"_L1, Qt::CaseInsensitive) == 0) {
449 if (i + 1 == arguments.size())
452 options.apkPath = arguments.at(++i);
453 }
else if (argument.compare(
"--depfile"_L1, Qt::CaseInsensitive) == 0) {
454 if (i + 1 == arguments.size())
457 options.depFilePath = arguments.at(++i);
458 }
else if (argument.compare(
"--builddir"_L1, Qt::CaseInsensitive) == 0) {
459 if (i + 1 == arguments.size())
462 options.buildDirectory = arguments.at(++i);
463 }
else if (argument.compare(
"--sign"_L1, Qt::CaseInsensitive) == 0) {
464 if (i + 2 < arguments.size() && !arguments.at(i + 1).startsWith(
"--"_L1) &&
465 !arguments.at(i + 2).startsWith(
"--"_L1)) {
466 options.keyStore = arguments.at(++i);
467 options.keyStoreAlias = arguments.at(++i);
469 const QString keyStore = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_PATH");
470 const QString storeAlias = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_ALIAS");
471 if (keyStore.isEmpty() || storeAlias.isEmpty()) {
473 fprintf(stderr,
"Package signing path and alias values are not specified.\n");
476 "Using package signing path and alias values found from the "
477 "environment variables.\n");
478 options.keyStore = keyStore;
479 options.keyStoreAlias = storeAlias;
484 if (options.keyStorePassword.isEmpty()) {
485 fprintf(stdout,
"Using package signing store password found from the environment "
487 options.keyStorePassword = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_STORE_PASS");
489 if (options.keyPass.isEmpty()) {
490 fprintf(stdout,
"Using package signing key password found from the environment "
492 options.keyPass = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_KEY_PASS");
494 }
else if (argument.compare(
"--storepass"_L1, Qt::CaseInsensitive) == 0) {
495 if (i + 1 == arguments.size())
498 options.keyStorePassword = arguments.at(++i);
499 }
else if (argument.compare(
"--storetype"_L1, Qt::CaseInsensitive) == 0) {
500 if (i + 1 == arguments.size())
503 options.storeType = arguments.at(++i);
504 }
else if (argument.compare(
"--keypass"_L1, Qt::CaseInsensitive) == 0) {
505 if (i + 1 == arguments.size())
508 options.keyPass = arguments.at(++i);
509 }
else if (argument.compare(
"--sigfile"_L1, Qt::CaseInsensitive) == 0) {
510 if (i + 1 == arguments.size())
513 options.sigFile = arguments.at(++i);
514 }
else if (argument.compare(
"--digestalg"_L1, Qt::CaseInsensitive) == 0) {
515 if (i + 1 == arguments.size())
518 options.digestAlg = arguments.at(++i);
519 }
else if (argument.compare(
"--sigalg"_L1, Qt::CaseInsensitive) == 0) {
520 if (i + 1 == arguments.size())
523 options.sigAlg = arguments.at(++i);
524 }
else if (argument.compare(
"--tsa"_L1, Qt::CaseInsensitive) == 0) {
525 if (i + 1 == arguments.size())
528 options.tsaUrl = arguments.at(++i);
529 }
else if (argument.compare(
"--tsacert"_L1, Qt::CaseInsensitive) == 0) {
530 if (i + 1 == arguments.size())
533 options.tsaCert = arguments.at(++i);
534 }
else if (argument.compare(
"--internalsf"_L1, Qt::CaseInsensitive) == 0) {
536 }
else if (argument.compare(
"--sectionsonly"_L1, Qt::CaseInsensitive) == 0) {
538 }
else if (argument.compare(
"--protected"_L1, Qt::CaseInsensitive) == 0) {
540 }
else if (argument.compare(
"--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
542 }
else if (argument.compare(
"--build-aar"_L1, Qt::CaseInsensitive) == 0) {
544 }
else if (argument.compare(
"--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
545 options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
546 }
else if (argument.compare(
"--no-rcc-bundle-cleanup"_L1,
547 Qt::CaseInsensitive) == 0) {
549 }
else if (argument.compare(
"--copy-dependencies-only"_L1,
550 Qt::CaseInsensitive) == 0) {
557 fprintf(stderr,
"Warning: Skipping %s, AAR packages are not installable.\n",
563 fprintf(stderr,
"Warning: Skipping -aab as --build-aar is present.\n");
566 if (!options.keyStore.isEmpty()) {
567 fprintf(stderr,
"Warning: Skipping --sign, signing AAR packages is not supported.\n");
568 options.keyStore.clear();
572 if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
575 if (options.inputFileName.isEmpty())
576 options.inputFileName =
"android-%1-deployment-settings.json"_L1.arg(QDir::current().dirName());
578 options
.timing = qEnvironmentVariableIsSet(
"ANDROIDDEPLOYQT_TIMING_OUTPUT");
580 if (!QDir::current().mkpath(options.outputDirectory)) {
581 fprintf(stderr,
"Invalid output directory: %s\n", qPrintable(options.outputDirectory));
582 options.outputDirectory.clear();
584 options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath();
585 if (!options.outputDirectory.endsWith(u'/'))
586 options.outputDirectory += u'/';
624 --device [device ID]: Use specified device for deployment. Default
625 is the device selected by default by adb.
626
627 --android-platform <platform>: Builds against the given android
628 platform. By default, the highest available version will be
629 used.
630
631 --release: Builds a package ready for release. By default, the
632 package will be signed with a debug key.
633
634 --sign <url/to/keystore> <alias>: Signs the package with the
635 specified keystore, alias and store password.
636 Optional arguments for use with signing:
637 --storepass <password>: Keystore password.
638 --storetype <type>: Keystore type.
639 --keypass <password>: Password for private key (if different
640 from keystore password.)
641 --sigfile <file>: Name of .SF/.DSA file.
642 --digestalg <name>: Name of digest algorithm. Default is
643 "SHA-256".
644 --sigalg <name>: Name of signature algorithm. Default is
645 "SHA256withRSA".
646 --tsa <url>: Location of the Time Stamping Authority.
647 --tsacert <alias>: Public key certificate for TSA.
648 --internalsf: Include the .SF file inside the signature block.
649 --sectionsonly: Do not compute hash of entire manifest.
650 --protected: Keystore has protected authentication path.
651 --jarsigner: Deprecated, ignored.
652
653 NOTE: To conceal the keystore information, the environment variables
654 QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to
655 set the values keysotore and alias respectively.
656 Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,
657 and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key
658 passwords respectively. This option needs only the --sign parameter.
659
660 --jdk <path/to/jdk>: Used to find the jarsigner tool when used
661 in combination with the --release argument. By default,
662 an attempt is made to detect the tool using the JAVA_HOME and
663 PATH environment variables, in that order.
664
665 --qml-import-paths: Specify additional search paths for QML
666 imports.
667
668 --verbose: Prints out information during processing.
669
677 --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
678
679 --build-aar: Build an AAR package. This option skips --aab, --install,
680 --reinstall, and --sign options if they are provided.
681
682 --qml-importscanner-binary <path/to/qmlimportscanner>: Override the
683 default qmlimportscanner binary path. By default the
684 qmlimportscanner binary is located using the Qt directory
685 specified in the input file.
686
687 --depfile <path/to/depfile>: Output a dependency file.
688
689 --builddir <path/to/build/directory>: build directory. Necessary when
690 generating a depfile because ninja requires relative paths.
691
692 --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
693 running androiddeployqt. This option simplifies debugging of
694 the resource bundle content, but it should not be used when deploying
695 a project, since it litters the "assets" directory.
696
697 --copy-dependencies-only: resolve application dependencies and stop
698 deploying process after all libraries and resources that the
699 application depends on have been copied.
700
701 --help: Displays this information.
702)");
708bool quasiLexicographicalReverseLessThan(
const QFileInfo &fi1,
const QFileInfo &fi2)
711 QString s1 = fi1.baseName();
712 QString s2 = fi2.baseName();
714 if (s1.size() != s2.size())
715 return s1.size() > s2.size();
718 return fi1.fileName() > fi2.fileName();
916 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
919 const QFileInfo& latestPlatform = fileInfos.constFirst();
920 if (dir.exists(latestPlatform.baseName()))
921 return latestPlatform.baseName();
922 return latestPlatform.fileName();
925QString extractPackageName(
Options *options)
928 const QString gradleBuildFile = options->androidSourceDirectory +
"/build.gradle"_L1;
929 QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace;
931 if (!packageName.isEmpty() && packageName !=
"androidPackageName"_L1)
935 QFile androidManifestXml(options->androidSourceDirectory +
"/AndroidManifest.xml"_L1);
936 if (androidManifestXml.open(QIODevice::ReadOnly)) {
937 QXmlStreamReader reader(&androidManifestXml);
938 while (!reader.atEnd()) {
940 if (reader.isStartElement() && reader.name() ==
"manifest"_L1) {
941 QString packageName = reader.attributes().value(
"package"_L1).toString();
942 if (!packageName.isEmpty() && packageName !=
"org.qtproject.example"_L1)
952bool parseCmakeBoolean(
const QJsonValue &value)
954 const QString stringValue = value.toString();
955 return (stringValue.compare(QString::fromUtf8(
"true"), Qt::CaseInsensitive)
956 || stringValue.compare(QString::fromUtf8(
"on"), Qt::CaseInsensitive)
957 || stringValue.compare(QString::fromUtf8(
"yes"), Qt::CaseInsensitive)
958 || stringValue.compare(QString::fromUtf8(
"y"), Qt::CaseInsensitive)
959 || stringValue.toInt() > 0);
962bool readInputFileDirectory(
Options *options, QJsonObject &jsonObject,
const QString keyName)
964 const QJsonValue qtDirectory = jsonObject.value(keyName);
965 if (qtDirectory.isUndefined()) {
966 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
967 if (keyName ==
"qtDataDirectory"_L1) {
968 options->architectures[it.key()].qtDirectories[keyName] =
"."_L1;
970 }
else if (keyName ==
"qtLibsDirectory"_L1) {
971 options->architectures[it.key()].qtDirectories[keyName] =
"lib"_L1;
973 }
else if (keyName ==
"qtLibExecsDirectory"_L1) {
974 options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
976 }
else if (keyName ==
"qtPluginsDirectory"_L1) {
977 options->architectures[it.key()].qtDirectories[keyName] =
"plugins"_L1;
979 }
else if (keyName ==
"qtQmlDirectory"_L1) {
980 options->architectures[it.key()].qtDirectories[keyName] =
"qml"_L1;
987 if (qtDirectory.isObject()) {
988 const QJsonObject object = qtDirectory.toObject();
989 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
990 if (it.value().isUndefined()) {
992 "Invalid '%s' record in deployment settings: %s\n",
994 qPrintable(it.value().toString()));
997 if (it.value().isNull())
999 if (!options->architectures.contains(it.key())) {
1000 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
1001 qPrintable(options->architectures.keys().join(u',')));
1004 options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
1006 }
else if (qtDirectory.isString()) {
1009 const QString directory = qtDirectory.toString();
1010 options->architectures[
"arm64-v8a"_L1].qtDirectories[keyName] = directory;
1011 options->architectures[
"armeabi-v7a"_L1].qtDirectories[keyName] = directory;
1012 options->architectures[
"x86"_L1].qtDirectories[keyName] = directory;
1013 options->architectures[
"x86_64"_L1].qtDirectories[keyName] = directory;
1015 fprintf(stderr,
"Invalid format for %s in json file %s.\n",
1016 qPrintable(keyName), qPrintable(options->inputFileName));
1022bool readInputFile(
Options *options)
1024 QFile file(options->inputFileName);
1025 if (!file.open(QIODevice::ReadOnly)) {
1026 fprintf(stderr,
"Cannot read from input file: %s\n", qPrintable(options->inputFileName));
1031 QJsonParseError jsonParseError;
1032 QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll(), &jsonParseError);
1033 if (jsonDocument.isNull()) {
1034 fprintf(stderr,
"Invalid json file: %s. Reason: %s at offset %i.\n",
1035 qPrintable(options->inputFileName),
1036 qPrintable(jsonParseError.errorString()),
1037 jsonParseError.offset);
1041 QJsonObject jsonObject = jsonDocument.object();
1044 QJsonValue sdkPath = jsonObject.value(
"sdk"_L1);
1045 if (sdkPath.isUndefined()) {
1046 fprintf(stderr,
"No SDK path in json file %s\n", qPrintable(options->inputFileName));
1050 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
1055 if (options->androidPlatform.isEmpty()) {
1056 const QJsonValue ver = jsonObject.value(
"android-compile-sdk-version"_L1);
1057 if (!ver.isUndefined()) {
1058 const auto value = ver.toString();
1059 options->androidPlatform = value.startsWith(
"android-"_L1) ?
1060 value :
"android-%1"_L1.arg(value);
1063 if (options->androidPlatform.isEmpty()) {
1064 options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
1065 if (options->androidPlatform.isEmpty())
1070 if (!QDir(options->sdkPath +
"/platforms/"_L1 + options->androidPlatform).exists()) {
1071 fprintf(stderr,
"Warning: Android platform '%s' does not exist in SDK.\n",
1072 qPrintable(options->androidPlatform));
1078 const QJsonValue value = jsonObject.value(
"sdkBuildToolsRevision"_L1);
1079 if (!value.isUndefined())
1080 options->sdkBuildToolsVersion = value.toString();
1084 const QJsonValue qtInstallDirectory = jsonObject.value(
"qt"_L1);
1085 if (qtInstallDirectory.isUndefined()) {
1086 fprintf(stderr,
"No Qt directory in json file %s\n", qPrintable(options->inputFileName));
1090 if (qtInstallDirectory.isObject()) {
1091 const QJsonObject object = qtInstallDirectory.toObject();
1092 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
1093 if (it.value().isUndefined()) {
1095 "Invalid 'qt' record in deployment settings: %s\n",
1096 qPrintable(it.value().toString()));
1099 if (it.value().isNull())
1101 options->architectures.insert(it.key(),
1104 }
else if (qtInstallDirectory.isString()) {
1107 const QString directory = qtInstallDirectory.toString();
1109 options->architectures.insert(
"arm64-v8a"_L1, qtInstallDirectoryWithTriple);
1110 options->architectures.insert(
"armeabi-v7a"_L1, qtInstallDirectoryWithTriple);
1111 options->architectures.insert(
"x86"_L1, qtInstallDirectoryWithTriple);
1112 options->architectures.insert(
"x86_64"_L1, qtInstallDirectoryWithTriple);
1116 options->qtHostDirectory = directory;
1118 fprintf(stderr,
"Invalid format for Qt install prefixes in json file %s.\n",
1119 qPrintable(options->inputFileName));
1124 if (!readInputFileDirectory(options, jsonObject,
"qtDataDirectory"_L1) ||
1125 !readInputFileDirectory(options, jsonObject,
"qtLibsDirectory"_L1) ||
1126 !readInputFileDirectory(options, jsonObject,
"qtLibExecsDirectory"_L1) ||
1127 !readInputFileDirectory(options, jsonObject,
"qtPluginsDirectory"_L1) ||
1128 !readInputFileDirectory(options, jsonObject,
"qtQmlDirectory"_L1))
1132 const QJsonValue qtHostDirectory = jsonObject.value(
"qtHostDir"_L1);
1133 if (!qtHostDirectory.isUndefined()) {
1134 if (qtHostDirectory.isString()) {
1135 options->qtHostDirectory = qtHostDirectory.toString();
1137 fprintf(stderr,
"Invalid format for Qt host directory in json file %s.\n",
1138 qPrintable(options->inputFileName));
1145 const auto extraPrefixDirs = jsonObject.value(
"extraPrefixDirs"_L1).toArray();
1146 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
1147 for (
const QJsonValue prefix : extraPrefixDirs) {
1148 options->extraPrefixDirs.push_back(prefix.toString());
1153 const auto androidDeployPlugins = jsonObject.value(
"android-deploy-plugins"_L1).toString();
1154 options->androidDeployPlugins = androidDeployPlugins.split(
";"_L1, Qt::SkipEmptyParts);
1158 const auto extraLibraryDirs = jsonObject.value(
"extraLibraryDirs"_L1).toArray();
1159 options->extraLibraryDirs.reserve(extraLibraryDirs.size());
1160 for (
const QJsonValue path : extraLibraryDirs) {
1161 options->extraLibraryDirs.push_back(path.toString());
1166 const QJsonValue androidSourcesDirectory = jsonObject.value(
"android-package-source-directory"_L1);
1167 if (!androidSourcesDirectory.isUndefined())
1168 options->androidSourceDirectory = androidSourcesDirectory.toString();
1172 const QJsonValue applicationArguments = jsonObject.value(
"android-application-arguments"_L1);
1173 if (!applicationArguments.isUndefined())
1174 options->applicationArguments = applicationArguments.toString();
1176 options->applicationArguments = QStringLiteral(
"");
1180 const QJsonValue androidVersionName = jsonObject.value(
"android-version-name"_L1);
1181 if (!androidVersionName.isUndefined())
1182 options->versionName = androidVersionName.toString();
1184 options->versionName = QStringLiteral(
"1.0");
1188 const QJsonValue androidVersionCode = jsonObject.value(
"android-version-code"_L1);
1189 if (!androidVersionCode.isUndefined())
1190 options->versionCode = androidVersionCode.toString();
1192 options->versionCode = QStringLiteral(
"1");
1196 const QJsonValue ver = jsonObject.value(
"android-min-sdk-version"_L1);
1197 if (!ver.isUndefined())
1198 options->minSdkVersion = ver.toString().toUtf8();
1202 const QJsonValue ver = jsonObject.value(
"android-target-sdk-version"_L1);
1203 if (!ver.isUndefined())
1204 options->targetSdkVersion = ver.toString().toUtf8();
1208 if (
const auto abi = jsonObject.value(
"abi"_L1); !abi.isUndefined())
1209 options->abi = jsonObject.value(
"abi"_L1).toString();
1213 const QJsonObject targetArchitectures = jsonObject.value(
"architectures"_L1).toObject();
1214 if (targetArchitectures.isEmpty()) {
1215 fprintf(stderr,
"No target architecture defined in json file.\n");
1218 for (
auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
1219 if (it.value().isUndefined()) {
1220 fprintf(stderr,
"Invalid architecture.\n");
1223 if (it.value().isNull())
1225 if (!options->architectures.contains(it.key())) {
1226 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
1227 qPrintable(options->architectures.keys().join(u',')));
1230 options->architectures[it.key()].triple = it.value().toString();
1231 options->architectures[it.key()].enabled =
true;
1236 const QJsonValue ndk = jsonObject.value(
"ndk"_L1);
1237 if (ndk.isUndefined()) {
1238 fprintf(stderr,
"No NDK path defined in json file.\n");
1241 options->ndkPath = ndk.toString();
1242 const QString ndkPropertiesPath = options->ndkPath + QStringLiteral(
"/source.properties");
1243 const QSettings settings(ndkPropertiesPath, QSettings::IniFormat);
1244 const QString ndkVersion = settings.value(QStringLiteral(
"Pkg.Revision")).toString();
1245 if (ndkVersion.isEmpty()) {
1246 fprintf(stderr,
"Couldn't retrieve the NDK version from \"%s\".\n",
1247 qPrintable(ndkPropertiesPath));
1250 options->ndkVersion = ndkVersion;
1254 const QJsonValue toolchainPrefix = jsonObject.value(
"toolchain-prefix"_L1);
1255 if (toolchainPrefix.isUndefined()) {
1256 fprintf(stderr,
"No toolchain prefix defined in json file.\n");
1259 options->toolchainPrefix = toolchainPrefix.toString();
1263 const QJsonValue ndkHost = jsonObject.value(
"ndk-host"_L1);
1264 if (ndkHost.isUndefined()) {
1265 fprintf(stderr,
"No NDK host defined in json file.\n");
1268 options->ndkHost = ndkHost.toString();
1272 const QJsonValue extraLibs = jsonObject.value(
"android-extra-libs"_L1);
1273 if (!extraLibs.isUndefined())
1274 options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts);
1278 const QJsonValue qmlSkipImportScanning = jsonObject.value(
"qml-skip-import-scanning"_L1);
1279 if (!qmlSkipImportScanning.isUndefined())
1284 const QJsonValue extraPlugins = jsonObject.value(
"android-extra-plugins"_L1);
1285 if (!extraPlugins.isUndefined())
1286 options->extraPlugins = extraPlugins.toString().split(u',');
1290 const QJsonValue systemLibsPath =
1291 jsonObject.value(
"android-system-libs-prefix"_L1);
1292 if (!systemLibsPath.isUndefined())
1293 options->systemLibsPath = systemLibsPath.toString();
1297 const QJsonValue noDeploy = jsonObject.value(
"android-no-deploy-qt-libs"_L1);
1298 if (!noDeploy.isUndefined()) {
1299 bool useUnbundled = parseCmakeBoolean(noDeploy);
1306 const QJsonValue stdcppPath = jsonObject.value(
"stdcpp-path"_L1);
1307 if (stdcppPath.isUndefined()) {
1308 fprintf(stderr,
"No stdcpp-path defined in json file.\n");
1311 options->stdCppPath = stdcppPath.toString();
1315 const QJsonValue qmlRootPath = jsonObject.value(
"qml-root-path"_L1);
1316 if (qmlRootPath.isString()) {
1317 options->rootPaths.push_back(qmlRootPath.toString());
1318 }
else if (qmlRootPath.isArray()) {
1319 auto qmlRootPaths = qmlRootPath.toArray();
1320 for (
auto path : qmlRootPaths) {
1321 if (path.isString())
1322 options->rootPaths.push_back(path.toString());
1325 options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
1330 const QJsonValue qmlImportPaths = jsonObject.value(
"qml-import-paths"_L1);
1331 if (!qmlImportPaths.isUndefined())
1332 options->qmlImportPaths = qmlImportPaths.toString().split(u',');
1336 const QJsonValue qmlImportScannerBinaryPath = jsonObject.value(
"qml-importscanner-binary"_L1);
1337 if (!qmlImportScannerBinaryPath.isUndefined())
1338 options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString();
1342 const QJsonValue rccBinaryPath = jsonObject.value(
"rcc-binary"_L1);
1343 if (!rccBinaryPath.isUndefined())
1344 options->rccBinaryPath = rccBinaryPath.toString();
1348 const QJsonValue genJavaQmlComponents = jsonObject.value(
"generate-java-qtquickview-contents"_L1);
1349 if (!genJavaQmlComponents.isUndefined() && genJavaQmlComponents.isBool()) {
1353 "Warning: Skipping the generation of Java QtQuickView contents from QML "
1354 "as it can be enabled only for an AAR target.\n");
1361 const QJsonValue qmlDomBinaryPath = jsonObject.value(
"qml-dom-binary"_L1);
1362 if (!qmlDomBinaryPath.isUndefined()) {
1363 options->qmlDomBinaryPath = qmlDomBinaryPath.toString();
1366 "No qmldom binary defined in json file which is required when "
1367 "building with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n");
1373 const QJsonValue qmlFiles = jsonObject.value(
"qml-files-for-code-generator"_L1);
1374 if (!qmlFiles.isUndefined() && qmlFiles.isArray()) {
1375 const QJsonArray jArray = qmlFiles.toArray();
1376 for (
auto &item : jArray)
1377 options->selectedJavaQmlComponents << item.toString();
1382 const QJsonValue applicationBinary = jsonObject.value(
"application-binary"_L1);
1383 if (applicationBinary.isUndefined()) {
1384 fprintf(stderr,
"No application binary defined in json file.\n");
1387 options->applicationBinary = applicationBinary.toString();
1389 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1392 auto appBinaryPath =
"%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary);
1393 if (!QFile::exists(appBinaryPath)) {
1394 fprintf(stderr,
"Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
1402 const QJsonValue androidPackageName = jsonObject.value(
"android-package-name"_L1);
1403 const QString extractedPackageName = extractPackageName(options);
1404 if (!extractedPackageName.isEmpty())
1405 options->packageName = extractedPackageName;
1406 else if (!androidPackageName.isUndefined())
1407 options->packageName = androidPackageName.toString();
1409 options->packageName =
"org.qtproject.example.%1"_L1.arg(options->applicationBinary);
1412 options->packageName = cleanPackageName(options->packageName, &cleaned);
1414 fprintf(stderr,
"Warning: Package name contained illegal characters and was cleaned "
1415 "to \"%s\"\n", qPrintable(options->packageName));
1420 const QJsonValue androidAppName = jsonObject.value(
"android-app-name"_L1);
1421 if (!androidAppName.isUndefined())
1422 options->appName = androidAppName.toString();
1424 options->appName = options->applicationBinary;
1731 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1732 const auto files = current.entryList(QDir::Files);
1733 result.reserve(dirs.size() + files.size());
1734 for (
const QString &dir : dirs) {
1735 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1737 for (
const QString &file : files) {
1738 result += rootDir.relativeFilePath(current.filePath(file));
1743bool copyAndroidExtraResources(
Options *options)
1745 if (options->extraPlugins.isEmpty())
1749 fprintf(stdout,
"Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1751 for (
const QString &extraResource : options->extraPlugins) {
1752 QFileInfo extraResourceInfo(extraResource);
1753 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1754 fprintf(stderr,
"External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1758 QDir resourceDir(extraResource);
1759 QString assetsDir = options->outputDirectory +
"/assets/"_L1 +
1760 resourceDir.dirName() + u'/';
1761 QString libsDir = options->outputDirectory +
"/libs/"_L1 + options->currentArchitecture + u'/';
1763 const QStringList files = allFilesInside(resourceDir, resourceDir);
1764 for (
const QString &resourceFile : files) {
1765 QString originFile(resourceDir.filePath(resourceFile));
1766 QString destinationFile;
1767 if (!resourceFile.endsWith(
".so"_L1)) {
1768 destinationFile = assetsDir + resourceFile;
1770 if (isDeployment(options, Options::Unbundled)
1771 || !checkArchitecture(*options, originFile)) {
1774 destinationFile = libsDir + resourceFile;
1775 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1778 if (!copyFileIfNewer(originFile, destinationFile,
1779 *options, options->createSymlinksOnly)) {
1788bool updateFile(
const QString &fileName,
const QHash<QString, QString> &replacements)
1790 QFile inputFile(fileName);
1791 if (!inputFile.open(QIODevice::ReadOnly)) {
1792 fprintf(stderr,
"Cannot open %s for reading.\n", qPrintable(fileName));
1798 QByteArray contents = inputFile.readAll();
1800 bool hasReplacements =
false;
1801 QHash<QString, QString>::const_iterator it;
1802 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1803 if (it.key() == it.value())
1807 int index = contents.indexOf(it.key().toUtf8());
1809 contents.replace(index, it.key().size(), it.value().toUtf8());
1810 hasReplacements =
true;
1817 if (hasReplacements) {
1820 if (!inputFile.open(QIODevice::WriteOnly)) {
1821 fprintf(stderr,
"Cannot open %s for writing.\n", qPrintable(fileName));
1827 QRegularExpression emptyLinesRegex(
"\\n\\s+\\n"_L1);
1828 contents = QString::fromUtf8(contents).replace(emptyLinesRegex,
"\n"_L1).toUtf8();
1830 inputFile.write(contents);
1837bool updateLibsXml(
Options *options)
1840 fprintf(stdout,
" -- res/values/libs.xml\n");
1842 QString fileName = options->outputDirectory +
"/res/values/libs.xml"_L1;
1843 if (!QFile::exists(fileName)) {
1844 fprintf(stderr,
"Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1849 QString allLocalLibs;
1852 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
2146 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2147 u'/' + relativeFileName;
2150 if (relativeFileName.startsWith(
"lib/"_L1)) {
2151 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2152 u'/' + relativeFileName.mid(
sizeof(
"lib/") - 1);
2154 return options->qtInstallDirectory + u'/' + relativeFileName;
2158static QString resolveLibDependency(
const Options *options,
const QString &fileName)
2160 if (QFile::exists(absoluteFilePath(options, fileName)))
2163 const QString abiSuffix = u'_' + options->currentArchitecture +
".so"_L1;
2164 QString alternative;
2165 if (fileName.endsWith(abiSuffix))
2166 alternative = fileName.chopped(abiSuffix.size()) +
".so"_L1;
2168 alternative = fileName.chopped(
sizeof(
".so") - 1) + abiSuffix;
2170 if (QFile::exists(absoluteFilePath(options, alternative)))
2176QList<QtDependency> findFilesRecursively(
const Options &options,
const QFileInfo &info,
const QString &rootPath)
2182 QList<QtDependency> ret;
2184 QDir dir(info.filePath());
2185 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2187 for (
const QString &entry : entries) {
2188 ret += findFilesRecursively(options,
2189 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2195 return QList<
QtDependency>() <<
QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2199QList<QtDependency> findFilesRecursively(
const Options &options,
const QString &fileName)
2207 QList<QtDependency> deps;
2208 for (
const auto &prefix : options.extraPrefixDirs) {
2209 QFileInfo info(prefix + u'/' + fileName);
2210 if (info.exists()) {
2212 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2214 return findFilesRecursively(options, info, prefix + u'/');
2219 if (
std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2220 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2221 QFileInfo info(options.qtInstallDirectory +
"/"_L1 + fileName);
2222 QFileInfo rootPath(options.qtInstallDirectory +
"/"_L1);
2223 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2228void readDependenciesFromFiles(
Options *options,
const QList<QtDependency> &files,
2229 QSet<QString> &usedDependencies,
2230 QSet<QString> &remainingDependencies)
2232 for (
const QtDependency &fileName : files) {
2233 if (usedDependencies.contains(fileName.absolutePath))
2236 if (fileName.absolutePath.endsWith(
".so"_L1)) {
2237 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2238 &remainingDependencies)) {
2239 fprintf(stdout,
"Skipping file dependency: %s\n",
2240 qPrintable(fileName.relativePath));
2338 if (reader.hasError()) {
2339 fprintf(stderr,
"Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2343 fprintf(stdout,
"No android dependencies for %s\n", qPrintable(moduleName));
2345 options->features.removeDuplicates();
2350QStringList getQtLibsFromElf(
const Options &options,
const QString &fileName)
2352 QString readElf = llvmReadobjPath(options);
2353 if (!QFile::exists(readElf)) {
2354 fprintf(stderr,
"Command does not exist: %s\n", qPrintable(readElf));
2355 return QStringList();
2358 readElf =
"%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2360 auto readElfCommand = openProcess(readElf);
2361 if (!readElfCommand) {
2362 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(readElf));
2363 return QStringList();
2368 bool readLibs =
false;
2370 while (fgets(buffer,
sizeof(buffer), readElfCommand.get()) !=
nullptr) {
2371 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2373 line = line.trimmed();
2375 if (line.startsWith(
"Arch: ")) {
2376 auto it = elfArchitectures.find(line.mid(6));
2377 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2379 fprintf(stdout,
"Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2383 readLibs = line.startsWith(
"NeededLibraries");
2386 if (!line.startsWith(
"lib"))
2388 library = QString::fromLatin1(line);
2389 QString libraryName =
"lib/"_L1 + library;
2390 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2398 const QString &fileName,
2399 QSet<QString> *usedDependencies,
2400 QSet<QString> *remainingDependencies)
2403 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2406 fprintf(stdout,
"Reading dependencies from %s\n", qPrintable(fileName));
2407 for (
const QString &dep : dependencies)
2408 fprintf(stdout,
" %s\n", qPrintable(dep));
2411 QList<QString> dependenciesToCheck;
2412 for (
const QString &dependency : dependencies) {
2413 if (usedDependencies->contains(dependency))
2416 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2417 usedDependencies->insert(dependency);
2418 if (!readDependenciesFromElf(options,
2419 absoluteDependencyPath,
2421 remainingDependencies)) {
2425 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2426 if (options->verbose)
2427 fprintf(stdout,
"Appending dependency: %s\n", qPrintable(dependency));
2428 dependenciesToCheck.append(dependency);
2431 for (
const QString &dependency : std::as_const(dependenciesToCheck)) {
2432 QString qtBaseName = dependency.mid(
sizeof(
"lib/lib") - 1);
2433 qtBaseName = qtBaseName.left(qtBaseName.size() - (
sizeof(
".so") - 1));
2434 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2442bool scanImports(
Options *options, QSet<QString> *usedDependencies)
2445 fprintf(stdout,
"Scanning for QML imports.\n");
2447 QString qmlImportScanner;
2448 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2449 qmlImportScanner = options->qmlImportScannerBinaryPath;
2451 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2452 "/qmlimportscanner"_L1);
2455 QStringList importPaths;
2461 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2462 if (QFile::exists(mainImportPath))
2463 importPaths += shellQuote(mainImportPath);
2467 for (
const QString &prefix : options->extraPrefixDirs)
2468 if (QFile::exists(prefix +
"/qml"_L1))
2469 importPaths += shellQuote(prefix +
"/qml"_L1);
2472 for (
const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2473 if (QFile::exists(qmlImportPath)) {
2474 importPaths += shellQuote(qmlImportPath);
2476 fprintf(stderr,
"Warning: QML import path %s does not exist.\n",
2477 qPrintable(qmlImportPath));
2481 bool qmlImportExists =
false;
2483 for (
const QString &import : importPaths) {
2484 if (QDir().exists(import)) {
2485 qmlImportExists =
true;
2492 if (!qmlImportExists) {
2493 fprintf(stderr,
"Warning: no 'qml' directory found under Qt install directory "
2494 "or import paths. Skipping QML dependency scanning.\n");
2498 if (!QFile::exists(qmlImportScanner)) {
2499 fprintf(stderr,
"%s: qmlimportscanner not found at %s\n",
2500 qmlImportExists ?
"Error"_L1.data() :
"Warning"_L1.data(),
2501 qPrintable(qmlImportScanner));
2505 for (
auto rootPath : options->rootPaths) {
2506 rootPath = QFileInfo(rootPath).absoluteFilePath();
2508 if (!rootPath.endsWith(u'/'))
2512 if (!rootPath.isEmpty())
2513 importPaths += shellQuote(rootPath);
2515 qmlImportScanner +=
" -rootPath %1"_L1.arg(shellQuote(rootPath));
2518 if (!options->qrcFiles.isEmpty()) {
2519 qmlImportScanner +=
" -qrcFiles"_L1;
2520 for (
const QString &qrcFile : options->qrcFiles)
2521 qmlImportScanner += u' ' + shellQuote(qrcFile);
2524 qmlImportScanner +=
" -importPath %1"_L1.arg(importPaths.join(u' '));
2527 fprintf(stdout,
"Running qmlimportscanner with the following command: %s\n",
2528 qmlImportScanner.toLocal8Bit().constData());
2531 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2532 if (qmlImportScannerCommand == 0) {
2533 fprintf(stderr,
"Couldn't run qmlimportscanner.\n");
2539 while (fgets(buffer,
sizeof(buffer), qmlImportScannerCommand.get()) !=
nullptr)
2540 output += QByteArray(buffer, qstrlen(buffer));
2542 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2543 if (jsonDocument.isNull()) {
2544 fprintf(stderr,
"Invalid json output from qmlimportscanner.\n");
2548 QJsonArray jsonArray = jsonDocument.array();
2549 for (
int i=0; i<jsonArray.count(); ++i) {
2550 QJsonValue value = jsonArray.at(i);
2551 if (!value.isObject()) {
2552 fprintf(stderr,
"Invalid format of qmlimportscanner output.\n");
2556 QJsonObject object = value.toObject();
2557 QString path = object.value(
"path"_L1).toString();
2558 if (path.isEmpty()) {
2559 fprintf(stderr,
"Warning: QML import could not be resolved in any of the import paths: %s\n",
2560 qPrintable(object.value(
"name"_L1).toString()));
2561 }
else if (object.value(
"type"_L1).toString() ==
"module"_L1) {
2998 const QString path = QString::fromLocal8Bit(qgetenv(
"PATH"));
2999#if defined(Q_OS_WIN32)
3000 QLatin1Char separator(
';');
3002 QLatin1Char separator(
':');
3005 const QStringList paths = path.split(separator);
3006 for (
const QString &path : paths) {
3007 QFileInfo fileInfo(path + u'/' + fileName);
3008 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
3009 return path + u'/' + fileName;
3015typedef QMap<QByteArray, QByteArray> GradleProperties;
3017static GradleProperties readGradleProperties(
const QString &path)
3019 GradleProperties properties;
3021 if (!file.open(QIODevice::ReadOnly))
3024 const auto lines = file.readAll().split(
'\n');
3025 for (
const QByteArray &line : lines) {
3026 if (line.trimmed().startsWith(
'#'))
3029 const int idx = line.indexOf(
'=');
3031 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
3037static bool mergeGradleProperties(
const QString &path, GradleProperties properties)
3039 const QString oldPathStr = path + u'~';
3040 QFile::remove(oldPathStr);
3041 QFile::rename(path, oldPathStr);
3043 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
3044 fprintf(stderr,
"Can't open file: %s for writing\n", qPrintable(file.fileName()));
3048 QFile oldFile(oldPathStr);
3049 if (oldFile.open(QIODevice::ReadOnly)) {
3051 while (oldFile.readLineInto(&line)) {
3052 QList<QByteArray> prop(line.split(
'='));
3053 if (prop.size() > 1) {
3054 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3055 if (it != properties.end()) {
3056 file.write(it.key() +
'=' + it.value() +
'\n');
3057 properties.erase(it);
3061 file.write(line.trimmed() +
'\n');
3064 QFile::remove(oldPathStr);
3067 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3068 file.write(it.key() +
'=' + it.value() +
'\n');
3074#if defined(Q_OS_WIN32)
3075void checkAndWarnGradleLongPaths(
const QString &outputDirectory)
3077 QStringList longFileNames;
3078 using F = QDirListing::IteratorFlag;
3079 for (
const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3080 F::FilesOnly | F::Recursive)) {
3081 if (dirEntry.size() >= MAX_PATH)
3082 longFileNames.append(dirEntry.filePath());
3085 if (!longFileNames.isEmpty()) {
3087 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3088 "Consider moving your project to reduce its path length.\n"
3089 "The following files have too long paths:\n%s.\n",
3090 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3095bool buildAndroidProject(
const Options &options)
3097 GradleProperties localProperties;
3098 localProperties[
"sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3099 const QString localPropertiesPath = options.outputDirectory +
"local.properties"_L1;
3100 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3103 const QString gradlePropertiesPath = options.outputDirectory +
"gradle.properties"_L1;
3104 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3106 const QString gradleBuildFilePath = options.outputDirectory +
"build.gradle"_L1;
3107 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3109 gradleProperties[
"buildDir"] =
"build";
3110 gradleProperties[
"qtAndroidDir"] =
3111 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3112 "/src/android/java"_L1)
3560 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3562 if (!options.keyStorePassword.isEmpty())
3563 apkSignCommand +=
" --ks-pass env:%1"_L1.arg(storePasswordVariable);
3565 if (!options.keyStoreAlias.isEmpty())
3566 apkSignCommand +=
" --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3568 if (!options.keyPass.isEmpty())
3569 apkSignCommand +=
" --key-pass env:%1"_L1.arg(keyPasswordVariable);
3571 if (options.verbose)
3572 apkSignCommand +=
" --verbose"_L1;
3574 apkSignCommand +=
" %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3576 auto apkSignerRunner = [](
const QString &command,
bool verbose) {
3577 auto apkSigner = openProcess(command);
3578 if (apkSigner == 0) {
3579 fprintf(stderr,
"Couldn't run apksigner.\n");
3584 while (fgets(buffer,
sizeof(buffer), apkSigner.get()) !=
nullptr)
3585 fprintf(stdout,
"%s", buffer);
3587 const int errorCode = pclose(apkSigner.release());
3588 if (errorCode != 0) {
3589 fprintf(stderr,
"apksigner command failed.\n");
3591 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3599 const SignerPasswordEnvironment passwords(options);
3600 if (!apkSignerRunner(apkSignCommand, options
.verbose))
3604 const QString apkVerifyCommand =
3605 "%1 verify --verbose %2"_L1
3606 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3608 if (options
.buildAAB && !signAAB(options))
3612 return apkSignerRunner(apkVerifyCommand,
true) && QFile::remove(packagePath(options, UnsignedAPK));
3618 SyntaxErrorOrHelpRequested = 1,
3619 CannotReadInputFile = 2,
3620 CannotCopyAndroidTemplate = 3,
3621 CannotReadDependencies = 4,
3622 CannotCopyGnuStl = 5,
3623 CannotCopyQtFiles = 6,
3624 CannotFindApplicationBinary = 7,
3625 CannotCopyAndroidExtraLibs = 10,
3626 CannotCopyAndroidSources = 11,
3627 CannotUpdateAndroidFiles = 12,
3628 CannotCreateAndroidProject = 13,
3629 CannotBuildAndroidProject = 14,
3630 CannotSignPackage = 15,
3631 CannotInstallApk = 16,
3632 CannotCopyAndroidExtraResources = 19,
3634 CannotCreateRcc = 21,
3635 CannotGenerateJavaQmlComponents = 22
3638bool writeDependencyFile(
const Options &options)
3641 fprintf(stdout,
"Writing dependency file.\n");
3643 QString relativeTargetPath;
3647 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() +
"/timestamp"_L1;
3648 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3650 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3653 QFile depFile(options.depFilePath);
3654 if (depFile.open(QIODevice::WriteOnly)) {
3655 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3656 depFile.write(
": ");
3658 for (
const auto &file : dependenciesForDepfile) {
3659 depFile.write(
" \\\n ");
3660 depFile.write(escapeAndEncodeDependencyPath(file));
3663 depFile.write(
"\n");
3668int generateJavaQmlComponents(
const Options &options)
3670 const auto firstCharToUpper = [](
const QString &str) -> QString {
3673 return str.left(1).toUpper() + str.mid(1);
3676 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3680 str[0] = str[0].toUpper();
3682 for (
int i = 0; i < str.size(); ++i) {
3683 if (str[i] ==
"."_L1) {
3686 if (j < str.size()) {
3687 str[j] = str[j].toUpper();
3694 const auto getImportPaths = [options](
const QString &buildPath,
const QString &libName,
3695 QStringList &appImports, QStringList &externalImports) ->
bool {
3696 QFile confRspFile(
"%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3697 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3699 QTextStream rspStream(&confRspFile);
3700 while (!rspStream.atEnd()) {
3701 QString currentLine = rspStream.readLine();
3702 if (currentLine.compare(
"-importPath"_L1) == 0) {
3703 currentLine = rspStream.readLine();
3704 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3705 appImports << currentLine;
3707 externalImports << currentLine;
3712 QSet<QString> qmldirDirectories;
3713 for (
const QString &path : appImports) {
3714 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3715 while (it.hasNext()) {
3716 const QDir dir(it.next());
3717 const QString absolutePath = dir.absolutePath();
3718 if (!absolutePath.startsWith(options.outputDirectory)
3719 && dir.exists(
"qmldir"_L1)) {
3720 qmldirDirectories.insert(absolutePath);
3724 appImports << qmldirDirectories.values();
3725 appImports.removeDuplicates();
3727 return appImports.count() + externalImports.count();
3730 struct ComponentInfo {
3739 QList<ComponentInfo> qmlComponents;
3740 bool isValid() {
return qmlComponents.size() && moduleName.size(); }
3743 const auto getModuleInfo = [](
const QString &qmldirPath) -> ModuleInfo {
3744 QFile qmlDirFile(qmldirPath +
"/qmldir"_L1);
3745 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3746 return ModuleInfo();
3747 ModuleInfo moduleInfo;
3748 QSet<QString> qmlComponentNames;
3749 QTextStream qmldirStream(&qmlDirFile);
3750 while (!qmldirStream.atEnd()) {
3751 const QString currentLine = qmldirStream.readLine();
3752 if (currentLine.size() && currentLine[0].isLower()) {
3754 if (currentLine.startsWith(
"module "_L1))
3755 moduleInfo.moduleName = currentLine.split(
" "_L1)[1];
3756 else if (currentLine.startsWith(
"prefer "_L1))
3757 moduleInfo.preferPath = currentLine.split(
" "_L1)[1];
3758 }
else if (currentLine.size()
3759 && (currentLine[0].isUpper() || currentLine.startsWith(
"singleton"_L1))) {
3760 const QStringList parts = currentLine.split(
" "_L1);
3761 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3762 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3763 qmlComponentNames.insert(parts.first());
3770 const auto extractDomInfo = [](
const QString &qmlDomExecPath,
const QString &qmldirPath,
3771 const QString &qmlFile,
3772 const QStringList &otherImportPaths) -> QJsonObject {
3774#if QT_CONFIG(process)
3775 QStringList qmlDomArgs {
"-d"_L1,
"-D"_L1,
"required"_L1,
"-f"_L1,
"+:propertyInfos"_L1 };
3776 for (
auto &importPath : otherImportPaths)
3777 qmlDomArgs <<
"-I"_L1 << importPath;
3778 qmlDomArgs <<
"%1/%2"_L1.arg(qmldirPath, qmlFile);
3779 const QString qmlDomCmd =
"%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3781 process.start(qmlDomExecPath, qmlDomArgs);
3782 if (!process.waitForStarted()) {
3783 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(qmlDomCmd));
3784 return QJsonObject();
3787 if (!process.waitForFinished(30000)) {
3788 fprintf(stderr,
"Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3789 return QJsonObject();
3791 domInfo = process.readAllStandardOutput();
3793 QJsonParseError jsonError;
3794 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3795 if (jsonError.error != QJsonParseError::NoError)
3796 fprintf(stderr,
"Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3797 return jsonDoc.object();
3799#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3800 return QJsonObject();
3804 const auto getComponent = [](
const QJsonObject &dom) -> QJsonObject {
3806 return QJsonObject();
3808 const QJsonObject currentItem = dom.value(
"currentItem"_L1).toObject();
3809 if (!currentItem.value(
"isValid"_L1).toBool(
false))
3810 return QJsonObject();
3812 const QJsonArray components =
3813 currentItem.value(
"components"_L1).toObject().value(
""_L1).toArray();
3814 if (components.isEmpty())
3815 return QJsonObject();
3816 return components.constBegin()->toObject();
3819 const auto getProperties = [](
const QJsonObject &component) -> QJsonArray {
3820 QJsonArray properties;
3821 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3822 if (objects.isEmpty())
3823 return QJsonArray();
3824 const QJsonObject propertiesObject =
3825 objects[0].toObject().value(
"propertyInfos"_L1).toObject();
3826 for (
const auto &jsonProperty : propertiesObject) {
3827 const QJsonArray propertyDefs =
3828 jsonProperty.toObject().value(
"propertyDefs"_L1).toArray();
3829 if (propertyDefs.isEmpty())
3832 properties.append(propertyDefs[0].toObject());
3837 const auto getMethods = [](
const QJsonObject &component) -> QJsonArray {
3839 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3840 if (objects.isEmpty())
3841 return QJsonArray();
3842 const QJsonObject methodsObject = objects[0].toObject().value(
"methods"_L1).toObject();
3843 for (
const auto &jsonMethod : methodsObject) {
3844 const QJsonArray overloads = jsonMethod.toArray();
3845 for (
const auto &m : overloads)
3851 const static QHash<QString, QString> qmlToJavaType = {
3852 {
"real"_L1,
"Double"_L1 }, {
"double"_L1,
"Double"_L1 }, {
"int"_L1,
"Integer"_L1 },
3853 {
"float"_L1,
"Float"_L1 }, {
"bool"_L1,
"Boolean"_L1 }, {
"string"_L1,
"String"_L1 },
3854 {
"void"_L1,
"Void"_L1 }
3857 const auto endBlock = [](QTextStream &stream,
int indentWidth = 0) {
3858 stream << QString(indentWidth, u' ') <<
"}\n";
3861 const auto createHeaderBlock = [](QTextStream &stream,
const QString &javaPackage) {
3862 stream <<
"/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3863 <<
"package %1;\n\n"_L1.arg(javaPackage)
3864 <<
"import org.qtproject.qt.android.QtSignalListener;\n"
3865 <<
"import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3868 const auto beginComponentBlock = [](QTextStream &stream,
const QString &libName,
3869 const QString &moduleName,
const QString &preferPath,
3870 const ComponentInfo &componentInfo,
int indentWidth = 8) {
3871 const QString indent(indentWidth, u' ');
3874 <<
"public final class %1 extends QtQuickViewContent {\n"_L1
3875 .arg(componentInfo.name)
3876 << indent <<
" @Override public String getLibraryName() {\n"_L1
3877 << indent <<
" return \"%1\";\n"_L1.arg(libName)
3878 << indent <<
" }\n"_L1
3879 << indent <<
" @Override public String getModuleName() {\n"_L1
3880 << indent <<
" return \"%1\";\n"_L1.arg(moduleName)
3881 << indent <<
" }\n"_L1
3882 << indent <<
" @Override public String getFilePath() {\n"_L1
3883 << indent <<
" return \"qrc%1%2\";\n"_L1.arg(preferPath)
3884 .arg(componentInfo.path)
3885 << indent <<
" }\n"_L1;
3888 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3889 const QJsonObject &propertyData,
3890 int indentWidth = 8) {
3891 const QString indent(indentWidth, u' ');
3892 const QString propertyName = propertyData[
"name"_L1].toString();
3893 if (propertyName.isEmpty())
3895 const QString upperPropertyName = firstCharToUpper(propertyName);
3896 const QString typeName = propertyData[
"typeName"_L1].toString();
3897 const bool isReadyonly = propertyData[
"isReadonly"_L1].toBool();
3899 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3903 <<
"public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3904 upperPropertyName, javaTypeName, propertyName);
3908 <<
"public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3909 .arg(upperPropertyName, javaTypeName, propertyName)
3911 <<
"public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3912 .arg(upperPropertyName, javaTypeName)
3914 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3915 propertyName, javaTypeName)
3919 enum class MethodType { Signal = 0, Function = 1 };
3921 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3922 const QJsonObject &methodData,
3923 int indentWidth = 8) {
3924 const QString indent(indentWidth, u' ');
3925 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Signal)
3927 const QJsonArray parameters = methodData[
"parameters"_L1].toArray();
3929 const QString methodName = methodData[
"name"_L1].toString();
3930 if (methodName.isEmpty())
3933 const QString upperMethodName = firstCharToUpper(methodName);
3934 if (parameters.size() <= 1) {
3935 const QString typeName = !parameters.isEmpty()
3936 ? parameters[0].toObject()[
"typeName"_L1].toString()
3938 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3940 <<
"public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3941 .arg(upperMethodName, javaTypeName)
3943 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3944 .arg(methodName, javaTypeName)
3948 const auto getJavaArgsString = [¶meters]() -> QString {
3949 QList<QString> javaArgsList;
3950 for (
const auto param : parameters) {
3951 const auto typeName = param[
"typeName"_L1].toString();
3952 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3953 const auto qmlParamName = param[
"name"_L1].toString();
3955 javaArgsList.emplace_back(
3956 QStringLiteral(
"%1%2").arg(javaTypeName,
" %1"_L1.arg(qmlParamName)));
3958 return javaArgsList.join(
", "_L1);
3961 const auto getJavaClassesString = [¶meters]() -> QString {
3962 QList<QString> javaArgsList;
3963 for (
const auto param : parameters) {
3964 const auto typeName = param[
"typeName"_L1].toString();
3965 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3967 javaArgsList.emplace_back(
3968 QStringLiteral(
"%1%2").arg(javaTypeName,
".class"_L1));
3970 return javaArgsList.join(
", "_L1);
3973 const auto javaParamsString = getJavaArgsString();
3974 const auto javaParamsClassesString = getJavaClassesString();
3977 QList<QString> objectToTypeConversion;
3978 for (
auto i = 0; i < parameters.size(); ++i) {
3979 const auto typeName = parameters.at(i).toObject().value(
"typeName"_L1).toString();
3980 objectToTypeConversion.emplace_back(
"(%1) args[%2]"_L1.arg(
3981 qmlToJavaType.value(typeName,
"Object"_L1), QString::number(i)));
3985 const auto signalInterfaceName =
"%1Listener"_L1.arg(methodName);
3986 const auto objectToTypeConversionString = objectToTypeConversion.join(
", "_L1);
3987 stream << indent <<
"@FunctionalInterface\n"
3988 << indent <<
"public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3989 <<
" default void onSignalEmitted(Object[] args) {\n"
3991 <<
" on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3994 <<
" void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3995 stream << indent <<
"}\n"_L1;
3999 <<
"public int connect%1(%2 signalListener) {\n"_L1.arg(
4000 firstCharToUpper(signalInterfaceName), signalInterfaceName)
4002 <<
" return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
4003 .arg(methodName, javaParamsClassesString)
4004 << indent <<
"}\n\n";
4008 const auto writeFunctionBlock = [](QTextStream &stream,
const QJsonObject &methodData,
4009 int indentWidth = 8) {
4010 const QString indent(indentWidth, u' ');
4011 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Function)
4014 const QJsonArray params = methodData[
"parameters"_L1].toArray();
4015 const QString functionName = methodData[
"name"_L1].toString();
4017 QList<QString> javaFunctionParams;
4018 QList<QString> javaParams;
4019 for (
const auto &value : params) {
4020 const auto object = value.toObject();
4021 if (!object.contains(
"typeName"_L1)) {
4022 qWarning() <<
" -- Skipping function" << functionName
4023 <<
"due to untyped function parameter detected while generating Java "
4024 "code for QML methods.";
4028 const auto qmlParamType = object[
"typeName"_L1].toString();
4029 if (!qmlToJavaType.contains(qmlParamType)) {
4030 qWarning() <<
" -- Skipping function" << functionName
4031 <<
"due to unsupported type detected in parameters:" << qmlParamType;
4035 const auto javaTypeName{ qmlToJavaType.value(object[
"typeName"_L1].toString(),
4037 const auto javaParamName = object[
"name"_L1].toString();
4038 javaFunctionParams.push_back(
4039 QString{
"%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
4040 javaParams.append(javaParamName);
4043 const auto functionSignature {
4044 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(
", "_L1))
4046 const auto functionCallParams {
4047 javaParams.isEmpty() ?
""_L1 :
", new Object[] { %1 }"_L1.arg(javaParams.join(
", "_L1))
4050 stream << indent << functionSignature
4051 << indent <<
" invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
4052 .arg(functionCallParams)
4056 constexpr static auto markerFileName =
"qml_java_contents"_L1;
4057 const QString libName(options.applicationBinary);
4058 QString javaPackageBase = options.packageName;
4059 const QString expectedBaseLeaf =
".%1"_L1.arg(libName);
4060 if (!javaPackageBase.endsWith(expectedBaseLeaf))
4061 javaPackageBase += expectedBaseLeaf;
4062 const QString baseSourceDir =
"%1/src/%2"_L1.arg(options.outputDirectory,
4063 QString(javaPackageBase).replace(u'.', u'/'));
4064 const QString buildPath(QDir(options.buildDirectory).absolutePath());
4067 fprintf(stdout,
"Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
4068 if (!QDir().current().mkpath(baseSourceDir)) {
4069 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(baseSourceDir));
4073 QStringList appImports;
4074 QStringList externalImports;
4075 if (!getImportPaths(buildPath, libName, appImports, externalImports))
4080 const QString srcDir =
"%1/src"_L1.arg(options.outputDirectory);
4081 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
4082 while (iter.hasNext())
4083 iter.nextFileInfo().dir().removeRecursively();
4086 int generatedComponents = 0;
4087 for (
const auto &importPath : appImports) {
4088 ModuleInfo moduleInfo = getModuleInfo(importPath);
4089 if (!moduleInfo.isValid())
4092 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
4093 if (moduleInfo.moduleName == libName) {
4095 "A QML module name (%s) cannot be the same as the target name when building "
4096 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4097 qPrintable(moduleInfo.moduleName));
4101 const QString javaPackage =
"%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4102 const QString outputDir =
4103 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4104 if (!QDir().current().mkpath(outputDir)) {
4105 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(outputDir));
4111 QFile markerFile(
"%1/%2"_L1.arg(outputDir, markerFileName));
4112 if (!markerFile.open(QFile::WriteOnly)) {
4113 fprintf(stderr,
"Cannot create %s file\n", qPrintable(markerFile.fileName()));
4120 for (
const auto &qmlComponent : moduleInfo.qmlComponents) {
4121 const bool isSelected = options.selectedJavaQmlComponents.contains(
4122 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4123 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4126 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4127 externalImports + appImports);
4128 QJsonObject component = getComponent(domInfo);
4129 if (component.isEmpty())
4132 QByteArray componentClassBody;
4133 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4135 createHeaderBlock(outputStream, javaPackage);
4137 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4138 qmlComponent, indentBase);
4141 const QJsonArray properties = getProperties(component);
4142 for (
const QJsonValue &p : std::as_const(properties))
4143 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4145 const QJsonArray methods = getMethods(component);
4146 for (
const QJsonValue &m : std::as_const(methods))
4147 beginSignalBlock(outputStream, m.toObject(), indentBase);
4149 for (
const QJsonValue &m : std::as_const(methods))
4150 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4153 endBlock(outputStream, indentBase);
4154 outputStream.flush();
4157 QFile outputFile(
"%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4158 if (outputFile.exists())
4159 outputFile.remove();
4160 if (!outputFile.open(QFile::WriteOnly)) {
4161 fprintf(stderr,
"Cannot open %s file to write.\n",
4162 qPrintable(outputFile.fileName()));
4165 outputFile.write(componentClassBody);
4168 generatedComponents++;
4171 return generatedComponents;
4174int main(
int argc,
char *argv[])
4176 QCoreApplication a(argc, argv);
4179 if (options
.helpRequested || options.outputDirectory.isEmpty()) {
4181 return SyntaxErrorOrHelpRequested;
4184 options.timer.start();
4186 if (!readInputFile(&options))
4187 return CannotReadInputFile;
4189 if (Q_UNLIKELY(options
.timing))
4190 fprintf(stdout,
"[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4193 "Generating Android Package\n"
4195 " Output directory: %s\n"
4196 " Application binary: %s\n"
4197 " Android build platform: %s\n"
4198 " Install to device: %s\n",
4199 qPrintable(options.inputFileName),
4200 qPrintable(options.outputDirectory),
4201 qPrintable(options.applicationBinary),
4202 qPrintable(options.androidPlatform),
4204 ? (options.installLocation.isEmpty() ?
"Default device" : qPrintable(options.installLocation))
4208 bool androidTemplatetCopied =
false;
4210 for (
auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4213 options.setCurrentQtArchitecture(it.key(),
4214 it.value().qtInstallDirectory,
4215 it.value().qtDirectories);
4219 cleanAndroidFiles(options);
4220 if (Q_UNLIKELY(options
.timing))
4221 fprintf(stdout,
"[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4223 if (!copyAndroidTemplate(options))
4224 return CannotCopyAndroidTemplate;
4226 if (Q_UNLIKELY(options
.timing))
4227 fprintf(stdout,
"[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4228 androidTemplatetCopied =
true;
4231 if (!readDependencies(&options))
4232 return CannotReadDependencies;
4234 if (Q_UNLIKELY(options
.timing))
4235 fprintf(stdout,
"[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4237 if (!copyQtFiles(&options))
4238 return CannotCopyQtFiles;
4240 if (Q_UNLIKELY(options
.timing))
4241 fprintf(stdout,
"[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4243 if (!copyAndroidExtraLibs(&options))
4244 return CannotCopyAndroidExtraLibs;