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",
558 options.uninstallApk ?
"--reinstall" :
"--install");
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;
1708 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1709 const auto files = current.entryList(QDir::Files);
1710 result.reserve(dirs.size() + files.size());
1711 for (
const QString &dir : dirs) {
1712 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1714 for (
const QString &file : files) {
1715 result += rootDir.relativeFilePath(current.filePath(file));
1720bool copyAndroidExtraResources(
Options *options)
1722 if (options->extraPlugins.isEmpty())
1725 if (options->verbose)
1726 fprintf(stdout,
"Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1728 for (
const QString &extraResource : options->extraPlugins) {
1729 QFileInfo extraResourceInfo(extraResource);
1730 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1731 fprintf(stderr,
"External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1735 QDir resourceDir(extraResource);
1736 QString assetsDir = options->outputDirectory +
"/assets/"_L1 +
1737 resourceDir.dirName() + u'/';
1738 QString libsDir = options->outputDirectory +
"/libs/"_L1 + options->currentArchitecture + u'/';
1740 const QStringList files = allFilesInside(resourceDir, resourceDir);
1741 for (
const QString &resourceFile : files) {
1742 QString originFile(resourceDir.filePath(resourceFile));
1743 QString destinationFile;
1744 if (!resourceFile.endsWith(
".so"_L1)) {
1745 destinationFile = assetsDir + resourceFile;
1747 if (isDeployment(options, Options::Unbundled)
1748 || !checkArchitecture(*options, originFile)) {
1751 destinationFile = libsDir + resourceFile;
1752 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1755 if (!copyFileIfNewer(originFile, destinationFile,
1756 *options, options->createSymlinksOnly)) {
1765bool updateFile(
const QString &fileName,
const QHash<QString, QString> &replacements)
1767 QFile inputFile(fileName);
1768 if (!inputFile.open(QIODevice::ReadOnly)) {
1769 fprintf(stderr,
"Cannot open %s for reading.\n", qPrintable(fileName));
1775 QByteArray contents = inputFile.readAll();
1777 bool hasReplacements =
false;
1778 QHash<QString, QString>::const_iterator it;
1779 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1780 if (it.key() == it.value())
1784 int index = contents.indexOf(it.key().toUtf8());
1786 contents.replace(index, it.key().size(), it.value().toUtf8());
1787 hasReplacements =
true;
1794 if (hasReplacements) {
1797 if (!inputFile.open(QIODevice::WriteOnly)) {
1798 fprintf(stderr,
"Cannot open %s for writing.\n", qPrintable(fileName));
1804 QRegularExpression emptyLinesRegex(
"\\n\\s+\\n"_L1);
1805 contents = QString::fromUtf8(contents).replace(emptyLinesRegex,
"\n"_L1).toUtf8();
1807 inputFile.write(contents);
1814bool updateLibsXml(
Options *options)
1816 if (options->verbose)
1817 fprintf(stdout,
" -- res/values/libs.xml\n");
1819 QString fileName = options->outputDirectory +
"/res/values/libs.xml"_L1;
1820 if (!QFile::exists(fileName)) {
1821 fprintf(stderr,
"Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1826 QString allLocalLibs;
1829 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
2103 for (
const auto &prefix : options->extraPrefixDirs) {
2104 const QString path = prefix + u'/' + relativeFileName;
2105 if (QFile::exists(path))
2109 if (relativeFileName.endsWith(
"-android-dependencies.xml"_L1)) {
2110 for (
const auto &dir : options->extraLibraryDirs) {
2111 const QString path = dir + u'/' + relativeFileName;
2112 if (QFile::exists(path))
2115 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2116 u'/' + relativeFileName;
2119 if (relativeFileName.startsWith(
"jar/"_L1)) {
2120 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2121 u'/' + relativeFileName;
2124 if (relativeFileName.startsWith(
"lib/"_L1)) {
2125 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2126 u'/' + relativeFileName.mid(
sizeof(
"lib/") - 1);
2128 return options->qtInstallDirectory + u'/' + relativeFileName;
2131QList<QtDependency> findFilesRecursively(
const Options &options,
const QFileInfo &info,
const QString &rootPath)
2137 QList<QtDependency> ret;
2139 QDir dir(info.filePath());
2140 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2142 for (
const QString &entry : entries) {
2143 ret += findFilesRecursively(options,
2144 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2150 return QList<
QtDependency>() <<
QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2154QList<QtDependency> findFilesRecursively(
const Options &options,
const QString &fileName)
2162 QList<QtDependency> deps;
2163 for (
const auto &prefix : options.extraPrefixDirs) {
2164 QFileInfo info(prefix + u'/' + fileName);
2165 if (info.exists()) {
2167 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2169 return findFilesRecursively(options, info, prefix + u'/');
2174 if (
std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2175 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2176 QFileInfo info(options.qtInstallDirectory +
"/"_L1 + fileName);
2177 QFileInfo rootPath(options.qtInstallDirectory +
"/"_L1);
2178 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2183void readDependenciesFromFiles(
Options *options,
const QList<QtDependency> &files,
2184 QSet<QString> &usedDependencies,
2185 QSet<QString> &remainingDependencies)
2187 for (
const QtDependency &fileName : files) {
2188 if (usedDependencies.contains(fileName.absolutePath))
2191 if (fileName.absolutePath.endsWith(
".so"_L1)) {
2192 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2290 if (reader.hasError()) {
2291 fprintf(stderr,
"Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2295 fprintf(stdout,
"No android dependencies for %s\n", qPrintable(moduleName));
2297 options->features.removeDuplicates();
2302QStringList getQtLibsFromElf(
const Options &options,
const QString &fileName)
2304 QString readElf = llvmReadobjPath(options);
2305 if (!QFile::exists(readElf)) {
2306 fprintf(stderr,
"Command does not exist: %s\n", qPrintable(readElf));
2307 return QStringList();
2310 readElf =
"%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2312 auto readElfCommand = openProcess(readElf);
2313 if (!readElfCommand) {
2314 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(readElf));
2315 return QStringList();
2320 bool readLibs =
false;
2322 while (fgets(buffer,
sizeof(buffer), readElfCommand.get()) !=
nullptr) {
2323 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2325 line = line.trimmed();
2327 if (line.startsWith(
"Arch: ")) {
2328 auto it = elfArchitectures.find(line.mid(6));
2329 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2330 if (options.verbose)
2331 fprintf(stdout,
"Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2335 readLibs = line.startsWith(
"NeededLibraries");
2338 if (!line.startsWith(
"lib"))
2340 library = QString::fromLatin1(line);
2341 QString libraryName =
"lib/"_L1 + library;
2342 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2350 const QString &fileName,
2351 QSet<QString> *usedDependencies,
2352 QSet<QString> *remainingDependencies)
2355 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2358 fprintf(stdout,
"Reading dependencies from %s\n", qPrintable(fileName));
2359 for (
const QString &dep : dependencies)
2360 fprintf(stdout,
" %s\n", qPrintable(dep));
2363 QList<QString> dependenciesToCheck;
2364 for (
const QString &dependency : dependencies) {
2365 if (usedDependencies->contains(dependency))
2368 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2369 usedDependencies->insert(dependency);
2370 if (!readDependenciesFromElf(options,
2371 absoluteDependencyPath,
2373 remainingDependencies)) {
2377 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2378 if (options->verbose)
2379 fprintf(stdout,
"Appending dependency: %s\n", qPrintable(dependency));
2380 dependenciesToCheck.append(dependency);
2383 for (
const QString &dependency : std::as_const(dependenciesToCheck)) {
2384 QString qtBaseName = dependency.mid(
sizeof(
"lib/lib") - 1);
2385 qtBaseName = qtBaseName.left(qtBaseName.size() - (
sizeof(
".so") - 1));
2386 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2394bool scanImports(
Options *options, QSet<QString> *usedDependencies)
2396 if (options->verbose)
2397 fprintf(stdout,
"Scanning for QML imports.\n");
2399 QString qmlImportScanner;
2400 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2401 qmlImportScanner = options->qmlImportScannerBinaryPath;
2403 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2404 "/qmlimportscanner"_L1);
2407 QStringList importPaths;
2413 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2414 if (QFile::exists(mainImportPath))
2415 importPaths += shellQuote(mainImportPath);
2419 for (
const QString &prefix : options->extraPrefixDirs)
2420 if (QFile::exists(prefix +
"/qml"_L1))
2421 importPaths += shellQuote(prefix +
"/qml"_L1);
2424 for (
const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2425 if (QFile::exists(qmlImportPath)) {
2426 importPaths += shellQuote(qmlImportPath);
2428 fprintf(stderr,
"Warning: QML import path %s does not exist.\n",
2429 qPrintable(qmlImportPath));
2433 bool qmlImportExists =
false;
2435 for (
const QString &import : importPaths) {
2436 if (QDir().exists(import)) {
2437 qmlImportExists =
true;
2444 if (!qmlImportExists) {
2445 fprintf(stderr,
"Warning: no 'qml' directory found under Qt install directory "
2446 "or import paths. Skipping QML dependency scanning.\n");
2450 if (!QFile::exists(qmlImportScanner)) {
2451 fprintf(stderr,
"%s: qmlimportscanner not found at %s\n",
2452 qmlImportExists ?
"Error"_L1.data() :
"Warning"_L1.data(),
2453 qPrintable(qmlImportScanner));
2457 for (
auto rootPath : options->rootPaths) {
2458 rootPath = QFileInfo(rootPath).absoluteFilePath();
2460 if (!rootPath.endsWith(u'/'))
2464 if (!rootPath.isEmpty())
2465 importPaths += shellQuote(rootPath);
2467 qmlImportScanner +=
" -rootPath %1"_L1.arg(shellQuote(rootPath));
2470 if (!options->qrcFiles.isEmpty()) {
2471 qmlImportScanner +=
" -qrcFiles"_L1;
2472 for (
const QString &qrcFile : options->qrcFiles)
2473 qmlImportScanner += u' ' + shellQuote(qrcFile);
2476 qmlImportScanner +=
" -importPath %1"_L1.arg(importPaths.join(u' '));
2479 fprintf(stdout,
"Running qmlimportscanner with the following command: %s\n",
2480 qmlImportScanner.toLocal8Bit().constData());
2483 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2484 if (qmlImportScannerCommand == 0) {
2485 fprintf(stderr,
"Couldn't run qmlimportscanner.\n");
2491 while (fgets(buffer,
sizeof(buffer), qmlImportScannerCommand.get()) !=
nullptr)
2492 output += QByteArray(buffer, qstrlen(buffer));
2494 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2495 if (jsonDocument.isNull()) {
2496 fprintf(stderr,
"Invalid json output from qmlimportscanner.\n");
2500 QJsonArray jsonArray = jsonDocument.array();
2501 for (
int i=0; i<jsonArray.count(); ++i) {
2502 QJsonValue value = jsonArray.at(i);
2503 if (!value.isObject()) {
2504 fprintf(stderr,
"Invalid format of qmlimportscanner output.\n");
2508 QJsonObject object = value.toObject();
2509 QString path = object.value(
"path"_L1).toString();
2510 if (path.isEmpty()) {
2511 fprintf(stderr,
"Warning: QML import could not be resolved in any of the import paths: %s\n",
2512 qPrintable(object.value(
"name"_L1).toString()));
2513 }
else if (object.value(
"type"_L1).toString() ==
"module"_L1) {
2950 const QString path = QString::fromLocal8Bit(qgetenv(
"PATH"));
2951#if defined(Q_OS_WIN32)
2952 QLatin1Char separator(
';');
2954 QLatin1Char separator(
':');
2957 const QStringList paths = path.split(separator);
2958 for (
const QString &path : paths) {
2959 QFileInfo fileInfo(path + u'/' + fileName);
2960 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2961 return path + u'/' + fileName;
2967typedef QMap<QByteArray, QByteArray> GradleProperties;
2969static GradleProperties readGradleProperties(
const QString &path)
2971 GradleProperties properties;
2973 if (!file.open(QIODevice::ReadOnly))
2976 const auto lines = file.readAll().split(
'\n');
2977 for (
const QByteArray &line : lines) {
2978 if (line.trimmed().startsWith(
'#'))
2981 const int idx = line.indexOf(
'=');
2983 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
2989static bool mergeGradleProperties(
const QString &path, GradleProperties properties)
2991 const QString oldPathStr = path + u'~';
2992 QFile::remove(oldPathStr);
2993 QFile::rename(path, oldPathStr);
2995 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
2996 fprintf(stderr,
"Can't open file: %s for writing\n", qPrintable(file.fileName()));
3000 QFile oldFile(oldPathStr);
3001 if (oldFile.open(QIODevice::ReadOnly)) {
3003 while (oldFile.readLineInto(&line)) {
3004 QList<QByteArray> prop(line.split(
'='));
3005 if (prop.size() > 1) {
3006 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3007 if (it != properties.end()) {
3008 file.write(it.key() +
'=' + it.value() +
'\n');
3009 properties.erase(it);
3013 file.write(line.trimmed() +
'\n');
3016 QFile::remove(oldPathStr);
3019 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3020 file.write(it.key() +
'=' + it.value() +
'\n');
3026#if defined(Q_OS_WIN32)
3027void checkAndWarnGradleLongPaths(
const QString &outputDirectory)
3029 QStringList longFileNames;
3030 using F = QDirListing::IteratorFlag;
3031 for (
const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3032 F::FilesOnly | F::Recursive)) {
3033 if (dirEntry.size() >= MAX_PATH)
3034 longFileNames.append(dirEntry.filePath());
3037 if (!longFileNames.isEmpty()) {
3039 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3040 "Consider moving your project to reduce its path length.\n"
3041 "The following files have too long paths:\n%s.\n",
3042 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3047bool buildAndroidProject(
const Options &options)
3049 GradleProperties localProperties;
3050 localProperties[
"sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3051 const QString localPropertiesPath = options.outputDirectory +
"local.properties"_L1;
3052 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3055 const QString gradlePropertiesPath = options.outputDirectory +
"gradle.properties"_L1;
3056 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3058 const QString gradleBuildFilePath = options.outputDirectory +
"build.gradle"_L1;
3059 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3298 options->currentArchitecture,
3299 options->stdCppName);
3300 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3303static QString zipalignPath(
const Options &options,
bool *ok)
3306 QString zipAlignTool = execSuffixAppended(options.sdkPath +
"/tools/zipalign"_L1);
3307 if (!QFile::exists(zipAlignTool)) {
3308 zipAlignTool = execSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3309 options.sdkBuildToolsVersion +
"/zipalign"_L1);
3310 if (!QFile::exists(zipAlignTool)) {
3311 fprintf(stderr,
"zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3316 return zipAlignTool;
3319bool signAAB(
const Options &options)
3321 if (options.verbose)
3322 fprintf(stdout,
"Signing Android package.\n");
3324 QString jdkPath = options.jdkPath;
3326 if (jdkPath.isEmpty())
3327 jdkPath = QString::fromLocal8Bit(qgetenv(
"JAVA_HOME"));
3329 QString jarSignerTool = execSuffixAppended(
"jarsigner"_L1);
3330 if (jdkPath.isEmpty() || !QFile::exists(jdkPath +
"/bin/"_L1 + jarSignerTool))
3331 jarSignerTool = findInPath(jarSignerTool);
3333 jarSignerTool = jdkPath +
"/bin/"_L1 + jarSignerTool;
3335 if (!QFile::exists(jarSignerTool)) {
3336 fprintf(stderr,
"Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3340 jarSignerTool =
"%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3341 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3343 if (!options.keyStorePassword.isEmpty())
3344 jarSignerTool +=
" -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3346 if (!options.storeType.isEmpty())
3347 jarSignerTool +=
" -storetype %1"_L1.arg(shellQuote(options.storeType));
3349 if (!options.keyPass.isEmpty())
3350 jarSignerTool +=
" -keypass %1"_L1.arg(shellQuote(options.keyPass));
3352 if (!options.sigFile.isEmpty())
3353 jarSignerTool +=
" -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3355 if (!options.signedJar.isEmpty())
3356 jarSignerTool +=
" -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3358 if (!options.tsaUrl.isEmpty())
3359 jarSignerTool +=
" -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3361 if (!options.tsaCert.isEmpty())
3362 jarSignerTool +=
" -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3364 if (options.internalSf)
3365 jarSignerTool +=
" -internalsf"_L1;
3367 if (options.sectionsOnly)
3368 jarSignerTool +=
" -sectionsonly"_L1;
3370 if (options.protectedAuthenticationPath)
3371 jarSignerTool +=
" -protected"_L1;
3373 auto jarSignPackage = [&](
const QString &file) {
3374 fprintf(stdout,
"Signing file %s\n", qPrintable(file));
3376 QString command = jarSignerTool +
" %1 %2"_L1.arg(shellQuote(file))
3377 .arg(shellQuote(options.keyStoreAlias));
3379 auto jarSignerCommand = openProcess(command);
3380 if (jarSignerCommand == 0) {
3381 fprintf(stderr,
"Couldn't run jarsigner.\n");
3387 while (fgets(buffer,
sizeof(buffer), jarSignerCommand.get()) !=
nullptr)
3388 fprintf(stdout,
"%s", buffer);
3391 const int errorCode = pclose(jarSignerCommand.release());
3392 if (errorCode != 0) {
3393 fprintf(stderr,
"jarsigner command failed.\n");
3394 if (!options.verbose)
3395 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3401 if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3406bool signPackage(
const Options &options)
3408 const QString apksignerTool = batSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3409 options.sdkBuildToolsVersion +
"/apksigner"_L1);
3468 QString apkSignCommand =
"%1 sign --ks %2"_L1
3469 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3471 if (!options.keyStorePassword.isEmpty())
3472 apkSignCommand +=
" --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3474 if (!options.keyStoreAlias.isEmpty())
3475 apkSignCommand +=
" --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3477 if (!options.keyPass.isEmpty())
3478 apkSignCommand +=
" --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3480 if (options.verbose)
3481 apkSignCommand +=
" --verbose"_L1;
3483 apkSignCommand +=
" %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3485 auto apkSignerRunner = [](
const QString &command,
bool verbose) {
3486 auto apkSigner = openProcess(command);
3487 if (apkSigner == 0) {
3488 fprintf(stderr,
"Couldn't run apksigner.\n");
3493 while (fgets(buffer,
sizeof(buffer), apkSigner.get()) !=
nullptr)
3494 fprintf(stdout,
"%s", buffer);
3496 const int errorCode = pclose(apkSigner.release());
3497 if (errorCode != 0) {
3498 fprintf(stderr,
"apksigner command failed.\n");
3500 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3507 if (!apkSignerRunner(apkSignCommand, options
.verbose))
3510 const QString apkVerifyCommand =
3511 "%1 verify --verbose %2"_L1
3512 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3514 if (options
.buildAAB && !signAAB(options))
3518 return apkSignerRunner(apkVerifyCommand,
true) && QFile::remove(packagePath(options, UnsignedAPK));
3524 SyntaxErrorOrHelpRequested = 1,
3525 CannotReadInputFile = 2,
3526 CannotCopyAndroidTemplate = 3,
3527 CannotReadDependencies = 4,
3528 CannotCopyGnuStl = 5,
3529 CannotCopyQtFiles = 6,
3530 CannotFindApplicationBinary = 7,
3531 CannotCopyAndroidExtraLibs = 10,
3532 CannotCopyAndroidSources = 11,
3533 CannotUpdateAndroidFiles = 12,
3534 CannotCreateAndroidProject = 13,
3535 CannotBuildAndroidProject = 14,
3536 CannotSignPackage = 15,
3537 CannotInstallApk = 16,
3538 CannotCopyAndroidExtraResources = 19,
3540 CannotCreateRcc = 21,
3541 CannotGenerateJavaQmlComponents = 22
3544bool writeDependencyFile(
const Options &options)
3546 if (options.verbose)
3547 fprintf(stdout,
"Writing dependency file.\n");
3549 QString relativeTargetPath;
3553 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() +
"/timestamp"_L1;
3554 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3556 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3559 QFile depFile(options.depFilePath);
3560 if (depFile.open(QIODevice::WriteOnly)) {
3561 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3562 depFile.write(
": ");
3564 for (
const auto &file : dependenciesForDepfile) {
3565 depFile.write(
" \\\n ");
3566 depFile.write(escapeAndEncodeDependencyPath(file));
3569 depFile.write(
"\n");
3574int generateJavaQmlComponents(
const Options &options)
3576 const auto firstCharToUpper = [](
const QString &str) -> QString {
3579 return str.left(1).toUpper() + str.mid(1);
3582 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3586 str[0] = str[0].toUpper();
3588 for (
int i = 0; i < str.size(); ++i) {
3589 if (str[i] ==
"."_L1) {
3592 if (j < str.size()) {
3593 str[j] = str[j].toUpper();
3600 const auto getImportPaths = [options](
const QString &buildPath,
const QString &libName,
3601 QStringList &appImports, QStringList &externalImports) ->
bool {
3602 QFile confRspFile(
"%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3603 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3605 QTextStream rspStream(&confRspFile);
3606 while (!rspStream.atEnd()) {
3607 QString currentLine = rspStream.readLine();
3608 if (currentLine.compare(
"-importPath"_L1) == 0) {
3609 currentLine = rspStream.readLine();
3610 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3611 appImports << currentLine;
3613 externalImports << currentLine;
3618 QSet<QString> qmldirDirectories;
3619 for (
const QString &path : appImports) {
3620 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3621 while (it.hasNext()) {
3622 const QDir dir(it.next());
3623 const QString absolutePath = dir.absolutePath();
3624 if (!absolutePath.startsWith(options.outputDirectory)
3625 && dir.exists(
"qmldir"_L1)) {
3626 qmldirDirectories.insert(absolutePath);
3630 appImports << qmldirDirectories.values();
3631 appImports.removeDuplicates();
3633 return appImports.count() + externalImports.count();
3636 struct ComponentInfo {
3645 QList<ComponentInfo> qmlComponents;
3646 bool isValid() {
return qmlComponents.size() && moduleName.size(); }
3649 const auto getModuleInfo = [](
const QString &qmldirPath) -> ModuleInfo {
3650 QFile qmlDirFile(qmldirPath +
"/qmldir"_L1);
3651 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3652 return ModuleInfo();
3653 ModuleInfo moduleInfo;
3654 QSet<QString> qmlComponentNames;
3655 QTextStream qmldirStream(&qmlDirFile);
3656 while (!qmldirStream.atEnd()) {
3657 const QString currentLine = qmldirStream.readLine();
3658 if (currentLine.size() && currentLine[0].isLower()) {
3660 if (currentLine.startsWith(
"module "_L1))
3661 moduleInfo.moduleName = currentLine.split(
" "_L1)[1];
3662 else if (currentLine.startsWith(
"prefer "_L1))
3663 moduleInfo.preferPath = currentLine.split(
" "_L1)[1];
3664 }
else if (currentLine.size()
3665 && (currentLine[0].isUpper() || currentLine.startsWith(
"singleton"_L1))) {
3666 const QStringList parts = currentLine.split(
" "_L1);
3667 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3668 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3669 qmlComponentNames.insert(parts.first());
3676 const auto extractDomInfo = [](
const QString &qmlDomExecPath,
const QString &qmldirPath,
3677 const QString &qmlFile,
3678 const QStringList &otherImportPaths) -> QJsonObject {
3680#if QT_CONFIG(process)
3681 QStringList qmlDomArgs {
"-d"_L1,
"-D"_L1,
"required"_L1,
"-f"_L1,
"+:propertyInfos"_L1 };
3682 for (
auto &importPath : otherImportPaths)
3683 qmlDomArgs <<
"-I"_L1 << importPath;
3684 qmlDomArgs <<
"%1/%2"_L1.arg(qmldirPath, qmlFile);
3685 const QString qmlDomCmd =
"%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3687 process.start(qmlDomExecPath, qmlDomArgs);
3688 if (!process.waitForStarted()) {
3689 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(qmlDomCmd));
3690 return QJsonObject();
3693 if (!process.waitForFinished(30000)) {
3694 fprintf(stderr,
"Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3695 return QJsonObject();
3697 domInfo = process.readAllStandardOutput();
3699 QJsonParseError jsonError;
3700 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3701 if (jsonError.error != QJsonParseError::NoError)
3702 fprintf(stderr,
"Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3703 return jsonDoc.object();
3705#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3706 return QJsonObject();
3710 const auto getComponent = [](
const QJsonObject &dom) -> QJsonObject {
3712 return QJsonObject();
3714 const QJsonObject currentItem = dom.value(
"currentItem"_L1).toObject();
3715 if (!currentItem.value(
"isValid"_L1).toBool(
false))
3716 return QJsonObject();
3718 const QJsonArray components =
3719 currentItem.value(
"components"_L1).toObject().value(
""_L1).toArray();
3720 if (components.isEmpty())
3721 return QJsonObject();
3722 return components.constBegin()->toObject();
3725 const auto getProperties = [](
const QJsonObject &component) -> QJsonArray {
3726 QJsonArray properties;
3727 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3728 if (objects.isEmpty())
3729 return QJsonArray();
3730 const QJsonObject propertiesObject =
3731 objects[0].toObject().value(
"propertyInfos"_L1).toObject();
3732 for (
const auto &jsonProperty : propertiesObject) {
3733 const QJsonArray propertyDefs =
3734 jsonProperty.toObject().value(
"propertyDefs"_L1).toArray();
3735 if (propertyDefs.isEmpty())
3738 properties.append(propertyDefs[0].toObject());
3743 const auto getMethods = [](
const QJsonObject &component) -> QJsonArray {
3745 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3746 if (objects.isEmpty())
3747 return QJsonArray();
3748 const QJsonObject methodsObject = objects[0].toObject().value(
"methods"_L1).toObject();
3749 for (
const auto &jsonMethod : methodsObject) {
3750 const QJsonArray overloads = jsonMethod.toArray();
3751 for (
const auto &m : overloads)
3757 const static QHash<QString, QString> qmlToJavaType = {
3758 {
"real"_L1,
"Double"_L1 }, {
"double"_L1,
"Double"_L1 }, {
"int"_L1,
"Integer"_L1 },
3759 {
"float"_L1,
"Float"_L1 }, {
"bool"_L1,
"Boolean"_L1 }, {
"string"_L1,
"String"_L1 },
3760 {
"void"_L1,
"Void"_L1 }
3763 const auto endBlock = [](QTextStream &stream,
int indentWidth = 0) {
3764 stream << QString(indentWidth, u' ') <<
"}\n";
3767 const auto createHeaderBlock = [](QTextStream &stream,
const QString &javaPackage) {
3768 stream <<
"/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3769 <<
"package %1;\n\n"_L1.arg(javaPackage)
3770 <<
"import org.qtproject.qt.android.QtSignalListener;\n"
3771 <<
"import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3774 const auto beginComponentBlock = [](QTextStream &stream,
const QString &libName,
3775 const QString &moduleName,
const QString &preferPath,
3776 const ComponentInfo &componentInfo,
int indentWidth = 8) {
3777 const QString indent(indentWidth, u' ');
3780 <<
"public final class %1 extends QtQuickViewContent {\n"_L1
3781 .arg(componentInfo.name)
3782 << indent <<
" @Override public String getLibraryName() {\n"_L1
3783 << indent <<
" return \"%1\";\n"_L1.arg(libName)
3784 << indent <<
" }\n"_L1
3785 << indent <<
" @Override public String getModuleName() {\n"_L1
3786 << indent <<
" return \"%1\";\n"_L1.arg(moduleName)
3787 << indent <<
" }\n"_L1
3788 << indent <<
" @Override public String getFilePath() {\n"_L1
3789 << indent <<
" return \"qrc%1%2\";\n"_L1.arg(preferPath)
3790 .arg(componentInfo.path)
3791 << indent <<
" }\n"_L1;
3794 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3795 const QJsonObject &propertyData,
3796 int indentWidth = 8) {
3797 const QString indent(indentWidth, u' ');
3798 const QString propertyName = propertyData[
"name"_L1].toString();
3799 if (propertyName.isEmpty())
3801 const QString upperPropertyName = firstCharToUpper(propertyName);
3802 const QString typeName = propertyData[
"typeName"_L1].toString();
3803 const bool isReadyonly = propertyData[
"isReadonly"_L1].toBool();
3805 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3809 <<
"public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3810 upperPropertyName, javaTypeName, propertyName);
3814 <<
"public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3815 .arg(upperPropertyName, javaTypeName, propertyName)
3817 <<
"public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3818 .arg(upperPropertyName, javaTypeName)
3820 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3821 propertyName, javaTypeName)
3825 enum class MethodType { Signal = 0, Function = 1 };
3827 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3828 const QJsonObject &methodData,
3829 int indentWidth = 8) {
3830 const QString indent(indentWidth, u' ');
3831 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Signal)
3833 const QJsonArray parameters = methodData[
"parameters"_L1].toArray();
3835 const QString methodName = methodData[
"name"_L1].toString();
3836 if (methodName.isEmpty())
3839 const QString upperMethodName = firstCharToUpper(methodName);
3840 if (parameters.size() <= 1) {
3841 const QString typeName = !parameters.isEmpty()
3842 ? parameters[0].toObject()[
"typeName"_L1].toString()
3844 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3846 <<
"public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3847 .arg(upperMethodName, javaTypeName)
3849 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3850 .arg(methodName, javaTypeName)
3854 const auto getJavaArgsString = [¶meters]() -> QString {
3855 QList<QString> javaArgsList;
3856 for (
const auto param : parameters) {
3857 const auto typeName = param[
"typeName"_L1].toString();
3858 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3859 const auto qmlParamName = param[
"name"_L1].toString();
3861 javaArgsList.emplace_back(
3862 QStringLiteral(
"%1%2").arg(javaTypeName,
" %1"_L1.arg(qmlParamName)));
3864 return javaArgsList.join(
", "_L1);
3867 const auto getJavaClassesString = [¶meters]() -> QString {
3868 QList<QString> javaArgsList;
3869 for (
const auto param : parameters) {
3870 const auto typeName = param[
"typeName"_L1].toString();
3871 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3873 javaArgsList.emplace_back(
3874 QStringLiteral(
"%1%2").arg(javaTypeName,
".class"_L1));
3876 return javaArgsList.join(
", "_L1);
3879 const auto javaParamsString = getJavaArgsString();
3880 const auto javaParamsClassesString = getJavaClassesString();
3883 QList<QString> objectToTypeConversion;
3884 for (
auto i = 0; i < parameters.size(); ++i) {
3885 const auto typeName = parameters.at(i).toObject().value(
"typeName"_L1).toString();
3886 objectToTypeConversion.emplace_back(
"(%1) args[%2]"_L1.arg(
3887 qmlToJavaType.value(typeName,
"Object"_L1), QString::number(i)));
3891 const auto signalInterfaceName =
"%1Listener"_L1.arg(methodName);
3892 const auto objectToTypeConversionString = objectToTypeConversion.join(
", "_L1);
3893 stream << indent <<
"@FunctionalInterface\n"
3894 << indent <<
"public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3895 <<
" default void onSignalEmitted(Object[] args) {\n"
3897 <<
" on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3900 <<
" void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3901 stream << indent <<
"}\n"_L1;
3905 <<
"public int connect%1(%2 signalListener) {\n"_L1.arg(
3906 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3908 <<
" return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3909 .arg(methodName, javaParamsClassesString)
3910 << indent <<
"}\n\n";
3914 const auto writeFunctionBlock = [](QTextStream &stream,
const QJsonObject &methodData,
3915 int indentWidth = 8) {
3916 const QString indent(indentWidth, u' ');
3917 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Function)
3920 const QJsonArray params = methodData[
"parameters"_L1].toArray();
3921 const QString functionName = methodData[
"name"_L1].toString();
3923 QList<QString> javaFunctionParams;
3924 QList<QString> javaParams;
3925 for (
const auto &value : params) {
3926 const auto object = value.toObject();
3927 if (!object.contains(
"typeName"_L1)) {
3928 qWarning() <<
" -- Skipping function" << functionName
3929 <<
"due to untyped function parameter detected while generating Java "
3930 "code for QML methods.";
3934 const auto qmlParamType = object[
"typeName"_L1].toString();
3935 if (!qmlToJavaType.contains(qmlParamType)) {
3936 qWarning() <<
" -- Skipping function" << functionName
3937 <<
"due to unsupported type detected in parameters:" << qmlParamType;
3941 const auto javaTypeName{ qmlToJavaType.value(object[
"typeName"_L1].toString(),
3943 const auto javaParamName = object[
"name"_L1].toString();
3944 javaFunctionParams.push_back(
3945 QString{
"%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
3946 javaParams.append(javaParamName);
3949 const auto functionSignature {
3950 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(
", "_L1))
3952 const auto functionCallParams {
3953 javaParams.isEmpty() ?
""_L1 :
", new Object[] { %1 }"_L1.arg(javaParams.join(
", "_L1))
3956 stream << indent << functionSignature
3957 << indent <<
" invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
3958 .arg(functionCallParams)
3962 constexpr static auto markerFileName =
"qml_java_contents"_L1;
3963 const QString libName(options.applicationBinary);
3964 QString javaPackageBase = options.packageName;
3965 const QString expectedBaseLeaf =
".%1"_L1.arg(libName);
3966 if (!javaPackageBase.endsWith(expectedBaseLeaf))
3967 javaPackageBase += expectedBaseLeaf;
3968 const QString baseSourceDir =
"%1/src/%2"_L1.arg(options.outputDirectory,
3969 QString(javaPackageBase).replace(u'.', u'/'));
3970 const QString buildPath(QDir(options.buildDirectory).absolutePath());
3973 fprintf(stdout,
"Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
3974 if (!QDir().current().mkpath(baseSourceDir)) {
3975 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(baseSourceDir));
3979 QStringList appImports;
3980 QStringList externalImports;
3981 if (!getImportPaths(buildPath, libName, appImports, externalImports))
3986 const QString srcDir =
"%1/src"_L1.arg(options.outputDirectory);
3987 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
3988 while (iter.hasNext())
3989 iter.nextFileInfo().dir().removeRecursively();
3992 int generatedComponents = 0;
3993 for (
const auto &importPath : appImports) {
3994 ModuleInfo moduleInfo = getModuleInfo(importPath);
3995 if (!moduleInfo.isValid())
3998 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
3999 if (moduleInfo.moduleName == libName) {
4001 "A QML module name (%s) cannot be the same as the target name when building "
4002 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4003 qPrintable(moduleInfo.moduleName));
4007 const QString javaPackage =
"%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4008 const QString outputDir =
4009 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4010 if (!QDir().current().mkpath(outputDir)) {
4011 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(outputDir));
4017 QFile markerFile(
"%1/%2"_L1.arg(outputDir, markerFileName));
4018 if (!markerFile.open(QFile::WriteOnly)) {
4019 fprintf(stderr,
"Cannot create %s file\n", qPrintable(markerFile.fileName()));
4026 for (
const auto &qmlComponent : moduleInfo.qmlComponents) {
4027 const bool isSelected = options.selectedJavaQmlComponents.contains(
4028 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4029 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4032 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4033 externalImports + appImports);
4034 QJsonObject component = getComponent(domInfo);
4035 if (component.isEmpty())
4038 QByteArray componentClassBody;
4039 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4041 createHeaderBlock(outputStream, javaPackage);
4043 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4044 qmlComponent, indentBase);
4047 const QJsonArray properties = getProperties(component);
4048 for (
const QJsonValue &p : std::as_const(properties))
4049 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4051 const QJsonArray methods = getMethods(component);
4052 for (
const QJsonValue &m : std::as_const(methods))
4053 beginSignalBlock(outputStream, m.toObject(), indentBase);
4055 for (
const QJsonValue &m : std::as_const(methods))
4056 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4059 endBlock(outputStream, indentBase);
4060 outputStream.flush();
4063 QFile outputFile(
"%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4064 if (outputFile.exists())
4065 outputFile.remove();
4066 if (!outputFile.open(QFile::WriteOnly)) {
4067 fprintf(stderr,
"Cannot open %s file to write.\n",
4068 qPrintable(outputFile.fileName()));
4071 outputFile.write(componentClassBody);
4074 generatedComponents++;
4077 return generatedComponents;
4080int main(
int argc,
char *argv[])
4082 QCoreApplication a(argc, argv);
4085 if (options
.helpRequested || options.outputDirectory.isEmpty()) {
4087 return SyntaxErrorOrHelpRequested;
4090 options.timer.start();
4092 if (!readInputFile(&options))
4093 return CannotReadInputFile;
4095 if (Q_UNLIKELY(options.timing))
4096 fprintf(stdout,
"[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4099 "Generating Android Package\n"
4101 " Output directory: %s\n"
4102 " Application binary: %s\n"
4103 " Android build platform: %s\n"
4104 " Install to device: %s\n",
4105 qPrintable(options.inputFileName),
4106 qPrintable(options.outputDirectory),
4107 qPrintable(options.applicationBinary),
4108 qPrintable(options.androidPlatform),
4110 ? (options.installLocation.isEmpty() ?
"Default device" : qPrintable(options.installLocation))
4114 bool androidTemplatetCopied =
false;
4116 for (
auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4119 options.setCurrentQtArchitecture(it.key(),
4120 it.value().qtInstallDirectory,
4121 it.value().qtDirectories);
4125 cleanAndroidFiles(options);
4126 if (Q_UNLIKELY(options.timing))
4127 fprintf(stdout,
"[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4129 if (!copyAndroidTemplate(options))
4130 return CannotCopyAndroidTemplate;
4132 if (Q_UNLIKELY(options.timing))
4133 fprintf(stdout,
"[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4134 androidTemplatetCopied =
true;
4137 if (!readDependencies(&options))
4138 return CannotReadDependencies;
4140 if (Q_UNLIKELY(options.timing))
4141 fprintf(stdout,
"[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4143 if (!copyQtFiles(&options))
4144 return CannotCopyQtFiles;
4146 if (Q_UNLIKELY(options.timing))
4147 fprintf(stdout,
"[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4149 if (!copyAndroidExtraLibs(&options))
4150 return CannotCopyAndroidExtraLibs;