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'/';
621 the device, it will be overwritten, but its data will be left
622 intact.
623
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
674 dependencies into the build directory and update the XML templates.
675 The project will not be built or installed.
676
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)
710 QString s1 = fi1.baseName();
711 QString s2 = fi2.baseName();
713 if (s1.size() == s2.size())
716 return s1.size() > s2.size();
909 if (fileInfos.isEmpty()) {
910 fprintf(stderr,
"No platforms found in %s", qPrintable(dir.absolutePath()));
914 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
916 const QFileInfo& latestPlatform = fileInfos.constFirst();
917 return latestPlatform.baseName();
920QString extractPackageName(
Options *options)
923 const QString gradleBuildFile = options->androidSourceDirectory +
"/build.gradle"_L1;
924 QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace;
926 if (!packageName.isEmpty() && packageName !=
"androidPackageName"_L1)
930 QFile androidManifestXml(options->androidSourceDirectory +
"/AndroidManifest.xml"_L1);
931 if (androidManifestXml.open(QIODevice::ReadOnly)) {
932 QXmlStreamReader reader(&androidManifestXml);
933 while (!reader.atEnd()) {
935 if (reader.isStartElement() && reader.name() ==
"manifest"_L1) {
936 QString packageName = reader.attributes().value(
"package"_L1).toString();
937 if (!packageName.isEmpty() && packageName !=
"org.qtproject.example"_L1)
947bool parseCmakeBoolean(
const QJsonValue &value)
949 const QString stringValue = value.toString();
950 return (stringValue.compare(QString::fromUtf8(
"true"), Qt::CaseInsensitive)
951 || stringValue.compare(QString::fromUtf8(
"on"), Qt::CaseInsensitive)
952 || stringValue.compare(QString::fromUtf8(
"yes"), Qt::CaseInsensitive)
953 || stringValue.compare(QString::fromUtf8(
"y"), Qt::CaseInsensitive)
954 || stringValue.toInt() > 0);
957bool readInputFileDirectory(
Options *options, QJsonObject &jsonObject,
const QString keyName)
959 const QJsonValue qtDirectory = jsonObject.value(keyName);
960 if (qtDirectory.isUndefined()) {
961 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
962 if (keyName ==
"qtDataDirectory"_L1) {
963 options->architectures[it.key()].qtDirectories[keyName] =
"."_L1;
965 }
else if (keyName ==
"qtLibsDirectory"_L1) {
966 options->architectures[it.key()].qtDirectories[keyName] =
"lib"_L1;
968 }
else if (keyName ==
"qtLibExecsDirectory"_L1) {
969 options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
971 }
else if (keyName ==
"qtPluginsDirectory"_L1) {
972 options->architectures[it.key()].qtDirectories[keyName] =
"plugins"_L1;
974 }
else if (keyName ==
"qtQmlDirectory"_L1) {
975 options->architectures[it.key()].qtDirectories[keyName] =
"qml"_L1;
982 if (qtDirectory.isObject()) {
983 const QJsonObject object = qtDirectory.toObject();
984 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
985 if (it.value().isUndefined()) {
987 "Invalid '%s' record in deployment settings: %s\n",
989 qPrintable(it.value().toString()));
992 if (it.value().isNull())
994 if (!options->architectures.contains(it.key())) {
995 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
996 qPrintable(options->architectures.keys().join(u',')));
999 options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
1001 }
else if (qtDirectory.isString()) {
1004 const QString directory = qtDirectory.toString();
1005 options->architectures[
"arm64-v8a"_L1].qtDirectories[keyName] = directory;
1006 options->architectures[
"armeabi-v7a"_L1].qtDirectories[keyName] = directory;
1007 options->architectures[
"x86"_L1].qtDirectories[keyName] = directory;
1008 options->architectures[
"x86_64"_L1].qtDirectories[keyName] = directory;
1010 fprintf(stderr,
"Invalid format for %s in json file %s.\n",
1011 qPrintable(keyName), qPrintable(options->inputFileName));
1017bool readInputFile(
Options *options)
1019 QFile file(options->inputFileName);
1020 if (!file.open(QIODevice::ReadOnly)) {
1021 fprintf(stderr,
"Cannot read from input file: %s\n", qPrintable(options->inputFileName));
1026 QJsonParseError jsonParseError;
1027 QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll(), &jsonParseError);
1028 if (jsonDocument.isNull()) {
1029 fprintf(stderr,
"Invalid json file: %s. Reason: %s at offset %i.\n",
1030 qPrintable(options->inputFileName),
1031 qPrintable(jsonParseError.errorString()),
1032 jsonParseError.offset);
1036 QJsonObject jsonObject = jsonDocument.object();
1039 QJsonValue sdkPath = jsonObject.value(
"sdk"_L1);
1040 if (sdkPath.isUndefined()) {
1041 fprintf(stderr,
"No SDK path in json file %s\n", qPrintable(options->inputFileName));
1045 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
1050 if (options->androidPlatform.isEmpty()) {
1051 const QJsonValue ver = jsonObject.value(
"android-compile-sdk-version"_L1);
1052 if (!ver.isUndefined()) {
1053 const auto value = ver.toString();
1054 options->androidPlatform = value.startsWith(
"android-"_L1) ?
1055 value :
"android-%1"_L1.arg(value);
1058 if (options->androidPlatform.isEmpty()) {
1059 options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
1060 if (options->androidPlatform.isEmpty())
1065 if (!QDir(options->sdkPath +
"/platforms/"_L1 + options->androidPlatform).exists()) {
1066 fprintf(stderr,
"Warning: Android platform '%s' does not exist in SDK.\n",
1067 qPrintable(options->androidPlatform));
1073 const QJsonValue value = jsonObject.value(
"sdkBuildToolsRevision"_L1);
1074 if (!value.isUndefined())
1075 options->sdkBuildToolsVersion = value.toString();
1079 const QJsonValue qtInstallDirectory = jsonObject.value(
"qt"_L1);
1080 if (qtInstallDirectory.isUndefined()) {
1081 fprintf(stderr,
"No Qt directory in json file %s\n", qPrintable(options->inputFileName));
1085 if (qtInstallDirectory.isObject()) {
1086 const QJsonObject object = qtInstallDirectory.toObject();
1087 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
1088 if (it.value().isUndefined()) {
1090 "Invalid 'qt' record in deployment settings: %s\n",
1091 qPrintable(it.value().toString()));
1094 if (it.value().isNull())
1096 options->architectures.insert(it.key(),
1099 }
else if (qtInstallDirectory.isString()) {
1102 const QString directory = qtInstallDirectory.toString();
1104 options->architectures.insert(
"arm64-v8a"_L1, qtInstallDirectoryWithTriple);
1105 options->architectures.insert(
"armeabi-v7a"_L1, qtInstallDirectoryWithTriple);
1106 options->architectures.insert(
"x86"_L1, qtInstallDirectoryWithTriple);
1107 options->architectures.insert(
"x86_64"_L1, qtInstallDirectoryWithTriple);
1111 options->qtHostDirectory = directory;
1113 fprintf(stderr,
"Invalid format for Qt install prefixes in json file %s.\n",
1114 qPrintable(options->inputFileName));
1119 if (!readInputFileDirectory(options, jsonObject,
"qtDataDirectory"_L1) ||
1120 !readInputFileDirectory(options, jsonObject,
"qtLibsDirectory"_L1) ||
1121 !readInputFileDirectory(options, jsonObject,
"qtLibExecsDirectory"_L1) ||
1122 !readInputFileDirectory(options, jsonObject,
"qtPluginsDirectory"_L1) ||
1123 !readInputFileDirectory(options, jsonObject,
"qtQmlDirectory"_L1))
1127 const QJsonValue qtHostDirectory = jsonObject.value(
"qtHostDir"_L1);
1128 if (!qtHostDirectory.isUndefined()) {
1129 if (qtHostDirectory.isString()) {
1130 options->qtHostDirectory = qtHostDirectory.toString();
1132 fprintf(stderr,
"Invalid format for Qt host directory in json file %s.\n",
1133 qPrintable(options->inputFileName));
1140 const auto extraPrefixDirs = jsonObject.value(
"extraPrefixDirs"_L1).toArray();
1141 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
1142 for (
const QJsonValue prefix : extraPrefixDirs) {
1143 options->extraPrefixDirs.push_back(prefix.toString());
1148 const auto androidDeployPlugins = jsonObject.value(
"android-deploy-plugins"_L1).toString();
1149 options->androidDeployPlugins = androidDeployPlugins.split(
";"_L1, Qt::SkipEmptyParts);
1153 const auto extraLibraryDirs = jsonObject.value(
"extraLibraryDirs"_L1).toArray();
1154 options->extraLibraryDirs.reserve(extraLibraryDirs.size());
1155 for (
const QJsonValue path : extraLibraryDirs) {
1156 options->extraLibraryDirs.push_back(path.toString());
1161 const QJsonValue androidSourcesDirectory = jsonObject.value(
"android-package-source-directory"_L1);
1162 if (!androidSourcesDirectory.isUndefined())
1163 options->androidSourceDirectory = androidSourcesDirectory.toString();
1167 const QJsonValue applicationArguments = jsonObject.value(
"android-application-arguments"_L1);
1168 if (!applicationArguments.isUndefined())
1169 options->applicationArguments = applicationArguments.toString();
1171 options->applicationArguments = QStringLiteral(
"");
1175 const QJsonValue androidVersionName = jsonObject.value(
"android-version-name"_L1);
1176 if (!androidVersionName.isUndefined())
1177 options->versionName = androidVersionName.toString();
1179 options->versionName = QStringLiteral(
"1.0");
1183 const QJsonValue androidVersionCode = jsonObject.value(
"android-version-code"_L1);
1184 if (!androidVersionCode.isUndefined())
1185 options->versionCode = androidVersionCode.toString();
1187 options->versionCode = QStringLiteral(
"1");
1191 const QJsonValue ver = jsonObject.value(
"android-min-sdk-version"_L1);
1192 if (!ver.isUndefined())
1193 options->minSdkVersion = ver.toString().toUtf8();
1197 const QJsonValue ver = jsonObject.value(
"android-target-sdk-version"_L1);
1198 if (!ver.isUndefined())
1199 options->targetSdkVersion = ver.toString().toUtf8();
1203 if (
const auto abi = jsonObject.value(
"abi"_L1); !abi.isUndefined())
1204 options->abi = jsonObject.value(
"abi"_L1).toString();
1208 const QJsonObject targetArchitectures = jsonObject.value(
"architectures"_L1).toObject();
1209 if (targetArchitectures.isEmpty()) {
1210 fprintf(stderr,
"No target architecture defined in json file.\n");
1213 for (
auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
1214 if (it.value().isUndefined()) {
1215 fprintf(stderr,
"Invalid architecture.\n");
1218 if (it.value().isNull())
1220 if (!options->architectures.contains(it.key())) {
1221 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
1222 qPrintable(options->architectures.keys().join(u',')));
1225 options->architectures[it.key()].triple = it.value().toString();
1226 options->architectures[it.key()].enabled =
true;
1231 const QJsonValue ndk = jsonObject.value(
"ndk"_L1);
1232 if (ndk.isUndefined()) {
1233 fprintf(stderr,
"No NDK path defined in json file.\n");
1236 options->ndkPath = ndk.toString();
1237 const QString ndkPropertiesPath = options->ndkPath + QStringLiteral(
"/source.properties");
1238 const QSettings settings(ndkPropertiesPath, QSettings::IniFormat);
1239 const QString ndkVersion = settings.value(QStringLiteral(
"Pkg.Revision")).toString();
1240 if (ndkVersion.isEmpty()) {
1241 fprintf(stderr,
"Couldn't retrieve the NDK version from \"%s\".\n",
1242 qPrintable(ndkPropertiesPath));
1245 options->ndkVersion = ndkVersion;
1249 const QJsonValue toolchainPrefix = jsonObject.value(
"toolchain-prefix"_L1);
1250 if (toolchainPrefix.isUndefined()) {
1251 fprintf(stderr,
"No toolchain prefix defined in json file.\n");
1254 options->toolchainPrefix = toolchainPrefix.toString();
1258 const QJsonValue ndkHost = jsonObject.value(
"ndk-host"_L1);
1259 if (ndkHost.isUndefined()) {
1260 fprintf(stderr,
"No NDK host defined in json file.\n");
1263 options->ndkHost = ndkHost.toString();
1267 const QJsonValue extraLibs = jsonObject.value(
"android-extra-libs"_L1);
1268 if (!extraLibs.isUndefined())
1269 options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts);
1273 const QJsonValue qmlSkipImportScanning = jsonObject.value(
"qml-skip-import-scanning"_L1);
1274 if (!qmlSkipImportScanning.isUndefined())
1279 const QJsonValue extraPlugins = jsonObject.value(
"android-extra-plugins"_L1);
1280 if (!extraPlugins.isUndefined())
1281 options->extraPlugins = extraPlugins.toString().split(u',');
1285 const QJsonValue systemLibsPath =
1286 jsonObject.value(
"android-system-libs-prefix"_L1);
1287 if (!systemLibsPath.isUndefined())
1288 options->systemLibsPath = systemLibsPath.toString();
1292 const QJsonValue noDeploy = jsonObject.value(
"android-no-deploy-qt-libs"_L1);
1293 if (!noDeploy.isUndefined()) {
1294 bool useUnbundled = parseCmakeBoolean(noDeploy);
1301 const QJsonValue stdcppPath = jsonObject.value(
"stdcpp-path"_L1);
1302 if (stdcppPath.isUndefined()) {
1303 fprintf(stderr,
"No stdcpp-path defined in json file.\n");
1306 options->stdCppPath = stdcppPath.toString();
1310 const QJsonValue qmlRootPath = jsonObject.value(
"qml-root-path"_L1);
1311 if (qmlRootPath.isString()) {
1312 options->rootPaths.push_back(qmlRootPath.toString());
1313 }
else if (qmlRootPath.isArray()) {
1314 auto qmlRootPaths = qmlRootPath.toArray();
1315 for (
auto path : qmlRootPaths) {
1316 if (path.isString())
1317 options->rootPaths.push_back(path.toString());
1320 options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
1325 const QJsonValue qmlImportPaths = jsonObject.value(
"qml-import-paths"_L1);
1326 if (!qmlImportPaths.isUndefined())
1327 options->qmlImportPaths = qmlImportPaths.toString().split(u',');
1331 const QJsonValue qmlImportScannerBinaryPath = jsonObject.value(
"qml-importscanner-binary"_L1);
1332 if (!qmlImportScannerBinaryPath.isUndefined())
1333 options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString();
1337 const QJsonValue rccBinaryPath = jsonObject.value(
"rcc-binary"_L1);
1338 if (!rccBinaryPath.isUndefined())
1339 options->rccBinaryPath = rccBinaryPath.toString();
1343 const QJsonValue genJavaQmlComponents = jsonObject.value(
"generate-java-qtquickview-contents"_L1);
1344 if (!genJavaQmlComponents.isUndefined() && genJavaQmlComponents.isBool()) {
1348 "Warning: Skipping the generation of Java QtQuickView contents from QML "
1349 "as it can be enabled only for an AAR target.\n");
1356 const QJsonValue qmlDomBinaryPath = jsonObject.value(
"qml-dom-binary"_L1);
1357 if (!qmlDomBinaryPath.isUndefined()) {
1358 options->qmlDomBinaryPath = qmlDomBinaryPath.toString();
1361 "No qmldom binary defined in json file which is required when "
1362 "building with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n");
1368 const QJsonValue qmlFiles = jsonObject.value(
"qml-files-for-code-generator"_L1);
1369 if (!qmlFiles.isUndefined() && qmlFiles.isArray()) {
1370 const QJsonArray jArray = qmlFiles.toArray();
1371 for (
auto &item : jArray)
1372 options->selectedJavaQmlComponents << item.toString();
1377 const QJsonValue applicationBinary = jsonObject.value(
"application-binary"_L1);
1378 if (applicationBinary.isUndefined()) {
1379 fprintf(stderr,
"No application binary defined in json file.\n");
1382 options->applicationBinary = applicationBinary.toString();
1384 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1387 auto appBinaryPath =
"%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary);
1388 if (!QFile::exists(appBinaryPath)) {
1389 fprintf(stderr,
"Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
1397 const QJsonValue androidPackageName = jsonObject.value(
"android-package-name"_L1);
1398 const QString extractedPackageName = extractPackageName(options);
1399 if (!extractedPackageName.isEmpty())
1400 options->packageName = extractedPackageName;
1401 else if (!androidPackageName.isUndefined())
1402 options->packageName = androidPackageName.toString();
1404 options->packageName =
"org.qtproject.example.%1"_L1.arg(options->applicationBinary);
1407 options->packageName = cleanPackageName(options->packageName, &cleaned);
1409 fprintf(stderr,
"Warning: Package name contained illegal characters and was cleaned "
1410 "to \"%s\"\n", qPrintable(options->packageName));
1415 const QJsonValue androidAppName = jsonObject.value(
"android-app-name"_L1);
1416 if (!androidAppName.isUndefined())
1417 options->appName = androidAppName.toString();
1419 options->appName = options->applicationBinary;
1724 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1725 const auto files = current.entryList(QDir::Files);
1726 result.reserve(dirs.size() + files.size());
1727 for (
const QString &dir : dirs) {
1728 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1730 for (
const QString &file : files) {
1731 result += rootDir.relativeFilePath(current.filePath(file));
1736bool copyAndroidExtraResources(
Options *options)
1738 if (options->extraPlugins.isEmpty())
1742 fprintf(stdout,
"Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1744 for (
const QString &extraResource : options->extraPlugins) {
1745 QFileInfo extraResourceInfo(extraResource);
1746 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1747 fprintf(stderr,
"External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1751 QDir resourceDir(extraResource);
1752 QString assetsDir = options->outputDirectory +
"/assets/"_L1 +
1753 resourceDir.dirName() + u'/';
1754 QString libsDir = options->outputDirectory +
"/libs/"_L1 + options->currentArchitecture + u'/';
1756 const QStringList files = allFilesInside(resourceDir, resourceDir);
1757 for (
const QString &resourceFile : files) {
1758 QString originFile(resourceDir.filePath(resourceFile));
1759 QString destinationFile;
1760 if (!resourceFile.endsWith(
".so"_L1)) {
1761 destinationFile = assetsDir + resourceFile;
1763 if (isDeployment(options, Options::Unbundled)
1764 || !checkArchitecture(*options, originFile)) {
1767 destinationFile = libsDir + resourceFile;
1768 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1771 if (!copyFileIfNewer(originFile, destinationFile,
1772 *options, options->createSymlinksOnly)) {
1781bool updateFile(
const QString &fileName,
const QHash<QString, QString> &replacements)
1783 QFile inputFile(fileName);
1784 if (!inputFile.open(QIODevice::ReadOnly)) {
1785 fprintf(stderr,
"Cannot open %s for reading.\n", qPrintable(fileName));
1791 QByteArray contents = inputFile.readAll();
1793 bool hasReplacements =
false;
1794 QHash<QString, QString>::const_iterator it;
1795 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1796 if (it.key() == it.value())
1800 int index = contents.indexOf(it.key().toUtf8());
1802 contents.replace(index, it.key().size(), it.value().toUtf8());
1803 hasReplacements =
true;
1810 if (hasReplacements) {
1813 if (!inputFile.open(QIODevice::WriteOnly)) {
1814 fprintf(stderr,
"Cannot open %s for writing.\n", qPrintable(fileName));
1820 QRegularExpression emptyLinesRegex(
"\\n\\s+\\n"_L1);
1821 contents = QString::fromUtf8(contents).replace(emptyLinesRegex,
"\n"_L1).toUtf8();
1823 inputFile.write(contents);
1830bool updateLibsXml(
Options *options)
1833 fprintf(stdout,
" -- res/values/libs.xml\n");
1835 QString fileName = options->outputDirectory +
"/res/values/libs.xml"_L1;
1836 if (!QFile::exists(fileName)) {
1837 fprintf(stderr,
"Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1842 QString allLocalLibs;
1845 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
2119 for (
const auto &prefix : options->extraPrefixDirs) {
2120 const QString path = prefix + u'/' + relativeFileName;
2121 if (QFile::exists(path))
2125 if (relativeFileName.endsWith(
"-android-dependencies.xml"_L1)) {
2126 for (
const auto &dir : options->extraLibraryDirs) {
2127 const QString path = dir + u'/' + relativeFileName;
2128 if (QFile::exists(path))
2131 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2132 u'/' + relativeFileName;
2135 if (relativeFileName.startsWith(
"jar/"_L1)) {
2136 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2137 u'/' + relativeFileName;
2140 if (relativeFileName.startsWith(
"lib/"_L1)) {
2141 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2142 u'/' + relativeFileName.mid(
sizeof(
"lib/") - 1);
2144 return options->qtInstallDirectory + u'/' + relativeFileName;
2147QList<QtDependency> findFilesRecursively(
const Options &options,
const QFileInfo &info,
const QString &rootPath)
2153 QList<QtDependency> ret;
2155 QDir dir(info.filePath());
2156 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2158 for (
const QString &entry : entries) {
2159 ret += findFilesRecursively(options,
2160 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2166 return QList<
QtDependency>() <<
QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2170QList<QtDependency> findFilesRecursively(
const Options &options,
const QString &fileName)
2178 QList<QtDependency> deps;
2179 for (
const auto &prefix : options.extraPrefixDirs) {
2180 QFileInfo info(prefix + u'/' + fileName);
2181 if (info.exists()) {
2183 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2185 return findFilesRecursively(options, info, prefix + u'/');
2190 if (
std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2191 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2192 QFileInfo info(options.qtInstallDirectory +
"/"_L1 + fileName);
2193 QFileInfo rootPath(options.qtInstallDirectory +
"/"_L1);
2194 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2199void readDependenciesFromFiles(
Options *options,
const QList<QtDependency> &files,
2200 QSet<QString> &usedDependencies,
2201 QSet<QString> &remainingDependencies)
2203 for (
const QtDependency &fileName : files) {
2204 if (usedDependencies.contains(fileName.absolutePath))
2207 if (fileName.absolutePath.endsWith(
".so"_L1)) {
2208 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2306 if (reader.hasError()) {
2307 fprintf(stderr,
"Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2311 fprintf(stdout,
"No android dependencies for %s\n", qPrintable(moduleName));
2313 options->features.removeDuplicates();
2318QStringList getQtLibsFromElf(
const Options &options,
const QString &fileName)
2320 QString readElf = llvmReadobjPath(options);
2321 if (!QFile::exists(readElf)) {
2322 fprintf(stderr,
"Command does not exist: %s\n", qPrintable(readElf));
2323 return QStringList();
2326 readElf =
"%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2328 auto readElfCommand = openProcess(readElf);
2329 if (!readElfCommand) {
2330 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(readElf));
2331 return QStringList();
2336 bool readLibs =
false;
2338 while (fgets(buffer,
sizeof(buffer), readElfCommand.get()) !=
nullptr) {
2339 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2341 line = line.trimmed();
2343 if (line.startsWith(
"Arch: ")) {
2344 auto it = elfArchitectures.find(line.mid(6));
2345 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2347 fprintf(stdout,
"Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2351 readLibs = line.startsWith(
"NeededLibraries");
2354 if (!line.startsWith(
"lib"))
2356 library = QString::fromLatin1(line);
2357 QString libraryName =
"lib/"_L1 + library;
2358 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2366 const QString &fileName,
2367 QSet<QString> *usedDependencies,
2368 QSet<QString> *remainingDependencies)
2371 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2374 fprintf(stdout,
"Reading dependencies from %s\n", qPrintable(fileName));
2375 for (
const QString &dep : dependencies)
2376 fprintf(stdout,
" %s\n", qPrintable(dep));
2379 QList<QString> dependenciesToCheck;
2380 for (
const QString &dependency : dependencies) {
2381 if (usedDependencies->contains(dependency))
2384 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2385 usedDependencies->insert(dependency);
2386 if (!readDependenciesFromElf(options,
2387 absoluteDependencyPath,
2389 remainingDependencies)) {
2393 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2394 if (options->verbose)
2395 fprintf(stdout,
"Appending dependency: %s\n", qPrintable(dependency));
2396 dependenciesToCheck.append(dependency);
2399 for (
const QString &dependency : std::as_const(dependenciesToCheck)) {
2400 QString qtBaseName = dependency.mid(
sizeof(
"lib/lib") - 1);
2401 qtBaseName = qtBaseName.left(qtBaseName.size() - (
sizeof(
".so") - 1));
2402 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2410bool scanImports(
Options *options, QSet<QString> *usedDependencies)
2413 fprintf(stdout,
"Scanning for QML imports.\n");
2415 QString qmlImportScanner;
2416 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2417 qmlImportScanner = options->qmlImportScannerBinaryPath;
2419 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2420 "/qmlimportscanner"_L1);
2423 QStringList importPaths;
2429 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2430 if (QFile::exists(mainImportPath))
2431 importPaths += shellQuote(mainImportPath);
2435 for (
const QString &prefix : options->extraPrefixDirs)
2436 if (QFile::exists(prefix +
"/qml"_L1))
2437 importPaths += shellQuote(prefix +
"/qml"_L1);
2440 for (
const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2441 if (QFile::exists(qmlImportPath)) {
2442 importPaths += shellQuote(qmlImportPath);
2444 fprintf(stderr,
"Warning: QML import path %s does not exist.\n",
2445 qPrintable(qmlImportPath));
2449 bool qmlImportExists =
false;
2451 for (
const QString &import : importPaths) {
2452 if (QDir().exists(import)) {
2453 qmlImportExists =
true;
2460 if (!qmlImportExists) {
2461 fprintf(stderr,
"Warning: no 'qml' directory found under Qt install directory "
2462 "or import paths. Skipping QML dependency scanning.\n");
2466 if (!QFile::exists(qmlImportScanner)) {
2467 fprintf(stderr,
"%s: qmlimportscanner not found at %s\n",
2468 qmlImportExists ?
"Error"_L1.data() :
"Warning"_L1.data(),
2469 qPrintable(qmlImportScanner));
2473 for (
auto rootPath : options->rootPaths) {
2474 rootPath = QFileInfo(rootPath).absoluteFilePath();
2476 if (!rootPath.endsWith(u'/'))
2480 if (!rootPath.isEmpty())
2481 importPaths += shellQuote(rootPath);
2483 qmlImportScanner +=
" -rootPath %1"_L1.arg(shellQuote(rootPath));
2486 if (!options->qrcFiles.isEmpty()) {
2487 qmlImportScanner +=
" -qrcFiles"_L1;
2488 for (
const QString &qrcFile : options->qrcFiles)
2489 qmlImportScanner += u' ' + shellQuote(qrcFile);
2492 qmlImportScanner +=
" -importPath %1"_L1.arg(importPaths.join(u' '));
2495 fprintf(stdout,
"Running qmlimportscanner with the following command: %s\n",
2496 qmlImportScanner.toLocal8Bit().constData());
2499 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2500 if (qmlImportScannerCommand == 0) {
2501 fprintf(stderr,
"Couldn't run qmlimportscanner.\n");
2507 while (fgets(buffer,
sizeof(buffer), qmlImportScannerCommand.get()) !=
nullptr)
2508 output += QByteArray(buffer, qstrlen(buffer));
2510 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2511 if (jsonDocument.isNull()) {
2512 fprintf(stderr,
"Invalid json output from qmlimportscanner.\n");
2516 QJsonArray jsonArray = jsonDocument.array();
2517 for (
int i=0; i<jsonArray.count(); ++i) {
2518 QJsonValue value = jsonArray.at(i);
2519 if (!value.isObject()) {
2520 fprintf(stderr,
"Invalid format of qmlimportscanner output.\n");
2524 QJsonObject object = value.toObject();
2525 QString path = object.value(
"path"_L1).toString();
2526 if (path.isEmpty()) {
2527 fprintf(stderr,
"Warning: QML import could not be resolved in any of the import paths: %s\n",
2528 qPrintable(object.value(
"name"_L1).toString()));
2529 }
else if (object.value(
"type"_L1).toString() ==
"module"_L1) {
2966 const QString path = QString::fromLocal8Bit(qgetenv(
"PATH"));
2967#if defined(Q_OS_WIN32)
2968 QLatin1Char separator(
';');
2970 QLatin1Char separator(
':');
2973 const QStringList paths = path.split(separator);
2974 for (
const QString &path : paths) {
2975 QFileInfo fileInfo(path + u'/' + fileName);
2976 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2977 return path + u'/' + fileName;
2983typedef QMap<QByteArray, QByteArray> GradleProperties;
2985static GradleProperties readGradleProperties(
const QString &path)
2987 GradleProperties properties;
2989 if (!file.open(QIODevice::ReadOnly))
2992 const auto lines = file.readAll().split(
'\n');
2993 for (
const QByteArray &line : lines) {
2994 if (line.trimmed().startsWith(
'#'))
2997 const int idx = line.indexOf(
'=');
2999 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
3005static bool mergeGradleProperties(
const QString &path, GradleProperties properties)
3007 const QString oldPathStr = path + u'~';
3008 QFile::remove(oldPathStr);
3009 QFile::rename(path, oldPathStr);
3011 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
3012 fprintf(stderr,
"Can't open file: %s for writing\n", qPrintable(file.fileName()));
3016 QFile oldFile(oldPathStr);
3017 if (oldFile.open(QIODevice::ReadOnly)) {
3019 while (oldFile.readLineInto(&line)) {
3020 QList<QByteArray> prop(line.split(
'='));
3021 if (prop.size() > 1) {
3022 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3023 if (it != properties.end()) {
3024 file.write(it.key() +
'=' + it.value() +
'\n');
3025 properties.erase(it);
3029 file.write(line.trimmed() +
'\n');
3032 QFile::remove(oldPathStr);
3035 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3036 file.write(it.key() +
'=' + it.value() +
'\n');
3042#if defined(Q_OS_WIN32)
3043void checkAndWarnGradleLongPaths(
const QString &outputDirectory)
3045 QStringList longFileNames;
3046 using F = QDirListing::IteratorFlag;
3047 for (
const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3048 F::FilesOnly | F::Recursive)) {
3049 if (dirEntry.size() >= MAX_PATH)
3050 longFileNames.append(dirEntry.filePath());
3053 if (!longFileNames.isEmpty()) {
3055 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3056 "Consider moving your project to reduce its path length.\n"
3057 "The following files have too long paths:\n%s.\n",
3058 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3063bool buildAndroidProject(
const Options &options)
3065 GradleProperties localProperties;
3066 localProperties[
"sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3067 const QString localPropertiesPath = options.outputDirectory +
"local.properties"_L1;
3068 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3071 const QString gradlePropertiesPath = options.outputDirectory +
"gradle.properties"_L1;
3072 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3074 const QString gradleBuildFilePath = options.outputDirectory +
"build.gradle"_L1;
3075 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3314 options->currentArchitecture,
3315 options->stdCppName);
3316 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3319static QString zipalignPath(
const Options &options,
bool *ok)
3322 QString zipAlignTool = execSuffixAppended(options.sdkPath +
"/tools/zipalign"_L1);
3323 if (!QFile::exists(zipAlignTool)) {
3324 zipAlignTool = execSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3325 options.sdkBuildToolsVersion +
"/zipalign"_L1);
3326 if (!QFile::exists(zipAlignTool)) {
3327 fprintf(stderr,
"zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3332 return zipAlignTool;
3335bool signAAB(
const Options &options)
3338 fprintf(stdout,
"Signing Android package.\n");
3340 QString jdkPath = options.jdkPath;
3342 if (jdkPath.isEmpty())
3343 jdkPath = QString::fromLocal8Bit(qgetenv(
"JAVA_HOME"));
3345 QString jarSignerTool = execSuffixAppended(
"jarsigner"_L1);
3346 if (jdkPath.isEmpty() || !QFile::exists(jdkPath +
"/bin/"_L1 + jarSignerTool))
3347 jarSignerTool = findInPath(jarSignerTool);
3349 jarSignerTool = jdkPath +
"/bin/"_L1 + jarSignerTool;
3351 if (!QFile::exists(jarSignerTool)) {
3352 fprintf(stderr,
"Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3356 jarSignerTool =
"%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3357 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3359 if (!options.keyStorePassword.isEmpty())
3360 jarSignerTool +=
" -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3362 if (!options.storeType.isEmpty())
3363 jarSignerTool +=
" -storetype %1"_L1.arg(shellQuote(options.storeType));
3365 if (!options.keyPass.isEmpty())
3366 jarSignerTool +=
" -keypass %1"_L1.arg(shellQuote(options.keyPass));
3368 if (!options.sigFile.isEmpty())
3369 jarSignerTool +=
" -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3371 if (!options.signedJar.isEmpty())
3372 jarSignerTool +=
" -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3374 if (!options.tsaUrl.isEmpty())
3375 jarSignerTool +=
" -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3377 if (!options.tsaCert.isEmpty())
3378 jarSignerTool +=
" -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3380 if (options.internalSf)
3381 jarSignerTool +=
" -internalsf"_L1;
3383 if (options.sectionsOnly)
3384 jarSignerTool +=
" -sectionsonly"_L1;
3386 if (options.protectedAuthenticationPath)
3387 jarSignerTool +=
" -protected"_L1;
3389 auto jarSignPackage = [&](
const QString &file) {
3390 fprintf(stdout,
"Signing file %s\n", qPrintable(file));
3392 QString command = jarSignerTool +
" %1 %2"_L1.arg(shellQuote(file))
3393 .arg(shellQuote(options.keyStoreAlias));
3395 auto jarSignerCommand = openProcess(command);
3396 if (jarSignerCommand == 0) {
3397 fprintf(stderr,
"Couldn't run jarsigner.\n");
3403 while (fgets(buffer,
sizeof(buffer), jarSignerCommand.get()) !=
nullptr)
3404 fprintf(stdout,
"%s", buffer);
3407 const int errorCode = pclose(jarSignerCommand.release());
3408 if (errorCode != 0) {
3409 fprintf(stderr,
"jarsigner command failed.\n");
3411 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3417 if (options
.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3422bool signPackage(
const Options &options)
3424 const QString apksignerTool = batSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3425 options.sdkBuildToolsVersion +
"/apksigner"_L1);
3484 QString apkSignCommand =
"%1 sign --ks %2"_L1
3485 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3487 if (!options.keyStorePassword.isEmpty())
3488 apkSignCommand +=
" --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3490 if (!options.keyStoreAlias.isEmpty())
3491 apkSignCommand +=
" --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3493 if (!options.keyPass.isEmpty())
3494 apkSignCommand +=
" --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3496 if (options.verbose)
3497 apkSignCommand +=
" --verbose"_L1;
3499 apkSignCommand +=
" %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3501 auto apkSignerRunner = [](
const QString &command,
bool verbose) {
3502 auto apkSigner = openProcess(command);
3503 if (apkSigner == 0) {
3504 fprintf(stderr,
"Couldn't run apksigner.\n");
3509 while (fgets(buffer,
sizeof(buffer), apkSigner.get()) !=
nullptr)
3510 fprintf(stdout,
"%s", buffer);
3512 const int errorCode = pclose(apkSigner.release());
3513 if (errorCode != 0) {
3514 fprintf(stderr,
"apksigner command failed.\n");
3516 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3523 if (!apkSignerRunner(apkSignCommand, options
.verbose))
3526 const QString apkVerifyCommand =
3527 "%1 verify --verbose %2"_L1
3528 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3530 if (options
.buildAAB && !signAAB(options))
3534 return apkSignerRunner(apkVerifyCommand,
true) && QFile::remove(packagePath(options, UnsignedAPK));
3540 SyntaxErrorOrHelpRequested = 1,
3541 CannotReadInputFile = 2,
3542 CannotCopyAndroidTemplate = 3,
3543 CannotReadDependencies = 4,
3544 CannotCopyGnuStl = 5,
3545 CannotCopyQtFiles = 6,
3546 CannotFindApplicationBinary = 7,
3547 CannotCopyAndroidExtraLibs = 10,
3548 CannotCopyAndroidSources = 11,
3549 CannotUpdateAndroidFiles = 12,
3550 CannotCreateAndroidProject = 13,
3551 CannotBuildAndroidProject = 14,
3552 CannotSignPackage = 15,
3553 CannotInstallApk = 16,
3554 CannotCopyAndroidExtraResources = 19,
3556 CannotCreateRcc = 21,
3557 CannotGenerateJavaQmlComponents = 22
3560bool writeDependencyFile(
const Options &options)
3563 fprintf(stdout,
"Writing dependency file.\n");
3565 QString relativeTargetPath;
3569 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() +
"/timestamp"_L1;
3570 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3572 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3575 QFile depFile(options.depFilePath);
3576 if (depFile.open(QIODevice::WriteOnly)) {
3577 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3578 depFile.write(
": ");
3580 for (
const auto &file : dependenciesForDepfile) {
3581 depFile.write(
" \\\n ");
3582 depFile.write(escapeAndEncodeDependencyPath(file));
3585 depFile.write(
"\n");
3590int generateJavaQmlComponents(
const Options &options)
3592 const auto firstCharToUpper = [](
const QString &str) -> QString {
3595 return str.left(1).toUpper() + str.mid(1);
3598 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3602 str[0] = str[0].toUpper();
3604 for (
int i = 0; i < str.size(); ++i) {
3605 if (str[i] ==
"."_L1) {
3608 if (j < str.size()) {
3609 str[j] = str[j].toUpper();
3616 const auto getImportPaths = [options](
const QString &buildPath,
const QString &libName,
3617 QStringList &appImports, QStringList &externalImports) ->
bool {
3618 QFile confRspFile(
"%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3619 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3621 QTextStream rspStream(&confRspFile);
3622 while (!rspStream.atEnd()) {
3623 QString currentLine = rspStream.readLine();
3624 if (currentLine.compare(
"-importPath"_L1) == 0) {
3625 currentLine = rspStream.readLine();
3626 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3627 appImports << currentLine;
3629 externalImports << currentLine;
3634 QSet<QString> qmldirDirectories;
3635 for (
const QString &path : appImports) {
3636 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3637 while (it.hasNext()) {
3638 const QDir dir(it.next());
3639 const QString absolutePath = dir.absolutePath();
3640 if (!absolutePath.startsWith(options.outputDirectory)
3641 && dir.exists(
"qmldir"_L1)) {
3642 qmldirDirectories.insert(absolutePath);
3646 appImports << qmldirDirectories.values();
3647 appImports.removeDuplicates();
3649 return appImports.count() + externalImports.count();
3652 struct ComponentInfo {
3661 QList<ComponentInfo> qmlComponents;
3662 bool isValid() {
return qmlComponents.size() && moduleName.size(); }
3665 const auto getModuleInfo = [](
const QString &qmldirPath) -> ModuleInfo {
3666 QFile qmlDirFile(qmldirPath +
"/qmldir"_L1);
3667 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3668 return ModuleInfo();
3669 ModuleInfo moduleInfo;
3670 QSet<QString> qmlComponentNames;
3671 QTextStream qmldirStream(&qmlDirFile);
3672 while (!qmldirStream.atEnd()) {
3673 const QString currentLine = qmldirStream.readLine();
3674 if (currentLine.size() && currentLine[0].isLower()) {
3676 if (currentLine.startsWith(
"module "_L1))
3677 moduleInfo.moduleName = currentLine.split(
" "_L1)[1];
3678 else if (currentLine.startsWith(
"prefer "_L1))
3679 moduleInfo.preferPath = currentLine.split(
" "_L1)[1];
3680 }
else if (currentLine.size()
3681 && (currentLine[0].isUpper() || currentLine.startsWith(
"singleton"_L1))) {
3682 const QStringList parts = currentLine.split(
" "_L1);
3683 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3684 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3685 qmlComponentNames.insert(parts.first());
3692 const auto extractDomInfo = [](
const QString &qmlDomExecPath,
const QString &qmldirPath,
3693 const QString &qmlFile,
3694 const QStringList &otherImportPaths) -> QJsonObject {
3696#if QT_CONFIG(process)
3697 QStringList qmlDomArgs {
"-d"_L1,
"-D"_L1,
"required"_L1,
"-f"_L1,
"+:propertyInfos"_L1 };
3698 for (
auto &importPath : otherImportPaths)
3699 qmlDomArgs <<
"-I"_L1 << importPath;
3700 qmlDomArgs <<
"%1/%2"_L1.arg(qmldirPath, qmlFile);
3701 const QString qmlDomCmd =
"%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3703 process.start(qmlDomExecPath, qmlDomArgs);
3704 if (!process.waitForStarted()) {
3705 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(qmlDomCmd));
3706 return QJsonObject();
3709 if (!process.waitForFinished(30000)) {
3710 fprintf(stderr,
"Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3711 return QJsonObject();
3713 domInfo = process.readAllStandardOutput();
3715 QJsonParseError jsonError;
3716 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3717 if (jsonError.error != QJsonParseError::NoError)
3718 fprintf(stderr,
"Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3719 return jsonDoc.object();
3721#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3722 return QJsonObject();
3726 const auto getComponent = [](
const QJsonObject &dom) -> QJsonObject {
3728 return QJsonObject();
3730 const QJsonObject currentItem = dom.value(
"currentItem"_L1).toObject();
3731 if (!currentItem.value(
"isValid"_L1).toBool(
false))
3732 return QJsonObject();
3734 const QJsonArray components =
3735 currentItem.value(
"components"_L1).toObject().value(
""_L1).toArray();
3736 if (components.isEmpty())
3737 return QJsonObject();
3738 return components.constBegin()->toObject();
3741 const auto getProperties = [](
const QJsonObject &component) -> QJsonArray {
3742 QJsonArray properties;
3743 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3744 if (objects.isEmpty())
3745 return QJsonArray();
3746 const QJsonObject propertiesObject =
3747 objects[0].toObject().value(
"propertyInfos"_L1).toObject();
3748 for (
const auto &jsonProperty : propertiesObject) {
3749 const QJsonArray propertyDefs =
3750 jsonProperty.toObject().value(
"propertyDefs"_L1).toArray();
3751 if (propertyDefs.isEmpty())
3754 properties.append(propertyDefs[0].toObject());
3759 const auto getMethods = [](
const QJsonObject &component) -> QJsonArray {
3761 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3762 if (objects.isEmpty())
3763 return QJsonArray();
3764 const QJsonObject methodsObject = objects[0].toObject().value(
"methods"_L1).toObject();
3765 for (
const auto &jsonMethod : methodsObject) {
3766 const QJsonArray overloads = jsonMethod.toArray();
3767 for (
const auto &m : overloads)
3773 const static QHash<QString, QString> qmlToJavaType = {
3774 {
"real"_L1,
"Double"_L1 }, {
"double"_L1,
"Double"_L1 }, {
"int"_L1,
"Integer"_L1 },
3775 {
"float"_L1,
"Float"_L1 }, {
"bool"_L1,
"Boolean"_L1 }, {
"string"_L1,
"String"_L1 },
3776 {
"void"_L1,
"Void"_L1 }
3779 const auto endBlock = [](QTextStream &stream,
int indentWidth = 0) {
3780 stream << QString(indentWidth, u' ') <<
"}\n";
3783 const auto createHeaderBlock = [](QTextStream &stream,
const QString &javaPackage) {
3784 stream <<
"/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3785 <<
"package %1;\n\n"_L1.arg(javaPackage)
3786 <<
"import org.qtproject.qt.android.QtSignalListener;\n"
3787 <<
"import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3790 const auto beginComponentBlock = [](QTextStream &stream,
const QString &libName,
3791 const QString &moduleName,
const QString &preferPath,
3792 const ComponentInfo &componentInfo,
int indentWidth = 8) {
3793 const QString indent(indentWidth, u' ');
3796 <<
"public final class %1 extends QtQuickViewContent {\n"_L1
3797 .arg(componentInfo.name)
3798 << indent <<
" @Override public String getLibraryName() {\n"_L1
3799 << indent <<
" return \"%1\";\n"_L1.arg(libName)
3800 << indent <<
" }\n"_L1
3801 << indent <<
" @Override public String getModuleName() {\n"_L1
3802 << indent <<
" return \"%1\";\n"_L1.arg(moduleName)
3803 << indent <<
" }\n"_L1
3804 << indent <<
" @Override public String getFilePath() {\n"_L1
3805 << indent <<
" return \"qrc%1%2\";\n"_L1.arg(preferPath)
3806 .arg(componentInfo.path)
3807 << indent <<
" }\n"_L1;
3810 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3811 const QJsonObject &propertyData,
3812 int indentWidth = 8) {
3813 const QString indent(indentWidth, u' ');
3814 const QString propertyName = propertyData[
"name"_L1].toString();
3815 if (propertyName.isEmpty())
3817 const QString upperPropertyName = firstCharToUpper(propertyName);
3818 const QString typeName = propertyData[
"typeName"_L1].toString();
3819 const bool isReadyonly = propertyData[
"isReadonly"_L1].toBool();
3821 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3825 <<
"public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3826 upperPropertyName, javaTypeName, propertyName);
3830 <<
"public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3831 .arg(upperPropertyName, javaTypeName, propertyName)
3833 <<
"public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3834 .arg(upperPropertyName, javaTypeName)
3836 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3837 propertyName, javaTypeName)
3841 enum class MethodType { Signal = 0, Function = 1 };
3843 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3844 const QJsonObject &methodData,
3845 int indentWidth = 8) {
3846 const QString indent(indentWidth, u' ');
3847 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Signal)
3849 const QJsonArray parameters = methodData[
"parameters"_L1].toArray();
3851 const QString methodName = methodData[
"name"_L1].toString();
3852 if (methodName.isEmpty())
3855 const QString upperMethodName = firstCharToUpper(methodName);
3856 if (parameters.size() <= 1) {
3857 const QString typeName = !parameters.isEmpty()
3858 ? parameters[0].toObject()[
"typeName"_L1].toString()
3860 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3862 <<
"public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3863 .arg(upperMethodName, javaTypeName)
3865 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3866 .arg(methodName, javaTypeName)
3870 const auto getJavaArgsString = [¶meters]() -> QString {
3871 QList<QString> javaArgsList;
3872 for (
const auto param : parameters) {
3873 const auto typeName = param[
"typeName"_L1].toString();
3874 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3875 const auto qmlParamName = param[
"name"_L1].toString();
3877 javaArgsList.emplace_back(
3878 QStringLiteral(
"%1%2").arg(javaTypeName,
" %1"_L1.arg(qmlParamName)));
3880 return javaArgsList.join(
", "_L1);
3883 const auto getJavaClassesString = [¶meters]() -> QString {
3884 QList<QString> javaArgsList;
3885 for (
const auto param : parameters) {
3886 const auto typeName = param[
"typeName"_L1].toString();
3887 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3889 javaArgsList.emplace_back(
3890 QStringLiteral(
"%1%2").arg(javaTypeName,
".class"_L1));
3892 return javaArgsList.join(
", "_L1);
3895 const auto javaParamsString = getJavaArgsString();
3896 const auto javaParamsClassesString = getJavaClassesString();
3899 QList<QString> objectToTypeConversion;
3900 for (
auto i = 0; i < parameters.size(); ++i) {
3901 const auto typeName = parameters.at(i).toObject().value(
"typeName"_L1).toString();
3902 objectToTypeConversion.emplace_back(
"(%1) args[%2]"_L1.arg(
3903 qmlToJavaType.value(typeName,
"Object"_L1), QString::number(i)));
3907 const auto signalInterfaceName =
"%1Listener"_L1.arg(methodName);
3908 const auto objectToTypeConversionString = objectToTypeConversion.join(
", "_L1);
3909 stream << indent <<
"@FunctionalInterface\n"
3910 << indent <<
"public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3911 <<
" default void onSignalEmitted(Object[] args) {\n"
3913 <<
" on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3916 <<
" void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3917 stream << indent <<
"}\n"_L1;
3921 <<
"public int connect%1(%2 signalListener) {\n"_L1.arg(
3922 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3924 <<
" return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3925 .arg(methodName, javaParamsClassesString)
3926 << indent <<
"}\n\n";
3930 const auto writeFunctionBlock = [](QTextStream &stream,
const QJsonObject &methodData,
3931 int indentWidth = 8) {
3932 const QString indent(indentWidth, u' ');
3933 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Function)
3936 const QJsonArray params = methodData[
"parameters"_L1].toArray();
3937 const QString functionName = methodData[
"name"_L1].toString();
3939 QList<QString> javaFunctionParams;
3940 QList<QString> javaParams;
3941 for (
const auto &value : params) {
3942 const auto object = value.toObject();
3943 if (!object.contains(
"typeName"_L1)) {
3944 qWarning() <<
" -- Skipping function" << functionName
3945 <<
"due to untyped function parameter detected while generating Java "
3946 "code for QML methods.";
3950 const auto qmlParamType = object[
"typeName"_L1].toString();
3951 if (!qmlToJavaType.contains(qmlParamType)) {
3952 qWarning() <<
" -- Skipping function" << functionName
3953 <<
"due to unsupported type detected in parameters:" << qmlParamType;
3957 const auto javaTypeName{ qmlToJavaType.value(object[
"typeName"_L1].toString(),
3959 const auto javaParamName = object[
"name"_L1].toString();
3960 javaFunctionParams.push_back(
3961 QString{
"%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
3962 javaParams.append(javaParamName);
3965 const auto functionSignature {
3966 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(
", "_L1))
3968 const auto functionCallParams {
3969 javaParams.isEmpty() ?
""_L1 :
", new Object[] { %1 }"_L1.arg(javaParams.join(
", "_L1))
3972 stream << indent << functionSignature
3973 << indent <<
" invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
3974 .arg(functionCallParams)
3978 constexpr static auto markerFileName =
"qml_java_contents"_L1;
3979 const QString libName(options.applicationBinary);
3980 QString javaPackageBase = options.packageName;
3981 const QString expectedBaseLeaf =
".%1"_L1.arg(libName);
3982 if (!javaPackageBase.endsWith(expectedBaseLeaf))
3983 javaPackageBase += expectedBaseLeaf;
3984 const QString baseSourceDir =
"%1/src/%2"_L1.arg(options.outputDirectory,
3985 QString(javaPackageBase).replace(u'.', u'/'));
3986 const QString buildPath(QDir(options.buildDirectory).absolutePath());
3989 fprintf(stdout,
"Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
3990 if (!QDir().current().mkpath(baseSourceDir)) {
3991 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(baseSourceDir));
3995 QStringList appImports;
3996 QStringList externalImports;
3997 if (!getImportPaths(buildPath, libName, appImports, externalImports))
4002 const QString srcDir =
"%1/src"_L1.arg(options.outputDirectory);
4003 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
4004 while (iter.hasNext())
4005 iter.nextFileInfo().dir().removeRecursively();
4008 int generatedComponents = 0;
4009 for (
const auto &importPath : appImports) {
4010 ModuleInfo moduleInfo = getModuleInfo(importPath);
4011 if (!moduleInfo.isValid())
4014 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
4015 if (moduleInfo.moduleName == libName) {
4017 "A QML module name (%s) cannot be the same as the target name when building "
4018 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4019 qPrintable(moduleInfo.moduleName));
4023 const QString javaPackage =
"%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4024 const QString outputDir =
4025 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4026 if (!QDir().current().mkpath(outputDir)) {
4027 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(outputDir));
4033 QFile markerFile(
"%1/%2"_L1.arg(outputDir, markerFileName));
4034 if (!markerFile.open(QFile::WriteOnly)) {
4035 fprintf(stderr,
"Cannot create %s file\n", qPrintable(markerFile.fileName()));
4042 for (
const auto &qmlComponent : moduleInfo.qmlComponents) {
4043 const bool isSelected = options.selectedJavaQmlComponents.contains(
4044 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4045 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4048 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4049 externalImports + appImports);
4050 QJsonObject component = getComponent(domInfo);
4051 if (component.isEmpty())
4054 QByteArray componentClassBody;
4055 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4057 createHeaderBlock(outputStream, javaPackage);
4059 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4060 qmlComponent, indentBase);
4063 const QJsonArray properties = getProperties(component);
4064 for (
const QJsonValue &p : std::as_const(properties))
4065 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4067 const QJsonArray methods = getMethods(component);
4068 for (
const QJsonValue &m : std::as_const(methods))
4069 beginSignalBlock(outputStream, m.toObject(), indentBase);
4071 for (
const QJsonValue &m : std::as_const(methods))
4072 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4075 endBlock(outputStream, indentBase);
4076 outputStream.flush();
4079 QFile outputFile(
"%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4080 if (outputFile.exists())
4081 outputFile.remove();
4082 if (!outputFile.open(QFile::WriteOnly)) {
4083 fprintf(stderr,
"Cannot open %s file to write.\n",
4084 qPrintable(outputFile.fileName()));
4087 outputFile.write(componentClassBody);
4090 generatedComponents++;
4093 return generatedComponents;
4096int main(
int argc,
char *argv[])
4098 QCoreApplication a(argc, argv);
4101 if (options
.helpRequested || options.outputDirectory.isEmpty()) {
4103 return SyntaxErrorOrHelpRequested;
4106 options.timer.start();
4108 if (!readInputFile(&options))
4109 return CannotReadInputFile;
4111 if (Q_UNLIKELY(options
.timing))
4112 fprintf(stdout,
"[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4115 "Generating Android Package\n"
4117 " Output directory: %s\n"
4118 " Application binary: %s\n"
4119 " Android build platform: %s\n"
4120 " Install to device: %s\n",
4121 qPrintable(options.inputFileName),
4122 qPrintable(options.outputDirectory),
4123 qPrintable(options.applicationBinary),
4124 qPrintable(options.androidPlatform),
4126 ? (options.installLocation.isEmpty() ?
"Default device" : qPrintable(options.installLocation))
4130 bool androidTemplatetCopied =
false;
4132 for (
auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4135 options.setCurrentQtArchitecture(it.key(),
4136 it.value().qtInstallDirectory,
4137 it.value().qtDirectories);
4141 cleanAndroidFiles(options);
4142 if (Q_UNLIKELY(options
.timing))
4143 fprintf(stdout,
"[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4145 if (!copyAndroidTemplate(options))
4146 return CannotCopyAndroidTemplate;
4148 if (Q_UNLIKELY(options
.timing))
4149 fprintf(stdout,
"[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4150 androidTemplatetCopied =
true;
4153 if (!readDependencies(&options))
4154 return CannotReadDependencies;
4156 if (Q_UNLIKELY(options
.timing))
4157 fprintf(stdout,
"[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4159 if (!copyQtFiles(&options))
4160 return CannotCopyQtFiles;
4162 if (Q_UNLIKELY(options
.timing))
4163 fprintf(stdout,
"[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4165 if (!copyAndroidExtraLibs(&options))
4166 return CannotCopyAndroidExtraLibs;