125 QCommandLineParser parser;
126 parser.setApplicationDescription(
"Qt HarmonyOS Deployment Tool"_L1);
128 QCommandLineOption inputOption(
"input"_L1,
"JSON configuration file"_L1,
"file"_L1);
129 QCommandLineOption outputOption(
"output"_L1,
"Output directory"_L1,
"dir"_L1);
130 QCommandLineOption hvigorOption(
"hvigor"_L1,
"Path to hvigorw"_L1,
"path"_L1);
131 QCommandLineOption installOption(
"install"_L1,
"Install to device"_L1);
132 QCommandLineOption releaseOption(
"release"_L1,
"Build release configuration"_L1);
133 QCommandLineOption verboseOption(
"verbose"_L1,
"Verbose output"_L1);
134 QCommandLineOption noBuildOption(
"no-build"_L1,
"Skip building"_L1);
135 QCommandLineOption testBundleOption(
"test-bundle"_L1,
"Enable test bundle mode"_L1);
136 QCommandLineOption depfileOption(
"depfile"_L1,
"Dependency file output"_L1,
"path"_L1);
137 QCommandLineOption depfileBaseOption(
"depfile-base"_L1,
"Base directory for depfile paths"_L1,
"dir"_L1);
138 QCommandLineOption signingCertPathOption(
"signing-cert-path"_L1,
139 "Path to the .cer file"_L1,
"path"_L1);
140 QCommandLineOption signingProfileOption(
"signing-profile"_L1,
141 "Path to the .p7b profile"_L1,
"path"_L1);
142 QCommandLineOption signingStoreFileOption(
"signing-store-file"_L1,
143 "Path to the .p12 keystore"_L1,
"path"_L1);
144 QCommandLineOption signingKeyAliasOption(
"signing-key-alias"_L1,
145 "Key alias inside the keystore"_L1,
"alias"_L1);
146 QCommandLineOption signingKeyPasswordOption(
"signing-key-password"_L1,
147 "Encrypted key password"_L1,
"pwd"_L1);
148 QCommandLineOption signingStorePasswordOption(
"signing-store-password"_L1,
149 "Encrypted keystore password"_L1,
"pwd"_L1);
150 QCommandLineOption signingAlgOption(
"signing-alg"_L1,
151 "Signature algorithm (default SHA256withECDSA)"_L1,
"alg"_L1);
152 QCommandLineOption helpOption(
"help"_L1,
"Show help"_L1);
154 parser.addOption(inputOption);
155 parser.addOption(outputOption);
156 parser.addOption(hvigorOption);
157 parser.addOption(installOption);
158 parser.addOption(releaseOption);
159 parser.addOption(verboseOption);
160 parser.addOption(noBuildOption);
161 parser.addOption(testBundleOption);
162 parser.addOption(depfileOption);
163 parser.addOption(depfileBaseOption);
164 parser.addOption(signingCertPathOption);
165 parser.addOption(signingProfileOption);
166 parser.addOption(signingStoreFileOption);
167 parser.addOption(signingKeyAliasOption);
168 parser.addOption(signingKeyPasswordOption);
169 parser.addOption(signingStorePasswordOption);
170 parser.addOption(signingAlgOption);
171 parser.addOption(helpOption);
173 if (!parser.parse(arguments)) {
174 fprintf(stderr,
"%s\n", qPrintable(parser.errorText()));
178 if (parser.isSet(helpOption)) {
183 if (!parser.isSet(inputOption)) {
184 fprintf(stderr,
"Error: --input option is required\n");
189 options->inputFile = parser.value(inputOption);
190 options->outputDirectory = parser.value(outputOption);
191 options->hvigorPath = parser.value(hvigorOption);
194 options
->verbose = parser.isSet(verboseOption);
197 options->depFilePath = parser.value(depfileOption);
198 options->depFileBase = parser.value(depfileBaseOption);
199 options->signingCertPath = parser.value(signingCertPathOption);
200 options->signingProfile = parser.value(signingProfileOption);
201 options->signingStoreFile = parser.value(signingStoreFileOption);
202 options->signingKeyAlias = parser.value(signingKeyAliasOption);
203 options->signingKeyPassword = parser.value(signingKeyPasswordOption);
204 options->signingStorePassword = parser.value(signingStorePasswordOption);
205 options->signingAlg = parser.value(signingAlgOption);
212 QFile inputFile(options->inputFile);
213 if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
214 fprintf(stderr,
"Failed to open input file: %s\n", qPrintable(options->inputFile));
218 QJsonParseError parseError;
219 QJsonDocument doc = QJsonDocument::fromJson(inputFile.readAll(), &parseError);
221 fprintf(stderr,
"Failed to parse JSON: %s\n", qPrintable(parseError.errorString()));
225 QJsonObject obj = doc.object();
227 options->applicationBinary = obj[
"application-binary"_L1].toString();
228 options->harmonyOsPackageSourceDirectory = obj[
"harmonyos-package-source-directory"_L1].toString();
229 options->harmonyOsAppName = obj[
"harmonyos-app-name"_L1].toString();
230 options->harmonyOsAppBundleName = obj[
"harmonyos-app-bundle-name"_L1].toString();
231 options->sdkRoot = obj[
"sdk-root"_L1].toString();
232 options->ndkRoot = obj[
"ndk-root"_L1].toString();
235 const QJsonValue rootPathValue = obj[
"qml-root-path"_L1];
236 if (rootPathValue.isArray()) {
237 for (
const QJsonValue &v : rootPathValue.toArray()) {
238 const QString s = v.toString();
240 options->qmlRootPaths.append(s);
243 const QString s = rootPathValue.toString();
245 options->qmlRootPaths.append(s);
250 options->qtLibsDirectory = obj[
"qtLibsDirectory"_L1].toString();
251 options->qtPluginsDirectory = obj[
"qtPluginsDirectory"_L1].toString();
252 options->qtQmlDirectory = obj[
"qtQmlDirectory"_L1].toString();
253 options->qtLibExecsDirectory = obj[
"qtLibExecsDirectory"_L1].toString();
254 options->qtHostDirectory = obj[
"qtHostDirectory"_L1].toString();
256 QJsonArray extraLibsDirsArray = obj[
"extra-libs-dirs"_L1].toArray();
257 for (
const QJsonValue &value : extraLibsDirsArray)
258 options->extraLibsDirs.append(value.toString());
261 if (obj[
"test-bundle"_L1].toBool())
263 options->testBinariesDirectory = obj[
"test-binaries-directory"_L1].toString();
264 QJsonArray excludeArray = obj[
"test-exclude-list"_L1].toArray();
265 for (
const QJsonValue &value : excludeArray)
266 options->testExcludeList.append(value.toString());
269 QJsonArray projectLibsArray = obj[
"project-libraries"_L1].toArray();
270 for (
const QJsonValue &value : projectLibsArray)
271 options->projectLibraries.append(value.toString());
274 QJsonArray importPathsArray = obj[
"qml-import-paths"_L1].toArray();
275 for (
const QJsonValue &value : importPathsArray)
276 options->qmlImportPaths.append(value.toString());
279 QJsonArray archArray = obj[
"harmonyos-target-arch"_L1].toArray();
280 for (
const QJsonValue &value : archArray)
281 options->targetArchs.append(value.toString());
282 if (options->targetArchs.isEmpty())
283 options->targetArchs.append(
"arm64-v8a"_L1);
286 options->permissions = obj[
"permissions"_L1].toArray();
289 options->harmonyOsAppVendor = obj[
"harmonyos-app-vendor"_L1].toString();
290 options->harmonyOsAppVersionCode = obj[
"harmonyos-app-version-code"_L1].toInt();
291 options->harmonyOsAppVersionName = obj[
"harmonyos-app-version-name"_L1].toString();
292 options->harmonyOsAppLabel = obj[
"harmonyos-app-label"_L1].toString();
293 options->harmonyOsAppIcon = obj[
"harmonyos-app-icon"_L1].toString();
296 options->harmonyOsModuleDescription = obj[
"harmonyos-module-description"_L1].toString();
297 const QJsonArray deviceTypesArray = obj[
"harmonyos-module-device-types"_L1].toArray();
298 for (
const QJsonValue &value : deviceTypesArray)
299 options->harmonyOsModuleDeviceTypes.append(value.toString());
300 options->harmonyOsAbilityOrientation = obj[
"harmonyos-ability-orientation"_L1].toString();
304 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
310 if (options->harmonyOsAppBundleName.isEmpty())
311 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
312 if (options->harmonyOsAppName.isEmpty())
313 options->harmonyOsAppName =
"QtAutoTests"_L1;
317 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
322 searchPath = QDir::cleanPath(options->qtLibsDirectory);
323 }
else if (!options->applicationBinary.isEmpty()) {
324 QFileInfo appInfo(options->applicationBinary);
325 searchPath = QDir::cleanPath(appInfo.absolutePath());
328 if (searchPath.isEmpty()) {
329 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
330 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
335 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
338 QString currentPath = searchPath;
339 for (
int i = 0; i < 10; ++i) {
341 QString templatePath = currentPath +
"/share/qt6/src/harmonyos/templates"_L1;
343 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
344 QDir(templatePath).exists() ?
"FOUND" :
"not found");
346 if (QDir(templatePath).exists()) {
347 options->harmonyOsPackageSourceDirectory = templatePath;
352 templatePath = currentPath +
"/src/harmonyos/templates"_L1;
354 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
355 QDir(templatePath).exists() ?
"FOUND" :
"not found");
357 if (QDir(templatePath).exists()) {
358 options->harmonyOsPackageSourceDirectory = templatePath;
363 int lastSlash = currentPath.lastIndexOf(
'/'_L1);
364 if (lastSlash <= 0) {
366 fprintf(stdout,
" Reached root directory\n");
369 currentPath = currentPath.left(lastSlash);
372 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
373 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
374 fprintf(stderr,
" and could not auto-detect template location\n");
375 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
378 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
382 if (options->harmonyOsAppName.isEmpty()) {
383 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
387 if (options->harmonyOsAppBundleName.isEmpty()) {
388 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
393 if (options->outputDirectory.isEmpty()) {
395 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
397 QFileInfo appInfo(options->applicationBinary);
398 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
399 appInfo.completeBaseName() +
"-harmonyos"_L1;
404 fprintf(stdout,
"Configuration loaded:\n");
406 fprintf(stdout,
" Mode: test bundle\n");
407 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
408 if (!options->testExcludeList.isEmpty())
409 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
411 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
413 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
414 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
415 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
416 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
417 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
655 fprintf(stdout,
"Customizing template files\n");
658 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
659 QFile qtAppConstantsFile(qtAppConstantsPath);
661 if (qtAppConstantsFile.exists()) {
662 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
663 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
667 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
668 qtAppConstantsFile.close();
674 appLibName =
"libtst_placeholder.so"_L1;
676 QFileInfo appInfo(options.applicationBinary);
677 appLibName = appInfo.fileName();
680 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
681 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
683 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
684 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
688 qtAppConstantsFile.write(content.toUtf8());
689 qtAppConstantsFile.close();
692 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
702 QString iconValue = options.harmonyOsAppIcon;
703 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
704 QFileInfo iconInfo(iconValue);
705 if (!iconInfo.exists() || !iconInfo.isFile()) {
706 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
709 QString safeStem = iconInfo.completeBaseName();
710 for (QChar &c : safeStem) {
711 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
712 c = QLatin1Char(
'_');
714 const QString destFileName = iconInfo.suffix().isEmpty()
716 : safeStem +
"."_L1 + iconInfo.suffix();
717 const QStringList destDirs = {
718 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
719 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
721 for (
const QString &destDir : destDirs) {
722 QDir().mkpath(destDir);
723 const QString destPath = destDir +
"/"_L1 + destFileName;
724 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
725 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
729 iconValue =
"$media:"_L1 + safeStem;
736 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
737 QFile appJsonFile(appJsonPath);
739 if (appJsonFile.exists()) {
740 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
741 fprintf(stderr,
"Failed to open app.json5 for reading\n");
744 QString content = QString::fromUtf8(appJsonFile.readAll());
747 auto replaceStringField =
748 [&content](QLatin1StringView key,
const QString &value) {
752 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
753 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
756 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
757 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
758 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
765 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
766 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
767 replaceStringField(
"icon"_L1, iconValue);
771 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
772 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
775 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
776 fprintf(stderr,
"Failed to open app.json5 for writing\n");
779 appJsonFile.write(content.toUtf8());
783 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
784 qPrintable(options.harmonyOsAppBundleName));
792 const QString displayLabel =
793 (!options.harmonyOsAppLabel.isEmpty()
794 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
795 ? options.harmonyOsAppLabel
796 : options.harmonyOsAppName;
801 QJsonArray transformedPermissions;
802 QList<PromotedReason> promotedReasons;
803 QSet<QString> seenPromotedIds;
804 for (
const QJsonValue &value : std::as_const(options.permissions)) {
805 if (!value.isObject()) {
806 transformedPermissions.append(value);
809 QJsonObject entry = value.toObject();
810 if (entry.contains(
"reason"_L1)) {
811 const QString reason = entry[
"reason"_L1].toString();
812 if (reasonNeedsPromotion(reason)) {
813 const QString permName = entry[
"name"_L1].toString();
814 const QString stringId = synthesizePermissionReasonId(permName);
815 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
816 if (!seenPromotedIds.contains(stringId)) {
817 promotedReasons.append({stringId, reason});
818 seenPromotedIds.insert(stringId);
822 transformedPermissions.append(entry);
828 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
830 for (
const QString &locale : locales) {
831 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
832 locale +
"/element/string.json"_L1;
833 QFile stringJsonFile(stringJsonPath);
835 if (!stringJsonFile.exists())
838 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
839 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
843 const QByteArray bytes = stringJsonFile.readAll();
844 stringJsonFile.close();
846 QJsonParseError parseErr;
847 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
848 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
849 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
850 qPrintable(locale), qPrintable(parseErr.errorString()));
853 QJsonObject root = doc.object();
854 QJsonArray strings = root[
"string"_L1].toArray();
857 QSet<QString> existingNames;
858 for (qsizetype i = 0; i < strings.size(); ++i) {
859 QJsonObject e = strings[i].toObject();
860 const QString name = e[
"name"_L1].toString();
861 existingNames.insert(name);
862 if (name ==
"QAbility_label"_L1) {
863 e[
"value"_L1] = displayLabel;
869 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
870 if (existingNames.contains(p.id))
874 e[
"value"_L1] = p.value;
876 existingNames.insert(p.id);
878 root[
"string"_L1] = strings;
881 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
882 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
886 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
887 stringJsonFile.close();
889 if (options.verbose) {
891 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
892 qPrintable(locale), qPrintable(displayLabel),
893 static_cast<
long long>(promotedReasons.size()));
898 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
899 QFile appScopeStringFile(appScopeStringPath);
901 if (appScopeStringFile.exists()) {
902 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
903 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
907 QString content = QString::fromUtf8(appScopeStringFile.readAll());
908 appScopeStringFile.close();
911 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
912 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
914 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
915 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
919 appScopeStringFile.write(content.toUtf8());
920 appScopeStringFile.close();
923 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
924 qPrintable(displayLabel));
929 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
930 QFile moduleJsonFile(moduleJsonPath);
932 if (moduleJsonFile.exists()) {
933 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
934 fprintf(stderr,
"Failed to open module.json5 for reading\n");
938 QString content = QString::fromUtf8(moduleJsonFile.readAll());
939 moduleJsonFile.close();
945 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
946 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
947 ?
"$string:module_desc"_L1
948 : jsonStringEscape(options.harmonyOsModuleDescription);
949 content.replace(descriptionSentinel, descriptionValue);
954 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
955 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
956 if (deviceTypes.isEmpty())
957 deviceTypes = QStringList{
"tablet"_L1,
"2in1"_L1 };
958 QStringList quotedDeviceTypes;
959 quotedDeviceTypes.reserve(deviceTypes.size());
960 for (
const QString &dt : std::as_const(deviceTypes))
961 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
962 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
971 const QString orientationSentinelLine =
972 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
973 QString orientationReplacement;
974 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
975 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
976 orientationReplacement =
" \"orientation\": \""_L1
977 + options.harmonyOsAbilityOrientation
981 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
982 qPrintable(options.harmonyOsAbilityOrientation));
985 content.replace(orientationSentinelLine, orientationReplacement);
991 if (!iconValue.isEmpty()) {
993 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
994 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1001 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1002 QString permissionsFragment;
1003 if (!transformedPermissions.isEmpty()) {
1004 QStringList entryStrings;
1005 entryStrings.reserve(transformedPermissions.size());
1006 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1007 if (!value.isObject())
1009 const QJsonObject entry = value.toObject();
1013 const QByteArray pretty =
1014 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1015 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1016 QStringList indented;
1017 indented.reserve(lines.size());
1018 for (
const QString &line : lines)
1019 indented.append(
" "_L1 + line);
1020 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1022 if (!entryStrings.isEmpty()) {
1023 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1026 content.replace(sentinel, permissionsFragment);
1028 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1029 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1033 moduleJsonFile.write(content.toUtf8());
1034 moduleJsonFile.close();
1037 fprintf(stdout,
" Updated module.json5 description\n");
1038 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1039 static_cast<
long long>(transformedPermissions.size()));
1044 fprintf(stdout,
"Template customization completed\n");
1251 if (options.testBinariesDirectory.isEmpty()) {
1252 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1256 if (!QDir(options.testBinariesDirectory).exists()) {
1257 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1258 qPrintable(options.testBinariesDirectory));
1263 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1266 QStringList foundHelpers;
1267 QSet<QString> helperNames;
1271 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1272 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1273 found, foundHelpers, helperNames);
1275 if (found.isEmpty()) {
1276 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1277 qPrintable(options.testBinariesDirectory));
1282 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1283 if (!foundHelpers.isEmpty())
1284 fprintf(stdout,
"Found %lld test helper libraries\n",
1285 static_cast<
long long>(foundHelpers.size()));
1289 for (
const QString &arch : options.targetArchs) {
1290 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1291 QDir().mkpath(archLibPath);
1293 for (
const QString &testBinary : found) {
1294 QFileInfo testInfo(testBinary);
1295 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1297 if (options.verbose)
1298 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1300 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1301 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1306 for (
const QString &helperLib : foundHelpers) {
1307 QFileInfo helperInfo(helperLib);
1308 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1310 if (options.verbose)
1311 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1313 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1314 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1321 for (
const QString &testBinary : found) {
1322 QString fileName = QFileInfo(testBinary).fileName();
1323 if (!bundledBinaries.contains(fileName))
1324 bundledBinaries.append(fileName);
1328 if (!options.depFilePath.isEmpty()) {
1329 for (
const QString &testBinary : found)
1330 dependenciesForDepfile << testBinary;
1331 for (
const QString &helperLib : foundHelpers)
1332 dependenciesForDepfile << helperLib;
1370 if (options.qtLibsDirectory.isEmpty())
1373 if (!QDir(options.qtLibsDirectory).exists()) {
1375 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1376 qPrintable(options.qtLibsDirectory));
1382 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1384 QDir libsDir(options.qtLibsDirectory);
1385 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1387 for (
const QFileInfo &entry : entries) {
1388 for (
const QString &arch : options.targetArchs) {
1389 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1390 QDir().mkpath(QFileInfo(destPath).absolutePath());
1391 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1394 if (!options.depFilePath.isEmpty())
1395 dependenciesForDepfile << entry.filePath();
1399 for (
const QString &arch : options.targetArchs) {
1400 QString stdCppPath = findStdCppLibrary(options, arch);
1401 if (!stdCppPath.isEmpty()) {
1402 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1403 if (options.verbose)
1404 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1405 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1407 if (!options.depFilePath.isEmpty())
1408 dependenciesForDepfile << stdCppPath;
1413 for (
const QString &extraDir : options.extraLibsDirs) {
1415 if (!dir.exists()) {
1416 if (options.verbose)
1417 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1421 if (options.verbose)
1422 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1424 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1425 for (
const QFileInfo &entry : entries) {
1429 const QString soname = readElfSoname(options, entry.filePath());
1430 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1431 ? soname : entry.fileName();
1432 for (
const QString &arch : options.targetArchs) {
1433 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1434 QDir().mkpath(QFileInfo(destPath).absolutePath());
1435 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1438 if (!options.depFilePath.isEmpty())
1439 dependenciesForDepfile << entry.filePath();
1731 fprintf(stdout,
"Detecting Qt library dependencies\n");
1734 QStringList toProcess;
1735 toProcess.append(options.applicationBinary);
1739 for (
const QString &projectLib : options.projectLibraries)
1740 toProcess.append(projectLib);
1742 QList<QtDependency> qtDependencies;
1743 QStringList detectedQtModules;
1744 bool needsStdCpp =
false;
1746 while (!toProcess.isEmpty()) {
1747 QString currentLib = toProcess.takeFirst();
1749 if (processedLibs.contains(currentLib))
1752 processedLibs.insert(currentLib);
1755 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
1757 QStringList deps = readElfDependencies(options, currentLib);
1759 for (
const QString &dep : deps) {
1761 if (dep ==
"libc++_shared.so"_L1) {
1767 if (isSystemLibrary(dep))
1771 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
1773 QString extraDepPath = findExtraDepLibrary(options, dep);
1774 if (extraDepPath.isEmpty())
1779 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
1780 if (options.verbose)
1781 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
1782 QtDependency extraDep;
1783 extraDep.relativePath =
"lib/"_L1 + dep;
1784 extraDep.absolutePath = extraDepPath;
1785 qtDependencies.append(extraDep);
1786 toProcess.append(extraDepPath);
1792 if (dep.startsWith(
"libQt6"_L1)) {
1793 QString moduleName = dep.mid(6);
1794 if (moduleName.endsWith(
".so"_L1))
1796 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
1797 detectedQtModules.append(moduleName);
1800 QString depPath = findQtLibrary(options, dep);
1801 if (depPath.isEmpty()) {
1802 if (options.verbose)
1803 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
1807 if (processedLibs.contains(depPath))
1810 if (options.verbose)
1811 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
1814 qtDep.relativePath =
"lib/"_L1 + dep;
1815 qtDep.absolutePath = depPath;
1816 qtDependencies.append(qtDep);
1819 toProcess.append(depPath);
1824 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
1826 fprintf(stdout,
"C++ standard library required\n");
1831 for (
const QString &arch : options.targetArchs) {
1832 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1833 QString stdCppPath = findStdCppLibrary(options, arch);
1834 if (stdCppPath.isEmpty()) {
1835 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
1837 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
1838 if (options.verbose)
1839 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1840 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
1842 "Failed to copy C++ standard library to: %s\n",
1843 qPrintable(destPath));
1848 if (!options.depFilePath.isEmpty())
1849 dependenciesForDepfile << stdCppPath;
1858 for (
const QtDependency &dep : qtDependencies) {
1859 QFileInfo libInfo(dep.relativePath);
1860 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
1863 if (!options.depFilePath.isEmpty())
1864 dependenciesForDepfile << dep.absolutePath;
1899 const QString &qtPluginsPath,
1900 QSet<QString> &processedLibs)
1903 QString qohosPlugin = qtPluginsPath +
"/platforms/libqohos.so"_L1;
1904 if (!QFile::exists(qohosPlugin)) {
1905 fprintf(stderr,
"Warning: Platform plugin libqohos.so not found at: %s\n",
1906 qPrintable(qohosPlugin));
1912 fprintf(stdout,
" Detecting platform plugin dependencies\n");
1914 QStringList pluginDeps = readElfDependencies(options, qohosPlugin);
1915 QList<QtDependency> additionalLibs;
1917 for (
const QString &dep : pluginDeps) {
1919 if (!dep.startsWith(
"libQt6"_L1))
1922 QString depPath = findQtLibrary(options, dep);
1923 if (depPath.isEmpty()) {
1924 if (options.verbose)
1925 fprintf(stdout,
" Warning: Could not find plugin dependency: %s\n",
1930 if (processedLibs.contains(depPath))
1933 if (options.verbose)
1934 fprintf(stdout,
" Found plugin dependency: %s\n", qPrintable(dep));
1936 processedLibs.insert(depPath);
1938 qtDep.relativePath =
"lib/"_L1 + dep;
1939 qtDep.absolutePath = depPath;
1940 additionalLibs.append(qtDep);
1944 for (
const QString &arch : options.targetArchs) {
1945 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1947 for (
const QtDependency &dep : additionalLibs) {
1948 QString destPath = archLibPath +
"/"_L1 + QFileInfo(dep.absolutePath).fileName();
1950 if (options.verbose) {
1951 fprintf(stdout,
" Copying plugin dependency for %s: %s\n",
1952 qPrintable(arch), qPrintable(QFileInfo(dep.absolutePath).fileName()));
1955 if (!copyFileIfNewer(dep.absolutePath, destPath, options.verbose)) {
1956 fprintf(stderr,
"Failed to copy library: %s\n", qPrintable(destPath));
1961 if (!options.depFilePath.isEmpty())
1962 dependenciesForDepfile << dep.absolutePath;
1967 for (
const QString &arch : options.targetArchs) {
1968 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1969 QString destPath = archLibPath +
"/libqohos.so"_L1;
1971 if (options.verbose)
1972 fprintf(stdout,
" Copying platform plugin for %s: libqohos.so (to root)\n",
1975 if (!copyFileIfNewer(qohosPlugin, destPath, options.verbose)) {
1976 fprintf(stderr,
"Failed to copy libqohos.so to: %s\n", qPrintable(destPath));
1981 if (!options.depFilePath.isEmpty())
1982 dependenciesForDepfile << qohosPlugin;
2092 QList<QmlImportInfo> imports;
2094 if (options.qmlRootPaths.isEmpty()) {
2097 "No QML root path specified, skipping QML import scanning\n");
2102 fprintf(stdout,
"Scanning for QML imports\n");
2105 QStringList searchPaths;
2106 if (!options.qtLibExecsDirectory.isEmpty())
2107 searchPaths.append(options.qtLibExecsDirectory);
2110 if (!options.qtHostDirectory.isEmpty())
2111 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2113 QString qmlImportScannerPath =
2114 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2117 if (qmlImportScannerPath.isEmpty()) {
2118 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2120 for (
int i = 0; i < 10; ++i) {
2121 qmlImportScannerPath = QStandardPaths::findExecutable(
2122 "qmlimportscanner"_L1,
2123 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2125 if (!qmlImportScannerPath.isEmpty())
2133 if (qmlImportScannerPath.isEmpty()) {
2136 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2141 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2142 qPrintable(qmlImportScannerPath));
2145 QStringList importPaths;
2148 for (
const QString &path : options.qmlImportPaths)
2149 if (QFile::exists(path))
2150 importPaths.append(path);
2155 QFileInfo appBinary(options.applicationBinary);
2156 QString appBuildDir = appBinary.absolutePath();
2157 if (QDir(appBuildDir).exists())
2158 importPaths.append(appBuildDir);
2161 if (!options.qtQmlDirectory.isEmpty() &&
2162 QDir(options.qtQmlDirectory).exists()) {
2163 importPaths.append(options.qtQmlDirectory);
2166 QFileInfo appBinary(options.applicationBinary);
2167 QDir qtDir(appBinary.absolutePath());
2168 for (
int i = 0; i < 10; ++i) {
2169 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2170 if (QDir(qmlDir).exists()) {
2171 importPaths.append(qmlDir);
2175 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2176 if (QDir(qmlDir).exists()) {
2177 importPaths.append(QDir::cleanPath(qmlDir));
2185 if (importPaths.isEmpty()) {
2186 fprintf(stderr,
"Warning: No QML import paths found\n");
2192 QStringList arguments;
2193 for (
const QString &rootPath : options.qmlRootPaths)
2194 arguments <<
"-rootPath"_L1 << rootPath;
2196 for (
const QString &importPath : importPaths)
2197 arguments <<
"-importPath"_L1 << importPath;
2200 fprintf(stdout,
" Root paths:\n");
2201 for (
const QString &rootPath : options.qmlRootPaths)
2202 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2203 fprintf(stdout,
" Import paths:\n");
2204 for (
const QString &path : importPaths)
2205 fprintf(stdout,
" %s\n", qPrintable(path));
2210 process.start(qmlImportScannerPath, arguments);
2212 if (!process.waitForFinished(30000)) {
2213 fprintf(stderr,
"Error: qmlimportscanner timed out\n");
2217 if (process.exitCode() != 0) {
2218 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2219 process.exitCode());
2220 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2225 QByteArray output = process.readAllStandardOutput();
2226 QJsonDocument doc = QJsonDocument::fromJson(output);
2228 if (!doc.isArray()) {
2229 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2233 QJsonArray array = doc.array();
2234 for (
const QJsonValue &value : array) {
2235 if (!value.isObject())
2238 QJsonObject obj = value.toObject();
2240 info.name = obj[
"name"_L1].toString();
2241 info.path = obj[
"path"_L1].toString();
2242 info.type = obj[
"type"_L1].toString();
2244 if (obj.contains(
"plugin"_L1))
2245 info.plugin = obj[
"plugin"_L1].toString();
2247 if (obj.contains(
"pluginIsOptional"_L1))
2248 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2250 if (obj.contains(
"prefer"_L1))
2251 info.prefer = obj[
"prefer"_L1].toString();
2254 if (obj.contains(
"components"_L1)) {
2255 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2256 for (
const QJsonValue &comp : componentsArray)
2257 info.components.append(comp.toString());
2261 if (obj.contains(
"scripts"_L1)) {
2262 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2263 for (
const QJsonValue &script : scriptsArray)
2264 info.scripts.append(script.toString());
2268 if (info.path.isEmpty()) {
2269 if (options.verbose)
2270 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2271 qPrintable(info.name));
2276 if (info.type !=
"module"_L1)
2279 if (options.verbose)
2280 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2281 qPrintable(info.path));
2283 imports.append(info);
2328 const QList<QmlImportInfo> &imports,
2329 QSet<QString> &processedLibs)
2331 if (imports.isEmpty())
2335 fprintf(stdout,
"Copying QML imports\n");
2339 QString qmlDestBase = options.outputDirectory +
"/entry/src/main/resources/resfile/qml"_L1;
2340 QDir().mkpath(qmlDestBase);
2343 QStringList qmlPluginsToScan;
2345 for (
const QmlImportInfo &import : imports) {
2346 if (options.verbose)
2347 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2351 QString relativePath;
2354 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2356 relativePath = import.path.mid(options.qtQmlDirectory.length());
2357 if (relativePath.startsWith(
'/'_L1))
2358 relativePath = relativePath.mid(1);
2362 relativePath = import.name;
2363 relativePath.replace(
'.'_L1,
'/'_L1);
2366 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2367 QDir().mkpath(destModuleDir);
2370 QString qmldirSrc = import.path +
"/qmldir"_L1;
2371 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2372 if (QFile::exists(qmldirSrc)) {
2373 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2374 if (options.verbose)
2375 fprintf(stdout,
" Copied qmldir\n");
2378 if (!options.depFilePath.isEmpty())
2379 dependenciesForDepfile << qmldirSrc;
2381 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2382 qPrintable(import.name));
2389 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2391 if (!import.plugin.isEmpty()) {
2393 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2394 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2396 if (QFile::exists(pluginSrc)) {
2398 for (
const QString &arch : options.targetArchs) {
2399 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2400 +
"/"_L1 + pluginFileName;
2402 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2403 if (options.verbose)
2404 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2405 qPrintable(arch), qPrintable(pluginFileName));
2406 processedLibs.insert(pluginSrc);
2409 if (!qmlPluginsToScan.contains(pluginSrc))
2410 qmlPluginsToScan.append(pluginSrc);
2413 if (!options.depFilePath.isEmpty())
2414 dependenciesForDepfile << pluginSrc;
2415 }
else if (!import.pluginIsOptional) {
2416 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2417 qPrintable(pluginFileName));
2420 }
else if (!import.pluginIsOptional) {
2421 if (options.verbose)
2422 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2423 qPrintable(pluginFileName));
2428 if (!qmlFilesAreEmbedded) {
2429 for (
const QString &component : import.components) {
2430 QFileInfo compInfo(component);
2431 if (!compInfo.exists()) {
2432 if (options.verbose)
2433 fprintf(stdout,
" Warning: Component file not found: %s\n",
2434 qPrintable(component));
2438 QString relativePath = component.mid(import.path.length());
2439 if (relativePath.startsWith(
'/'_L1))
2440 relativePath = relativePath.mid(1);
2442 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2443 QFileInfo destInfo(destFile);
2444 QDir().mkpath(destInfo.absolutePath());
2446 if (copyFileIfNewer(component, destFile, options.verbose)) {
2447 if (options.verbose)
2448 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
2451 if (!options.depFilePath.isEmpty())
2452 dependenciesForDepfile << component;
2457 for (
const QString &script : import.scripts) {
2458 QFileInfo scriptInfo(script);
2459 if (!scriptInfo.exists())
2462 QString relativePath = script.mid(import.path.length());
2463 if (relativePath.startsWith(
'/'_L1))
2464 relativePath = relativePath.mid(1);
2466 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2467 QFileInfo destInfo(destFile);
2468 QDir().mkpath(destInfo.absolutePath());
2470 if (copyFileIfNewer(script, destFile, options.verbose)) {
2471 if (options.verbose)
2472 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
2475 if (!options.depFilePath.isEmpty())
2476 dependenciesForDepfile << script;
2480 if (options.verbose)
2481 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
2486 if (!qmlPluginsToScan.isEmpty()) {
2488 fprintf(stdout,
"Scanning QML plugin dependencies\n");
2490 QStringList toProcess = qmlPluginsToScan;
2491 while (!toProcess.isEmpty()) {
2492 QString pluginPath = toProcess.takeFirst();
2495 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
2497 QStringList deps = readElfDependencies(options, pluginPath);
2498 for (
const QString &dep : deps) {
2500 if (isSystemLibrary(dep))
2505 if (dep.startsWith(
"libQt6"_L1)) {
2506 depPath = findQtLibrary(options, dep);
2507 if (depPath.isEmpty()) {
2508 if (options.verbose)
2509 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2512 }
else if (!options.extraLibsDirs.isEmpty()) {
2513 depPath = findExtraDepLibrary(options, dep);
2514 if (depPath.isEmpty())
2520 if (processedLibs.contains(depPath))
2523 if (options.verbose)
2524 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2527 for (
const QString &arch : options.targetArchs) {
2528 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
2529 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
2530 if (options.verbose)
2531 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
2534 if (!options.depFilePath.isEmpty())
2535 dependenciesForDepfile << depPath;
2539 processedLibs.insert(depPath);
2541 toProcess.append(depPath);
2547 fprintf(stdout,
"QML imports copied successfully\n");
2559 const char *envName;
2560 const char *cliFlag;
2562 QByteArray envValue;
2564 QByteArray resolved()
const
2566 if (!cliValue.isEmpty())
2567 return cliValue.toUtf8();
2570 QString sourceLabel()
const
2572 return cliValue.isEmpty()
2573 ? QString::fromLatin1(envName)
2574 : QString::fromLatin1(cliFlag);
2578 Field required[] = {
2579 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path",
2580 options.signingCertPath, qgetenv(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
2581 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile",
2582 options.signingProfile, qgetenv(
"QT_HARMONYOS_SIGNING_PROFILE") },
2583 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file",
2584 options.signingStoreFile, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
2585 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias",
2586 options.signingKeyAlias, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
2587 {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
2588 options.signingKeyPassword, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
2589 {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
2590 options.signingStorePassword, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
2593 QByteArray signAlg = options.signingAlg.isEmpty()
2594 ? qgetenv(
"QT_HARMONYOS_SIGNING_ALG")
2595 : options.signingAlg.toUtf8();
2597 bool anySet = !signAlg.isEmpty();
2598 for (
const Field &f : required)
2599 anySet = anySet || !f.resolved().isEmpty();
2603 QStringList missing;
2604 for (
const Field &f : required) {
2605 if (f.resolved().isEmpty())
2606 missing << QString::fromLatin1(f.cliFlag) +
" / "_L1
2607 + QString::fromLatin1(f.envName);
2609 if (!missing.isEmpty()) {
2610 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
2611 " are missing (neither CLI flag nor env var was supplied):\n");
2612 for (
const QString &name : missing)
2613 fprintf(stderr,
" %s\n", qPrintable(name));
2617 if (signAlg.isEmpty())
2618 signAlg =
"SHA256withECDSA";
2620 const QString buildProfilePath = options.outputDirectory +
"/build-profile.json5"_L1;
2621 QFile profileFile(buildProfilePath);
2622 if (!profileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2623 fprintf(stderr,
"Error: cannot open %s for reading: %s\n",
2624 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
2627 QByteArray content = profileFile.readAll();
2628 profileFile.close();
2632 static const QByteArray needle =
"\"signingConfigs\": []";
2633 const int idx = content.indexOf(needle);
2635 fprintf(stderr,
"Error: '%s' not found in %s. The template may have been modified;\n"
2636 " cannot inject signing configuration safely.\n",
2637 needle.constData(), qPrintable(buildProfilePath));
2643 auto escapeJsonString = [](
const QByteArray &in) {
2645 out.reserve(in.size());
2647 if (c ==
'\\' || c ==
'"')
2654 QByteArray replacement;
2655 replacement.append(
"\"signingConfigs\": [\n");
2656 replacement.append(
" {\n");
2657 replacement.append(
" \"name\": \"default\",\n");
2658 replacement.append(
" \"type\": \"HarmonyOS\",\n");
2659 replacement.append(
" \"material\": {\n");
2660 auto appendField = [&](
const char *key,
const QByteArray &value,
bool last) {
2661 replacement.append(
" \"");
2662 replacement.append(key);
2663 replacement.append(
"\": \"");
2664 replacement.append(escapeJsonString(value));
2665 replacement.append(
'"');
2667 replacement.append(
',');
2668 replacement.append(
'\n');
2670 appendField(
"certpath", required[0].resolved(),
false);
2671 appendField(
"keyAlias", required[3].resolved(),
false);
2672 appendField(
"keyPassword", required[4].resolved(),
false);
2673 appendField(
"profile", required[1].resolved(),
false);
2674 appendField(
"signAlg", signAlg,
false);
2675 appendField(
"storeFile", required[2].resolved(),
false);
2676 appendField(
"storePassword", required[5].resolved(),
true);
2677 replacement.append(
" }\n");
2678 replacement.append(
" }\n");
2679 replacement.append(
" ]");
2681 content.replace(idx, needle.size(), replacement);
2683 if (!profileFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
2684 fprintf(stderr,
"Error: cannot open %s for writing: %s\n",
2685 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
2688 if (profileFile.write(content) != content.size()) {
2689 fprintf(stderr,
"Error: failed to write full content to %s\n",
2690 qPrintable(buildProfilePath));
2691 profileFile.close();
2694 profileFile.close();
2697 fprintf(stdout,
"Injected HAP signingConfig into %s\n", qPrintable(buildProfilePath));
2698 fprintf(stdout,
" certpath: %s (from %s)\n",
2699 required[0].resolved().constData(), qPrintable(required[0].sourceLabel()));
2700 fprintf(stdout,
" profile: %s (from %s)\n",
2701 required[1].resolved().constData(), qPrintable(required[1].sourceLabel()));
2702 fprintf(stdout,
" storeFile: %s (from %s)\n",
2703 required[2].resolved().constData(), qPrintable(required[2].sourceLabel()));
2704 fprintf(stdout,
" keyAlias: %s (from %s)\n",
2705 required[3].resolved().constData(), qPrintable(required[3].sourceLabel()));
2706 fprintf(stdout,
" signAlg: %s\n", signAlg.constData());
2715 fprintf(stdout,
"Skipping HAP build (--no-build specified)\n");
2720 QString hvigorPath = options.hvigorPath;
2722 if (hvigorPath.isEmpty()) {
2723 QByteArray envHvigor = qgetenv(
"QT_HARMONYOS_HVIGOR");
2724 if (!envHvigor.isEmpty())
2725 hvigorPath = QString::fromLocal8Bit(envHvigor);
2728 if (hvigorPath.isEmpty()) {
2730 QString candidate = options.outputDirectory +
"/hvigorw"_L1;
2731 if (QFile::exists(candidate))
2732 hvigorPath = candidate;
2735 if (hvigorPath.isEmpty()) {
2736 fprintf(stderr,
"Warning: No hvigor path specified, skipping build\n");
2737 fprintf(stderr,
"Use --hvigor <path> or set QT_HARMONYOS_HVIGOR\n");
2742 fprintf(stdout,
"Building HarmonyOS HAP package\n");
2745 if (!QFile::exists(hvigorPath)) {
2746 fprintf(stderr,
"Error: hvigorw not found at: %s\n", qPrintable(hvigorPath));
2751 QString buildTask = options.releaseMode ?
"assembleHap"_L1 :
"assembleHap"_L1;
2754 fprintf(stdout,
" Build mode: %s\n", options
.releaseMode ?
"release" :
"debug");
2755 fprintf(stdout,
" Running: %s %s\n", qPrintable(hvigorPath), qPrintable(buildTask));
2760 process.setWorkingDirectory(options.outputDirectory);
2761 process.setProcessChannelMode(QProcess::MergedChannels);
2763 QStringList arguments;
2764 arguments << buildTask;
2766 process.start(hvigorPath, arguments);
2768 if (!process.waitForStarted()) {
2769 fprintf(stderr,
"Failed to start hvigorw\n");
2774 while (process.state() != QProcess::NotRunning) {
2775 if (!process.waitForReadyRead(1000)) {
2777 if (process.state() == QProcess::NotRunning)
2783 QByteArray output = process.readAll();
2784 fprintf(stdout,
"%s", output.constData());
2791 QByteArray output = process.readAll();
2792 if (!output.isEmpty()) {
2793 fprintf(stdout,
"%s", output.constData());
2798 if (process.exitCode() != 0) {
2799 fprintf(stderr,
"hvigorw failed with exit code %d\n", process.exitCode());
2801 fprintf(stderr,
"Run with --verbose to see build output\n");
2806 fprintf(stdout,
"HAP build completed successfully\n");
2809 if (hapOutputPath) {
2810 QString hapSearchPath = options.outputDirectory +
"/entry/build/default/outputs/default"_L1;
2811 QDir hapDir(hapSearchPath);
2813 if (hapDir.exists()) {
2814 QStringList hapFiles = hapDir.entryList(QStringList() <<
"*.hap"_L1, QDir::Files);
2815 if (!hapFiles.isEmpty()) {
2816 *hapOutputPath = hapDir.absoluteFilePath(hapFiles.first());
2818 fprintf(stdout,
" Generated HAP: %s\n", qPrintable(*hapOutputPath));
2925 QCoreApplication app(argc, argv);
2926 QCoreApplication::setApplicationName(
"harmonydeployqt"_L1);
2927 QCoreApplication::setApplicationVersion(
"1.0"_L1);
2930 options.timer.start();
2932 if (!parseCommandLine(app.arguments(), &options))
2936 fprintf(stdout,
"Qt HarmonyOS Deployment Tool\n");
2937 fprintf(stdout,
"==============================\n\n");
2944 fprintf(stdout,
"\nDeployment process started...\n");
2946 QString hapOutputPath;
2950 fprintf(stderr,
"Failed to copy template\n");
2956 fprintf(stderr,
"Failed to customize template\n");
2961 QStringList bundledBinaries;
2963 if (!copyTestBinaries(options, bundledBinaries)) {
2964 fprintf(stderr,
"Failed to copy test binaries\n");
2968 fprintf(stderr,
"Failed to copy Qt libraries\n");
2972 fprintf(stderr,
"Failed to copy Qt plugins\n");
2976 fprintf(stderr,
"Failed to copy QML modules\n");
2981 fprintf(stderr,
"Failed to copy application binary\n");
2985 fprintf(stderr,
"Failed to copy project libraries\n");
2988 QSet<QString> processedLibs;
2989 if (!detectAndCopyDependencies(options, processedLibs)) {
2990 fprintf(stderr,
"Failed to detect and copy dependencies\n");
2993 QList<QmlImportInfo> qmlImports = scanQmlImports(options);
2995 fprintf(stderr,
"Failed to copy QML files\n");
2998 if (!copyQmlImports(options, qmlImports, processedLibs)) {
2999 fprintf(stderr,
"Failed to copy QML imports\n");
3002 if (!copyPlugins(options, processedLibs)) {
3003 fprintf(stderr,
"Failed to copy plugins\n");
3009 fprintf(stderr,
"Failed to inject signing configuration\n");
3014 if (!buildHap(options, &hapOutputPath)) {
3015 fprintf(stderr,
"Failed to build HAP\n");
3021 if (!writeTestBinariesList(options, bundledBinaries)) {
3022 fprintf(stderr,
"Failed to write binaries.txt\n");
3029 if (!options.depFilePath.isEmpty()) {
3030 if (hapOutputPath.isEmpty()) {
3033 hapOutputPath = options.outputDirectory
3034 +
"/entry/build/default/outputs/default/autotests.hap"_L1;
3037 QString targetName = QFileInfo(options.applicationBinary).completeBaseName();
3038 if (targetName.startsWith(
"lib"_L1))
3039 targetName = targetName.mid(3);
3040 hapOutputPath = options.outputDirectory +
"/entry/build/default/outputs/default/"_L1
3041 + targetName +
".hap"_L1;
3044 if (!writeDepfile(options, hapOutputPath))
3045 fprintf(stderr,
"Warning: Failed to write dependency file\n");
3050 if (!installToDevice(options, hapOutputPath)) {
3051 fprintf(stderr,
"Failed to install to device\n");
3056 fprintf(stdout,
"\n==============================================\n");
3057 fprintf(stdout,
"Deployment completed successfully!\n");
3058 fprintf(stdout,
"==============================================\n");
3059 fprintf(stdout,
"Project location: %s\n", qPrintable(options.outputDirectory));
3061 if (!hapOutputPath.isEmpty())
3062 fprintf(stdout,
"HAP package: %s\n", qPrintable(hapOutputPath));
3065 fprintf(stdout,
"\nTotal time: %lld ms\n", options.timer.elapsed());