157 QCommandLineParser parser;
158 parser.setApplicationDescription(
"Qt HarmonyOS Deployment Tool"_L1);
160 QCommandLineOption inputOption(
"input"_L1,
"JSON configuration file"_L1,
"file"_L1);
161 QCommandLineOption outputOption(
"output"_L1,
"Output directory"_L1,
"dir"_L1);
162 QCommandLineOption hvigorOption(
"hvigor"_L1,
"Path to hvigorw"_L1,
"path"_L1);
163 QCommandLineOption installOption(
"install"_L1,
"Install to device"_L1);
164 QCommandLineOption releaseOption(
"release"_L1,
"Build release configuration"_L1);
165 QCommandLineOption verboseOption(
"verbose"_L1,
"Verbose output"_L1);
166 QCommandLineOption noBuildOption(
"no-build"_L1,
"Skip building"_L1);
167 QCommandLineOption testBundleOption(
"test-bundle"_L1,
"Enable test bundle mode"_L1);
168 QCommandLineOption depfileOption(
"depfile"_L1,
"Dependency file output"_L1,
"path"_L1);
169 QCommandLineOption depfileBaseOption(
"depfile-base"_L1,
"Base directory for depfile paths"_L1,
"dir"_L1);
170 QCommandLineOption signingCertPathOption(
"signing-cert-path"_L1,
171 "Path to the .cer file"_L1,
"path"_L1);
172 QCommandLineOption signingProfileOption(
"signing-profile"_L1,
173 "Path to the .p7b profile"_L1,
"path"_L1);
174 QCommandLineOption signingStoreFileOption(
"signing-store-file"_L1,
175 "Path to the .p12 keystore"_L1,
"path"_L1);
176 QCommandLineOption signingKeyAliasOption(
"signing-key-alias"_L1,
177 "Key alias inside the keystore"_L1,
"alias"_L1);
178 QCommandLineOption signingKeyPasswordOption(
"signing-key-password"_L1,
179 "Encrypted key password"_L1,
"pwd"_L1);
180 QCommandLineOption signingStorePasswordOption(
"signing-store-password"_L1,
181 "Encrypted keystore password"_L1,
"pwd"_L1);
182 QCommandLineOption signingAlgOption(
"signing-alg"_L1,
183 "Signature algorithm (default SHA256withECDSA)"_L1,
"alg"_L1);
184 QCommandLineOption helpOption(
"help"_L1,
"Show help"_L1);
186 parser.addOption(inputOption);
187 parser.addOption(outputOption);
188 parser.addOption(hvigorOption);
189 parser.addOption(installOption);
190 parser.addOption(releaseOption);
191 parser.addOption(verboseOption);
192 parser.addOption(noBuildOption);
193 parser.addOption(testBundleOption);
194 parser.addOption(depfileOption);
195 parser.addOption(depfileBaseOption);
196 parser.addOption(signingCertPathOption);
197 parser.addOption(signingProfileOption);
198 parser.addOption(signingStoreFileOption);
199 parser.addOption(signingKeyAliasOption);
200 parser.addOption(signingKeyPasswordOption);
201 parser.addOption(signingStorePasswordOption);
202 parser.addOption(signingAlgOption);
203 parser.addOption(helpOption);
205 if (!parser.parse(arguments)) {
206 fprintf(stderr,
"%s\n", qPrintable(parser.errorText()));
210 if (parser.isSet(helpOption)) {
215 if (!parser.isSet(inputOption)) {
216 fprintf(stderr,
"Error: --input option is required\n");
221 options->inputFile = parser.value(inputOption);
222 options->outputDirectory = parser.value(outputOption);
223 options->hvigorPath = parser.value(hvigorOption);
226 options
->verbose = parser.isSet(verboseOption);
229 options->depFilePath = parser.value(depfileOption);
230 options->depFileBase = parser.value(depfileBaseOption);
231 options->signingCertPath = parser.value(signingCertPathOption);
232 options->signingProfile = parser.value(signingProfileOption);
233 options->signingStoreFile = parser.value(signingStoreFileOption);
234 options->signingKeyAlias = parser.value(signingKeyAliasOption);
235 options->signingKeyPassword = parser.value(signingKeyPasswordOption);
236 options->signingStorePassword = parser.value(signingStorePasswordOption);
237 options->signingAlg = parser.value(signingAlgOption);
244 QFile inputFile(options->inputFile);
245 if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
246 fprintf(stderr,
"Failed to open input file: %s\n", qPrintable(options->inputFile));
250 QJsonParseError parseError;
251 QJsonDocument doc = QJsonDocument::fromJson(inputFile.readAll(), &parseError);
253 fprintf(stderr,
"Failed to parse JSON: %s\n", qPrintable(parseError.errorString()));
257 QJsonObject obj = doc.object();
259 options->applicationBinary = obj[
"application-binary"_L1].toString();
260 options->harmonyOsPackageSourceDirectory = obj[
"harmonyos-package-source-directory"_L1].toString();
261 options->harmonyOsAppName = obj[
"harmonyos-app-name"_L1].toString();
262 options->harmonyOsAppBundleName = obj[
"harmonyos-app-bundle-name"_L1].toString();
263 options->sdkRoot = obj[
"sdk-root"_L1].toString();
264 options->ndkRoot = obj[
"ndk-root"_L1].toString();
267 const QJsonValue rootPathValue = obj[
"qml-root-path"_L1];
268 if (rootPathValue.isArray()) {
269 for (
const QJsonValue &v : rootPathValue.toArray()) {
270 const QString s = v.toString();
272 options->qmlRootPaths.append(s);
275 const QString s = rootPathValue.toString();
277 options->qmlRootPaths.append(s);
282 options->qtLibsDirectory = obj[
"qtLibsDirectory"_L1].toString();
283 options->qtPluginsDirectory = obj[
"qtPluginsDirectory"_L1].toString();
284 options->qtQmlDirectory = obj[
"qtQmlDirectory"_L1].toString();
285 options->qtLibExecsDirectory = obj[
"qtLibExecsDirectory"_L1].toString();
286 options->qtHostDirectory = obj[
"qtHostDirectory"_L1].toString();
288 QJsonArray extraLibsDirsArray = obj[
"extra-libs-dirs"_L1].toArray();
289 for (
const QJsonValue &value : extraLibsDirsArray)
290 options->extraLibsDirs.append(value.toString());
293 if (obj[
"test-bundle"_L1].toBool())
295 options->testBinariesDirectory = obj[
"test-binaries-directory"_L1].toString();
296 QJsonArray excludeArray = obj[
"test-exclude-list"_L1].toArray();
297 for (
const QJsonValue &value : excludeArray)
298 options->testExcludeList.append(value.toString());
301 QJsonArray projectLibsArray = obj[
"project-libraries"_L1].toArray();
302 for (
const QJsonValue &value : projectLibsArray)
303 options->projectLibraries.append(value.toString());
306 QJsonArray importPathsArray = obj[
"qml-import-paths"_L1].toArray();
307 for (
const QJsonValue &value : importPathsArray)
308 options->qmlImportPaths.append(value.toString());
311 QJsonArray pluginsImportPathsArray = obj[
"plugins-import-paths"_L1].toArray();
312 for (
const QJsonValue &value : pluginsImportPathsArray)
313 options->pluginsImportPaths.append(value.toString());
316 QJsonArray archArray = obj[
"harmonyos-target-arch"_L1].toArray();
317 for (
const QJsonValue &value : archArray)
318 options->targetArchs.append(value.toString());
319 if (options->targetArchs.isEmpty())
320 options->targetArchs.append(
"arm64-v8a"_L1);
323 options->permissions = obj[
"permissions"_L1].toArray();
326 options->harmonyOsAppVendor = obj[
"harmonyos-app-vendor"_L1].toString();
327 options->harmonyOsAppVersionCode = obj[
"harmonyos-app-version-code"_L1].toInt();
328 options->harmonyOsAppVersionName = obj[
"harmonyos-app-version-name"_L1].toString();
329 options->harmonyOsAppLabel = obj[
"harmonyos-app-label"_L1].toString();
330 options->harmonyOsAppIcon = obj[
"harmonyos-app-icon"_L1].toString();
333 options->harmonyOsCompatibleSdkVersion =
334 obj[
"harmonyos-compatible-sdk-version"_L1].toString();
335 options->harmonyOsTargetSdkVersion =
336 obj[
"harmonyos-target-sdk-version"_L1].toString();
337 options->harmonyOsCompileSdkVersion =
338 obj[
"harmonyos-compile-sdk-version"_L1].toString();
343 const QJsonArray extraPluginsArray =
344 obj[
"harmonyos-extra-plugins"_L1].toArray();
345 for (
const QJsonValue &v : extraPluginsArray) {
346 const QString s = v.toString();
348 options->extraPlugins.append(s);
354 options->harmonyOsModuleDescription = obj[
"harmonyos-module-description"_L1].toString();
355 const QJsonArray deviceTypesArray = obj[
"harmonyos-module-device-types"_L1].toArray();
356 for (
const QJsonValue &value : deviceTypesArray)
357 options->harmonyOsModuleDeviceTypes.append(value.toString());
358 options->harmonyOsAbilityOrientation = obj[
"harmonyos-ability-orientation"_L1].toString();
362 if (options->applicationBinary.isEmpty()) {
363 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
371 if (!QFile::exists(options->applicationBinary)) {
372 fprintf(stderr,
"Error: application binary does not exist: %s\n",
373 qPrintable(options->applicationBinary));
374 fprintf(stderr,
" Build the project before running harmonydeployqt.\n");
381 if (options->harmonyOsAppBundleName.isEmpty())
382 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
383 if (options->harmonyOsAppName.isEmpty())
384 options->harmonyOsAppName =
"QtAutoTests"_L1;
388 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
393 searchPath = QDir::cleanPath(options->qtLibsDirectory);
394 }
else if (!options->applicationBinary.isEmpty()) {
395 QFileInfo appInfo(options->applicationBinary);
396 searchPath = QDir::cleanPath(appInfo.absolutePath());
399 if (searchPath.isEmpty()) {
400 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
401 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
406 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
409 for (
int i = 0; i < 10; ++i) {
411 QString templatePath = searchPath +
"/share/qt6/src/harmonyos/templates"_L1;
413 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
414 QDir(templatePath).exists() ?
"FOUND" :
"not found");
416 if (QDir(templatePath).exists()) {
417 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
422 templatePath = searchPath +
"/src/harmonyos/templates"_L1;
424 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
425 QDir(templatePath).exists() ?
"FOUND" :
"not found");
427 if (QDir(templatePath).exists()) {
428 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
433 const auto lastSlash = searchPath.lastIndexOf(
'/'_L1);
434 if (lastSlash <= 0) {
436 fprintf(stdout,
" Reached root directory\n");
439 searchPath.resize(lastSlash);
442 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
443 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
444 fprintf(stderr,
" and could not auto-detect template location\n");
445 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
448 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
452 if (options->harmonyOsAppName.isEmpty()) {
453 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
457 if (options->harmonyOsAppBundleName.isEmpty()) {
458 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
463 if (options->outputDirectory.isEmpty()) {
465 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
467 QFileInfo appInfo(options->applicationBinary);
468 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
469 appInfo.completeBaseName() +
"-harmonyos"_L1;
474 fprintf(stdout,
"Configuration loaded:\n");
476 fprintf(stdout,
" Mode: test bundle\n");
477 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
478 if (!options->testExcludeList.isEmpty())
479 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
481 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
483 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
484 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
485 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
486 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
487 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
754 fprintf(stdout,
"Customizing template files\n");
757 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
758 QFile qtAppConstantsFile(qtAppConstantsPath);
760 if (qtAppConstantsFile.exists()) {
761 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
762 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
766 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
767 qtAppConstantsFile.close();
773 appLibName =
"libtst_placeholder.so"_L1;
775 QFileInfo appInfo(options.applicationBinary);
776 appLibName = appInfo.fileName();
779 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
780 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
782 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
783 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
787 qtAppConstantsFile.write(content.toUtf8());
788 qtAppConstantsFile.close();
791 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
801 QString iconValue = options.harmonyOsAppIcon;
802 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
803 QFileInfo iconInfo(iconValue);
804 if (!iconInfo.exists() || !iconInfo.isFile()) {
805 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
808 QString safeStem = iconInfo.completeBaseName();
809 for (QChar &c : safeStem) {
810 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
811 c = QLatin1Char(
'_');
813 const QString destFileName = iconInfo.suffix().isEmpty()
815 : safeStem +
"."_L1 + iconInfo.suffix();
816 const QStringList destDirs = {
817 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
818 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
820 for (
const QString &destDir : destDirs) {
821 QDir().mkpath(destDir);
822 const QString destPath = destDir +
"/"_L1 + destFileName;
823 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
824 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
828 iconValue =
"$media:"_L1 + safeStem;
835 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
836 QFile appJsonFile(appJsonPath);
838 if (appJsonFile.exists()) {
839 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
840 fprintf(stderr,
"Failed to open app.json5 for reading\n");
843 QString content = QString::fromUtf8(appJsonFile.readAll());
846 auto replaceStringField =
847 [&content](QLatin1StringView key,
const QString &value) {
851 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
852 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
855 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
856 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
857 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
864 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
865 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
866 replaceStringField(
"icon"_L1, iconValue);
870 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
871 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
874 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
875 fprintf(stderr,
"Failed to open app.json5 for writing\n");
878 appJsonFile.write(content.toUtf8());
882 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
883 qPrintable(options.harmonyOsAppBundleName));
891 const QString displayLabel =
892 (!options.harmonyOsAppLabel.isEmpty()
893 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
894 ? options.harmonyOsAppLabel
895 : options.harmonyOsAppName;
900 QJsonArray transformedPermissions;
901 QList<PromotedReason> promotedReasons;
902 QSet<QString> seenPromotedIds;
903 for (
const QJsonValue &value : std::as_const(options.permissions)) {
904 if (!value.isObject()) {
905 transformedPermissions.append(value);
908 QJsonObject entry = value.toObject();
909 if (entry.contains(
"reason"_L1)) {
910 const QString reason = entry[
"reason"_L1].toString();
911 if (reasonNeedsPromotion(reason)) {
912 const QString permName = entry[
"name"_L1].toString();
913 const QString stringId = synthesizePermissionReasonId(permName);
914 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
915 if (!seenPromotedIds.contains(stringId)) {
916 promotedReasons.append({stringId, reason});
917 seenPromotedIds.insert(stringId);
921 transformedPermissions.append(entry);
927 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
929 for (
const QString &locale : locales) {
930 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
931 locale +
"/element/string.json"_L1;
932 QFile stringJsonFile(stringJsonPath);
934 if (!stringJsonFile.exists())
937 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
938 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
942 const QByteArray bytes = stringJsonFile.readAll();
943 stringJsonFile.close();
945 QJsonParseError parseErr;
946 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
947 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
948 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
949 qPrintable(locale), qPrintable(parseErr.errorString()));
952 QJsonObject root = doc.object();
953 QJsonArray strings = root[
"string"_L1].toArray();
956 QSet<QString> existingNames;
957 for (qsizetype i = 0; i < strings.size(); ++i) {
958 QJsonObject e = strings[i].toObject();
959 const QString name = e[
"name"_L1].toString();
960 existingNames.insert(name);
961 if (name ==
"QAbility_label"_L1) {
962 e[
"value"_L1] = displayLabel;
968 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
969 if (existingNames.contains(p.id))
973 e[
"value"_L1] = p.value;
975 existingNames.insert(p.id);
977 root[
"string"_L1] = strings;
980 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
981 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
985 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
986 stringJsonFile.close();
988 if (options.verbose) {
990 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
991 qPrintable(locale), qPrintable(displayLabel),
992 static_cast<
long long>(promotedReasons.size()));
997 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
998 QFile appScopeStringFile(appScopeStringPath);
1000 if (appScopeStringFile.exists()) {
1001 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1002 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
1006 QString content = QString::fromUtf8(appScopeStringFile.readAll());
1007 appScopeStringFile.close();
1010 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
1011 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
1013 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1014 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
1018 appScopeStringFile.write(content.toUtf8());
1019 appScopeStringFile.close();
1022 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
1023 qPrintable(displayLabel));
1028 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
1029 QFile moduleJsonFile(moduleJsonPath);
1031 if (moduleJsonFile.exists()) {
1032 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1033 fprintf(stderr,
"Failed to open module.json5 for reading\n");
1037 QString content = QString::fromUtf8(moduleJsonFile.readAll());
1038 moduleJsonFile.close();
1044 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
1045 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
1046 ?
"$string:module_desc"_L1
1047 : jsonStringEscape(options.harmonyOsModuleDescription);
1048 content.replace(descriptionSentinel, descriptionValue);
1051 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
1052 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
1053 if (deviceTypes.isEmpty())
1054 deviceTypes = QStringList{
"phone"_L1,
"tablet"_L1,
"2in1"_L1 };
1055 QStringList quotedDeviceTypes;
1056 quotedDeviceTypes.reserve(deviceTypes.size());
1057 for (
const QString &dt : std::as_const(deviceTypes))
1058 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
1059 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
1068 const QString orientationSentinelLine =
1069 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
1070 QString orientationReplacement;
1071 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
1072 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
1073 orientationReplacement =
" \"orientation\": \""_L1
1074 + options.harmonyOsAbilityOrientation
1078 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
1079 qPrintable(options.harmonyOsAbilityOrientation));
1082 content.replace(orientationSentinelLine, orientationReplacement);
1088 if (!iconValue.isEmpty()) {
1090 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
1091 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1098 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1099 QString permissionsFragment;
1100 if (!transformedPermissions.isEmpty()) {
1101 QStringList entryStrings;
1102 entryStrings.reserve(transformedPermissions.size());
1103 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1104 if (!value.isObject())
1106 const QJsonObject entry = value.toObject();
1110 const QByteArray pretty =
1111 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1112 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1113 QStringList indented;
1114 indented.reserve(lines.size());
1115 for (
const QString &line : lines)
1116 indented.append(
" "_L1 + line);
1117 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1119 if (!entryStrings.isEmpty()) {
1120 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1123 content.replace(sentinel, permissionsFragment);
1125 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1126 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1130 moduleJsonFile.write(content.toUtf8());
1131 moduleJsonFile.close();
1134 fprintf(stdout,
" Updated module.json5 description\n");
1135 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1136 static_cast<
long long>(transformedPermissions.size()));
1149 const bool anySdkVersionSet =
1150 !options.harmonyOsCompatibleSdkVersion.isEmpty()
1151 || !options.harmonyOsTargetSdkVersion.isEmpty()
1152 || !options.harmonyOsCompileSdkVersion.isEmpty();
1154 const QString buildProfilePath =
1155 options.outputDirectory +
"/build-profile.json5"_L1;
1156 QFile buildProfileFile(buildProfilePath);
1157 if (anySdkVersionSet && buildProfileFile.exists()) {
1158 if (!buildProfileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1159 fprintf(stderr,
"Failed to open build-profile.json5 for reading\n");
1162 QString content = QString::fromUtf8(buildProfileFile.readAll());
1163 buildProfileFile.close();
1165 if (!options.harmonyOsCompatibleSdkVersion.isEmpty()) {
1168 "\"compatibleSdkVersion\":\\s*\"[^\"]*\""_L1),
1169 "\"compatibleSdkVersion\": \""_L1
1170 + jsonStringEscape(options.harmonyOsCompatibleSdkVersion)
1174 const QString targetSentinel =
"/* %%INSERT_TARGET_SDK_VERSION%% */"_L1;
1175 const QString targetReplacement =
1176 options.harmonyOsTargetSdkVersion.isEmpty()
1178 :
"\"targetSdkVersion\": \""_L1
1179 + jsonStringEscape(options.harmonyOsTargetSdkVersion)
1181 content.replace(targetSentinel, targetReplacement);
1183 const QString compileSentinel =
"/* %%INSERT_COMPILE_SDK_VERSION%% */"_L1;
1184 const QString compileReplacement =
1185 options.harmonyOsCompileSdkVersion.isEmpty()
1187 :
"\"compileSdkVersion\": \""_L1
1188 + jsonStringEscape(options.harmonyOsCompileSdkVersion)
1190 content.replace(compileSentinel, compileReplacement);
1192 if (!buildProfileFile.open(
1193 QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1194 fprintf(stderr,
"Failed to open build-profile.json5 for writing\n");
1197 buildProfileFile.write(content.toUtf8());
1198 buildProfileFile.close();
1202 " Updated build-profile.json5 SDK versions"
1203 " (compatible=%s, target=%s, compile=%s)\n",
1204 qPrintable(options.harmonyOsCompatibleSdkVersion),
1205 qPrintable(options.harmonyOsTargetSdkVersion),
1206 qPrintable(options.harmonyOsCompileSdkVersion));
1212 fprintf(stdout,
"Template customization completed\n");
1419 if (options.testBinariesDirectory.isEmpty()) {
1420 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1424 if (!QDir(options.testBinariesDirectory).exists()) {
1425 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1426 qPrintable(options.testBinariesDirectory));
1431 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1434 QStringList foundHelpers;
1435 QSet<QString> helperNames;
1439 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1440 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1441 found, foundHelpers, helperNames);
1443 if (found.isEmpty()) {
1444 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1445 qPrintable(options.testBinariesDirectory));
1450 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1451 if (!foundHelpers.isEmpty())
1452 fprintf(stdout,
"Found %lld test helper libraries\n",
1453 static_cast<
long long>(foundHelpers.size()));
1457 for (
const QString &arch : options.targetArchs) {
1458 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1459 QDir().mkpath(archLibPath);
1461 for (
const QString &testBinary : found) {
1462 QFileInfo testInfo(testBinary);
1463 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1465 if (options.verbose)
1466 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1468 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1469 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1474 for (
const QString &helperLib : foundHelpers) {
1475 QFileInfo helperInfo(helperLib);
1476 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1478 if (options.verbose)
1479 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1481 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1482 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1489 for (
const QString &testBinary : found) {
1490 QString fileName = QFileInfo(testBinary).fileName();
1491 if (!bundledBinaries.contains(fileName))
1492 bundledBinaries.append(fileName);
1496 if (!options.depFilePath.isEmpty()) {
1497 for (
const QString &testBinary : found)
1498 dependenciesForDepfile << testBinary;
1499 for (
const QString &helperLib : foundHelpers)
1500 dependenciesForDepfile << helperLib;
1538 if (options.qtLibsDirectory.isEmpty())
1541 if (!QDir(options.qtLibsDirectory).exists()) {
1543 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1544 qPrintable(options.qtLibsDirectory));
1550 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1552 QDir libsDir(options.qtLibsDirectory);
1553 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1555 for (
const QFileInfo &entry : entries) {
1556 for (
const QString &arch : options.targetArchs) {
1557 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1558 QDir().mkpath(QFileInfo(destPath).absolutePath());
1559 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1562 if (!options.depFilePath.isEmpty())
1563 dependenciesForDepfile << entry.filePath();
1567 for (
const QString &arch : options.targetArchs) {
1568 QString stdCppPath = findStdCppLibrary(options, arch);
1569 if (!stdCppPath.isEmpty()) {
1570 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1571 if (options.verbose)
1572 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1573 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1575 if (!options.depFilePath.isEmpty())
1576 dependenciesForDepfile << stdCppPath;
1581 for (
const QString &extraDir : options.extraLibsDirs) {
1583 if (!dir.exists()) {
1584 if (options.verbose)
1585 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1589 if (options.verbose)
1590 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1592 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1593 for (
const QFileInfo &entry : entries) {
1597 const QString soname = readElfSoname(options, entry.filePath());
1598 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1599 ? soname : entry.fileName();
1600 for (
const QString &arch : options.targetArchs) {
1601 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1602 QDir().mkpath(QFileInfo(destPath).absolutePath());
1603 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1606 if (!options.depFilePath.isEmpty())
1607 dependenciesForDepfile << entry.filePath();
2210 fprintf(stdout,
"Detecting Qt library dependencies\n");
2213 QStringList toProcess;
2214 toProcess.append(options.applicationBinary);
2218 for (
const QString &projectLib : options.projectLibraries)
2219 toProcess.append(projectLib);
2221 QList<QtDependency> qtDependencies;
2222 QStringList detectedQtModules;
2223 bool needsStdCpp =
false;
2225 while (!toProcess.isEmpty()) {
2226 QString currentLib = toProcess.takeFirst();
2228 if (processedLibs.contains(currentLib))
2231 processedLibs.insert(currentLib);
2234 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
2236 QStringList deps = readElfDependencies(options, currentLib);
2238 for (
const QString &dep : deps) {
2240 if (dep ==
"libc++_shared.so"_L1) {
2246 if (isSystemLibrary(dep))
2250 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
2252 QString extraDepPath = findExtraDepLibrary(options, dep);
2253 if (extraDepPath.isEmpty())
2258 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
2259 if (options.verbose)
2260 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
2261 QtDependency extraDep;
2262 extraDep.relativePath =
"lib/"_L1 + dep;
2263 extraDep.absolutePath = extraDepPath;
2264 qtDependencies.append(extraDep);
2265 toProcess.append(extraDepPath);
2271 if (dep.startsWith(
"libQt6"_L1)) {
2272 QString moduleName = dep.mid(6);
2273 if (moduleName.endsWith(
".so"_L1))
2275 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
2276 detectedQtModules.append(moduleName);
2279 QString depPath = findQtLibrary(options, dep);
2280 if (depPath.isEmpty()) {
2281 if (options.verbose)
2282 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2286 if (processedLibs.contains(depPath))
2289 if (options.verbose)
2290 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2293 qtDep.relativePath =
"lib/"_L1 + dep;
2294 qtDep.absolutePath = depPath;
2295 qtDependencies.append(std::move(qtDep));
2298 toProcess.append(std::move(depPath));
2303 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
2305 fprintf(stdout,
"C++ standard library required\n");
2310 for (
const QString &arch : options.targetArchs) {
2311 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
2312 QString stdCppPath = findStdCppLibrary(options, arch);
2313 if (stdCppPath.isEmpty()) {
2314 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
2316 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
2317 if (options.verbose)
2318 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
2319 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
2321 "Failed to copy C++ standard library to: %s\n",
2322 qPrintable(destPath));
2327 if (!options.depFilePath.isEmpty())
2328 dependenciesForDepfile << stdCppPath;
2337 for (
const QtDependency &dep : qtDependencies) {
2338 QFileInfo libInfo(dep.relativePath);
2339 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
2342 if (!options.depFilePath.isEmpty())
2343 dependenciesForDepfile << dep.absolutePath;
2574 QList<QmlImportInfo> imports;
2576 if (options.qmlRootPaths.isEmpty()) {
2579 "No QML root path specified, skipping QML import scanning\n");
2584 fprintf(stdout,
"Scanning for QML imports\n");
2587 QStringList searchPaths;
2588 if (!options.qtLibExecsDirectory.isEmpty())
2589 searchPaths.append(options.qtLibExecsDirectory);
2592 if (!options.qtHostDirectory.isEmpty())
2593 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2595 QString qmlImportScannerPath =
2596 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2599 if (qmlImportScannerPath.isEmpty()) {
2600 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2602 for (
int i = 0; i < 10; ++i) {
2603 qmlImportScannerPath = QStandardPaths::findExecutable(
2604 "qmlimportscanner"_L1,
2605 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2607 if (!qmlImportScannerPath.isEmpty())
2615 if (qmlImportScannerPath.isEmpty()) {
2618 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2623 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2624 qPrintable(qmlImportScannerPath));
2627 QStringList importPaths;
2630 for (
const QString &path : options.qmlImportPaths)
2631 if (QFile::exists(path))
2632 importPaths.append(path);
2637 QFileInfo appBinary(options.applicationBinary);
2638 QString appBuildDir = appBinary.absolutePath();
2639 if (QDir(appBuildDir).exists())
2640 importPaths.append(appBuildDir);
2643 if (!options.qtQmlDirectory.isEmpty() &&
2644 QDir(options.qtQmlDirectory).exists()) {
2645 importPaths.append(options.qtQmlDirectory);
2648 QFileInfo appBinary(options.applicationBinary);
2649 QDir qtDir(appBinary.absolutePath());
2650 for (
int i = 0; i < 10; ++i) {
2651 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2652 if (QDir(qmlDir).exists()) {
2653 importPaths.append(qmlDir);
2657 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2658 if (QDir(qmlDir).exists()) {
2659 importPaths.append(QDir::cleanPath(qmlDir));
2667 if (importPaths.isEmpty()) {
2668 fprintf(stderr,
"Warning: No QML import paths found\n");
2674 QStringList arguments;
2675 for (
const QString &rootPath : options.qmlRootPaths)
2676 arguments <<
"-rootPath"_L1 << rootPath;
2678 for (
const QString &importPath : importPaths)
2679 arguments <<
"-importPath"_L1 << importPath;
2682 fprintf(stdout,
" Root paths:\n");
2683 for (
const QString &rootPath : options.qmlRootPaths)
2684 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2685 fprintf(stdout,
" Import paths:\n");
2686 for (
const QString &path : importPaths)
2687 fprintf(stdout,
" %s\n", qPrintable(path));
2692 process.start(qmlImportScannerPath, arguments);
2694 if (!process.waitForFinished(30000)) {
2696 process.waitForFinished(2000);
2697 fprintf(stderr,
"Error: qmlimportscanner timed out after 30 seconds\n");
2698 fprintf(stderr,
" Command: %s %s\n", qPrintable(qmlImportScannerPath),
2699 qPrintable(arguments.join(u' ')));
2700 const QByteArray errorOutput = process.readAllStandardError();
2701 if (!errorOutput.isEmpty())
2702 fprintf(stderr,
"%s\n", errorOutput.constData());
2708 if (process.exitStatus() != QProcess::NormalExit) {
2709 fprintf(stderr,
"Error: qmlimportscanner crashed\n");
2710 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2716 if (process.exitCode() != 0) {
2717 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2718 process.exitCode());
2719 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2726 QByteArray output = process.readAllStandardOutput();
2727 QJsonDocument doc = QJsonDocument::fromJson(output);
2729 if (!doc.isArray()) {
2730 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2736 QJsonArray array = doc.array();
2737 for (
const QJsonValue &value : array) {
2738 if (!value.isObject())
2741 QJsonObject obj = value.toObject();
2743 info.name = obj[
"name"_L1].toString();
2744 info.path = obj[
"path"_L1].toString();
2745 info.type = obj[
"type"_L1].toString();
2747 if (obj.contains(
"plugin"_L1))
2748 info.plugin = obj[
"plugin"_L1].toString();
2750 if (obj.contains(
"pluginIsOptional"_L1))
2751 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2753 if (obj.contains(
"prefer"_L1))
2754 info.prefer = obj[
"prefer"_L1].toString();
2757 if (obj.contains(
"components"_L1)) {
2758 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2759 for (
const QJsonValue &comp : componentsArray)
2760 info.components.append(comp.toString());
2764 if (obj.contains(
"scripts"_L1)) {
2765 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2766 for (
const QJsonValue &script : scriptsArray)
2767 info.scripts.append(script.toString());
2771 if (info.path.isEmpty()) {
2772 if (options.verbose)
2773 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2774 qPrintable(info.name));
2779 if (info.type !=
"module"_L1)
2782 if (options.verbose)
2783 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2784 qPrintable(info.path));
2786 imports.append(info);
2831 const QList<QmlImportInfo> &imports,
2832 QSet<QString> &processedLibs)
2834 if (imports.isEmpty())
2838 fprintf(stdout,
"Copying QML imports\n");
2842 QString qmlDestBase = hapQmlDir(options);
2843 QDir().mkpath(qmlDestBase);
2846 QStringList qmlPluginsToScan;
2848 for (
const QmlImportInfo &import : imports) {
2849 if (options.verbose)
2850 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2854 QString relativePath;
2857 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2859 relativePath = import.path.mid(options.qtQmlDirectory.length());
2860 if (relativePath.startsWith(
'/'_L1))
2861 relativePath = relativePath.mid(1);
2865 relativePath = import.name;
2866 relativePath.replace(
'.'_L1,
'/'_L1);
2869 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2870 QDir().mkpath(destModuleDir);
2873 QString qmldirSrc = import.path +
"/qmldir"_L1;
2874 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2875 if (QFile::exists(qmldirSrc)) {
2876 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2877 if (options.verbose)
2878 fprintf(stdout,
" Copied qmldir\n");
2881 if (!options.depFilePath.isEmpty())
2882 dependenciesForDepfile << qmldirSrc;
2884 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2885 qPrintable(import.name));
2892 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2894 if (!import.plugin.isEmpty()) {
2896 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2897 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2899 if (QFile::exists(pluginSrc)) {
2901 for (
const QString &arch : options.targetArchs) {
2902 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2903 +
"/"_L1 + pluginFileName;
2905 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2906 if (options.verbose)
2907 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2908 qPrintable(arch), qPrintable(pluginFileName));
2909 processedLibs.insert(pluginSrc);
2912 if (!qmlPluginsToScan.contains(pluginSrc))
2913 qmlPluginsToScan.append(pluginSrc);
2916 if (!options.depFilePath.isEmpty())
2917 dependenciesForDepfile << pluginSrc;
2918 }
else if (!import.pluginIsOptional) {
2919 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2920 qPrintable(pluginFileName));
2923 }
else if (!import.pluginIsOptional) {
2924 if (options.verbose)
2925 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2926 qPrintable(pluginFileName));
2931 if (!qmlFilesAreEmbedded) {
2932 for (
const QString &component : import.components) {
2933 QFileInfo compInfo(component);
2934 if (!compInfo.exists()) {
2935 if (options.verbose)
2936 fprintf(stdout,
" Warning: Component file not found: %s\n",
2937 qPrintable(component));
2941 QString relativePath = component.mid(import.path.length());
2942 if (relativePath.startsWith(
'/'_L1))
2943 relativePath = relativePath.mid(1);
2945 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2946 QFileInfo destInfo(destFile);
2947 QDir().mkpath(destInfo.absolutePath());
2949 if (copyFileIfNewer(component, destFile, options.verbose)) {
2950 if (options.verbose)
2951 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
2954 if (!options.depFilePath.isEmpty())
2955 dependenciesForDepfile << component;
2960 for (
const QString &script : import.scripts) {
2961 QFileInfo scriptInfo(script);
2962 if (!scriptInfo.exists())
2965 QString relativePath = script.mid(import.path.length());
2966 if (relativePath.startsWith(
'/'_L1))
2967 relativePath = relativePath.mid(1);
2969 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2970 QFileInfo destInfo(destFile);
2971 QDir().mkpath(destInfo.absolutePath());
2973 if (copyFileIfNewer(script, destFile, options.verbose)) {
2974 if (options.verbose)
2975 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
2978 if (!options.depFilePath.isEmpty())
2979 dependenciesForDepfile << script;
2983 if (options.verbose)
2984 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
2989 if (!qmlPluginsToScan.isEmpty()) {
2991 fprintf(stdout,
"Scanning QML plugin dependencies\n");
2993 QStringList toProcess = qmlPluginsToScan;
2994 while (!toProcess.isEmpty()) {
2995 QString pluginPath = toProcess.takeFirst();
2998 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
3000 QStringList deps = readElfDependencies(options, pluginPath);
3001 for (
const QString &dep : deps) {
3003 if (isSystemLibrary(dep))
3008 if (dep.startsWith(
"libQt6"_L1)) {
3009 depPath = findQtLibrary(options, dep);
3010 if (depPath.isEmpty()) {
3011 if (options.verbose)
3012 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
3015 }
else if (!options.extraLibsDirs.isEmpty()) {
3016 depPath = findExtraDepLibrary(options, dep);
3017 if (depPath.isEmpty())
3023 if (processedLibs.contains(depPath))
3026 if (options.verbose)
3027 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
3030 for (
const QString &arch : options.targetArchs) {
3031 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
3032 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
3033 if (options.verbose)
3034 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
3037 if (!options.depFilePath.isEmpty())
3038 dependenciesForDepfile << depPath;
3042 processedLibs.insert(depPath);
3044 toProcess.append(depPath);
3050 fprintf(stdout,
"QML imports copied successfully\n");
3062 const char *envName;
3063 const char *cliFlag;
3065 QByteArray envValue;
3067 QByteArray resolved()
const
3069 if (!cliValue.isEmpty())
3070 return cliValue.toUtf8();
3073 QString sourceLabel()
const
3075 return cliValue.isEmpty()
3076 ? QString::fromLatin1(envName)
3077 : QString::fromLatin1(cliFlag);
3081 Field required[] = {
3082 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path",
3083 options.signingCertPath, qgetenv(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
3084 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile",
3085 options.signingProfile, qgetenv(
"QT_HARMONYOS_SIGNING_PROFILE") },
3086 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file",
3087 options.signingStoreFile, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
3088 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias",
3089 options.signingKeyAlias, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
3090 {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
3091 options.signingKeyPassword, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
3092 {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
3093 options.signingStorePassword, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
3096 QByteArray signAlg = options.signingAlg.isEmpty()
3097 ? qgetenv(
"QT_HARMONYOS_SIGNING_ALG")
3098 : options.signingAlg.toUtf8();
3100 bool anySet = !signAlg.isEmpty();
3101 for (
const Field &f : required)
3102 anySet = anySet || !f.resolved().isEmpty();
3106 QStringList missing;
3107 for (
const Field &f : required) {
3108 if (f.resolved().isEmpty())
3109 missing << QString::fromLatin1(f.cliFlag) +
" / "_L1
3110 + QString::fromLatin1(f.envName);
3112 if (!missing.isEmpty()) {
3113 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
3114 " are missing (neither CLI flag nor env var was supplied):\n");
3115 for (
const QString &name : missing)
3116 fprintf(stderr,
" %s\n", qPrintable(name));
3120 if (signAlg.isEmpty())
3121 signAlg =
"SHA256withECDSA";
3123 const QString buildProfilePath = options.outputDirectory +
"/build-profile.json5"_L1;
3124 QFile profileFile(buildProfilePath);
3125 if (!profileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
3126 fprintf(stderr,
"Error: cannot open %s for reading: %s\n",
3127 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3130 QByteArray content = profileFile.readAll();
3131 profileFile.close();
3135 static const QByteArray needle =
"\"signingConfigs\": []";
3136 const int idx = content.indexOf(needle);
3138 fprintf(stderr,
"Error: '%s' not found in %s. The template may have been modified;\n"
3139 " cannot inject signing configuration safely.\n",
3140 needle.constData(), qPrintable(buildProfilePath));
3146 auto escapeJsonString = [](
const QByteArray &in) {
3148 out.reserve(in.size());
3150 if (c ==
'\\' || c ==
'"')
3157 QByteArray replacement;
3158 replacement.append(
"\"signingConfigs\": [\n");
3159 replacement.append(
" {\n");
3160 replacement.append(
" \"name\": \"default\",\n");
3161 replacement.append(
" \"type\": \"HarmonyOS\",\n");
3162 replacement.append(
" \"material\": {\n");
3163 auto appendField = [&](
const char *key,
const QByteArray &value,
bool last) {
3164 replacement.append(
" \"");
3165 replacement.append(key);
3166 replacement.append(
"\": \"");
3167 replacement.append(escapeJsonString(value));
3168 replacement.append(
'"');
3170 replacement.append(
',');
3171 replacement.append(
'\n');
3173 appendField(
"certpath", required[0].resolved(),
false);
3174 appendField(
"keyAlias", required[3].resolved(),
false);
3175 appendField(
"keyPassword", required[4].resolved(),
false);
3176 appendField(
"profile", required[1].resolved(),
false);
3177 appendField(
"signAlg", signAlg,
false);
3178 appendField(
"storeFile", required[2].resolved(),
false);
3179 appendField(
"storePassword", required[5].resolved(),
true);
3180 replacement.append(
" }\n");
3181 replacement.append(
" }\n");
3182 replacement.append(
" ]");
3184 content.replace(idx, needle.size(), replacement);
3186 if (!profileFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
3187 fprintf(stderr,
"Error: cannot open %s for writing: %s\n",
3188 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3191 if (profileFile.write(content) != content.size()) {
3192 fprintf(stderr,
"Error: failed to write full content to %s\n",
3193 qPrintable(buildProfilePath));
3194 profileFile.close();
3197 profileFile.close();
3200 fprintf(stdout,
"Injected HAP signingConfig into %s\n", qPrintable(buildProfilePath));
3201 fprintf(stdout,
" certpath: %s (from %s)\n",
3202 required[0].resolved().constData(), qPrintable(required[0].sourceLabel()));
3203 fprintf(stdout,
" profile: %s (from %s)\n",
3204 required[1].resolved().constData(), qPrintable(required[1].sourceLabel()));
3205 fprintf(stdout,
" storeFile: %s (from %s)\n",
3206 required[2].resolved().constData(), qPrintable(required[2].sourceLabel()));
3207 fprintf(stdout,
" keyAlias: %s (from %s)\n",
3208 required[3].resolved().constData(), qPrintable(required[3].sourceLabel()));
3209 fprintf(stdout,
" signAlg: %s\n", signAlg.constData());
3433 QCoreApplication app(argc, argv);
3434 QCoreApplication::setApplicationName(
"harmonydeployqt"_L1);
3435 QCoreApplication::setApplicationVersion(
"1.0"_L1);
3438 options.timer.start();
3440 if (!parseCommandLine(app.arguments(), &options))
3444 fprintf(stdout,
"Qt HarmonyOS Deployment Tool\n");
3445 fprintf(stdout,
"==============================\n\n");
3451 const QList<TestQmlModule> testQmlModules =
3452 options.testBundleMode ? findTestQmlModules(options) : QList<TestQmlModule>{};
3454 if (options.testBundleMode && !verifyUniqueTestQmlModuleDeployDirs(testQmlModules))
3458 fprintf(stdout,
"\nDeployment process started...\n");
3460 QString hapOutputPath;
3464 fprintf(stderr,
"Failed to copy template\n");
3470 fprintf(stderr,
"Failed to customize template\n");
3475 QStringList bundledBinaries;
3477 if (!copyTestBinaries(options, bundledBinaries)) {
3478 fprintf(stderr,
"Failed to copy test binaries\n");
3482 fprintf(stderr,
"Failed to copy Qt libraries\n");
3486 fprintf(stderr,
"Failed to copy Qt plugins\n");
3490 fprintf(stderr,
"Failed to copy QML modules\n");
3493 if (!copyTestQmlModules(testQmlModules, options)) {
3494 fprintf(stderr,
"Failed to copy test QML modules\n");
3499 fprintf(stderr,
"Failed to copy application binary\n");
3503 fprintf(stderr,
"Failed to copy project libraries\n");
3506 QSet<QString> processedLibs;
3507 if (!detectAndCopyDependencies(options, processedLibs)) {
3508 fprintf(stderr,
"Failed to detect and copy dependencies\n");
3511 bool qmlScanOk =
true;
3512 QList<QmlImportInfo> qmlImports = scanQmlImports(options, &qmlScanOk);
3514 fprintf(stderr,
"Failed to scan QML imports\n");
3518 fprintf(stderr,
"Failed to copy QML files\n");
3521 if (!copyQmlImports(options, qmlImports, processedLibs)) {
3522 fprintf(stderr,
"Failed to copy QML imports\n");
3525 if (!copyPlugins(options, processedLibs)) {
3526 fprintf(stderr,
"Failed to copy plugins\n");
3532 fprintf(stderr,
"Failed to copy extra plugins\n");
3537 fprintf(stderr,
"Failed to inject signing configuration\n");
3542 if (!buildHap(options, &hapOutputPath)) {
3543 fprintf(stderr,
"Failed to build HAP\n");
3549 if (!writeTestBinariesList(options, bundledBinaries)) {
3550 fprintf(stderr,
"Failed to write binaries.txt\n");
3557 if (!options.depFilePath.isEmpty()) {
3558 if (hapOutputPath.isEmpty()) {
3561 hapOutputPath = options.outputDirectory
3562 +
"/entry/build/default/outputs/default/autotests.hap"_L1;
3565 QString targetName = QFileInfo(options.applicationBinary).completeBaseName();
3566 if (targetName.startsWith(
"lib"_L1))
3567 targetName = targetName.mid(3);
3568 hapOutputPath = options.outputDirectory +
"/entry/build/default/outputs/default/"_L1
3569 + targetName +
".hap"_L1;
3572 if (!writeDepfile(options, hapOutputPath))
3573 fprintf(stderr,
"Warning: Failed to write dependency file\n");
3578 if (!installToDevice(options, hapOutputPath)) {
3579 fprintf(stderr,
"Failed to install to device\n");
3584 fprintf(stdout,
"\n==============================================\n");
3585 fprintf(stdout,
"Deployment completed successfully!\n");
3586 fprintf(stdout,
"==============================================\n");
3587 fprintf(stdout,
"Project location: %s\n", qPrintable(options.outputDirectory));
3589 if (!hapOutputPath.isEmpty())
3590 fprintf(stdout,
"HAP package: %s\n", qPrintable(hapOutputPath));
3593 fprintf(stdout,
"\nTotal time: %lld ms\n", options.timer.elapsed());