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) {
2139 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2140 u'/' + relativeFileName;
2143 if (relativeFileName.startsWith(
"lib/"_L1)) {
2144 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2145 u'/' + relativeFileName.mid(
sizeof(
"lib/") - 1);
2147 return options->qtInstallDirectory + u'/' + relativeFileName;
2151static QString resolveLibDependency(
const Options *options,
const QString &fileName)
2153 if (QFile::exists(absoluteFilePath(options, fileName)))
2156 const QString abiSuffix = u'_' + options->currentArchitecture +
".so"_L1;
2157 QString alternative;
2158 if (fileName.endsWith(abiSuffix))
2159 alternative = fileName.chopped(abiSuffix.size()) +
".so"_L1;
2161 alternative = fileName.chopped(
sizeof(
".so") - 1) + abiSuffix;
2163 if (QFile::exists(absoluteFilePath(options, alternative)))
2169QList<QtDependency> findFilesRecursively(
const Options &options,
const QFileInfo &info,
const QString &rootPath)
2175 QList<QtDependency> ret;
2177 QDir dir(info.filePath());
2178 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2180 for (
const QString &entry : entries) {
2181 ret += findFilesRecursively(options,
2182 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2188 return QList<
QtDependency>() <<
QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2192QList<QtDependency> findFilesRecursively(
const Options &options,
const QString &fileName)
2200 QList<QtDependency> deps;
2201 for (
const auto &prefix : options.extraPrefixDirs) {
2202 QFileInfo info(prefix + u'/' + fileName);
2203 if (info.exists()) {
2205 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2207 return findFilesRecursively(options, info, prefix + u'/');
2212 if (
std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2213 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2214 QFileInfo info(options.qtInstallDirectory +
"/"_L1 + fileName);
2215 QFileInfo rootPath(options.qtInstallDirectory +
"/"_L1);
2216 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2221void readDependenciesFromFiles(
Options *options,
const QList<QtDependency> &files,
2222 QSet<QString> &usedDependencies,
2223 QSet<QString> &remainingDependencies)
2225 for (
const QtDependency &fileName : files) {
2226 if (usedDependencies.contains(fileName.absolutePath))
2229 if (fileName.absolutePath.endsWith(
".so"_L1)) {
2230 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2231 &remainingDependencies)) {
2232 fprintf(stdout,
"Skipping file dependency: %s\n",
2233 qPrintable(fileName.relativePath));
2331 if (reader.hasError()) {
2332 fprintf(stderr,
"Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2336 fprintf(stdout,
"No android dependencies for %s\n", qPrintable(moduleName));
2338 options->features.removeDuplicates();
2343QStringList getQtLibsFromElf(
const Options &options,
const QString &fileName)
2345 QString readElf = llvmReadobjPath(options);
2346 if (!QFile::exists(readElf)) {
2347 fprintf(stderr,
"Command does not exist: %s\n", qPrintable(readElf));
2348 return QStringList();
2351 readElf =
"%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2353 auto readElfCommand = openProcess(readElf);
2354 if (!readElfCommand) {
2355 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(readElf));
2356 return QStringList();
2361 bool readLibs =
false;
2363 while (fgets(buffer,
sizeof(buffer), readElfCommand.get()) !=
nullptr) {
2364 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2366 line = line.trimmed();
2368 if (line.startsWith(
"Arch: ")) {
2369 auto it = elfArchitectures.find(line.mid(6));
2370 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2372 fprintf(stdout,
"Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2376 readLibs = line.startsWith(
"NeededLibraries");
2379 if (!line.startsWith(
"lib"))
2381 library = QString::fromLatin1(line);
2382 QString libraryName =
"lib/"_L1 + library;
2383 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2391 const QString &fileName,
2392 QSet<QString> *usedDependencies,
2393 QSet<QString> *remainingDependencies)
2396 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2399 fprintf(stdout,
"Reading dependencies from %s\n", qPrintable(fileName));
2400 for (
const QString &dep : dependencies)
2401 fprintf(stdout,
" %s\n", qPrintable(dep));
2404 QList<QString> dependenciesToCheck;
2405 for (
const QString &dependency : dependencies) {
2406 if (usedDependencies->contains(dependency))
2409 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2410 usedDependencies->insert(dependency);
2411 if (!readDependenciesFromElf(options,
2412 absoluteDependencyPath,
2414 remainingDependencies)) {
2418 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2419 if (options->verbose)
2420 fprintf(stdout,
"Appending dependency: %s\n", qPrintable(dependency));
2421 dependenciesToCheck.append(dependency);
2424 for (
const QString &dependency : std::as_const(dependenciesToCheck)) {
2425 QString qtBaseName = dependency.mid(
sizeof(
"lib/lib") - 1);
2426 qtBaseName = qtBaseName.left(qtBaseName.size() - (
sizeof(
".so") - 1));
2427 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2435bool scanImports(
Options *options, QSet<QString> *usedDependencies)
2438 fprintf(stdout,
"Scanning for QML imports.\n");
2440 QString qmlImportScanner;
2441 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2442 qmlImportScanner = options->qmlImportScannerBinaryPath;
2444 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2445 "/qmlimportscanner"_L1);
2448 QStringList importPaths;
2454 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2455 if (QFile::exists(mainImportPath))
2456 importPaths += shellQuote(mainImportPath);
2460 for (
const QString &prefix : options->extraPrefixDirs)
2461 if (QFile::exists(prefix +
"/qml"_L1))
2462 importPaths += shellQuote(prefix +
"/qml"_L1);
2465 for (
const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2466 if (QFile::exists(qmlImportPath)) {
2467 importPaths += shellQuote(qmlImportPath);
2469 fprintf(stderr,
"Warning: QML import path %s does not exist.\n",
2470 qPrintable(qmlImportPath));
2474 bool qmlImportExists =
false;
2476 for (
const QString &import : importPaths) {
2477 if (QDir().exists(import)) {
2478 qmlImportExists =
true;
2485 if (!qmlImportExists) {
2486 fprintf(stderr,
"Warning: no 'qml' directory found under Qt install directory "
2487 "or import paths. Skipping QML dependency scanning.\n");
2491 if (!QFile::exists(qmlImportScanner)) {
2492 fprintf(stderr,
"%s: qmlimportscanner not found at %s\n",
2493 qmlImportExists ?
"Error"_L1.data() :
"Warning"_L1.data(),
2494 qPrintable(qmlImportScanner));
2498 for (
auto rootPath : options->rootPaths) {
2499 rootPath = QFileInfo(rootPath).absoluteFilePath();
2501 if (!rootPath.endsWith(u'/'))
2505 if (!rootPath.isEmpty())
2506 importPaths += shellQuote(rootPath);
2508 qmlImportScanner +=
" -rootPath %1"_L1.arg(shellQuote(rootPath));
2511 if (!options->qrcFiles.isEmpty()) {
2512 qmlImportScanner +=
" -qrcFiles"_L1;
2513 for (
const QString &qrcFile : options->qrcFiles)
2514 qmlImportScanner += u' ' + shellQuote(qrcFile);
2517 qmlImportScanner +=
" -importPath %1"_L1.arg(importPaths.join(u' '));
2520 fprintf(stdout,
"Running qmlimportscanner with the following command: %s\n",
2521 qmlImportScanner.toLocal8Bit().constData());
2524 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2525 if (qmlImportScannerCommand == 0) {
2526 fprintf(stderr,
"Couldn't run qmlimportscanner.\n");
2532 while (fgets(buffer,
sizeof(buffer), qmlImportScannerCommand.get()) !=
nullptr)
2533 output += QByteArray(buffer, qstrlen(buffer));
2535 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2536 if (jsonDocument.isNull()) {
2537 fprintf(stderr,
"Invalid json output from qmlimportscanner.\n");
2541 QJsonArray jsonArray = jsonDocument.array();
2542 for (
int i=0; i<jsonArray.count(); ++i) {
2543 QJsonValue value = jsonArray.at(i);
2544 if (!value.isObject()) {
2545 fprintf(stderr,
"Invalid format of qmlimportscanner output.\n");
2549 QJsonObject object = value.toObject();
2550 QString path = object.value(
"path"_L1).toString();
2551 if (path.isEmpty()) {
2552 fprintf(stderr,
"Warning: QML import could not be resolved in any of the import paths: %s\n",
2553 qPrintable(object.value(
"name"_L1).toString()));
2554 }
else if (object.value(
"type"_L1).toString() ==
"module"_L1) {
2991 const QString path = QString::fromLocal8Bit(qgetenv(
"PATH"));
2992#if defined(Q_OS_WIN32)
2993 QLatin1Char separator(
';');
2995 QLatin1Char separator(
':');
2998 const QStringList paths = path.split(separator);
2999 for (
const QString &path : paths) {
3000 QFileInfo fileInfo(path + u'/' + fileName);
3001 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
3002 return path + u'/' + fileName;
3008typedef QMap<QByteArray, QByteArray> GradleProperties;
3010static GradleProperties readGradleProperties(
const QString &path)
3012 GradleProperties properties;
3014 if (!file.open(QIODevice::ReadOnly))
3017 const auto lines = file.readAll().split(
'\n');
3018 for (
const QByteArray &line : lines) {
3019 if (line.trimmed().startsWith(
'#'))
3022 const int idx = line.indexOf(
'=');
3024 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
3030static bool mergeGradleProperties(
const QString &path, GradleProperties properties)
3032 const QString oldPathStr = path + u'~';
3033 QFile::remove(oldPathStr);
3034 QFile::rename(path, oldPathStr);
3036 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
3037 fprintf(stderr,
"Can't open file: %s for writing\n", qPrintable(file.fileName()));
3041 QFile oldFile(oldPathStr);
3042 if (oldFile.open(QIODevice::ReadOnly)) {
3044 while (oldFile.readLineInto(&line)) {
3045 QList<QByteArray> prop(line.split(
'='));
3046 if (prop.size() > 1) {
3047 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3048 if (it != properties.end()) {
3049 file.write(it.key() +
'=' + it.value() +
'\n');
3050 properties.erase(it);
3054 file.write(line.trimmed() +
'\n');
3057 QFile::remove(oldPathStr);
3060 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3061 file.write(it.key() +
'=' + it.value() +
'\n');
3067#if defined(Q_OS_WIN32)
3068void checkAndWarnGradleLongPaths(
const QString &outputDirectory)
3070 QStringList longFileNames;
3071 using F = QDirListing::IteratorFlag;
3072 for (
const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3073 F::FilesOnly | F::Recursive)) {
3074 if (dirEntry.size() >= MAX_PATH)
3075 longFileNames.append(dirEntry.filePath());
3078 if (!longFileNames.isEmpty()) {
3080 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3081 "Consider moving your project to reduce its path length.\n"
3082 "The following files have too long paths:\n%s.\n",
3083 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3088bool buildAndroidProject(
const Options &options)
3090 GradleProperties localProperties;
3091 localProperties[
"sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3092 const QString localPropertiesPath = options.outputDirectory +
"local.properties"_L1;
3093 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3096 const QString gradlePropertiesPath = options.outputDirectory +
"gradle.properties"_L1;
3097 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3099 const QString gradleBuildFilePath = options.outputDirectory +
"build.gradle"_L1;
3100 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3339 options->currentArchitecture,
3340 options->stdCppName);
3341 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3344static QString zipalignPath(
const Options &options,
bool *ok)
3347 QString zipAlignTool = execSuffixAppended(options.sdkPath +
"/tools/zipalign"_L1);
3348 if (!QFile::exists(zipAlignTool)) {
3349 zipAlignTool = execSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3350 options.sdkBuildToolsVersion +
"/zipalign"_L1);
3351 if (!QFile::exists(zipAlignTool)) {
3352 fprintf(stderr,
"zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3357 return zipAlignTool;
3360bool signAAB(
const Options &options)
3363 fprintf(stdout,
"Signing Android package.\n");
3365 QString jdkPath = options.jdkPath;
3367 if (jdkPath.isEmpty())
3368 jdkPath = QString::fromLocal8Bit(qgetenv(
"JAVA_HOME"));
3370 QString jarSignerTool = execSuffixAppended(
"jarsigner"_L1);
3371 if (jdkPath.isEmpty() || !QFile::exists(jdkPath +
"/bin/"_L1 + jarSignerTool))
3372 jarSignerTool = findInPath(jarSignerTool);
3374 jarSignerTool = jdkPath +
"/bin/"_L1 + jarSignerTool;
3376 if (!QFile::exists(jarSignerTool)) {
3377 fprintf(stderr,
"Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3381 jarSignerTool =
"%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3382 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3384 if (!options.keyStorePassword.isEmpty())
3385 jarSignerTool +=
" -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3387 if (!options.storeType.isEmpty())
3388 jarSignerTool +=
" -storetype %1"_L1.arg(shellQuote(options.storeType));
3390 if (!options.keyPass.isEmpty())
3391 jarSignerTool +=
" -keypass %1"_L1.arg(shellQuote(options.keyPass));
3393 if (!options.sigFile.isEmpty())
3394 jarSignerTool +=
" -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3396 if (!options.signedJar.isEmpty())
3397 jarSignerTool +=
" -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3399 if (!options.tsaUrl.isEmpty())
3400 jarSignerTool +=
" -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3402 if (!options.tsaCert.isEmpty())
3403 jarSignerTool +=
" -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3405 if (options.internalSf)
3406 jarSignerTool +=
" -internalsf"_L1;
3408 if (options.sectionsOnly)
3409 jarSignerTool +=
" -sectionsonly"_L1;
3411 if (options.protectedAuthenticationPath)
3412 jarSignerTool +=
" -protected"_L1;
3414 auto jarSignPackage = [&](
const QString &file) {
3415 fprintf(stdout,
"Signing file %s\n", qPrintable(file));
3417 QString command = jarSignerTool +
" %1 %2"_L1.arg(shellQuote(file))
3418 .arg(shellQuote(options.keyStoreAlias));
3420 auto jarSignerCommand = openProcess(command);
3421 if (jarSignerCommand == 0) {
3422 fprintf(stderr,
"Couldn't run jarsigner.\n");
3428 while (fgets(buffer,
sizeof(buffer), jarSignerCommand.get()) !=
nullptr)
3429 fprintf(stdout,
"%s", buffer);
3432 const int errorCode = pclose(jarSignerCommand.release());
3433 if (errorCode != 0) {
3434 fprintf(stderr,
"jarsigner command failed.\n");
3436 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3442 if (options
.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3447bool signPackage(
const Options &options)
3449 const QString apksignerTool = batSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3450 options.sdkBuildToolsVersion +
"/apksigner"_L1);
3509 QString apkSignCommand =
"%1 sign --ks %2"_L1
3510 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3512 if (!options.keyStorePassword.isEmpty())
3513 apkSignCommand +=
" --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3515 if (!options.keyStoreAlias.isEmpty())
3516 apkSignCommand +=
" --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3518 if (!options.keyPass.isEmpty())
3519 apkSignCommand +=
" --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3521 if (options.verbose)
3522 apkSignCommand +=
" --verbose"_L1;
3524 apkSignCommand +=
" %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3526 auto apkSignerRunner = [](
const QString &command,
bool verbose) {
3527 auto apkSigner = openProcess(command);
3528 if (apkSigner == 0) {
3529 fprintf(stderr,
"Couldn't run apksigner.\n");
3534 while (fgets(buffer,
sizeof(buffer), apkSigner.get()) !=
nullptr)
3535 fprintf(stdout,
"%s", buffer);
3537 const int errorCode = pclose(apkSigner.release());
3538 if (errorCode != 0) {
3539 fprintf(stderr,
"apksigner command failed.\n");
3541 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3548 if (!apkSignerRunner(apkSignCommand, options
.verbose))
3551 const QString apkVerifyCommand =
3552 "%1 verify --verbose %2"_L1
3553 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3555 if (options
.buildAAB && !signAAB(options))
3559 return apkSignerRunner(apkVerifyCommand,
true) && QFile::remove(packagePath(options, UnsignedAPK));
3565 SyntaxErrorOrHelpRequested = 1,
3566 CannotReadInputFile = 2,
3567 CannotCopyAndroidTemplate = 3,
3568 CannotReadDependencies = 4,
3569 CannotCopyGnuStl = 5,
3570 CannotCopyQtFiles = 6,
3571 CannotFindApplicationBinary = 7,
3572 CannotCopyAndroidExtraLibs = 10,
3573 CannotCopyAndroidSources = 11,
3574 CannotUpdateAndroidFiles = 12,
3575 CannotCreateAndroidProject = 13,
3576 CannotBuildAndroidProject = 14,
3577 CannotSignPackage = 15,
3578 CannotInstallApk = 16,
3579 CannotCopyAndroidExtraResources = 19,
3581 CannotCreateRcc = 21,
3582 CannotGenerateJavaQmlComponents = 22
3585bool writeDependencyFile(
const Options &options)
3588 fprintf(stdout,
"Writing dependency file.\n");
3590 QString relativeTargetPath;
3594 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() +
"/timestamp"_L1;
3595 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3597 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3600 QFile depFile(options.depFilePath);
3601 if (depFile.open(QIODevice::WriteOnly)) {
3602 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3603 depFile.write(
": ");
3605 for (
const auto &file : dependenciesForDepfile) {
3606 depFile.write(
" \\\n ");
3607 depFile.write(escapeAndEncodeDependencyPath(file));
3610 depFile.write(
"\n");
3615int generateJavaQmlComponents(
const Options &options)
3617 const auto firstCharToUpper = [](
const QString &str) -> QString {
3620 return str.left(1).toUpper() + str.mid(1);
3623 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3627 str[0] = str[0].toUpper();
3629 for (
int i = 0; i < str.size(); ++i) {
3630 if (str[i] ==
"."_L1) {
3633 if (j < str.size()) {
3634 str[j] = str[j].toUpper();
3641 const auto getImportPaths = [options](
const QString &buildPath,
const QString &libName,
3642 QStringList &appImports, QStringList &externalImports) ->
bool {
3643 QFile confRspFile(
"%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3644 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3646 QTextStream rspStream(&confRspFile);
3647 while (!rspStream.atEnd()) {
3648 QString currentLine = rspStream.readLine();
3649 if (currentLine.compare(
"-importPath"_L1) == 0) {
3650 currentLine = rspStream.readLine();
3651 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3652 appImports << currentLine;
3654 externalImports << currentLine;
3659 QSet<QString> qmldirDirectories;
3660 for (
const QString &path : appImports) {
3661 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3662 while (it.hasNext()) {
3663 const QDir dir(it.next());
3664 const QString absolutePath = dir.absolutePath();
3665 if (!absolutePath.startsWith(options.outputDirectory)
3666 && dir.exists(
"qmldir"_L1)) {
3667 qmldirDirectories.insert(absolutePath);
3671 appImports << qmldirDirectories.values();
3672 appImports.removeDuplicates();
3674 return appImports.count() + externalImports.count();
3677 struct ComponentInfo {
3686 QList<ComponentInfo> qmlComponents;
3687 bool isValid() {
return qmlComponents.size() && moduleName.size(); }
3690 const auto getModuleInfo = [](
const QString &qmldirPath) -> ModuleInfo {
3691 QFile qmlDirFile(qmldirPath +
"/qmldir"_L1);
3692 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3693 return ModuleInfo();
3694 ModuleInfo moduleInfo;
3695 QSet<QString> qmlComponentNames;
3696 QTextStream qmldirStream(&qmlDirFile);
3697 while (!qmldirStream.atEnd()) {
3698 const QString currentLine = qmldirStream.readLine();
3699 if (currentLine.size() && currentLine[0].isLower()) {
3701 if (currentLine.startsWith(
"module "_L1))
3702 moduleInfo.moduleName = currentLine.split(
" "_L1)[1];
3703 else if (currentLine.startsWith(
"prefer "_L1))
3704 moduleInfo.preferPath = currentLine.split(
" "_L1)[1];
3705 }
else if (currentLine.size()
3706 && (currentLine[0].isUpper() || currentLine.startsWith(
"singleton"_L1))) {
3707 const QStringList parts = currentLine.split(
" "_L1);
3708 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3709 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3710 qmlComponentNames.insert(parts.first());
3717 const auto extractDomInfo = [](
const QString &qmlDomExecPath,
const QString &qmldirPath,
3718 const QString &qmlFile,
3719 const QStringList &otherImportPaths) -> QJsonObject {
3721#if QT_CONFIG(process)
3722 QStringList qmlDomArgs {
"-d"_L1,
"-D"_L1,
"required"_L1,
"-f"_L1,
"+:propertyInfos"_L1 };
3723 for (
auto &importPath : otherImportPaths)
3724 qmlDomArgs <<
"-I"_L1 << importPath;
3725 qmlDomArgs <<
"%1/%2"_L1.arg(qmldirPath, qmlFile);
3726 const QString qmlDomCmd =
"%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3728 process.start(qmlDomExecPath, qmlDomArgs);
3729 if (!process.waitForStarted()) {
3730 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(qmlDomCmd));
3731 return QJsonObject();
3734 if (!process.waitForFinished(30000)) {
3735 fprintf(stderr,
"Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3736 return QJsonObject();
3738 domInfo = process.readAllStandardOutput();
3740 QJsonParseError jsonError;
3741 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3742 if (jsonError.error != QJsonParseError::NoError)
3743 fprintf(stderr,
"Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3744 return jsonDoc.object();
3746#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3747 return QJsonObject();
3751 const auto getComponent = [](
const QJsonObject &dom) -> QJsonObject {
3753 return QJsonObject();
3755 const QJsonObject currentItem = dom.value(
"currentItem"_L1).toObject();
3756 if (!currentItem.value(
"isValid"_L1).toBool(
false))
3757 return QJsonObject();
3759 const QJsonArray components =
3760 currentItem.value(
"components"_L1).toObject().value(
""_L1).toArray();
3761 if (components.isEmpty())
3762 return QJsonObject();
3763 return components.constBegin()->toObject();
3766 const auto getProperties = [](
const QJsonObject &component) -> QJsonArray {
3767 QJsonArray properties;
3768 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3769 if (objects.isEmpty())
3770 return QJsonArray();
3771 const QJsonObject propertiesObject =
3772 objects[0].toObject().value(
"propertyInfos"_L1).toObject();
3773 for (
const auto &jsonProperty : propertiesObject) {
3774 const QJsonArray propertyDefs =
3775 jsonProperty.toObject().value(
"propertyDefs"_L1).toArray();
3776 if (propertyDefs.isEmpty())
3779 properties.append(propertyDefs[0].toObject());
3784 const auto getMethods = [](
const QJsonObject &component) -> QJsonArray {
3786 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3787 if (objects.isEmpty())
3788 return QJsonArray();
3789 const QJsonObject methodsObject = objects[0].toObject().value(
"methods"_L1).toObject();
3790 for (
const auto &jsonMethod : methodsObject) {
3791 const QJsonArray overloads = jsonMethod.toArray();
3792 for (
const auto &m : overloads)
3798 const static QHash<QString, QString> qmlToJavaType = {
3799 {
"real"_L1,
"Double"_L1 }, {
"double"_L1,
"Double"_L1 }, {
"int"_L1,
"Integer"_L1 },
3800 {
"float"_L1,
"Float"_L1 }, {
"bool"_L1,
"Boolean"_L1 }, {
"string"_L1,
"String"_L1 },
3801 {
"void"_L1,
"Void"_L1 }
3804 const auto endBlock = [](QTextStream &stream,
int indentWidth = 0) {
3805 stream << QString(indentWidth, u' ') <<
"}\n";
3808 const auto createHeaderBlock = [](QTextStream &stream,
const QString &javaPackage) {
3809 stream <<
"/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3810 <<
"package %1;\n\n"_L1.arg(javaPackage)
3811 <<
"import org.qtproject.qt.android.QtSignalListener;\n"
3812 <<
"import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3815 const auto beginComponentBlock = [](QTextStream &stream,
const QString &libName,
3816 const QString &moduleName,
const QString &preferPath,
3817 const ComponentInfo &componentInfo,
int indentWidth = 8) {
3818 const QString indent(indentWidth, u' ');
3821 <<
"public final class %1 extends QtQuickViewContent {\n"_L1
3822 .arg(componentInfo.name)
3823 << indent <<
" @Override public String getLibraryName() {\n"_L1
3824 << indent <<
" return \"%1\";\n"_L1.arg(libName)
3825 << indent <<
" }\n"_L1
3826 << indent <<
" @Override public String getModuleName() {\n"_L1
3827 << indent <<
" return \"%1\";\n"_L1.arg(moduleName)
3828 << indent <<
" }\n"_L1
3829 << indent <<
" @Override public String getFilePath() {\n"_L1
3830 << indent <<
" return \"qrc%1%2\";\n"_L1.arg(preferPath)
3831 .arg(componentInfo.path)
3832 << indent <<
" }\n"_L1;
3835 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3836 const QJsonObject &propertyData,
3837 int indentWidth = 8) {
3838 const QString indent(indentWidth, u' ');
3839 const QString propertyName = propertyData[
"name"_L1].toString();
3840 if (propertyName.isEmpty())
3842 const QString upperPropertyName = firstCharToUpper(propertyName);
3843 const QString typeName = propertyData[
"typeName"_L1].toString();
3844 const bool isReadyonly = propertyData[
"isReadonly"_L1].toBool();
3846 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3850 <<
"public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3851 upperPropertyName, javaTypeName, propertyName);
3855 <<
"public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3856 .arg(upperPropertyName, javaTypeName, propertyName)
3858 <<
"public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3859 .arg(upperPropertyName, javaTypeName)
3861 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3862 propertyName, javaTypeName)
3866 enum class MethodType { Signal = 0, Function = 1 };
3868 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3869 const QJsonObject &methodData,
3870 int indentWidth = 8) {
3871 const QString indent(indentWidth, u' ');
3872 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Signal)
3874 const QJsonArray parameters = methodData[
"parameters"_L1].toArray();
3876 const QString methodName = methodData[
"name"_L1].toString();
3877 if (methodName.isEmpty())
3880 const QString upperMethodName = firstCharToUpper(methodName);
3881 if (parameters.size() <= 1) {
3882 const QString typeName = !parameters.isEmpty()
3883 ? parameters[0].toObject()[
"typeName"_L1].toString()
3885 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3887 <<
"public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3888 .arg(upperMethodName, javaTypeName)
3890 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3891 .arg(methodName, javaTypeName)
3895 const auto getJavaArgsString = [¶meters]() -> QString {
3896 QList<QString> javaArgsList;
3897 for (
const auto param : parameters) {
3898 const auto typeName = param[
"typeName"_L1].toString();
3899 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3900 const auto qmlParamName = param[
"name"_L1].toString();
3902 javaArgsList.emplace_back(
3903 QStringLiteral(
"%1%2").arg(javaTypeName,
" %1"_L1.arg(qmlParamName)));
3905 return javaArgsList.join(
", "_L1);
3908 const auto getJavaClassesString = [¶meters]() -> QString {
3909 QList<QString> javaArgsList;
3910 for (
const auto param : parameters) {
3911 const auto typeName = param[
"typeName"_L1].toString();
3912 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3914 javaArgsList.emplace_back(
3915 QStringLiteral(
"%1%2").arg(javaTypeName,
".class"_L1));
3917 return javaArgsList.join(
", "_L1);
3920 const auto javaParamsString = getJavaArgsString();
3921 const auto javaParamsClassesString = getJavaClassesString();
3924 QList<QString> objectToTypeConversion;
3925 for (
auto i = 0; i < parameters.size(); ++i) {
3926 const auto typeName = parameters.at(i).toObject().value(
"typeName"_L1).toString();
3927 objectToTypeConversion.emplace_back(
"(%1) args[%2]"_L1.arg(
3928 qmlToJavaType.value(typeName,
"Object"_L1), QString::number(i)));
3932 const auto signalInterfaceName =
"%1Listener"_L1.arg(methodName);
3933 const auto objectToTypeConversionString = objectToTypeConversion.join(
", "_L1);
3934 stream << indent <<
"@FunctionalInterface\n"
3935 << indent <<
"public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3936 <<
" default void onSignalEmitted(Object[] args) {\n"
3938 <<
" on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3941 <<
" void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3942 stream << indent <<
"}\n"_L1;
3946 <<
"public int connect%1(%2 signalListener) {\n"_L1.arg(
3947 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3949 <<
" return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3950 .arg(methodName, javaParamsClassesString)
3951 << indent <<
"}\n\n";
3955 const auto writeFunctionBlock = [](QTextStream &stream,
const QJsonObject &methodData,
3956 int indentWidth = 8) {
3957 const QString indent(indentWidth, u' ');
3958 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Function)
3961 const QJsonArray params = methodData[
"parameters"_L1].toArray();
3962 const QString functionName = methodData[
"name"_L1].toString();
3964 QList<QString> javaFunctionParams;
3965 QList<QString> javaParams;
3966 for (
const auto &value : params) {
3967 const auto object = value.toObject();
3968 if (!object.contains(
"typeName"_L1)) {
3969 qWarning() <<
" -- Skipping function" << functionName
3970 <<
"due to untyped function parameter detected while generating Java "
3971 "code for QML methods.";
3975 const auto qmlParamType = object[
"typeName"_L1].toString();
3976 if (!qmlToJavaType.contains(qmlParamType)) {
3977 qWarning() <<
" -- Skipping function" << functionName
3978 <<
"due to unsupported type detected in parameters:" << qmlParamType;
3982 const auto javaTypeName{ qmlToJavaType.value(object[
"typeName"_L1].toString(),
3984 const auto javaParamName = object[
"name"_L1].toString();
3985 javaFunctionParams.push_back(
3986 QString{
"%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
3987 javaParams.append(javaParamName);
3990 const auto functionSignature {
3991 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(
", "_L1))
3993 const auto functionCallParams {
3994 javaParams.isEmpty() ?
""_L1 :
", new Object[] { %1 }"_L1.arg(javaParams.join(
", "_L1))
3997 stream << indent << functionSignature
3998 << indent <<
" invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
3999 .arg(functionCallParams)
4003 constexpr static auto markerFileName =
"qml_java_contents"_L1;
4004 const QString libName(options.applicationBinary);
4005 QString javaPackageBase = options.packageName;
4006 const QString expectedBaseLeaf =
".%1"_L1.arg(libName);
4007 if (!javaPackageBase.endsWith(expectedBaseLeaf))
4008 javaPackageBase += expectedBaseLeaf;
4009 const QString baseSourceDir =
"%1/src/%2"_L1.arg(options.outputDirectory,
4010 QString(javaPackageBase).replace(u'.', u'/'));
4011 const QString buildPath(QDir(options.buildDirectory).absolutePath());
4014 fprintf(stdout,
"Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
4015 if (!QDir().current().mkpath(baseSourceDir)) {
4016 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(baseSourceDir));
4020 QStringList appImports;
4021 QStringList externalImports;
4022 if (!getImportPaths(buildPath, libName, appImports, externalImports))
4027 const QString srcDir =
"%1/src"_L1.arg(options.outputDirectory);
4028 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
4029 while (iter.hasNext())
4030 iter.nextFileInfo().dir().removeRecursively();
4033 int generatedComponents = 0;
4034 for (
const auto &importPath : appImports) {
4035 ModuleInfo moduleInfo = getModuleInfo(importPath);
4036 if (!moduleInfo.isValid())
4039 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
4040 if (moduleInfo.moduleName == libName) {
4042 "A QML module name (%s) cannot be the same as the target name when building "
4043 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4044 qPrintable(moduleInfo.moduleName));
4048 const QString javaPackage =
"%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4049 const QString outputDir =
4050 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4051 if (!QDir().current().mkpath(outputDir)) {
4052 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(outputDir));
4058 QFile markerFile(
"%1/%2"_L1.arg(outputDir, markerFileName));
4059 if (!markerFile.open(QFile::WriteOnly)) {
4060 fprintf(stderr,
"Cannot create %s file\n", qPrintable(markerFile.fileName()));
4067 for (
const auto &qmlComponent : moduleInfo.qmlComponents) {
4068 const bool isSelected = options.selectedJavaQmlComponents.contains(
4069 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4070 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4073 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4074 externalImports + appImports);
4075 QJsonObject component = getComponent(domInfo);
4076 if (component.isEmpty())
4079 QByteArray componentClassBody;
4080 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4082 createHeaderBlock(outputStream, javaPackage);
4084 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4085 qmlComponent, indentBase);
4088 const QJsonArray properties = getProperties(component);
4089 for (
const QJsonValue &p : std::as_const(properties))
4090 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4092 const QJsonArray methods = getMethods(component);
4093 for (
const QJsonValue &m : std::as_const(methods))
4094 beginSignalBlock(outputStream, m.toObject(), indentBase);
4096 for (
const QJsonValue &m : std::as_const(methods))
4097 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4100 endBlock(outputStream, indentBase);
4101 outputStream.flush();
4104 QFile outputFile(
"%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4105 if (outputFile.exists())
4106 outputFile.remove();
4107 if (!outputFile.open(QFile::WriteOnly)) {
4108 fprintf(stderr,
"Cannot open %s file to write.\n",
4109 qPrintable(outputFile.fileName()));
4112 outputFile.write(componentClassBody);
4115 generatedComponents++;
4118 return generatedComponents;
4121int main(
int argc,
char *argv[])
4123 QCoreApplication a(argc, argv);
4126 if (options
.helpRequested || options.outputDirectory.isEmpty()) {
4128 return SyntaxErrorOrHelpRequested;
4131 options.timer.start();
4133 if (!readInputFile(&options))
4134 return CannotReadInputFile;
4136 if (Q_UNLIKELY(options
.timing))
4137 fprintf(stdout,
"[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4140 "Generating Android Package\n"
4142 " Output directory: %s\n"
4143 " Application binary: %s\n"
4144 " Android build platform: %s\n"
4145 " Install to device: %s\n",
4146 qPrintable(options.inputFileName),
4147 qPrintable(options.outputDirectory),
4148 qPrintable(options.applicationBinary),
4149 qPrintable(options.androidPlatform),
4151 ? (options.installLocation.isEmpty() ?
"Default device" : qPrintable(options.installLocation))
4155 bool androidTemplatetCopied =
false;
4157 for (
auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4160 options.setCurrentQtArchitecture(it.key(),
4161 it.value().qtInstallDirectory,
4162 it.value().qtDirectories);
4166 cleanAndroidFiles(options);
4167 if (Q_UNLIKELY(options
.timing))
4168 fprintf(stdout,
"[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4170 if (!copyAndroidTemplate(options))
4171 return CannotCopyAndroidTemplate;
4173 if (Q_UNLIKELY(options
.timing))
4174 fprintf(stdout,
"[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4175 androidTemplatetCopied =
true;
4178 if (!readDependencies(&options))
4179 return CannotReadDependencies;
4181 if (Q_UNLIKELY(options
.timing))
4182 fprintf(stdout,
"[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4184 if (!copyQtFiles(&options))
4185 return CannotCopyQtFiles;
4187 if (Q_UNLIKELY(options
.timing))
4188 fprintf(stdout,
"[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4190 if (!copyAndroidExtraLibs(&options))
4191 return CannotCopyAndroidExtraLibs;