168 QCommandLineParser parser;
169 parser.setApplicationDescription(
"Qt HarmonyOS Deployment Tool"_L1);
171 QCommandLineOption inputOption(
"input"_L1,
"JSON configuration file"_L1,
"file"_L1);
172 QCommandLineOption outputOption(
"output"_L1,
"Output directory"_L1,
"dir"_L1);
173 QCommandLineOption hvigorOption(
"hvigor"_L1,
"Path to hvigorw"_L1,
"path"_L1);
174 QCommandLineOption installOption(
"install"_L1,
"Install to device"_L1);
175 QCommandLineOption releaseOption(
"release"_L1,
"Build release configuration"_L1);
176 QCommandLineOption verboseOption(
"verbose"_L1,
"Verbose output"_L1);
177 QCommandLineOption noBuildOption(
"no-build"_L1,
"Skip building"_L1);
178 QCommandLineOption testBundleOption(
"test-bundle"_L1,
"Enable test bundle mode"_L1);
179 QCommandLineOption depfileOption(
"depfile"_L1,
"Dependency file output"_L1,
"path"_L1);
180 QCommandLineOption depfileBaseOption(
"depfile-base"_L1,
"Base directory for depfile paths"_L1,
"dir"_L1);
181 QCommandLineOption signingCertPathOption(
"signing-cert-path"_L1,
182 "Path to the .cer file"_L1,
"path"_L1);
183 QCommandLineOption signingProfileOption(
"signing-profile"_L1,
184 "Path to the .p7b profile"_L1,
"path"_L1);
185 QCommandLineOption signingStoreFileOption(
"signing-store-file"_L1,
186 "Path to the .p12 keystore"_L1,
"path"_L1);
187 QCommandLineOption signingKeyAliasOption(
"signing-key-alias"_L1,
188 "Key alias inside the keystore"_L1,
"alias"_L1);
189 QCommandLineOption signingKeyPasswordOption(
"signing-key-password"_L1,
190 "Encrypted key password"_L1,
"pwd"_L1);
191 QCommandLineOption signingStorePasswordOption(
"signing-store-password"_L1,
192 "Encrypted keystore password"_L1,
"pwd"_L1);
193 QCommandLineOption signingPlainKeyPasswordOption(
"signing-plain-key-password"_L1,
194 "Plain text key password"_L1,
"pwd"_L1);
195 QCommandLineOption signingPlainStorePasswordOption(
"signing-plain-store-password"_L1,
196 "Plain text keystore password"_L1,
"pwd"_L1);
197 QCommandLineOption signingAlgOption(
"signing-alg"_L1,
198 "Signature algorithm (default SHA256withECDSA)"_L1,
"alg"_L1);
199 QCommandLineOption helpOption(
"help"_L1,
"Show help"_L1);
201 parser.addOption(inputOption);
202 parser.addOption(outputOption);
203 parser.addOption(hvigorOption);
204 parser.addOption(installOption);
205 parser.addOption(releaseOption);
206 parser.addOption(verboseOption);
207 parser.addOption(noBuildOption);
208 parser.addOption(testBundleOption);
209 parser.addOption(depfileOption);
210 parser.addOption(depfileBaseOption);
211 parser.addOption(signingCertPathOption);
212 parser.addOption(signingProfileOption);
213 parser.addOption(signingStoreFileOption);
214 parser.addOption(signingKeyAliasOption);
215 parser.addOption(signingKeyPasswordOption);
216 parser.addOption(signingStorePasswordOption);
217 parser.addOption(signingPlainKeyPasswordOption);
218 parser.addOption(signingPlainStorePasswordOption);
219 parser.addOption(signingAlgOption);
220 parser.addOption(helpOption);
222 if (!parser.parse(arguments)) {
223 fprintf(stderr,
"%s\n", qPrintable(parser.errorText()));
227 if (parser.isSet(helpOption)) {
232 if (!parser.isSet(inputOption)) {
233 fprintf(stderr,
"Error: --input option is required\n");
238 options->inputFile = parser.value(inputOption);
239 options->outputDirectory = parser.value(outputOption);
240 options->hvigorPath = parser.value(hvigorOption);
243 options
->verbose = parser.isSet(verboseOption);
246 options->depFilePath = parser.value(depfileOption);
247 options->depFileBase = parser.value(depfileBaseOption);
248 options->signingCertPath = parser.value(signingCertPathOption);
249 options->signingProfile = parser.value(signingProfileOption);
250 options->signingStoreFile = parser.value(signingStoreFileOption);
251 options->signingKeyAlias = parser.value(signingKeyAliasOption);
252 options->signingKeyPassword = parser.value(signingKeyPasswordOption);
253 options->signingStorePassword = parser.value(signingStorePasswordOption);
254 options->signingPlainKeyPassword = parser.value(signingPlainKeyPasswordOption);
255 options->signingPlainStorePassword = parser.value(signingPlainStorePasswordOption);
256 options->signingAlg = parser.value(signingAlgOption);
288 QFile inputFile(options->inputFile);
289 if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
290 fprintf(stderr,
"Failed to open input file: %s\n", qPrintable(options->inputFile));
294 QJsonParseError parseError;
295 QJsonDocument doc = QJsonDocument::fromJson(inputFile.readAll(), &parseError);
297 fprintf(stderr,
"Failed to parse JSON: %s\n", qPrintable(parseError.errorString()));
301 QJsonObject obj = doc.object();
303 options->applicationBinary = obj[
"application-binary"_L1].toString();
304 options->harmonyOsPackageSourceDirectory = obj[
"harmonyos-package-source-directory"_L1].toString();
305 options->harmonyOsAppName = obj[
"harmonyos-app-name"_L1].toString();
306 options->harmonyOsAppBundleName = obj[
"harmonyos-app-bundle-name"_L1].toString();
307 options->sdkRoot = obj[
"sdk-root"_L1].toString();
308 options->ndkRoot = obj[
"ndk-root"_L1].toString();
311 const QJsonValue rootPathValue = obj[
"qml-root-path"_L1];
312 if (rootPathValue.isArray()) {
313 for (
const QJsonValue &v : rootPathValue.toArray()) {
314 const QString s = v.toString();
316 options->qmlRootPaths.append(s);
319 const QString s = rootPathValue.toString();
321 options->qmlRootPaths.append(s);
326 options->qtLibsDirectory = obj[
"qtLibsDirectory"_L1].toString();
327 options->qtPluginsDirectory = obj[
"qtPluginsDirectory"_L1].toString();
328 options->qtQmlDirectory = obj[
"qtQmlDirectory"_L1].toString();
329 options->qtLibExecsDirectory = obj[
"qtLibExecsDirectory"_L1].toString();
330 options->qtHostDirectory = obj[
"qtHostDirectory"_L1].toString();
332 QJsonArray extraLibsDirsArray = obj[
"extra-libs-dirs"_L1].toArray();
333 for (
const QJsonValue &value : extraLibsDirsArray)
334 options->extraLibsDirs.append(value.toString());
337 if (obj[
"test-bundle"_L1].toBool())
339 options->testBinariesDirectory = obj[
"test-binaries-directory"_L1].toString();
340 QJsonArray excludeArray = obj[
"test-exclude-list"_L1].toArray();
341 for (
const QJsonValue &value : excludeArray)
342 options->testExcludeList.append(value.toString());
345 QJsonArray projectLibsArray = obj[
"project-libraries"_L1].toArray();
346 for (
const QJsonValue &value : projectLibsArray)
347 options->projectLibraries.append(value.toString());
350 QJsonArray importPathsArray = obj[
"qml-import-paths"_L1].toArray();
351 for (
const QJsonValue &value : importPathsArray)
352 options->qmlImportPaths.append(value.toString());
355 QJsonArray pluginsImportPathsArray = obj[
"plugins-import-paths"_L1].toArray();
356 for (
const QJsonValue &value : pluginsImportPathsArray)
357 options->pluginsImportPaths.append(value.toString());
360 QJsonArray archArray = obj[
"harmonyos-target-arch"_L1].toArray();
361 for (
const QJsonValue &value : archArray)
362 options->targetArchs.append(value.toString());
363 if (options->targetArchs.isEmpty())
364 options->targetArchs.append(
"arm64-v8a"_L1);
367 options->permissions = obj[
"permissions"_L1].toArray();
370 options->harmonyOsAppVendor = obj[
"harmonyos-app-vendor"_L1].toString();
372 options->harmonyOsAppVersionName = obj[
"harmonyos-app-version-name"_L1].toString();
373 options->harmonyOsAppLabel = obj[
"harmonyos-app-label"_L1].toString();
374 options->harmonyOsAppIcon = obj[
"harmonyos-app-icon"_L1].toString();
377 options->harmonyOsCompatibleSdkVersion =
378 obj[
"harmonyos-compatible-sdk-version"_L1].toString();
379 options->harmonyOsTargetSdkVersion =
380 obj[
"harmonyos-target-sdk-version"_L1].toString();
381 options->harmonyOsCompileSdkVersion =
382 obj[
"harmonyos-compile-sdk-version"_L1].toString();
385 if (options->harmonyOsTargetSdkVersion.isEmpty()
386 && options->harmonyOsCompileSdkVersion.isEmpty()) {
387 options->harmonyOsTargetSdkVersion = sdkVersion(options->sdkRoot);
393 const QJsonArray extraPluginsArray =
394 obj[
"harmonyos-extra-plugins"_L1].toArray();
395 for (
const QJsonValue &v : extraPluginsArray) {
396 const QString s = v.toString();
398 options->extraPlugins.append(s);
404 options->harmonyOsModuleDescription = obj[
"harmonyos-module-description"_L1].toString();
405 const QJsonArray deviceTypesArray = obj[
"harmonyos-module-device-types"_L1].toArray();
406 for (
const QJsonValue &value : deviceTypesArray)
407 options->harmonyOsModuleDeviceTypes.append(value.toString());
408 options->harmonyOsAbilityOrientation = obj[
"harmonyos-ability-orientation"_L1].toString();
412 if (options->applicationBinary.isEmpty()) {
413 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
421 if (!QFile::exists(options->applicationBinary)) {
422 fprintf(stderr,
"Error: application binary does not exist: %s\n",
423 qPrintable(options->applicationBinary));
424 fprintf(stderr,
" Build the project before running harmonydeployqt.\n");
431 if (options->harmonyOsAppBundleName.isEmpty())
432 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
433 if (options->harmonyOsAppName.isEmpty())
434 options->harmonyOsAppName =
"QtAutoTests"_L1;
438 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
443 searchPath = QDir::cleanPath(options->qtLibsDirectory);
444 }
else if (!options->applicationBinary.isEmpty()) {
445 QFileInfo appInfo(options->applicationBinary);
446 searchPath = QDir::cleanPath(appInfo.absolutePath());
449 if (searchPath.isEmpty()) {
450 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
451 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
456 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
459 for (
int i = 0; i < 10; ++i) {
461 QString templatePath = searchPath +
"/share/qt6/src/harmonyos/templates"_L1;
463 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
464 QDir(templatePath).exists() ?
"FOUND" :
"not found");
466 if (QDir(templatePath).exists()) {
467 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
472 templatePath = searchPath +
"/src/harmonyos/templates"_L1;
474 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
475 QDir(templatePath).exists() ?
"FOUND" :
"not found");
477 if (QDir(templatePath).exists()) {
478 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
483 const auto lastSlash = searchPath.lastIndexOf(
'/'_L1);
484 if (lastSlash <= 0) {
486 fprintf(stdout,
" Reached root directory\n");
489 searchPath.resize(lastSlash);
492 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
493 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
494 fprintf(stderr,
" and could not auto-detect template location\n");
495 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
498 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
502 if (options->harmonyOsAppName.isEmpty()) {
503 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
507 if (options->harmonyOsAppBundleName.isEmpty()) {
508 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
513 if (options->outputDirectory.isEmpty()) {
515 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
517 QFileInfo appInfo(options->applicationBinary);
518 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
519 appInfo.completeBaseName() +
"-harmonyos"_L1;
524 fprintf(stdout,
"Configuration loaded:\n");
526 fprintf(stdout,
" Mode: test bundle\n");
527 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
528 if (!options->testExcludeList.isEmpty())
529 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
531 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
533 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
534 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
535 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
536 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
537 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
804 fprintf(stdout,
"Customizing template files\n");
807 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
808 QFile qtAppConstantsFile(qtAppConstantsPath);
810 if (qtAppConstantsFile.exists()) {
811 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
812 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
816 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
817 qtAppConstantsFile.close();
823 appLibName =
"libtst_placeholder.so"_L1;
825 QFileInfo appInfo(options.applicationBinary);
826 appLibName = appInfo.fileName();
829 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
830 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
832 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
833 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
837 qtAppConstantsFile.write(content.toUtf8());
838 qtAppConstantsFile.close();
841 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
851 QString iconValue = options.harmonyOsAppIcon;
852 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
853 QFileInfo iconInfo(iconValue);
854 if (!iconInfo.exists() || !iconInfo.isFile()) {
855 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
858 QString safeStem = iconInfo.completeBaseName();
859 for (QChar &c : safeStem) {
860 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
861 c = QLatin1Char(
'_');
863 const QString destFileName = iconInfo.suffix().isEmpty()
865 : safeStem +
"."_L1 + iconInfo.suffix();
866 const QStringList destDirs = {
867 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
868 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
870 for (
const QString &destDir : destDirs) {
871 QDir().mkpath(destDir);
872 const QString destPath = destDir +
"/"_L1 + destFileName;
873 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
874 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
878 iconValue =
"$media:"_L1 + safeStem;
885 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
886 QFile appJsonFile(appJsonPath);
888 if (appJsonFile.exists()) {
889 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
890 fprintf(stderr,
"Failed to open app.json5 for reading\n");
893 QString content = QString::fromUtf8(appJsonFile.readAll());
896 auto replaceStringField =
897 [&content](QLatin1StringView key,
const QString &value) {
901 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
902 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
905 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
906 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
907 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
914 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
915 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
916 replaceStringField(
"icon"_L1, iconValue);
920 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
921 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
924 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
925 fprintf(stderr,
"Failed to open app.json5 for writing\n");
928 appJsonFile.write(content.toUtf8());
932 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
933 qPrintable(options.harmonyOsAppBundleName));
941 const QString displayLabel =
942 (!options.harmonyOsAppLabel.isEmpty()
943 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
944 ? options.harmonyOsAppLabel
945 : options.harmonyOsAppName;
950 QJsonArray transformedPermissions;
951 QList<PromotedReason> promotedReasons;
952 QSet<QString> seenPromotedIds;
953 for (
const QJsonValue &value : std::as_const(options.permissions)) {
954 if (!value.isObject()) {
955 transformedPermissions.append(value);
958 QJsonObject entry = value.toObject();
959 if (entry.contains(
"reason"_L1)) {
960 const QString reason = entry[
"reason"_L1].toString();
961 if (reasonNeedsPromotion(reason)) {
962 const QString permName = entry[
"name"_L1].toString();
963 const QString stringId = synthesizePermissionReasonId(permName);
964 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
965 if (!seenPromotedIds.contains(stringId)) {
966 promotedReasons.append({stringId, reason});
967 seenPromotedIds.insert(stringId);
971 transformedPermissions.append(entry);
977 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
979 for (
const QString &locale : locales) {
980 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
981 locale +
"/element/string.json"_L1;
982 QFile stringJsonFile(stringJsonPath);
984 if (!stringJsonFile.exists())
987 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
988 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
992 const QByteArray bytes = stringJsonFile.readAll();
993 stringJsonFile.close();
995 QJsonParseError parseErr;
996 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
997 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
998 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
999 qPrintable(locale), qPrintable(parseErr.errorString()));
1002 QJsonObject root = doc.object();
1003 QJsonArray strings = root[
"string"_L1].toArray();
1006 QSet<QString> existingNames;
1007 for (qsizetype i = 0; i < strings.size(); ++i) {
1008 QJsonObject e = strings[i].toObject();
1009 const QString name = e[
"name"_L1].toString();
1010 existingNames.insert(name);
1011 if (name ==
"QAbility_label"_L1) {
1012 e[
"value"_L1] = displayLabel;
1018 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
1019 if (existingNames.contains(p.id))
1022 e[
"name"_L1] = p.id;
1023 e[
"value"_L1] = p.value;
1025 existingNames.insert(p.id);
1027 root[
"string"_L1] = strings;
1028 doc.setObject(root);
1030 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1031 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
1035 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
1036 stringJsonFile.close();
1038 if (options.verbose) {
1040 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
1041 qPrintable(locale), qPrintable(displayLabel),
1042 static_cast<
long long>(promotedReasons.size()));
1047 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
1048 QFile appScopeStringFile(appScopeStringPath);
1050 if (appScopeStringFile.exists()) {
1051 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1052 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
1056 QString content = QString::fromUtf8(appScopeStringFile.readAll());
1057 appScopeStringFile.close();
1060 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
1061 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
1063 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1064 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
1068 appScopeStringFile.write(content.toUtf8());
1069 appScopeStringFile.close();
1072 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
1073 qPrintable(displayLabel));
1078 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
1079 QFile moduleJsonFile(moduleJsonPath);
1081 if (moduleJsonFile.exists()) {
1082 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1083 fprintf(stderr,
"Failed to open module.json5 for reading\n");
1087 QString content = QString::fromUtf8(moduleJsonFile.readAll());
1088 moduleJsonFile.close();
1094 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
1095 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
1096 ?
"$string:module_desc"_L1
1097 : jsonStringEscape(options.harmonyOsModuleDescription);
1098 content.replace(descriptionSentinel, descriptionValue);
1101 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
1102 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
1103 if (deviceTypes.isEmpty())
1104 deviceTypes = QStringList{
"phone"_L1,
"tablet"_L1,
"2in1"_L1 };
1105 QStringList quotedDeviceTypes;
1106 quotedDeviceTypes.reserve(deviceTypes.size());
1107 for (
const QString &dt : std::as_const(deviceTypes))
1108 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
1109 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
1118 const QString orientationSentinelLine =
1119 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
1120 QString orientationReplacement;
1121 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
1122 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
1123 orientationReplacement =
" \"orientation\": \""_L1
1124 + options.harmonyOsAbilityOrientation
1128 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
1129 qPrintable(options.harmonyOsAbilityOrientation));
1132 content.replace(orientationSentinelLine, orientationReplacement);
1138 if (!iconValue.isEmpty()) {
1140 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
1141 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1148 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1149 QString permissionsFragment;
1150 if (!transformedPermissions.isEmpty()) {
1151 QStringList entryStrings;
1152 entryStrings.reserve(transformedPermissions.size());
1153 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1154 if (!value.isObject())
1156 const QJsonObject entry = value.toObject();
1160 const QByteArray pretty =
1161 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1162 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1163 QStringList indented;
1164 indented.reserve(lines.size());
1165 for (
const QString &line : lines)
1166 indented.append(
" "_L1 + line);
1167 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1169 if (!entryStrings.isEmpty()) {
1170 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1173 content.replace(sentinel, permissionsFragment);
1175 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1176 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1180 moduleJsonFile.write(content.toUtf8());
1181 moduleJsonFile.close();
1184 fprintf(stdout,
" Updated module.json5 description\n");
1185 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1186 static_cast<
long long>(transformedPermissions.size()));
1199 const bool anySdkVersionSet =
1200 !options.harmonyOsCompatibleSdkVersion.isEmpty()
1201 || !options.harmonyOsTargetSdkVersion.isEmpty()
1202 || !options.harmonyOsCompileSdkVersion.isEmpty();
1204 const QString buildProfilePath =
1205 options.outputDirectory +
"/build-profile.json5"_L1;
1206 QFile buildProfileFile(buildProfilePath);
1207 if (anySdkVersionSet && buildProfileFile.exists()) {
1208 if (!buildProfileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1209 fprintf(stderr,
"Failed to open build-profile.json5 for reading\n");
1212 QString content = QString::fromUtf8(buildProfileFile.readAll());
1213 buildProfileFile.close();
1215 if (!options.harmonyOsCompatibleSdkVersion.isEmpty()) {
1218 "\"compatibleSdkVersion\":\\s*\"[^\"]*\""_L1),
1219 "\"compatibleSdkVersion\": \""_L1
1220 + jsonStringEscape(options.harmonyOsCompatibleSdkVersion)
1224 const QString targetSentinel =
"/* %%INSERT_TARGET_SDK_VERSION%% */"_L1;
1225 const QString targetReplacement =
1226 options.harmonyOsTargetSdkVersion.isEmpty()
1228 :
"\"targetSdkVersion\": \""_L1
1229 + jsonStringEscape(options.harmonyOsTargetSdkVersion)
1231 content.replace(targetSentinel, targetReplacement);
1233 const QString compileSentinel =
"/* %%INSERT_COMPILE_SDK_VERSION%% */"_L1;
1234 const QString compileReplacement =
1235 options.harmonyOsCompileSdkVersion.isEmpty()
1237 :
"\"compileSdkVersion\": \""_L1
1238 + jsonStringEscape(options.harmonyOsCompileSdkVersion)
1240 content.replace(compileSentinel, compileReplacement);
1242 if (!buildProfileFile.open(
1243 QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1244 fprintf(stderr,
"Failed to open build-profile.json5 for writing\n");
1247 buildProfileFile.write(content.toUtf8());
1248 buildProfileFile.close();
1252 " Updated build-profile.json5 SDK versions"
1253 " (compatible=%s, target=%s, compile=%s)\n",
1254 qPrintable(options.harmonyOsCompatibleSdkVersion),
1255 qPrintable(options.harmonyOsTargetSdkVersion),
1256 qPrintable(options.harmonyOsCompileSdkVersion));
1262 fprintf(stdout,
"Template customization completed\n");
1469 if (options.testBinariesDirectory.isEmpty()) {
1470 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1474 if (!QDir(options.testBinariesDirectory).exists()) {
1475 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1476 qPrintable(options.testBinariesDirectory));
1481 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1484 QStringList foundHelpers;
1485 QSet<QString> helperNames;
1489 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1490 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1491 found, foundHelpers, helperNames);
1493 if (found.isEmpty()) {
1494 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1495 qPrintable(options.testBinariesDirectory));
1500 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1501 if (!foundHelpers.isEmpty())
1502 fprintf(stdout,
"Found %lld test helper libraries\n",
1503 static_cast<
long long>(foundHelpers.size()));
1507 for (
const QString &arch : options.targetArchs) {
1508 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1509 QDir().mkpath(archLibPath);
1511 for (
const QString &testBinary : found) {
1512 QFileInfo testInfo(testBinary);
1513 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1515 if (options.verbose)
1516 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1518 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1519 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1524 for (
const QString &helperLib : foundHelpers) {
1525 QFileInfo helperInfo(helperLib);
1526 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1528 if (options.verbose)
1529 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1531 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1532 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1539 for (
const QString &testBinary : found) {
1540 QString fileName = QFileInfo(testBinary).fileName();
1541 if (!bundledBinaries.contains(fileName))
1542 bundledBinaries.append(fileName);
1546 if (!options.depFilePath.isEmpty()) {
1547 for (
const QString &testBinary : found)
1548 dependenciesForDepfile << testBinary;
1549 for (
const QString &helperLib : foundHelpers)
1550 dependenciesForDepfile << helperLib;
1588 if (options.qtLibsDirectory.isEmpty())
1591 if (!QDir(options.qtLibsDirectory).exists()) {
1593 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1594 qPrintable(options.qtLibsDirectory));
1600 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1602 QDir libsDir(options.qtLibsDirectory);
1603 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1605 for (
const QFileInfo &entry : entries) {
1606 for (
const QString &arch : options.targetArchs) {
1607 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1608 QDir().mkpath(QFileInfo(destPath).absolutePath());
1609 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1612 if (!options.depFilePath.isEmpty())
1613 dependenciesForDepfile << entry.filePath();
1617 for (
const QString &arch : options.targetArchs) {
1618 QString stdCppPath = findStdCppLibrary(options, arch);
1619 if (!stdCppPath.isEmpty()) {
1620 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1621 if (options.verbose)
1622 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1623 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1625 if (!options.depFilePath.isEmpty())
1626 dependenciesForDepfile << stdCppPath;
1631 for (
const QString &extraDir : options.extraLibsDirs) {
1633 if (!dir.exists()) {
1634 if (options.verbose)
1635 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1639 if (options.verbose)
1640 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1642 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1643 for (
const QFileInfo &entry : entries) {
1647 const QString soname = readElfSoname(options, entry.filePath());
1648 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1649 ? soname : entry.fileName();
1650 for (
const QString &arch : options.targetArchs) {
1651 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1652 QDir().mkpath(QFileInfo(destPath).absolutePath());
1653 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1656 if (!options.depFilePath.isEmpty())
1657 dependenciesForDepfile << entry.filePath();
2260 fprintf(stdout,
"Detecting Qt library dependencies\n");
2263 QStringList toProcess;
2264 toProcess.append(options.applicationBinary);
2268 for (
const QString &projectLib : options.projectLibraries)
2269 toProcess.append(projectLib);
2271 QList<QtDependency> qtDependencies;
2272 QStringList detectedQtModules;
2273 bool needsStdCpp =
false;
2275 while (!toProcess.isEmpty()) {
2276 QString currentLib = toProcess.takeFirst();
2278 if (processedLibs.contains(currentLib))
2281 processedLibs.insert(currentLib);
2284 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
2286 QStringList deps = readElfDependencies(options, currentLib);
2288 for (
const QString &dep : deps) {
2290 if (dep ==
"libc++_shared.so"_L1) {
2296 if (isSystemLibrary(dep))
2300 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
2302 QString extraDepPath = findExtraDepLibrary(options, dep);
2303 if (extraDepPath.isEmpty())
2308 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
2309 if (options.verbose)
2310 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
2311 QtDependency extraDep;
2312 extraDep.relativePath =
"lib/"_L1 + dep;
2313 extraDep.absolutePath = extraDepPath;
2314 qtDependencies.append(extraDep);
2315 toProcess.append(extraDepPath);
2321 if (dep.startsWith(
"libQt6"_L1)) {
2322 QString moduleName = dep.mid(6);
2323 if (moduleName.endsWith(
".so"_L1))
2325 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
2326 detectedQtModules.append(moduleName);
2329 QString depPath = findQtLibrary(options, dep);
2330 if (depPath.isEmpty()) {
2331 if (options.verbose)
2332 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2336 if (processedLibs.contains(depPath))
2339 if (options.verbose)
2340 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2343 qtDep.relativePath =
"lib/"_L1 + dep;
2344 qtDep.absolutePath = depPath;
2345 qtDependencies.append(std::move(qtDep));
2348 toProcess.append(std::move(depPath));
2353 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
2355 fprintf(stdout,
"C++ standard library required\n");
2360 for (
const QString &arch : options.targetArchs) {
2361 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
2362 QString stdCppPath = findStdCppLibrary(options, arch);
2363 if (stdCppPath.isEmpty()) {
2364 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
2366 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
2367 if (options.verbose)
2368 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
2369 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
2371 "Failed to copy C++ standard library to: %s\n",
2372 qPrintable(destPath));
2377 if (!options.depFilePath.isEmpty())
2378 dependenciesForDepfile << stdCppPath;
2387 for (
const QtDependency &dep : qtDependencies) {
2388 QFileInfo libInfo(dep.relativePath);
2389 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
2392 if (!options.depFilePath.isEmpty())
2393 dependenciesForDepfile << dep.absolutePath;
2624 QList<QmlImportInfo> imports;
2626 if (options.qmlRootPaths.isEmpty()) {
2629 "No QML root path specified, skipping QML import scanning\n");
2634 fprintf(stdout,
"Scanning for QML imports\n");
2637 QStringList searchPaths;
2638 if (!options.qtLibExecsDirectory.isEmpty())
2639 searchPaths.append(options.qtLibExecsDirectory);
2642 if (!options.qtHostDirectory.isEmpty())
2643 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2645 QString qmlImportScannerPath =
2646 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2649 if (qmlImportScannerPath.isEmpty()) {
2650 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2652 for (
int i = 0; i < 10; ++i) {
2653 qmlImportScannerPath = QStandardPaths::findExecutable(
2654 "qmlimportscanner"_L1,
2655 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2657 if (!qmlImportScannerPath.isEmpty())
2665 if (qmlImportScannerPath.isEmpty()) {
2668 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2673 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2674 qPrintable(qmlImportScannerPath));
2677 QStringList importPaths;
2680 for (
const QString &path : options.qmlImportPaths)
2681 if (QFile::exists(path))
2682 importPaths.append(path);
2687 QFileInfo appBinary(options.applicationBinary);
2688 QString appBuildDir = appBinary.absolutePath();
2689 if (QDir(appBuildDir).exists())
2690 importPaths.append(appBuildDir);
2693 if (!options.qtQmlDirectory.isEmpty() &&
2694 QDir(options.qtQmlDirectory).exists()) {
2695 importPaths.append(options.qtQmlDirectory);
2698 QFileInfo appBinary(options.applicationBinary);
2699 QDir qtDir(appBinary.absolutePath());
2700 for (
int i = 0; i < 10; ++i) {
2701 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2702 if (QDir(qmlDir).exists()) {
2703 importPaths.append(qmlDir);
2707 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2708 if (QDir(qmlDir).exists()) {
2709 importPaths.append(QDir::cleanPath(qmlDir));
2717 if (importPaths.isEmpty()) {
2718 fprintf(stderr,
"Warning: No QML import paths found\n");
2724 QStringList arguments;
2725 for (
const QString &rootPath : options.qmlRootPaths)
2726 arguments <<
"-rootPath"_L1 << rootPath;
2728 for (
const QString &importPath : importPaths)
2729 arguments <<
"-importPath"_L1 << importPath;
2732 fprintf(stdout,
" Root paths:\n");
2733 for (
const QString &rootPath : options.qmlRootPaths)
2734 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2735 fprintf(stdout,
" Import paths:\n");
2736 for (
const QString &path : importPaths)
2737 fprintf(stdout,
" %s\n", qPrintable(path));
2742 process.start(qmlImportScannerPath, arguments);
2744 if (!process.waitForFinished(30000)) {
2746 process.waitForFinished(2000);
2747 fprintf(stderr,
"Error: qmlimportscanner timed out after 30 seconds\n");
2748 fprintf(stderr,
" Command: %s %s\n", qPrintable(qmlImportScannerPath),
2749 qPrintable(arguments.join(u' ')));
2750 const QByteArray errorOutput = process.readAllStandardError();
2751 if (!errorOutput.isEmpty())
2752 fprintf(stderr,
"%s\n", errorOutput.constData());
2758 if (process.exitStatus() != QProcess::NormalExit) {
2759 fprintf(stderr,
"Error: qmlimportscanner crashed\n");
2760 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2766 if (process.exitCode() != 0) {
2767 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2768 process.exitCode());
2769 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2776 QByteArray output = process.readAllStandardOutput();
2777 QJsonDocument doc = QJsonDocument::fromJson(output);
2779 if (!doc.isArray()) {
2780 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2786 QJsonArray array = doc.array();
2787 for (
const QJsonValue &value : array) {
2788 if (!value.isObject())
2791 QJsonObject obj = value.toObject();
2793 info.name = obj[
"name"_L1].toString();
2794 info.path = obj[
"path"_L1].toString();
2795 info.type = obj[
"type"_L1].toString();
2797 if (obj.contains(
"plugin"_L1))
2798 info.plugin = obj[
"plugin"_L1].toString();
2800 if (obj.contains(
"pluginIsOptional"_L1))
2801 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2803 if (obj.contains(
"prefer"_L1))
2804 info.prefer = obj[
"prefer"_L1].toString();
2807 if (obj.contains(
"components"_L1)) {
2808 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2809 for (
const QJsonValue &comp : componentsArray)
2810 info.components.append(comp.toString());
2814 if (obj.contains(
"scripts"_L1)) {
2815 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2816 for (
const QJsonValue &script : scriptsArray)
2817 info.scripts.append(script.toString());
2821 if (info.path.isEmpty()) {
2822 if (options.verbose)
2823 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2824 qPrintable(info.name));
2829 if (info.type !=
"module"_L1)
2832 if (options.verbose)
2833 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2834 qPrintable(info.path));
2836 imports.append(info);
2881 const QList<QmlImportInfo> &imports,
2882 QSet<QString> &processedLibs)
2884 if (imports.isEmpty())
2888 fprintf(stdout,
"Copying QML imports\n");
2892 QString qmlDestBase = hapQmlDir(options);
2893 QDir().mkpath(qmlDestBase);
2896 QStringList qmlPluginsToScan;
2898 for (
const QmlImportInfo &import : imports) {
2899 if (options.verbose)
2900 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2904 QString relativePath;
2907 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2909 relativePath = import.path.mid(options.qtQmlDirectory.length());
2910 if (relativePath.startsWith(
'/'_L1))
2911 relativePath = relativePath.mid(1);
2915 relativePath = import.name;
2916 relativePath.replace(
'.'_L1,
'/'_L1);
2919 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2920 QDir().mkpath(destModuleDir);
2923 QString qmldirSrc = import.path +
"/qmldir"_L1;
2924 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2925 if (QFile::exists(qmldirSrc)) {
2926 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2927 if (options.verbose)
2928 fprintf(stdout,
" Copied qmldir\n");
2931 if (!options.depFilePath.isEmpty())
2932 dependenciesForDepfile << qmldirSrc;
2934 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2935 qPrintable(import.name));
2942 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2944 if (!import.plugin.isEmpty()) {
2946 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2947 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2949 if (QFile::exists(pluginSrc)) {
2951 for (
const QString &arch : options.targetArchs) {
2952 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2953 +
"/"_L1 + pluginFileName;
2955 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2956 if (options.verbose)
2957 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2958 qPrintable(arch), qPrintable(pluginFileName));
2959 processedLibs.insert(pluginSrc);
2962 if (!qmlPluginsToScan.contains(pluginSrc))
2963 qmlPluginsToScan.append(pluginSrc);
2966 if (!options.depFilePath.isEmpty())
2967 dependenciesForDepfile << pluginSrc;
2968 }
else if (!import.pluginIsOptional) {
2969 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2970 qPrintable(pluginFileName));
2973 }
else if (!import.pluginIsOptional) {
2974 if (options.verbose)
2975 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2976 qPrintable(pluginFileName));
2981 if (!qmlFilesAreEmbedded) {
2982 for (
const QString &component : import.components) {
2983 QFileInfo compInfo(component);
2984 if (!compInfo.exists()) {
2985 if (options.verbose)
2986 fprintf(stdout,
" Warning: Component file not found: %s\n",
2987 qPrintable(component));
2991 QString relativePath = component.mid(import.path.length());
2992 if (relativePath.startsWith(
'/'_L1))
2993 relativePath = relativePath.mid(1);
2995 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2996 QFileInfo destInfo(destFile);
2997 QDir().mkpath(destInfo.absolutePath());
2999 if (copyFileIfNewer(component, destFile, options.verbose)) {
3000 if (options.verbose)
3001 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
3004 if (!options.depFilePath.isEmpty())
3005 dependenciesForDepfile << component;
3010 for (
const QString &script : import.scripts) {
3011 QFileInfo scriptInfo(script);
3012 if (!scriptInfo.exists())
3015 QString relativePath = script.mid(import.path.length());
3016 if (relativePath.startsWith(
'/'_L1))
3017 relativePath = relativePath.mid(1);
3019 QString destFile = destModuleDir +
"/"_L1 + relativePath;
3020 QFileInfo destInfo(destFile);
3021 QDir().mkpath(destInfo.absolutePath());
3023 if (copyFileIfNewer(script, destFile, options.verbose)) {
3024 if (options.verbose)
3025 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
3028 if (!options.depFilePath.isEmpty())
3029 dependenciesForDepfile << script;
3033 if (options.verbose)
3034 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
3039 if (!qmlPluginsToScan.isEmpty()) {
3041 fprintf(stdout,
"Scanning QML plugin dependencies\n");
3043 QStringList toProcess = qmlPluginsToScan;
3044 while (!toProcess.isEmpty()) {
3045 QString pluginPath = toProcess.takeFirst();
3048 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
3050 QStringList deps = readElfDependencies(options, pluginPath);
3051 for (
const QString &dep : deps) {
3053 if (isSystemLibrary(dep))
3058 if (dep.startsWith(
"libQt6"_L1)) {
3059 depPath = findQtLibrary(options, dep);
3060 if (depPath.isEmpty()) {
3061 if (options.verbose)
3062 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
3065 }
else if (!options.extraLibsDirs.isEmpty()) {
3066 depPath = findExtraDepLibrary(options, dep);
3067 if (depPath.isEmpty())
3073 if (processedLibs.contains(depPath))
3076 if (options.verbose)
3077 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
3080 for (
const QString &arch : options.targetArchs) {
3081 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
3082 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
3083 if (options.verbose)
3084 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
3087 if (!options.depFilePath.isEmpty())
3088 dependenciesForDepfile << depPath;
3092 processedLibs.insert(depPath);
3094 toProcess.append(depPath);
3100 fprintf(stdout,
"QML imports copied successfully\n");
3128 const char *envName;
3129 const char *cliFlag;
3133 QString resolved()
const
3135 if (!cliValue.isEmpty())
3139 QString label()
const
3141 return QString::fromLatin1(cliFlag) +
" / "_L1 + QString::fromLatin1(envName);
3145 Field required[] = {
3146 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path", options.signingCertPath,
3147 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
3148 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile", options.signingProfile,
3149 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_PROFILE") },
3150 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file", options.signingStoreFile,
3151 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
3152 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias", options.signingKeyAlias,
3153 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
3157 Field passwords[][2] = {
3158 { {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
3159 options.signingKeyPassword, qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
3160 {
"QT_HARMONYOS_SIGNING_PLAIN_KEY_PASSWORD",
"--signing-plain-key-password",
3161 options.signingPlainKeyPassword,
3162 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_PLAIN_KEY_PASSWORD") } },
3163 { {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
3164 options.signingStorePassword,
3165 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
3166 {
"QT_HARMONYOS_SIGNING_PLAIN_STORE_PASSWORD",
"--signing-plain-store-password",
3167 options.signingPlainStorePassword,
3168 qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_PLAIN_STORE_PASSWORD") } },
3171 QString signAlg = options.signingAlg.isEmpty()
3172 ? qEnvironmentVariable(
"QT_HARMONYOS_SIGNING_ALG")
3173 : options.signingAlg;
3175 bool anySet = !signAlg.isEmpty();
3176 for (
const Field &f : required)
3177 anySet = anySet || !f.resolved().isEmpty();
3178 for (
const auto &forms : passwords)
3179 anySet = anySet || !forms[0].resolved().isEmpty() || !forms[1].resolved().isEmpty();
3183 QStringList missing;
3184 for (
const Field &f : required) {
3185 if (f.resolved().isEmpty())
3186 missing << f.label();
3188 for (
const auto &forms : passwords) {
3189 if (forms[0].resolved().isEmpty() && forms[1].resolved().isEmpty())
3190 missing << forms[0].label() +
" or "_L1 + forms[1].label();
3192 if (!missing.isEmpty()) {
3193 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
3194 " are missing (neither CLI flag nor env var was supplied):\n");
3195 for (
const QString &name : missing)
3196 fprintf(stderr,
" %s\n", qPrintable(name));
3200 for (
const auto &forms : passwords) {
3201 if (!forms[0].resolved().isEmpty() && !forms[1].resolved().isEmpty()) {
3202 fprintf(stderr,
"Error: both %s and %s are set; pass only one of them\n",
3203 qPrintable(forms[0].label()), qPrintable(forms[1].label()));
3208 const bool encryptedKeyPassword = !passwords[0][0].resolved().isEmpty();
3209 const bool encryptedStorePassword = !passwords[1][0].resolved().isEmpty();
3210 if (encryptedKeyPassword != encryptedStorePassword) {
3212 "Error: the key password and the keystore password must both be\n"
3213 " hvigor-encrypted or both plain text.\n");
3219 signing->certPath = required[0].resolved();
3220 signing->profile = required[1].resolved();
3221 signing->storeFile = required[2].resolved();
3222 signing->keyAlias = required[3].resolved();
3223 signing->keyPassword = passwords[0][encryptedKeyPassword ? 0 : 1].resolved();
3224 signing->storePassword = passwords[1][encryptedStorePassword ? 0 : 1].resolved();
3225 signing->signAlg = signAlg.isEmpty() ?
"SHA256withECDSA"_L1 : signAlg;
3227 const std::pair<
const char *, QString> files[] = {
3228 { required[0].cliFlag, signing->certPath },
3229 { required[1].cliFlag, signing->profile },
3230 { required[2].cliFlag, signing->storeFile },
3232 for (
const auto &file : files) {
3233 if (!QFile::exists(file.second)) {
3234 fprintf(stderr,
"Error: signing input %s does not exist: %s\n", file.first,
3235 qPrintable(file.second));
3241 fprintf(stdout,
"HAP signing enabled\n");
3242 fprintf(stdout,
" certpath: %s\n", qPrintable(signing->certPath));
3243 fprintf(stdout,
" profile: %s\n", qPrintable(signing->profile));
3244 fprintf(stdout,
" storeFile: %s\n", qPrintable(signing->storeFile));
3245 fprintf(stdout,
" keyAlias: %s\n", qPrintable(signing->keyAlias));
3246 fprintf(stdout,
" signAlg: %s\n", qPrintable(signing->signAlg));
3247 fprintf(stdout,
" passwords: %s\n",
3531 fprintf(stdout,
"Skipping HAP build (--no-build specified)\n");
3536 QString hvigorPath = options.hvigorPath;
3538 if (hvigorPath.isEmpty()) {
3539 QByteArray envHvigor = qgetenv(
"QT_HARMONYOS_HVIGOR");
3540 if (!envHvigor.isEmpty())
3541 hvigorPath = QString::fromLocal8Bit(envHvigor);
3544 if (hvigorPath.isEmpty()) {
3546 QString candidate = options.outputDirectory +
"/hvigorw"_L1;
3547 if (QFile::exists(candidate))
3548 hvigorPath = std::move(candidate);
3551 if (hvigorPath.isEmpty()) {
3552 fprintf(stderr,
"Warning: No hvigor path specified, skipping build\n");
3553 fprintf(stderr,
"Use --hvigor <path> or set QT_HARMONYOS_HVIGOR\n");
3558 fprintf(stdout,
"Building HarmonyOS HAP package\n");
3561 if (!QFile::exists(hvigorPath)) {
3562 fprintf(stderr,
"Error: hvigorw not found at: %s\n", qPrintable(hvigorPath));
3568 QString buildTask =
"assembleHap"_L1;
3569 QString buildMode = options.releaseMode ?
"release"_L1 :
"debug"_L1;
3572 fprintf(stdout,
" Build mode: %s\n", qPrintable(buildMode));
3573 fprintf(stdout,
" Running: %s %s\n", qPrintable(hvigorPath), qPrintable(buildTask));
3578 process.setWorkingDirectory(options.outputDirectory);
3579 process.setProcessChannelMode(QProcess::MergedChannels);
3581 QStringList arguments;
3582 arguments << buildTask <<
"-p"_L1 << (
"buildMode="_L1 + buildMode);
3585 arguments <<
"--no-daemon"_L1;
3587 process.start(hvigorPath, arguments);
3589 if (!process.waitForStarted()) {
3590 fprintf(stderr,
"Failed to start hvigorw\n");
3595 while (process.state() != QProcess::NotRunning) {
3596 if (!process.waitForReadyRead(1000)) {
3598 if (process.state() == QProcess::NotRunning)
3604 QByteArray output = process.readAll();
3605 fprintf(stdout,
"%s", output.constData());
3612 QByteArray output = process.readAll();
3613 if (!output.isEmpty()) {
3614 fprintf(stdout,
"%s", output.constData());
3619 if (process.exitCode() != 0) {
3620 fprintf(stderr,
"hvigorw failed with exit code %d\n", process.exitCode());
3622 fprintf(stderr,
"Run with --verbose to see build output\n");
3627 fprintf(stdout,
"HAP build completed successfully\n");
3630 if (hapOutputPath) {
3631 QString hapSearchPath = options.outputDirectory +
"/entry/build/default/outputs/default"_L1;
3632 QDir hapDir(hapSearchPath);
3634 if (hapDir.exists()) {
3635 QStringList hapFiles = hapDir.entryList(QStringList() <<
"*.hap"_L1, QDir::Files);
3638 for (qsizetype i = 0; i < hapFiles.size(); ++i) {
3639 if (hapFiles.at(i).endsWith(
"-unsigned.hap"_L1)) {
3640 hapFiles.move(i, 0);
3644 if (!hapFiles.isEmpty()) {
3645 *hapOutputPath = hapDir.absoluteFilePath(hapFiles.first());
3647 fprintf(stdout,
" Generated HAP: %s\n", qPrintable(*hapOutputPath));
3754 QCoreApplication app(argc, argv);
3755 QCoreApplication::setApplicationName(
"harmonydeployqt"_L1);
3756 QCoreApplication::setApplicationVersion(
"1.0"_L1);
3759 options.timer.start();
3761 if (!parseCommandLine(app.arguments(), &options))
3765 fprintf(stdout,
"Qt HarmonyOS Deployment Tool\n");
3766 fprintf(stdout,
"==============================\n\n");
3778 const QList<TestQmlModule> testQmlModules =
3779 options.testBundleMode ? findTestQmlModules(options) : QList<TestQmlModule>{};
3781 if (options.testBundleMode && !verifyUniqueTestQmlModuleDeployDirs(testQmlModules))
3785 fprintf(stdout,
"\nDeployment process started...\n");
3787 QString hapOutputPath;
3791 fprintf(stderr,
"Failed to copy template\n");
3797 fprintf(stderr,
"Failed to customize template\n");
3802 QStringList bundledBinaries;
3804 if (!copyTestBinaries(options, bundledBinaries)) {
3805 fprintf(stderr,
"Failed to copy test binaries\n");
3809 fprintf(stderr,
"Failed to copy Qt libraries\n");
3813 fprintf(stderr,
"Failed to copy Qt plugins\n");
3817 fprintf(stderr,
"Failed to copy QML modules\n");
3820 if (!copyTestQmlModules(testQmlModules, options)) {
3821 fprintf(stderr,
"Failed to copy test QML modules\n");
3826 fprintf(stderr,
"Failed to copy application binary\n");
3830 fprintf(stderr,
"Failed to copy project libraries\n");
3833 QSet<QString> processedLibs;
3834 if (!detectAndCopyDependencies(options, processedLibs)) {
3835 fprintf(stderr,
"Failed to detect and copy dependencies\n");
3838 bool qmlScanOk =
true;
3839 QList<QmlImportInfo> qmlImports = scanQmlImports(options, &qmlScanOk);
3841 fprintf(stderr,
"Failed to scan QML imports\n");
3845 fprintf(stderr,
"Failed to copy QML files\n");
3848 if (!copyQmlImports(options, qmlImports, processedLibs)) {
3849 fprintf(stderr,
"Failed to copy QML imports\n");
3852 if (!copyPlugins(options, processedLibs)) {
3853 fprintf(stderr,
"Failed to copy plugins\n");
3859 fprintf(stderr,
"Failed to copy extra plugins\n");
3864 fprintf(stderr,
"Failed to inject signing configuration\n");
3869 if (!buildHap(options, &hapOutputPath)) {
3870 fprintf(stderr,
"Failed to build HAP\n");
3874 if (!signHap(options, signing, &hapOutputPath)) {
3875 fprintf(stderr,
"Failed to sign HAP\n");
3881 if (!writeTestBinariesList(options, bundledBinaries)) {
3882 fprintf(stderr,
"Failed to write binaries.txt\n");
3889 if (!options.depFilePath.isEmpty()) {
3890 const QString unsignedHapPath = options.outputDirectory
3891 +
"/entry/build/default/outputs/default/entry-default-unsigned.hap"_L1;
3892 if (!writeDepfile(options, unsignedHapPath))
3893 fprintf(stderr,
"Warning: Failed to write dependency file\n");
3898 if (!installToDevice(options, hapOutputPath)) {
3899 fprintf(stderr,
"Failed to install to device\n");
3904 fprintf(stdout,
"\n==============================================\n");
3905 fprintf(stdout,
"Deployment completed successfully!\n");
3906 fprintf(stdout,
"==============================================\n");
3907 fprintf(stdout,
"Project location: %s\n", qPrintable(options.outputDirectory));
3909 if (!hapOutputPath.isEmpty())
3910 fprintf(stdout,
"HAP package: %s\n", qPrintable(hapOutputPath));
3913 fprintf(stdout,
"\nTotal time: %lld ms\n", options.timer.elapsed());