388 QStringList arguments = QCoreApplication::arguments();
389 for (
int i=0; i<arguments.size(); ++i) {
390 const QString &argument = arguments.at(i);
391 if (argument.compare(
"--output"_L1, Qt::CaseInsensitive) == 0) {
392 if (i + 1 == arguments.size())
395 options.outputDirectory = arguments.at(++i).trimmed();
396 }
else if (argument.compare(
"--input"_L1, Qt::CaseInsensitive) == 0) {
397 if (i + 1 == arguments.size())
400 options.inputFileName = arguments.at(++i);
401 }
else if (argument.compare(
"--aab"_L1, Qt::CaseInsensitive) == 0) {
404 }
else if (!options
.buildAAB && argument.compare(
"--no-build"_L1, Qt::CaseInsensitive) == 0) {
406 }
else if (argument.compare(
"--install"_L1, Qt::CaseInsensitive) == 0) {
409 }
else if (argument.compare(
"--reinstall"_L1, Qt::CaseInsensitive) == 0) {
412 }
else if (argument.compare(
"--android-platform"_L1, Qt::CaseInsensitive) == 0) {
413 if (i + 1 == arguments.size())
416 options.androidPlatform = arguments.at(++i);
417 }
else if (argument.compare(
"--help"_L1, Qt::CaseInsensitive) == 0) {
419 }
else if (argument.compare(
"--verbose"_L1, Qt::CaseInsensitive) == 0) {
421 }
else if (argument.compare(
"--deployment"_L1, Qt::CaseInsensitive) == 0) {
422 if (i + 1 == arguments.size()) {
425 QString deploymentMechanism = arguments.at(++i);
426 if (deploymentMechanism.compare(
"bundled"_L1, Qt::CaseInsensitive) == 0) {
428 }
else if (deploymentMechanism.compare(
"unbundled"_L1,
429 Qt::CaseInsensitive) == 0) {
432 fprintf(stderr,
"Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
436 }
else if (argument.compare(
"--device"_L1, Qt::CaseInsensitive) == 0) {
437 if (i + 1 == arguments.size())
440 options.installLocation = arguments.at(++i);
441 }
else if (argument.compare(
"--release"_L1, Qt::CaseInsensitive) == 0) {
443 }
else if (argument.compare(
"--jdk"_L1, Qt::CaseInsensitive) == 0) {
444 if (i + 1 == arguments.size())
447 options.jdkPath = arguments.at(++i);
448 }
else if (argument.compare(
"--apk"_L1, Qt::CaseInsensitive) == 0) {
449 if (i + 1 == arguments.size())
452 options.apkPath = arguments.at(++i);
453 }
else if (argument.compare(
"--depfile"_L1, Qt::CaseInsensitive) == 0) {
454 if (i + 1 == arguments.size())
457 options.depFilePath = arguments.at(++i);
458 }
else if (argument.compare(
"--builddir"_L1, Qt::CaseInsensitive) == 0) {
459 if (i + 1 == arguments.size())
462 options.buildDirectory = arguments.at(++i);
463 }
else if (argument.compare(
"--sign"_L1, Qt::CaseInsensitive) == 0) {
464 if (i + 2 < arguments.size() && !arguments.at(i + 1).startsWith(
"--"_L1) &&
465 !arguments.at(i + 2).startsWith(
"--"_L1)) {
466 options.keyStore = arguments.at(++i);
467 options.keyStoreAlias = arguments.at(++i);
469 const QString keyStore = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_PATH");
470 const QString storeAlias = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_ALIAS");
471 if (keyStore.isEmpty() || storeAlias.isEmpty()) {
473 fprintf(stderr,
"Package signing path and alias values are not specified.\n");
476 "Using package signing path and alias values found from the "
477 "environment variables.\n");
478 options.keyStore = keyStore;
479 options.keyStoreAlias = storeAlias;
484 if (options.keyStorePassword.isEmpty()) {
485 fprintf(stdout,
"Using package signing store password found from the environment "
487 options.keyStorePassword = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_STORE_PASS");
489 if (options.keyPass.isEmpty()) {
490 fprintf(stdout,
"Using package signing key password found from the environment "
492 options.keyPass = qEnvironmentVariable(
"QT_ANDROID_KEYSTORE_KEY_PASS");
494 }
else if (argument.compare(
"--storepass"_L1, Qt::CaseInsensitive) == 0) {
495 if (i + 1 == arguments.size())
498 options.keyStorePassword = arguments.at(++i);
499 }
else if (argument.compare(
"--storetype"_L1, Qt::CaseInsensitive) == 0) {
500 if (i + 1 == arguments.size())
503 options.storeType = arguments.at(++i);
504 }
else if (argument.compare(
"--keypass"_L1, Qt::CaseInsensitive) == 0) {
505 if (i + 1 == arguments.size())
508 options.keyPass = arguments.at(++i);
509 }
else if (argument.compare(
"--sigfile"_L1, Qt::CaseInsensitive) == 0) {
510 if (i + 1 == arguments.size())
513 options.sigFile = arguments.at(++i);
514 }
else if (argument.compare(
"--digestalg"_L1, Qt::CaseInsensitive) == 0) {
515 if (i + 1 == arguments.size())
518 options.digestAlg = arguments.at(++i);
519 }
else if (argument.compare(
"--sigalg"_L1, Qt::CaseInsensitive) == 0) {
520 if (i + 1 == arguments.size())
523 options.sigAlg = arguments.at(++i);
524 }
else if (argument.compare(
"--tsa"_L1, Qt::CaseInsensitive) == 0) {
525 if (i + 1 == arguments.size())
528 options.tsaUrl = arguments.at(++i);
529 }
else if (argument.compare(
"--tsacert"_L1, Qt::CaseInsensitive) == 0) {
530 if (i + 1 == arguments.size())
533 options.tsaCert = arguments.at(++i);
534 }
else if (argument.compare(
"--internalsf"_L1, Qt::CaseInsensitive) == 0) {
536 }
else if (argument.compare(
"--sectionsonly"_L1, Qt::CaseInsensitive) == 0) {
538 }
else if (argument.compare(
"--protected"_L1, Qt::CaseInsensitive) == 0) {
540 }
else if (argument.compare(
"--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
542 }
else if (argument.compare(
"--build-aar"_L1, Qt::CaseInsensitive) == 0) {
544 }
else if (argument.compare(
"--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
545 options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
546 }
else if (argument.compare(
"--no-rcc-bundle-cleanup"_L1,
547 Qt::CaseInsensitive) == 0) {
549 }
else if (argument.compare(
"--copy-dependencies-only"_L1,
550 Qt::CaseInsensitive) == 0) {
557 fprintf(stderr,
"Warning: Skipping %s, AAR packages are not installable.\n",
563 fprintf(stderr,
"Warning: Skipping -aab as --build-aar is present.\n");
566 if (!options.keyStore.isEmpty()) {
567 fprintf(stderr,
"Warning: Skipping --sign, signing AAR packages is not supported.\n");
568 options.keyStore.clear();
572 if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
575 if (options.inputFileName.isEmpty())
576 options.inputFileName =
"android-%1-deployment-settings.json"_L1.arg(QDir::current().dirName());
578 options
.timing = qEnvironmentVariableIsSet(
"ANDROIDDEPLOYQT_TIMING_OUTPUT");
580 if (!QDir::current().mkpath(options.outputDirectory)) {
581 fprintf(stderr,
"Invalid output directory: %s\n", qPrintable(options.outputDirectory));
582 options.outputDirectory.clear();
584 options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath();
585 if (!options.outputDirectory.endsWith(u'/'))
586 options.outputDirectory += u'/';
624 --device [device ID]: Use specified device for deployment. Default
625 is the device selected by default by adb.
626
627 --android-platform <platform>: Builds against the given android
628 platform. By default, the highest available version will be
629 used.
630
631 --release: Builds a package ready for release. By default, the
632 package will be signed with a debug key.
633
634 --sign <url/to/keystore> <alias>: Signs the package with the
635 specified keystore, alias and store password.
636 Optional arguments for use with signing:
637 --storepass <password>: Keystore password.
638 --storetype <type>: Keystore type.
639 --keypass <password>: Password for private key (if different
640 from keystore password.)
641 --sigfile <file>: Name of .SF/.DSA file.
642 --digestalg <name>: Name of digest algorithm. Default is
643 "SHA-256".
644 --sigalg <name>: Name of signature algorithm. Default is
645 "SHA256withRSA".
646 --tsa <url>: Location of the Time Stamping Authority.
647 --tsacert <alias>: Public key certificate for TSA.
648 --internalsf: Include the .SF file inside the signature block.
649 --sectionsonly: Do not compute hash of entire manifest.
650 --protected: Keystore has protected authentication path.
651 --jarsigner: Deprecated, ignored.
652
653 NOTE: To conceal the keystore information, the environment variables
654 QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to
655 set the values keysotore and alias respectively.
656 Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,
657 and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key
658 passwords respectively. This option needs only the --sign parameter.
659
660 --jdk <path/to/jdk>: Used to find the jarsigner tool when used
661 in combination with the --release argument. By default,
662 an attempt is made to detect the tool using the JAVA_HOME and
663 PATH environment variables, in that order.
664
665 --qml-import-paths: Specify additional search paths for QML
666 imports.
667
668 --verbose: Prints out information during processing.
669
677 --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
678
679 --build-aar: Build an AAR package. This option skips --aab, --install,
680 --reinstall, and --sign options if they are provided.
681
682 --qml-importscanner-binary <path/to/qmlimportscanner>: Override the
683 default qmlimportscanner binary path. By default the
684 qmlimportscanner binary is located using the Qt directory
685 specified in the input file.
686
687 --depfile <path/to/depfile>: Output a dependency file.
688
689 --builddir <path/to/build/directory>: build directory. Necessary when
690 generating a depfile because ninja requires relative paths.
691
692 --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
693 running androiddeployqt. This option simplifies debugging of
694 the resource bundle content, but it should not be used when deploying
695 a project, since it litters the "assets" directory.
696
697 --copy-dependencies-only: resolve application dependencies and stop
698 deploying process after all libraries and resources that the
699 application depends on have been copied.
700
701 --help: Displays this information.
702)");
708bool quasiLexicographicalReverseLessThan(
const QFileInfo &fi1,
const QFileInfo &fi2)
711 QString s1 = fi1.baseName();
712 QString s2 = fi2.baseName();
714 if (s1.size() != s2.size())
715 return s1.size() > s2.size();
718 return fi1.fileName() > fi2.fileName();
916 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
919 const QFileInfo& latestPlatform = fileInfos.constFirst();
920 if (dir.exists(latestPlatform.baseName()))
921 return latestPlatform.baseName();
922 return latestPlatform.fileName();
925QString extractPackageName(
Options *options)
928 const QString gradleBuildFile = options->androidSourceDirectory +
"/build.gradle"_L1;
929 QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace;
931 if (!packageName.isEmpty() && packageName !=
"androidPackageName"_L1)
935 QFile androidManifestXml(options->androidSourceDirectory +
"/AndroidManifest.xml"_L1);
936 if (androidManifestXml.open(QIODevice::ReadOnly)) {
937 QXmlStreamReader reader(&androidManifestXml);
938 while (!reader.atEnd()) {
940 if (reader.isStartElement() && reader.name() ==
"manifest"_L1) {
941 QString packageName = reader.attributes().value(
"package"_L1).toString();
942 if (!packageName.isEmpty() && packageName !=
"org.qtproject.example"_L1)
952bool parseCmakeBoolean(
const QJsonValue &value)
954 const QString stringValue = value.toString();
955 return (stringValue.compare(QString::fromUtf8(
"true"), Qt::CaseInsensitive)
956 || stringValue.compare(QString::fromUtf8(
"on"), Qt::CaseInsensitive)
957 || stringValue.compare(QString::fromUtf8(
"yes"), Qt::CaseInsensitive)
958 || stringValue.compare(QString::fromUtf8(
"y"), Qt::CaseInsensitive)
959 || stringValue.toInt() > 0);
962bool readInputFileDirectory(
Options *options, QJsonObject &jsonObject,
const QString keyName)
964 const QJsonValue qtDirectory = jsonObject.value(keyName);
965 if (qtDirectory.isUndefined()) {
966 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
967 if (keyName ==
"qtDataDirectory"_L1) {
968 options->architectures[it.key()].qtDirectories[keyName] =
"."_L1;
970 }
else if (keyName ==
"qtLibsDirectory"_L1) {
971 options->architectures[it.key()].qtDirectories[keyName] =
"lib"_L1;
973 }
else if (keyName ==
"qtLibExecsDirectory"_L1) {
974 options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
976 }
else if (keyName ==
"qtPluginsDirectory"_L1) {
977 options->architectures[it.key()].qtDirectories[keyName] =
"plugins"_L1;
979 }
else if (keyName ==
"qtQmlDirectory"_L1) {
980 options->architectures[it.key()].qtDirectories[keyName] =
"qml"_L1;
987 if (qtDirectory.isObject()) {
988 const QJsonObject object = qtDirectory.toObject();
989 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
990 if (it.value().isUndefined()) {
992 "Invalid '%s' record in deployment settings: %s\n",
994 qPrintable(it.value().toString()));
997 if (it.value().isNull())
999 if (!options->architectures.contains(it.key())) {
1000 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
1001 qPrintable(options->architectures.keys().join(u',')));
1004 options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
1006 }
else if (qtDirectory.isString()) {
1009 const QString directory = qtDirectory.toString();
1010 options->architectures[
"arm64-v8a"_L1].qtDirectories[keyName] = directory;
1011 options->architectures[
"armeabi-v7a"_L1].qtDirectories[keyName] = directory;
1012 options->architectures[
"x86"_L1].qtDirectories[keyName] = directory;
1013 options->architectures[
"x86_64"_L1].qtDirectories[keyName] = directory;
1015 fprintf(stderr,
"Invalid format for %s in json file %s.\n",
1016 qPrintable(keyName), qPrintable(options->inputFileName));
1022bool readInputFile(
Options *options)
1024 QFile file(options->inputFileName);
1025 if (!file.open(QIODevice::ReadOnly)) {
1026 fprintf(stderr,
"Cannot read from input file: %s\n", qPrintable(options->inputFileName));
1031 QJsonParseError jsonParseError;
1032 QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll(), &jsonParseError);
1033 if (jsonDocument.isNull()) {
1034 fprintf(stderr,
"Invalid json file: %s. Reason: %s at offset %i.\n",
1035 qPrintable(options->inputFileName),
1036 qPrintable(jsonParseError.errorString()),
1037 jsonParseError.offset);
1041 QJsonObject jsonObject = jsonDocument.object();
1044 QJsonValue sdkPath = jsonObject.value(
"sdk"_L1);
1045 if (sdkPath.isUndefined()) {
1046 fprintf(stderr,
"No SDK path in json file %s\n", qPrintable(options->inputFileName));
1050 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
1055 if (options->androidPlatform.isEmpty()) {
1056 const QJsonValue ver = jsonObject.value(
"android-compile-sdk-version"_L1);
1057 if (!ver.isUndefined()) {
1058 const auto value = ver.toString();
1059 options->androidPlatform = value.startsWith(
"android-"_L1) ?
1060 value :
"android-%1"_L1.arg(value);
1063 if (options->androidPlatform.isEmpty()) {
1064 options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
1065 if (options->androidPlatform.isEmpty())
1070 if (!QDir(options->sdkPath +
"/platforms/"_L1 + options->androidPlatform).exists()) {
1071 fprintf(stderr,
"Warning: Android platform '%s' does not exist in SDK.\n",
1072 qPrintable(options->androidPlatform));
1078 const QJsonValue value = jsonObject.value(
"sdkBuildToolsRevision"_L1);
1079 if (!value.isUndefined())
1080 options->sdkBuildToolsVersion = value.toString();
1084 const QJsonValue qtInstallDirectory = jsonObject.value(
"qt"_L1);
1085 if (qtInstallDirectory.isUndefined()) {
1086 fprintf(stderr,
"No Qt directory in json file %s\n", qPrintable(options->inputFileName));
1090 if (qtInstallDirectory.isObject()) {
1091 const QJsonObject object = qtInstallDirectory.toObject();
1092 for (
auto it = object.constBegin(); it != object.constEnd(); ++it) {
1093 if (it.value().isUndefined()) {
1095 "Invalid 'qt' record in deployment settings: %s\n",
1096 qPrintable(it.value().toString()));
1099 if (it.value().isNull())
1101 options->architectures.insert(it.key(),
1104 }
else if (qtInstallDirectory.isString()) {
1107 const QString directory = qtInstallDirectory.toString();
1109 options->architectures.insert(
"arm64-v8a"_L1, qtInstallDirectoryWithTriple);
1110 options->architectures.insert(
"armeabi-v7a"_L1, qtInstallDirectoryWithTriple);
1111 options->architectures.insert(
"x86"_L1, qtInstallDirectoryWithTriple);
1112 options->architectures.insert(
"x86_64"_L1, qtInstallDirectoryWithTriple);
1116 options->qtHostDirectory = directory;
1118 fprintf(stderr,
"Invalid format for Qt install prefixes in json file %s.\n",
1119 qPrintable(options->inputFileName));
1124 if (!readInputFileDirectory(options, jsonObject,
"qtDataDirectory"_L1) ||
1125 !readInputFileDirectory(options, jsonObject,
"qtLibsDirectory"_L1) ||
1126 !readInputFileDirectory(options, jsonObject,
"qtLibExecsDirectory"_L1) ||
1127 !readInputFileDirectory(options, jsonObject,
"qtPluginsDirectory"_L1) ||
1128 !readInputFileDirectory(options, jsonObject,
"qtQmlDirectory"_L1))
1132 const QJsonValue qtHostDirectory = jsonObject.value(
"qtHostDir"_L1);
1133 if (!qtHostDirectory.isUndefined()) {
1134 if (qtHostDirectory.isString()) {
1135 options->qtHostDirectory = qtHostDirectory.toString();
1137 fprintf(stderr,
"Invalid format for Qt host directory in json file %s.\n",
1138 qPrintable(options->inputFileName));
1145 const auto extraPrefixDirs = jsonObject.value(
"extraPrefixDirs"_L1).toArray();
1146 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
1147 for (
const QJsonValue prefix : extraPrefixDirs) {
1148 options->extraPrefixDirs.push_back(prefix.toString());
1153 const auto androidDeployPlugins = jsonObject.value(
"android-deploy-plugins"_L1).toString();
1154 options->androidDeployPlugins = androidDeployPlugins.split(
";"_L1, Qt::SkipEmptyParts);
1158 const auto extraLibraryDirs = jsonObject.value(
"extraLibraryDirs"_L1).toArray();
1159 options->extraLibraryDirs.reserve(extraLibraryDirs.size());
1160 for (
const QJsonValue path : extraLibraryDirs) {
1161 options->extraLibraryDirs.push_back(path.toString());
1166 const QJsonValue androidSourcesDirectory = jsonObject.value(
"android-package-source-directory"_L1);
1167 if (!androidSourcesDirectory.isUndefined())
1168 options->androidSourceDirectory = androidSourcesDirectory.toString();
1172 const QJsonValue applicationArguments = jsonObject.value(
"android-application-arguments"_L1);
1173 if (!applicationArguments.isUndefined())
1174 options->applicationArguments = applicationArguments.toString();
1176 options->applicationArguments = QStringLiteral(
"");
1180 const QJsonValue androidVersionName = jsonObject.value(
"android-version-name"_L1);
1181 if (!androidVersionName.isUndefined())
1182 options->versionName = androidVersionName.toString();
1184 options->versionName = QStringLiteral(
"1.0");
1188 const QJsonValue androidVersionCode = jsonObject.value(
"android-version-code"_L1);
1189 if (!androidVersionCode.isUndefined())
1190 options->versionCode = androidVersionCode.toString();
1192 options->versionCode = QStringLiteral(
"1");
1196 const QJsonValue ver = jsonObject.value(
"android-min-sdk-version"_L1);
1197 if (!ver.isUndefined())
1198 options->minSdkVersion = ver.toString().toUtf8();
1202 const QJsonValue ver = jsonObject.value(
"android-target-sdk-version"_L1);
1203 if (!ver.isUndefined())
1204 options->targetSdkVersion = ver.toString().toUtf8();
1208 if (
const auto abi = jsonObject.value(
"abi"_L1); !abi.isUndefined())
1209 options->abi = jsonObject.value(
"abi"_L1).toString();
1213 const QJsonObject targetArchitectures = jsonObject.value(
"architectures"_L1).toObject();
1214 if (targetArchitectures.isEmpty()) {
1215 fprintf(stderr,
"No target architecture defined in json file.\n");
1218 for (
auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
1219 if (it.value().isUndefined()) {
1220 fprintf(stderr,
"Invalid architecture.\n");
1223 if (it.value().isNull())
1225 if (!options->architectures.contains(it.key())) {
1226 fprintf(stderr,
"Architecture %s unknown (%s).", qPrintable(it.key()),
1227 qPrintable(options->architectures.keys().join(u',')));
1230 options->architectures[it.key()].triple = it.value().toString();
1231 options->architectures[it.key()].enabled =
true;
1236 const QJsonValue ndk = jsonObject.value(
"ndk"_L1);
1237 if (ndk.isUndefined()) {
1238 fprintf(stderr,
"No NDK path defined in json file.\n");
1241 options->ndkPath = ndk.toString();
1242 const QString ndkPropertiesPath = options->ndkPath + QStringLiteral(
"/source.properties");
1243 const QSettings settings(ndkPropertiesPath, QSettings::IniFormat);
1244 const QString ndkVersion = settings.value(QStringLiteral(
"Pkg.Revision")).toString();
1245 if (ndkVersion.isEmpty()) {
1246 fprintf(stderr,
"Couldn't retrieve the NDK version from \"%s\".\n",
1247 qPrintable(ndkPropertiesPath));
1250 options->ndkVersion = ndkVersion;
1254 const QJsonValue toolchainPrefix = jsonObject.value(
"toolchain-prefix"_L1);
1255 if (toolchainPrefix.isUndefined()) {
1256 fprintf(stderr,
"No toolchain prefix defined in json file.\n");
1259 options->toolchainPrefix = toolchainPrefix.toString();
1263 const QJsonValue ndkHost = jsonObject.value(
"ndk-host"_L1);
1264 if (ndkHost.isUndefined()) {
1265 fprintf(stderr,
"No NDK host defined in json file.\n");
1268 options->ndkHost = ndkHost.toString();
1272 const QJsonValue extraLibs = jsonObject.value(
"android-extra-libs"_L1);
1273 if (!extraLibs.isUndefined())
1274 options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts);
1278 const QJsonValue qmlSkipImportScanning = jsonObject.value(
"qml-skip-import-scanning"_L1);
1279 if (!qmlSkipImportScanning.isUndefined())
1284 const QJsonValue extraPlugins = jsonObject.value(
"android-extra-plugins"_L1);
1285 if (!extraPlugins.isUndefined())
1286 options->extraPlugins = extraPlugins.toString().split(u',');
1290 const QJsonValue systemLibsPath =
1291 jsonObject.value(
"android-system-libs-prefix"_L1);
1292 if (!systemLibsPath.isUndefined())
1293 options->systemLibsPath = systemLibsPath.toString();
1297 const QJsonValue noDeploy = jsonObject.value(
"android-no-deploy-qt-libs"_L1);
1298 if (!noDeploy.isUndefined()) {
1299 bool useUnbundled = parseCmakeBoolean(noDeploy);
1306 const QJsonValue stdcppPath = jsonObject.value(
"stdcpp-path"_L1);
1307 if (stdcppPath.isUndefined()) {
1308 fprintf(stderr,
"No stdcpp-path defined in json file.\n");
1311 options->stdCppPath = stdcppPath.toString();
1315 const QJsonValue qmlRootPath = jsonObject.value(
"qml-root-path"_L1);
1316 if (qmlRootPath.isString()) {
1317 options->rootPaths.push_back(qmlRootPath.toString());
1318 }
else if (qmlRootPath.isArray()) {
1319 auto qmlRootPaths = qmlRootPath.toArray();
1320 for (
auto path : qmlRootPaths) {
1321 if (path.isString())
1322 options->rootPaths.push_back(path.toString());
1325 options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
1330 const QJsonValue qmlImportPaths = jsonObject.value(
"qml-import-paths"_L1);
1331 if (!qmlImportPaths.isUndefined())
1332 options->qmlImportPaths = qmlImportPaths.toString().split(u',');
1336 const QJsonValue qmlImportScannerBinaryPath = jsonObject.value(
"qml-importscanner-binary"_L1);
1337 if (!qmlImportScannerBinaryPath.isUndefined())
1338 options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString();
1342 const QJsonValue rccBinaryPath = jsonObject.value(
"rcc-binary"_L1);
1343 if (!rccBinaryPath.isUndefined())
1344 options->rccBinaryPath = rccBinaryPath.toString();
1348 const QJsonValue genJavaQmlComponents = jsonObject.value(
"generate-java-qtquickview-contents"_L1);
1349 if (!genJavaQmlComponents.isUndefined() && genJavaQmlComponents.isBool()) {
1353 "Warning: Skipping the generation of Java QtQuickView contents from QML "
1354 "as it can be enabled only for an AAR target.\n");
1361 const QJsonValue qmlDomBinaryPath = jsonObject.value(
"qml-dom-binary"_L1);
1362 if (!qmlDomBinaryPath.isUndefined()) {
1363 options->qmlDomBinaryPath = qmlDomBinaryPath.toString();
1366 "No qmldom binary defined in json file which is required when "
1367 "building with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n");
1373 const QJsonValue qmlFiles = jsonObject.value(
"qml-files-for-code-generator"_L1);
1374 if (!qmlFiles.isUndefined() && qmlFiles.isArray()) {
1375 const QJsonArray jArray = qmlFiles.toArray();
1376 for (
auto &item : jArray)
1377 options->selectedJavaQmlComponents << item.toString();
1382 const QJsonValue applicationBinary = jsonObject.value(
"application-binary"_L1);
1383 if (applicationBinary.isUndefined()) {
1384 fprintf(stderr,
"No application binary defined in json file.\n");
1387 options->applicationBinary = applicationBinary.toString();
1389 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1392 auto appBinaryPath =
"%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary);
1393 if (!QFile::exists(appBinaryPath)) {
1394 fprintf(stderr,
"Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
1402 const QJsonValue androidPackageName = jsonObject.value(
"android-package-name"_L1);
1403 const QString extractedPackageName = extractPackageName(options);
1404 if (!extractedPackageName.isEmpty())
1405 options->packageName = extractedPackageName;
1406 else if (!androidPackageName.isUndefined())
1407 options->packageName = androidPackageName.toString();
1409 options->packageName =
"org.qtproject.example.%1"_L1.arg(options->applicationBinary);
1412 options->packageName = cleanPackageName(options->packageName, &cleaned);
1414 fprintf(stderr,
"Warning: Package name contained illegal characters and was cleaned "
1415 "to \"%s\"\n", qPrintable(options->packageName));
1420 const QJsonValue androidAppName = jsonObject.value(
"android-app-name"_L1);
1421 if (!androidAppName.isUndefined())
1422 options->appName = androidAppName.toString();
1424 options->appName = options->applicationBinary;
1729 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1730 const auto files = current.entryList(QDir::Files);
1731 result.reserve(dirs.size() + files.size());
1732 for (
const QString &dir : dirs) {
1733 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1735 for (
const QString &file : files) {
1736 result += rootDir.relativeFilePath(current.filePath(file));
1741bool copyAndroidExtraResources(
Options *options)
1743 if (options->extraPlugins.isEmpty())
1747 fprintf(stdout,
"Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1749 for (
const QString &extraResource : options->extraPlugins) {
1750 QFileInfo extraResourceInfo(extraResource);
1751 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1752 fprintf(stderr,
"External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1756 QDir resourceDir(extraResource);
1757 QString assetsDir = options->outputDirectory +
"/assets/"_L1 +
1758 resourceDir.dirName() + u'/';
1759 QString libsDir = options->outputDirectory +
"/libs/"_L1 + options->currentArchitecture + u'/';
1761 const QStringList files = allFilesInside(resourceDir, resourceDir);
1762 for (
const QString &resourceFile : files) {
1763 QString originFile(resourceDir.filePath(resourceFile));
1764 QString destinationFile;
1765 if (!resourceFile.endsWith(
".so"_L1)) {
1766 destinationFile = assetsDir + resourceFile;
1768 if (isDeployment(options, Options::Unbundled)
1769 || !checkArchitecture(*options, originFile)) {
1772 destinationFile = libsDir + resourceFile;
1773 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1776 if (!copyFileIfNewer(originFile, destinationFile,
1777 *options, options->createSymlinksOnly)) {
1786bool updateFile(
const QString &fileName,
const QHash<QString, QString> &replacements)
1788 QFile inputFile(fileName);
1789 if (!inputFile.open(QIODevice::ReadOnly)) {
1790 fprintf(stderr,
"Cannot open %s for reading.\n", qPrintable(fileName));
1796 QByteArray contents = inputFile.readAll();
1798 bool hasReplacements =
false;
1799 QHash<QString, QString>::const_iterator it;
1800 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1801 if (it.key() == it.value())
1805 int index = contents.indexOf(it.key().toUtf8());
1807 contents.replace(index, it.key().size(), it.value().toUtf8());
1808 hasReplacements =
true;
1815 if (hasReplacements) {
1818 if (!inputFile.open(QIODevice::WriteOnly)) {
1819 fprintf(stderr,
"Cannot open %s for writing.\n", qPrintable(fileName));
1825 QRegularExpression emptyLinesRegex(
"\\n\\s+\\n"_L1);
1826 contents = QString::fromUtf8(contents).replace(emptyLinesRegex,
"\n"_L1).toUtf8();
1828 inputFile.write(contents);
1835bool updateLibsXml(
Options *options)
1838 fprintf(stdout,
" -- res/values/libs.xml\n");
1840 QString fileName = options->outputDirectory +
"/res/values/libs.xml"_L1;
1841 if (!QFile::exists(fileName)) {
1842 fprintf(stderr,
"Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1847 QString allLocalLibs;
1850 for (
auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
2144 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2145 u'/' + relativeFileName;
2148 if (relativeFileName.startsWith(
"lib/"_L1)) {
2149 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2150 u'/' + relativeFileName.mid(
sizeof(
"lib/") - 1);
2152 return options->qtInstallDirectory + u'/' + relativeFileName;
2156static QString resolveLibDependency(
const Options *options,
const QString &fileName)
2158 if (QFile::exists(absoluteFilePath(options, fileName)))
2161 const QString abiSuffix = u'_' + options->currentArchitecture +
".so"_L1;
2162 QString alternative;
2163 if (fileName.endsWith(abiSuffix))
2164 alternative = fileName.chopped(abiSuffix.size()) +
".so"_L1;
2166 alternative = fileName.chopped(
sizeof(
".so") - 1) + abiSuffix;
2168 if (QFile::exists(absoluteFilePath(options, alternative)))
2174QList<QtDependency> findFilesRecursively(
const Options &options,
const QFileInfo &info,
const QString &rootPath)
2180 QList<QtDependency> ret;
2182 QDir dir(info.filePath());
2183 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2185 for (
const QString &entry : entries) {
2186 ret += findFilesRecursively(options,
2187 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2193 return QList<
QtDependency>() <<
QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2197QList<QtDependency> findFilesRecursively(
const Options &options,
const QString &fileName)
2205 QList<QtDependency> deps;
2206 for (
const auto &prefix : options.extraPrefixDirs) {
2207 QFileInfo info(prefix + u'/' + fileName);
2208 if (info.exists()) {
2210 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2212 return findFilesRecursively(options, info, prefix + u'/');
2217 if (
std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2218 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2219 QFileInfo info(options.qtInstallDirectory +
"/"_L1 + fileName);
2220 QFileInfo rootPath(options.qtInstallDirectory +
"/"_L1);
2221 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2226void readDependenciesFromFiles(
Options *options,
const QList<QtDependency> &files,
2227 QSet<QString> &usedDependencies,
2228 QSet<QString> &remainingDependencies)
2230 for (
const QtDependency &fileName : files) {
2231 if (usedDependencies.contains(fileName.absolutePath))
2234 if (fileName.absolutePath.endsWith(
".so"_L1)) {
2235 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2236 &remainingDependencies)) {
2237 fprintf(stdout,
"Skipping file dependency: %s\n",
2238 qPrintable(fileName.relativePath));
2336 if (reader.hasError()) {
2337 fprintf(stderr,
"Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2341 fprintf(stdout,
"No android dependencies for %s\n", qPrintable(moduleName));
2343 options->features.removeDuplicates();
2348QStringList getQtLibsFromElf(
const Options &options,
const QString &fileName)
2350 QString readElf = llvmReadobjPath(options);
2351 if (!QFile::exists(readElf)) {
2352 fprintf(stderr,
"Command does not exist: %s\n", qPrintable(readElf));
2353 return QStringList();
2356 readElf =
"%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2358 auto readElfCommand = openProcess(readElf);
2359 if (!readElfCommand) {
2360 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(readElf));
2361 return QStringList();
2366 bool readLibs =
false;
2368 while (fgets(buffer,
sizeof(buffer), readElfCommand.get()) !=
nullptr) {
2369 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2371 line = line.trimmed();
2373 if (line.startsWith(
"Arch: ")) {
2374 auto it = elfArchitectures.find(line.mid(6));
2375 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2377 fprintf(stdout,
"Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2381 readLibs = line.startsWith(
"NeededLibraries");
2384 if (!line.startsWith(
"lib"))
2386 library = QString::fromLatin1(line);
2387 QString libraryName =
"lib/"_L1 + library;
2388 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2396 const QString &fileName,
2397 QSet<QString> *usedDependencies,
2398 QSet<QString> *remainingDependencies)
2401 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2404 fprintf(stdout,
"Reading dependencies from %s\n", qPrintable(fileName));
2405 for (
const QString &dep : dependencies)
2406 fprintf(stdout,
" %s\n", qPrintable(dep));
2409 QList<QString> dependenciesToCheck;
2410 for (
const QString &dependency : dependencies) {
2411 if (usedDependencies->contains(dependency))
2414 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2415 usedDependencies->insert(dependency);
2416 if (!readDependenciesFromElf(options,
2417 absoluteDependencyPath,
2419 remainingDependencies)) {
2423 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2424 if (options->verbose)
2425 fprintf(stdout,
"Appending dependency: %s\n", qPrintable(dependency));
2426 dependenciesToCheck.append(dependency);
2429 for (
const QString &dependency : std::as_const(dependenciesToCheck)) {
2430 QString qtBaseName = dependency.mid(
sizeof(
"lib/lib") - 1);
2431 qtBaseName = qtBaseName.left(qtBaseName.size() - (
sizeof(
".so") - 1));
2432 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2440bool scanImports(
Options *options, QSet<QString> *usedDependencies)
2443 fprintf(stdout,
"Scanning for QML imports.\n");
2445 QString qmlImportScanner;
2446 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2447 qmlImportScanner = options->qmlImportScannerBinaryPath;
2449 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2450 "/qmlimportscanner"_L1);
2453 QStringList importPaths;
2459 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2460 if (QFile::exists(mainImportPath))
2461 importPaths += shellQuote(mainImportPath);
2465 for (
const QString &prefix : options->extraPrefixDirs)
2466 if (QFile::exists(prefix +
"/qml"_L1))
2467 importPaths += shellQuote(prefix +
"/qml"_L1);
2470 for (
const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2471 if (QFile::exists(qmlImportPath)) {
2472 importPaths += shellQuote(qmlImportPath);
2474 fprintf(stderr,
"Warning: QML import path %s does not exist.\n",
2475 qPrintable(qmlImportPath));
2479 bool qmlImportExists =
false;
2481 for (
const QString &import : importPaths) {
2482 if (QDir().exists(import)) {
2483 qmlImportExists =
true;
2490 if (!qmlImportExists) {
2491 fprintf(stderr,
"Warning: no 'qml' directory found under Qt install directory "
2492 "or import paths. Skipping QML dependency scanning.\n");
2496 if (!QFile::exists(qmlImportScanner)) {
2497 fprintf(stderr,
"%s: qmlimportscanner not found at %s\n",
2498 qmlImportExists ?
"Error"_L1.data() :
"Warning"_L1.data(),
2499 qPrintable(qmlImportScanner));
2503 for (
auto rootPath : options->rootPaths) {
2504 rootPath = QFileInfo(rootPath).absoluteFilePath();
2506 if (!rootPath.endsWith(u'/'))
2510 if (!rootPath.isEmpty())
2511 importPaths += shellQuote(rootPath);
2513 qmlImportScanner +=
" -rootPath %1"_L1.arg(shellQuote(rootPath));
2516 if (!options->qrcFiles.isEmpty()) {
2517 qmlImportScanner +=
" -qrcFiles"_L1;
2518 for (
const QString &qrcFile : options->qrcFiles)
2519 qmlImportScanner += u' ' + shellQuote(qrcFile);
2522 qmlImportScanner +=
" -importPath %1"_L1.arg(importPaths.join(u' '));
2525 fprintf(stdout,
"Running qmlimportscanner with the following command: %s\n",
2526 qmlImportScanner.toLocal8Bit().constData());
2529 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2530 if (qmlImportScannerCommand == 0) {
2531 fprintf(stderr,
"Couldn't run qmlimportscanner.\n");
2537 while (fgets(buffer,
sizeof(buffer), qmlImportScannerCommand.get()) !=
nullptr)
2538 output += QByteArray(buffer, qstrlen(buffer));
2540 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2541 if (jsonDocument.isNull()) {
2542 fprintf(stderr,
"Invalid json output from qmlimportscanner.\n");
2546 QJsonArray jsonArray = jsonDocument.array();
2547 for (
int i=0; i<jsonArray.count(); ++i) {
2548 QJsonValue value = jsonArray.at(i);
2549 if (!value.isObject()) {
2550 fprintf(stderr,
"Invalid format of qmlimportscanner output.\n");
2554 QJsonObject object = value.toObject();
2555 QString path = object.value(
"path"_L1).toString();
2556 if (path.isEmpty()) {
2557 fprintf(stderr,
"Warning: QML import could not be resolved in any of the import paths: %s\n",
2558 qPrintable(object.value(
"name"_L1).toString()));
2559 }
else if (object.value(
"type"_L1).toString() ==
"module"_L1) {
2996 const QString path = QString::fromLocal8Bit(qgetenv(
"PATH"));
2997#if defined(Q_OS_WIN32)
2998 QLatin1Char separator(
';');
3000 QLatin1Char separator(
':');
3003 const QStringList paths = path.split(separator);
3004 for (
const QString &path : paths) {
3005 QFileInfo fileInfo(path + u'/' + fileName);
3006 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
3007 return path + u'/' + fileName;
3013typedef QMap<QByteArray, QByteArray> GradleProperties;
3015static GradleProperties readGradleProperties(
const QString &path)
3017 GradleProperties properties;
3019 if (!file.open(QIODevice::ReadOnly))
3022 const auto lines = file.readAll().split(
'\n');
3023 for (
const QByteArray &line : lines) {
3024 if (line.trimmed().startsWith(
'#'))
3027 const int idx = line.indexOf(
'=');
3029 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
3035static bool mergeGradleProperties(
const QString &path, GradleProperties properties)
3037 const QString oldPathStr = path + u'~';
3038 QFile::remove(oldPathStr);
3039 QFile::rename(path, oldPathStr);
3041 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
3042 fprintf(stderr,
"Can't open file: %s for writing\n", qPrintable(file.fileName()));
3046 QFile oldFile(oldPathStr);
3047 if (oldFile.open(QIODevice::ReadOnly)) {
3049 while (oldFile.readLineInto(&line)) {
3050 QList<QByteArray> prop(line.split(
'='));
3051 if (prop.size() > 1) {
3052 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3053 if (it != properties.end()) {
3054 file.write(it.key() +
'=' + it.value() +
'\n');
3055 properties.erase(it);
3059 file.write(line.trimmed() +
'\n');
3062 QFile::remove(oldPathStr);
3065 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3066 file.write(it.key() +
'=' + it.value() +
'\n');
3072#if defined(Q_OS_WIN32)
3073void checkAndWarnGradleLongPaths(
const QString &outputDirectory)
3075 QStringList longFileNames;
3076 using F = QDirListing::IteratorFlag;
3077 for (
const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3078 F::FilesOnly | F::Recursive)) {
3079 if (dirEntry.size() >= MAX_PATH)
3080 longFileNames.append(dirEntry.filePath());
3083 if (!longFileNames.isEmpty()) {
3085 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3086 "Consider moving your project to reduce its path length.\n"
3087 "The following files have too long paths:\n%s.\n",
3088 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3093bool buildAndroidProject(
const Options &options)
3095 GradleProperties localProperties;
3096 localProperties[
"sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3097 const QString localPropertiesPath = options.outputDirectory +
"local.properties"_L1;
3098 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3101 const QString gradlePropertiesPath = options.outputDirectory +
"gradle.properties"_L1;
3102 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3104 const QString gradleBuildFilePath = options.outputDirectory +
"build.gradle"_L1;
3105 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3107 gradleProperties[
"buildDir"] =
"build";
3108 gradleProperties[
"qtAndroidDir"] =
3109 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3110 "/src/android/java"_L1)
3353 options->currentArchitecture,
3354 options->stdCppName);
3355 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3358static QString zipalignPath(
const Options &options,
bool *ok)
3361 QString zipAlignTool = execSuffixAppended(options.sdkPath +
"/tools/zipalign"_L1);
3362 if (!QFile::exists(zipAlignTool)) {
3363 zipAlignTool = execSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3364 options.sdkBuildToolsVersion +
"/zipalign"_L1);
3365 if (!QFile::exists(zipAlignTool)) {
3366 fprintf(stderr,
"zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3371 return zipAlignTool;
3374bool signAAB(
const Options &options)
3377 fprintf(stdout,
"Signing Android package.\n");
3379 QString jdkPath = options.jdkPath;
3381 if (jdkPath.isEmpty())
3382 jdkPath = QString::fromLocal8Bit(qgetenv(
"JAVA_HOME"));
3384 QString jarSignerTool = execSuffixAppended(
"jarsigner"_L1);
3385 if (jdkPath.isEmpty() || !QFile::exists(jdkPath +
"/bin/"_L1 + jarSignerTool))
3386 jarSignerTool = findInPath(jarSignerTool);
3388 jarSignerTool = jdkPath +
"/bin/"_L1 + jarSignerTool;
3390 if (!QFile::exists(jarSignerTool)) {
3391 fprintf(stderr,
"Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3395 jarSignerTool =
"%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3396 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3398 if (!options.keyStorePassword.isEmpty())
3399 jarSignerTool +=
" -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3401 if (!options.storeType.isEmpty())
3402 jarSignerTool +=
" -storetype %1"_L1.arg(shellQuote(options.storeType));
3404 if (!options.keyPass.isEmpty())
3405 jarSignerTool +=
" -keypass %1"_L1.arg(shellQuote(options.keyPass));
3407 if (!options.sigFile.isEmpty())
3408 jarSignerTool +=
" -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3410 if (!options.signedJar.isEmpty())
3411 jarSignerTool +=
" -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3413 if (!options.tsaUrl.isEmpty())
3414 jarSignerTool +=
" -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3416 if (!options.tsaCert.isEmpty())
3417 jarSignerTool +=
" -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3419 if (options.internalSf)
3420 jarSignerTool +=
" -internalsf"_L1;
3422 if (options.sectionsOnly)
3423 jarSignerTool +=
" -sectionsonly"_L1;
3425 if (options.protectedAuthenticationPath)
3426 jarSignerTool +=
" -protected"_L1;
3428 auto jarSignPackage = [&](
const QString &file) {
3429 fprintf(stdout,
"Signing file %s\n", qPrintable(file));
3431 QString command = jarSignerTool +
" %1 %2"_L1.arg(shellQuote(file))
3432 .arg(shellQuote(options.keyStoreAlias));
3434 auto jarSignerCommand = openProcess(command);
3435 if (jarSignerCommand == 0) {
3436 fprintf(stderr,
"Couldn't run jarsigner.\n");
3442 while (fgets(buffer,
sizeof(buffer), jarSignerCommand.get()) !=
nullptr)
3443 fprintf(stdout,
"%s", buffer);
3446 const int errorCode = pclose(jarSignerCommand.release());
3447 if (errorCode != 0) {
3448 fprintf(stderr,
"jarsigner command failed.\n");
3450 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3456 if (options
.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3461bool signPackage(
const Options &options)
3463 const QString apksignerTool = batSuffixAppended(options.sdkPath +
"/build-tools/"_L1 +
3464 options.sdkBuildToolsVersion +
"/apksigner"_L1);
3523 QString apkSignCommand =
"%1 sign --ks %2"_L1
3524 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3526 if (!options.keyStorePassword.isEmpty())
3527 apkSignCommand +=
" --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3529 if (!options.keyStoreAlias.isEmpty())
3530 apkSignCommand +=
" --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3532 if (!options.keyPass.isEmpty())
3533 apkSignCommand +=
" --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3535 if (options.verbose)
3536 apkSignCommand +=
" --verbose"_L1;
3538 apkSignCommand +=
" %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3540 auto apkSignerRunner = [](
const QString &command,
bool verbose) {
3541 auto apkSigner = openProcess(command);
3542 if (apkSigner == 0) {
3543 fprintf(stderr,
"Couldn't run apksigner.\n");
3548 while (fgets(buffer,
sizeof(buffer), apkSigner.get()) !=
nullptr)
3549 fprintf(stdout,
"%s", buffer);
3551 const int errorCode = pclose(apkSigner.release());
3552 if (errorCode != 0) {
3553 fprintf(stderr,
"apksigner command failed.\n");
3555 fprintf(stderr,
" -- Run with --verbose for more information.\n");
3562 if (!apkSignerRunner(apkSignCommand, options
.verbose))
3565 const QString apkVerifyCommand =
3566 "%1 verify --verbose %2"_L1
3567 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3569 if (options
.buildAAB && !signAAB(options))
3573 return apkSignerRunner(apkVerifyCommand,
true) && QFile::remove(packagePath(options, UnsignedAPK));
3579 SyntaxErrorOrHelpRequested = 1,
3580 CannotReadInputFile = 2,
3581 CannotCopyAndroidTemplate = 3,
3582 CannotReadDependencies = 4,
3583 CannotCopyGnuStl = 5,
3584 CannotCopyQtFiles = 6,
3585 CannotFindApplicationBinary = 7,
3586 CannotCopyAndroidExtraLibs = 10,
3587 CannotCopyAndroidSources = 11,
3588 CannotUpdateAndroidFiles = 12,
3589 CannotCreateAndroidProject = 13,
3590 CannotBuildAndroidProject = 14,
3591 CannotSignPackage = 15,
3592 CannotInstallApk = 16,
3593 CannotCopyAndroidExtraResources = 19,
3595 CannotCreateRcc = 21,
3596 CannotGenerateJavaQmlComponents = 22
3599bool writeDependencyFile(
const Options &options)
3602 fprintf(stdout,
"Writing dependency file.\n");
3604 QString relativeTargetPath;
3608 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() +
"/timestamp"_L1;
3609 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3611 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3614 QFile depFile(options.depFilePath);
3615 if (depFile.open(QIODevice::WriteOnly)) {
3616 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3617 depFile.write(
": ");
3619 for (
const auto &file : dependenciesForDepfile) {
3620 depFile.write(
" \\\n ");
3621 depFile.write(escapeAndEncodeDependencyPath(file));
3624 depFile.write(
"\n");
3629int generateJavaQmlComponents(
const Options &options)
3631 const auto firstCharToUpper = [](
const QString &str) -> QString {
3634 return str.left(1).toUpper() + str.mid(1);
3637 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3641 str[0] = str[0].toUpper();
3643 for (
int i = 0; i < str.size(); ++i) {
3644 if (str[i] ==
"."_L1) {
3647 if (j < str.size()) {
3648 str[j] = str[j].toUpper();
3655 const auto getImportPaths = [options](
const QString &buildPath,
const QString &libName,
3656 QStringList &appImports, QStringList &externalImports) ->
bool {
3657 QFile confRspFile(
"%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3658 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3660 QTextStream rspStream(&confRspFile);
3661 while (!rspStream.atEnd()) {
3662 QString currentLine = rspStream.readLine();
3663 if (currentLine.compare(
"-importPath"_L1) == 0) {
3664 currentLine = rspStream.readLine();
3665 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3666 appImports << currentLine;
3668 externalImports << currentLine;
3673 QSet<QString> qmldirDirectories;
3674 for (
const QString &path : appImports) {
3675 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3676 while (it.hasNext()) {
3677 const QDir dir(it.next());
3678 const QString absolutePath = dir.absolutePath();
3679 if (!absolutePath.startsWith(options.outputDirectory)
3680 && dir.exists(
"qmldir"_L1)) {
3681 qmldirDirectories.insert(absolutePath);
3685 appImports << qmldirDirectories.values();
3686 appImports.removeDuplicates();
3688 return appImports.count() + externalImports.count();
3691 struct ComponentInfo {
3700 QList<ComponentInfo> qmlComponents;
3701 bool isValid() {
return qmlComponents.size() && moduleName.size(); }
3704 const auto getModuleInfo = [](
const QString &qmldirPath) -> ModuleInfo {
3705 QFile qmlDirFile(qmldirPath +
"/qmldir"_L1);
3706 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3707 return ModuleInfo();
3708 ModuleInfo moduleInfo;
3709 QSet<QString> qmlComponentNames;
3710 QTextStream qmldirStream(&qmlDirFile);
3711 while (!qmldirStream.atEnd()) {
3712 const QString currentLine = qmldirStream.readLine();
3713 if (currentLine.size() && currentLine[0].isLower()) {
3715 if (currentLine.startsWith(
"module "_L1))
3716 moduleInfo.moduleName = currentLine.split(
" "_L1)[1];
3717 else if (currentLine.startsWith(
"prefer "_L1))
3718 moduleInfo.preferPath = currentLine.split(
" "_L1)[1];
3719 }
else if (currentLine.size()
3720 && (currentLine[0].isUpper() || currentLine.startsWith(
"singleton"_L1))) {
3721 const QStringList parts = currentLine.split(
" "_L1);
3722 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3723 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3724 qmlComponentNames.insert(parts.first());
3731 const auto extractDomInfo = [](
const QString &qmlDomExecPath,
const QString &qmldirPath,
3732 const QString &qmlFile,
3733 const QStringList &otherImportPaths) -> QJsonObject {
3735#if QT_CONFIG(process)
3736 QStringList qmlDomArgs {
"-d"_L1,
"-D"_L1,
"required"_L1,
"-f"_L1,
"+:propertyInfos"_L1 };
3737 for (
auto &importPath : otherImportPaths)
3738 qmlDomArgs <<
"-I"_L1 << importPath;
3739 qmlDomArgs <<
"%1/%2"_L1.arg(qmldirPath, qmlFile);
3740 const QString qmlDomCmd =
"%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3742 process.start(qmlDomExecPath, qmlDomArgs);
3743 if (!process.waitForStarted()) {
3744 fprintf(stderr,
"Cannot execute command %s\n", qPrintable(qmlDomCmd));
3745 return QJsonObject();
3748 if (!process.waitForFinished(30000)) {
3749 fprintf(stderr,
"Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3750 return QJsonObject();
3752 domInfo = process.readAllStandardOutput();
3754 QJsonParseError jsonError;
3755 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3756 if (jsonError.error != QJsonParseError::NoError)
3757 fprintf(stderr,
"Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3758 return jsonDoc.object();
3760#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3761 return QJsonObject();
3765 const auto getComponent = [](
const QJsonObject &dom) -> QJsonObject {
3767 return QJsonObject();
3769 const QJsonObject currentItem = dom.value(
"currentItem"_L1).toObject();
3770 if (!currentItem.value(
"isValid"_L1).toBool(
false))
3771 return QJsonObject();
3773 const QJsonArray components =
3774 currentItem.value(
"components"_L1).toObject().value(
""_L1).toArray();
3775 if (components.isEmpty())
3776 return QJsonObject();
3777 return components.constBegin()->toObject();
3780 const auto getProperties = [](
const QJsonObject &component) -> QJsonArray {
3781 QJsonArray properties;
3782 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3783 if (objects.isEmpty())
3784 return QJsonArray();
3785 const QJsonObject propertiesObject =
3786 objects[0].toObject().value(
"propertyInfos"_L1).toObject();
3787 for (
const auto &jsonProperty : propertiesObject) {
3788 const QJsonArray propertyDefs =
3789 jsonProperty.toObject().value(
"propertyDefs"_L1).toArray();
3790 if (propertyDefs.isEmpty())
3793 properties.append(propertyDefs[0].toObject());
3798 const auto getMethods = [](
const QJsonObject &component) -> QJsonArray {
3800 const QJsonArray objects = component.value(
"objects"_L1).toArray();
3801 if (objects.isEmpty())
3802 return QJsonArray();
3803 const QJsonObject methodsObject = objects[0].toObject().value(
"methods"_L1).toObject();
3804 for (
const auto &jsonMethod : methodsObject) {
3805 const QJsonArray overloads = jsonMethod.toArray();
3806 for (
const auto &m : overloads)
3812 const static QHash<QString, QString> qmlToJavaType = {
3813 {
"real"_L1,
"Double"_L1 }, {
"double"_L1,
"Double"_L1 }, {
"int"_L1,
"Integer"_L1 },
3814 {
"float"_L1,
"Float"_L1 }, {
"bool"_L1,
"Boolean"_L1 }, {
"string"_L1,
"String"_L1 },
3815 {
"void"_L1,
"Void"_L1 }
3818 const auto endBlock = [](QTextStream &stream,
int indentWidth = 0) {
3819 stream << QString(indentWidth, u' ') <<
"}\n";
3822 const auto createHeaderBlock = [](QTextStream &stream,
const QString &javaPackage) {
3823 stream <<
"/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3824 <<
"package %1;\n\n"_L1.arg(javaPackage)
3825 <<
"import org.qtproject.qt.android.QtSignalListener;\n"
3826 <<
"import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3829 const auto beginComponentBlock = [](QTextStream &stream,
const QString &libName,
3830 const QString &moduleName,
const QString &preferPath,
3831 const ComponentInfo &componentInfo,
int indentWidth = 8) {
3832 const QString indent(indentWidth, u' ');
3835 <<
"public final class %1 extends QtQuickViewContent {\n"_L1
3836 .arg(componentInfo.name)
3837 << indent <<
" @Override public String getLibraryName() {\n"_L1
3838 << indent <<
" return \"%1\";\n"_L1.arg(libName)
3839 << indent <<
" }\n"_L1
3840 << indent <<
" @Override public String getModuleName() {\n"_L1
3841 << indent <<
" return \"%1\";\n"_L1.arg(moduleName)
3842 << indent <<
" }\n"_L1
3843 << indent <<
" @Override public String getFilePath() {\n"_L1
3844 << indent <<
" return \"qrc%1%2\";\n"_L1.arg(preferPath)
3845 .arg(componentInfo.path)
3846 << indent <<
" }\n"_L1;
3849 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3850 const QJsonObject &propertyData,
3851 int indentWidth = 8) {
3852 const QString indent(indentWidth, u' ');
3853 const QString propertyName = propertyData[
"name"_L1].toString();
3854 if (propertyName.isEmpty())
3856 const QString upperPropertyName = firstCharToUpper(propertyName);
3857 const QString typeName = propertyData[
"typeName"_L1].toString();
3858 const bool isReadyonly = propertyData[
"isReadonly"_L1].toBool();
3860 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3864 <<
"public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3865 upperPropertyName, javaTypeName, propertyName);
3869 <<
"public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3870 .arg(upperPropertyName, javaTypeName, propertyName)
3872 <<
"public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3873 .arg(upperPropertyName, javaTypeName)
3875 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3876 propertyName, javaTypeName)
3880 enum class MethodType { Signal = 0, Function = 1 };
3882 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3883 const QJsonObject &methodData,
3884 int indentWidth = 8) {
3885 const QString indent(indentWidth, u' ');
3886 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Signal)
3888 const QJsonArray parameters = methodData[
"parameters"_L1].toArray();
3890 const QString methodName = methodData[
"name"_L1].toString();
3891 if (methodName.isEmpty())
3894 const QString upperMethodName = firstCharToUpper(methodName);
3895 if (parameters.size() <= 1) {
3896 const QString typeName = !parameters.isEmpty()
3897 ? parameters[0].toObject()[
"typeName"_L1].toString()
3899 const QString javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3901 <<
"public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3902 .arg(upperMethodName, javaTypeName)
3904 <<
" return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3905 .arg(methodName, javaTypeName)
3909 const auto getJavaArgsString = [¶meters]() -> QString {
3910 QList<QString> javaArgsList;
3911 for (
const auto param : parameters) {
3912 const auto typeName = param[
"typeName"_L1].toString();
3913 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3914 const auto qmlParamName = param[
"name"_L1].toString();
3916 javaArgsList.emplace_back(
3917 QStringLiteral(
"%1%2").arg(javaTypeName,
" %1"_L1.arg(qmlParamName)));
3919 return javaArgsList.join(
", "_L1);
3922 const auto getJavaClassesString = [¶meters]() -> QString {
3923 QList<QString> javaArgsList;
3924 for (
const auto param : parameters) {
3925 const auto typeName = param[
"typeName"_L1].toString();
3926 const auto javaTypeName = qmlToJavaType.value(typeName,
"Object"_L1);
3928 javaArgsList.emplace_back(
3929 QStringLiteral(
"%1%2").arg(javaTypeName,
".class"_L1));
3931 return javaArgsList.join(
", "_L1);
3934 const auto javaParamsString = getJavaArgsString();
3935 const auto javaParamsClassesString = getJavaClassesString();
3938 QList<QString> objectToTypeConversion;
3939 for (
auto i = 0; i < parameters.size(); ++i) {
3940 const auto typeName = parameters.at(i).toObject().value(
"typeName"_L1).toString();
3941 objectToTypeConversion.emplace_back(
"(%1) args[%2]"_L1.arg(
3942 qmlToJavaType.value(typeName,
"Object"_L1), QString::number(i)));
3946 const auto signalInterfaceName =
"%1Listener"_L1.arg(methodName);
3947 const auto objectToTypeConversionString = objectToTypeConversion.join(
", "_L1);
3948 stream << indent <<
"@FunctionalInterface\n"
3949 << indent <<
"public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3950 <<
" default void onSignalEmitted(Object[] args) {\n"
3952 <<
" on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3955 <<
" void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3956 stream << indent <<
"}\n"_L1;
3960 <<
"public int connect%1(%2 signalListener) {\n"_L1.arg(
3961 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3963 <<
" return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3964 .arg(methodName, javaParamsClassesString)
3965 << indent <<
"}\n\n";
3969 const auto writeFunctionBlock = [](QTextStream &stream,
const QJsonObject &methodData,
3970 int indentWidth = 8) {
3971 const QString indent(indentWidth, u' ');
3972 if (MethodType(methodData[
"methodType"_L1].toInt()) != MethodType::Function)
3975 const QJsonArray params = methodData[
"parameters"_L1].toArray();
3976 const QString functionName = methodData[
"name"_L1].toString();
3978 QList<QString> javaFunctionParams;
3979 QList<QString> javaParams;
3980 for (
const auto &value : params) {
3981 const auto object = value.toObject();
3982 if (!object.contains(
"typeName"_L1)) {
3983 qWarning() <<
" -- Skipping function" << functionName
3984 <<
"due to untyped function parameter detected while generating Java "
3985 "code for QML methods.";
3989 const auto qmlParamType = object[
"typeName"_L1].toString();
3990 if (!qmlToJavaType.contains(qmlParamType)) {
3991 qWarning() <<
" -- Skipping function" << functionName
3992 <<
"due to unsupported type detected in parameters:" << qmlParamType;
3996 const auto javaTypeName{ qmlToJavaType.value(object[
"typeName"_L1].toString(),
3998 const auto javaParamName = object[
"name"_L1].toString();
3999 javaFunctionParams.push_back(
4000 QString{
"%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
4001 javaParams.append(javaParamName);
4004 const auto functionSignature {
4005 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(
", "_L1))
4007 const auto functionCallParams {
4008 javaParams.isEmpty() ?
""_L1 :
", new Object[] { %1 }"_L1.arg(javaParams.join(
", "_L1))
4011 stream << indent << functionSignature
4012 << indent <<
" invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
4013 .arg(functionCallParams)
4017 constexpr static auto markerFileName =
"qml_java_contents"_L1;
4018 const QString libName(options.applicationBinary);
4019 QString javaPackageBase = options.packageName;
4020 const QString expectedBaseLeaf =
".%1"_L1.arg(libName);
4021 if (!javaPackageBase.endsWith(expectedBaseLeaf))
4022 javaPackageBase += expectedBaseLeaf;
4023 const QString baseSourceDir =
"%1/src/%2"_L1.arg(options.outputDirectory,
4024 QString(javaPackageBase).replace(u'.', u'/'));
4025 const QString buildPath(QDir(options.buildDirectory).absolutePath());
4028 fprintf(stdout,
"Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
4029 if (!QDir().current().mkpath(baseSourceDir)) {
4030 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(baseSourceDir));
4034 QStringList appImports;
4035 QStringList externalImports;
4036 if (!getImportPaths(buildPath, libName, appImports, externalImports))
4041 const QString srcDir =
"%1/src"_L1.arg(options.outputDirectory);
4042 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
4043 while (iter.hasNext())
4044 iter.nextFileInfo().dir().removeRecursively();
4047 int generatedComponents = 0;
4048 for (
const auto &importPath : appImports) {
4049 ModuleInfo moduleInfo = getModuleInfo(importPath);
4050 if (!moduleInfo.isValid())
4053 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
4054 if (moduleInfo.moduleName == libName) {
4056 "A QML module name (%s) cannot be the same as the target name when building "
4057 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4058 qPrintable(moduleInfo.moduleName));
4062 const QString javaPackage =
"%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4063 const QString outputDir =
4064 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4065 if (!QDir().current().mkpath(outputDir)) {
4066 fprintf(stderr,
"Cannot create %s directory\n", qPrintable(outputDir));
4072 QFile markerFile(
"%1/%2"_L1.arg(outputDir, markerFileName));
4073 if (!markerFile.open(QFile::WriteOnly)) {
4074 fprintf(stderr,
"Cannot create %s file\n", qPrintable(markerFile.fileName()));
4081 for (
const auto &qmlComponent : moduleInfo.qmlComponents) {
4082 const bool isSelected = options.selectedJavaQmlComponents.contains(
4083 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4084 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4087 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4088 externalImports + appImports);
4089 QJsonObject component = getComponent(domInfo);
4090 if (component.isEmpty())
4093 QByteArray componentClassBody;
4094 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4096 createHeaderBlock(outputStream, javaPackage);
4098 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4099 qmlComponent, indentBase);
4102 const QJsonArray properties = getProperties(component);
4103 for (
const QJsonValue &p : std::as_const(properties))
4104 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4106 const QJsonArray methods = getMethods(component);
4107 for (
const QJsonValue &m : std::as_const(methods))
4108 beginSignalBlock(outputStream, m.toObject(), indentBase);
4110 for (
const QJsonValue &m : std::as_const(methods))
4111 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4114 endBlock(outputStream, indentBase);
4115 outputStream.flush();
4118 QFile outputFile(
"%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4119 if (outputFile.exists())
4120 outputFile.remove();
4121 if (!outputFile.open(QFile::WriteOnly)) {
4122 fprintf(stderr,
"Cannot open %s file to write.\n",
4123 qPrintable(outputFile.fileName()));
4126 outputFile.write(componentClassBody);
4129 generatedComponents++;
4132 return generatedComponents;
4135int main(
int argc,
char *argv[])
4137 QCoreApplication a(argc, argv);
4140 if (options
.helpRequested || options.outputDirectory.isEmpty()) {
4142 return SyntaxErrorOrHelpRequested;
4145 options.timer.start();
4147 if (!readInputFile(&options))
4148 return CannotReadInputFile;
4150 if (Q_UNLIKELY(options
.timing))
4151 fprintf(stdout,
"[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4154 "Generating Android Package\n"
4156 " Output directory: %s\n"
4157 " Application binary: %s\n"
4158 " Android build platform: %s\n"
4159 " Install to device: %s\n",
4160 qPrintable(options.inputFileName),
4161 qPrintable(options.outputDirectory),
4162 qPrintable(options.applicationBinary),
4163 qPrintable(options.androidPlatform),
4165 ? (options.installLocation.isEmpty() ?
"Default device" : qPrintable(options.installLocation))
4169 bool androidTemplatetCopied =
false;
4171 for (
auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4174 options.setCurrentQtArchitecture(it.key(),
4175 it.value().qtInstallDirectory,
4176 it.value().qtDirectories);
4180 cleanAndroidFiles(options);
4181 if (Q_UNLIKELY(options
.timing))
4182 fprintf(stdout,
"[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4184 if (!copyAndroidTemplate(options))
4185 return CannotCopyAndroidTemplate;
4187 if (Q_UNLIKELY(options
.timing))
4188 fprintf(stdout,
"[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4189 androidTemplatetCopied =
true;
4192 if (!readDependencies(&options))
4193 return CannotReadDependencies;
4195 if (Q_UNLIKELY(options
.timing))
4196 fprintf(stdout,
"[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4198 if (!copyQtFiles(&options))
4199 return CannotCopyQtFiles;
4201 if (Q_UNLIKELY(options
.timing))
4202 fprintf(stdout,
"[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4204 if (!copyAndroidExtraLibs(&options))
4205 return CannotCopyAndroidExtraLibs;