154 QCommandLineParser parser;
155 parser.setApplicationDescription(
"Qt HarmonyOS Deployment Tool"_L1);
157 QCommandLineOption inputOption(
"input"_L1,
"JSON configuration file"_L1,
"file"_L1);
158 QCommandLineOption outputOption(
"output"_L1,
"Output directory"_L1,
"dir"_L1);
159 QCommandLineOption hvigorOption(
"hvigor"_L1,
"Path to hvigorw"_L1,
"path"_L1);
160 QCommandLineOption installOption(
"install"_L1,
"Install to device"_L1);
161 QCommandLineOption releaseOption(
"release"_L1,
"Build release configuration"_L1);
162 QCommandLineOption verboseOption(
"verbose"_L1,
"Verbose output"_L1);
163 QCommandLineOption noBuildOption(
"no-build"_L1,
"Skip building"_L1);
164 QCommandLineOption testBundleOption(
"test-bundle"_L1,
"Enable test bundle mode"_L1);
165 QCommandLineOption depfileOption(
"depfile"_L1,
"Dependency file output"_L1,
"path"_L1);
166 QCommandLineOption depfileBaseOption(
"depfile-base"_L1,
"Base directory for depfile paths"_L1,
"dir"_L1);
167 QCommandLineOption signingCertPathOption(
"signing-cert-path"_L1,
168 "Path to the .cer file"_L1,
"path"_L1);
169 QCommandLineOption signingProfileOption(
"signing-profile"_L1,
170 "Path to the .p7b profile"_L1,
"path"_L1);
171 QCommandLineOption signingStoreFileOption(
"signing-store-file"_L1,
172 "Path to the .p12 keystore"_L1,
"path"_L1);
173 QCommandLineOption signingKeyAliasOption(
"signing-key-alias"_L1,
174 "Key alias inside the keystore"_L1,
"alias"_L1);
175 QCommandLineOption signingKeyPasswordOption(
"signing-key-password"_L1,
176 "Encrypted key password"_L1,
"pwd"_L1);
177 QCommandLineOption signingStorePasswordOption(
"signing-store-password"_L1,
178 "Encrypted keystore password"_L1,
"pwd"_L1);
179 QCommandLineOption signingAlgOption(
"signing-alg"_L1,
180 "Signature algorithm (default SHA256withECDSA)"_L1,
"alg"_L1);
181 QCommandLineOption helpOption(
"help"_L1,
"Show help"_L1);
183 parser.addOption(inputOption);
184 parser.addOption(outputOption);
185 parser.addOption(hvigorOption);
186 parser.addOption(installOption);
187 parser.addOption(releaseOption);
188 parser.addOption(verboseOption);
189 parser.addOption(noBuildOption);
190 parser.addOption(testBundleOption);
191 parser.addOption(depfileOption);
192 parser.addOption(depfileBaseOption);
193 parser.addOption(signingCertPathOption);
194 parser.addOption(signingProfileOption);
195 parser.addOption(signingStoreFileOption);
196 parser.addOption(signingKeyAliasOption);
197 parser.addOption(signingKeyPasswordOption);
198 parser.addOption(signingStorePasswordOption);
199 parser.addOption(signingAlgOption);
200 parser.addOption(helpOption);
202 if (!parser.parse(arguments)) {
203 fprintf(stderr,
"%s\n", qPrintable(parser.errorText()));
207 if (parser.isSet(helpOption)) {
212 if (!parser.isSet(inputOption)) {
213 fprintf(stderr,
"Error: --input option is required\n");
218 options->inputFile = parser.value(inputOption);
219 options->outputDirectory = parser.value(outputOption);
220 options->hvigorPath = parser.value(hvigorOption);
223 options
->verbose = parser.isSet(verboseOption);
226 options->depFilePath = parser.value(depfileOption);
227 options->depFileBase = parser.value(depfileBaseOption);
228 options->signingCertPath = parser.value(signingCertPathOption);
229 options->signingProfile = parser.value(signingProfileOption);
230 options->signingStoreFile = parser.value(signingStoreFileOption);
231 options->signingKeyAlias = parser.value(signingKeyAliasOption);
232 options->signingKeyPassword = parser.value(signingKeyPasswordOption);
233 options->signingStorePassword = parser.value(signingStorePasswordOption);
234 options->signingAlg = parser.value(signingAlgOption);
241 QFile inputFile(options->inputFile);
242 if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
243 fprintf(stderr,
"Failed to open input file: %s\n", qPrintable(options->inputFile));
247 QJsonParseError parseError;
248 QJsonDocument doc = QJsonDocument::fromJson(inputFile.readAll(), &parseError);
250 fprintf(stderr,
"Failed to parse JSON: %s\n", qPrintable(parseError.errorString()));
254 QJsonObject obj = doc.object();
256 options->applicationBinary = obj[
"application-binary"_L1].toString();
257 options->harmonyOsPackageSourceDirectory = obj[
"harmonyos-package-source-directory"_L1].toString();
258 options->harmonyOsAppName = obj[
"harmonyos-app-name"_L1].toString();
259 options->harmonyOsAppBundleName = obj[
"harmonyos-app-bundle-name"_L1].toString();
260 options->sdkRoot = obj[
"sdk-root"_L1].toString();
261 options->ndkRoot = obj[
"ndk-root"_L1].toString();
264 const QJsonValue rootPathValue = obj[
"qml-root-path"_L1];
265 if (rootPathValue.isArray()) {
266 for (
const QJsonValue &v : rootPathValue.toArray()) {
267 const QString s = v.toString();
269 options->qmlRootPaths.append(s);
272 const QString s = rootPathValue.toString();
274 options->qmlRootPaths.append(s);
279 options->qtLibsDirectory = obj[
"qtLibsDirectory"_L1].toString();
280 options->qtPluginsDirectory = obj[
"qtPluginsDirectory"_L1].toString();
281 options->qtQmlDirectory = obj[
"qtQmlDirectory"_L1].toString();
282 options->qtLibExecsDirectory = obj[
"qtLibExecsDirectory"_L1].toString();
283 options->qtHostDirectory = obj[
"qtHostDirectory"_L1].toString();
285 QJsonArray extraLibsDirsArray = obj[
"extra-libs-dirs"_L1].toArray();
286 for (
const QJsonValue &value : extraLibsDirsArray)
287 options->extraLibsDirs.append(value.toString());
290 if (obj[
"test-bundle"_L1].toBool())
292 options->testBinariesDirectory = obj[
"test-binaries-directory"_L1].toString();
293 QJsonArray excludeArray = obj[
"test-exclude-list"_L1].toArray();
294 for (
const QJsonValue &value : excludeArray)
295 options->testExcludeList.append(value.toString());
298 QJsonArray projectLibsArray = obj[
"project-libraries"_L1].toArray();
299 for (
const QJsonValue &value : projectLibsArray)
300 options->projectLibraries.append(value.toString());
303 QJsonArray importPathsArray = obj[
"qml-import-paths"_L1].toArray();
304 for (
const QJsonValue &value : importPathsArray)
305 options->qmlImportPaths.append(value.toString());
308 QJsonArray pluginsImportPathsArray = obj[
"plugins-import-paths"_L1].toArray();
309 for (
const QJsonValue &value : pluginsImportPathsArray)
310 options->pluginsImportPaths.append(value.toString());
313 QJsonArray archArray = obj[
"harmonyos-target-arch"_L1].toArray();
314 for (
const QJsonValue &value : archArray)
315 options->targetArchs.append(value.toString());
316 if (options->targetArchs.isEmpty())
317 options->targetArchs.append(
"arm64-v8a"_L1);
320 options->permissions = obj[
"permissions"_L1].toArray();
323 options->harmonyOsAppVendor = obj[
"harmonyos-app-vendor"_L1].toString();
324 options->harmonyOsAppVersionCode = obj[
"harmonyos-app-version-code"_L1].toInt();
325 options->harmonyOsAppVersionName = obj[
"harmonyos-app-version-name"_L1].toString();
326 options->harmonyOsAppLabel = obj[
"harmonyos-app-label"_L1].toString();
327 options->harmonyOsAppIcon = obj[
"harmonyos-app-icon"_L1].toString();
330 options->harmonyOsCompatibleSdkVersion =
331 obj[
"harmonyos-compatible-sdk-version"_L1].toString();
332 options->harmonyOsTargetSdkVersion =
333 obj[
"harmonyos-target-sdk-version"_L1].toString();
334 options->harmonyOsCompileSdkVersion =
335 obj[
"harmonyos-compile-sdk-version"_L1].toString();
340 const QJsonArray extraPluginsArray =
341 obj[
"harmonyos-extra-plugins"_L1].toArray();
342 for (
const QJsonValue &v : extraPluginsArray) {
343 const QString s = v.toString();
345 options->extraPlugins.append(s);
350 options->harmonyOsModuleDescription = obj[
"harmonyos-module-description"_L1].toString();
351 const QJsonArray deviceTypesArray = obj[
"harmonyos-module-device-types"_L1].toArray();
352 for (
const QJsonValue &value : deviceTypesArray)
353 options->harmonyOsModuleDeviceTypes.append(value.toString());
354 options->harmonyOsAbilityOrientation = obj[
"harmonyos-ability-orientation"_L1].toString();
358 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
364 if (options->harmonyOsAppBundleName.isEmpty())
365 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
366 if (options->harmonyOsAppName.isEmpty())
367 options->harmonyOsAppName =
"QtAutoTests"_L1;
371 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
376 searchPath = QDir::cleanPath(options->qtLibsDirectory);
377 }
else if (!options->applicationBinary.isEmpty()) {
378 QFileInfo appInfo(options->applicationBinary);
379 searchPath = QDir::cleanPath(appInfo.absolutePath());
382 if (searchPath.isEmpty()) {
383 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
384 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
389 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
392 for (
int i = 0; i < 10; ++i) {
394 QString templatePath = searchPath +
"/share/qt6/src/harmonyos/templates"_L1;
396 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
397 QDir(templatePath).exists() ?
"FOUND" :
"not found");
399 if (QDir(templatePath).exists()) {
400 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
405 templatePath = searchPath +
"/src/harmonyos/templates"_L1;
407 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
408 QDir(templatePath).exists() ?
"FOUND" :
"not found");
410 if (QDir(templatePath).exists()) {
411 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
416 const auto lastSlash = searchPath.lastIndexOf(
'/'_L1);
417 if (lastSlash <= 0) {
419 fprintf(stdout,
" Reached root directory\n");
422 searchPath.resize(lastSlash);
425 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
426 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
427 fprintf(stderr,
" and could not auto-detect template location\n");
428 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
431 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
435 if (options->harmonyOsAppName.isEmpty()) {
436 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
440 if (options->harmonyOsAppBundleName.isEmpty()) {
441 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
446 if (options->outputDirectory.isEmpty()) {
448 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
450 QFileInfo appInfo(options->applicationBinary);
451 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
452 appInfo.completeBaseName() +
"-harmonyos"_L1;
457 fprintf(stdout,
"Configuration loaded:\n");
459 fprintf(stdout,
" Mode: test bundle\n");
460 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
461 if (!options->testExcludeList.isEmpty())
462 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
464 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
466 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
467 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
468 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
469 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
470 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
708 fprintf(stdout,
"Customizing template files\n");
711 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
712 QFile qtAppConstantsFile(qtAppConstantsPath);
714 if (qtAppConstantsFile.exists()) {
715 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
716 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
720 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
721 qtAppConstantsFile.close();
727 appLibName =
"libtst_placeholder.so"_L1;
729 QFileInfo appInfo(options.applicationBinary);
730 appLibName = appInfo.fileName();
733 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
734 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
736 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
737 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
741 qtAppConstantsFile.write(content.toUtf8());
742 qtAppConstantsFile.close();
745 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
755 QString iconValue = options.harmonyOsAppIcon;
756 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
757 QFileInfo iconInfo(iconValue);
758 if (!iconInfo.exists() || !iconInfo.isFile()) {
759 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
762 QString safeStem = iconInfo.completeBaseName();
763 for (QChar &c : safeStem) {
764 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
765 c = QLatin1Char(
'_');
767 const QString destFileName = iconInfo.suffix().isEmpty()
769 : safeStem +
"."_L1 + iconInfo.suffix();
770 const QStringList destDirs = {
771 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
772 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
774 for (
const QString &destDir : destDirs) {
775 QDir().mkpath(destDir);
776 const QString destPath = destDir +
"/"_L1 + destFileName;
777 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
778 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
782 iconValue =
"$media:"_L1 + safeStem;
789 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
790 QFile appJsonFile(appJsonPath);
792 if (appJsonFile.exists()) {
793 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
794 fprintf(stderr,
"Failed to open app.json5 for reading\n");
797 QString content = QString::fromUtf8(appJsonFile.readAll());
800 auto replaceStringField =
801 [&content](QLatin1StringView key,
const QString &value) {
805 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
806 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
809 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
810 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
811 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
818 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
819 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
820 replaceStringField(
"icon"_L1, iconValue);
824 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
825 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
828 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
829 fprintf(stderr,
"Failed to open app.json5 for writing\n");
832 appJsonFile.write(content.toUtf8());
836 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
837 qPrintable(options.harmonyOsAppBundleName));
845 const QString displayLabel =
846 (!options.harmonyOsAppLabel.isEmpty()
847 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
848 ? options.harmonyOsAppLabel
849 : options.harmonyOsAppName;
854 QJsonArray transformedPermissions;
855 QList<PromotedReason> promotedReasons;
856 QSet<QString> seenPromotedIds;
857 for (
const QJsonValue &value : std::as_const(options.permissions)) {
858 if (!value.isObject()) {
859 transformedPermissions.append(value);
862 QJsonObject entry = value.toObject();
863 if (entry.contains(
"reason"_L1)) {
864 const QString reason = entry[
"reason"_L1].toString();
865 if (reasonNeedsPromotion(reason)) {
866 const QString permName = entry[
"name"_L1].toString();
867 const QString stringId = synthesizePermissionReasonId(permName);
868 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
869 if (!seenPromotedIds.contains(stringId)) {
870 promotedReasons.append({stringId, reason});
871 seenPromotedIds.insert(stringId);
875 transformedPermissions.append(entry);
881 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
883 for (
const QString &locale : locales) {
884 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
885 locale +
"/element/string.json"_L1;
886 QFile stringJsonFile(stringJsonPath);
888 if (!stringJsonFile.exists())
891 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
892 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
896 const QByteArray bytes = stringJsonFile.readAll();
897 stringJsonFile.close();
899 QJsonParseError parseErr;
900 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
901 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
902 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
903 qPrintable(locale), qPrintable(parseErr.errorString()));
906 QJsonObject root = doc.object();
907 QJsonArray strings = root[
"string"_L1].toArray();
910 QSet<QString> existingNames;
911 for (qsizetype i = 0; i < strings.size(); ++i) {
912 QJsonObject e = strings[i].toObject();
913 const QString name = e[
"name"_L1].toString();
914 existingNames.insert(name);
915 if (name ==
"QAbility_label"_L1) {
916 e[
"value"_L1] = displayLabel;
922 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
923 if (existingNames.contains(p.id))
927 e[
"value"_L1] = p.value;
929 existingNames.insert(p.id);
931 root[
"string"_L1] = strings;
934 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
935 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
939 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
940 stringJsonFile.close();
942 if (options.verbose) {
944 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
945 qPrintable(locale), qPrintable(displayLabel),
946 static_cast<
long long>(promotedReasons.size()));
951 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
952 QFile appScopeStringFile(appScopeStringPath);
954 if (appScopeStringFile.exists()) {
955 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
956 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
960 QString content = QString::fromUtf8(appScopeStringFile.readAll());
961 appScopeStringFile.close();
964 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
965 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
967 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
968 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
972 appScopeStringFile.write(content.toUtf8());
973 appScopeStringFile.close();
976 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
977 qPrintable(displayLabel));
982 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
983 QFile moduleJsonFile(moduleJsonPath);
985 if (moduleJsonFile.exists()) {
986 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
987 fprintf(stderr,
"Failed to open module.json5 for reading\n");
991 QString content = QString::fromUtf8(moduleJsonFile.readAll());
992 moduleJsonFile.close();
998 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
999 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
1000 ?
"$string:module_desc"_L1
1001 : jsonStringEscape(options.harmonyOsModuleDescription);
1002 content.replace(descriptionSentinel, descriptionValue);
1005 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
1006 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
1007 if (deviceTypes.isEmpty())
1008 deviceTypes = QStringList{
"phone"_L1,
"tablet"_L1,
"2in1"_L1 };
1009 QStringList quotedDeviceTypes;
1010 quotedDeviceTypes.reserve(deviceTypes.size());
1011 for (
const QString &dt : std::as_const(deviceTypes))
1012 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
1013 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
1022 const QString orientationSentinelLine =
1023 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
1024 QString orientationReplacement;
1025 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
1026 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
1027 orientationReplacement =
" \"orientation\": \""_L1
1028 + options.harmonyOsAbilityOrientation
1032 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
1033 qPrintable(options.harmonyOsAbilityOrientation));
1036 content.replace(orientationSentinelLine, orientationReplacement);
1042 if (!iconValue.isEmpty()) {
1044 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
1045 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1052 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1053 QString permissionsFragment;
1054 if (!transformedPermissions.isEmpty()) {
1055 QStringList entryStrings;
1056 entryStrings.reserve(transformedPermissions.size());
1057 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1058 if (!value.isObject())
1060 const QJsonObject entry = value.toObject();
1064 const QByteArray pretty =
1065 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1066 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1067 QStringList indented;
1068 indented.reserve(lines.size());
1069 for (
const QString &line : lines)
1070 indented.append(
" "_L1 + line);
1071 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1073 if (!entryStrings.isEmpty()) {
1074 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1077 content.replace(sentinel, permissionsFragment);
1079 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1080 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1084 moduleJsonFile.write(content.toUtf8());
1085 moduleJsonFile.close();
1088 fprintf(stdout,
" Updated module.json5 description\n");
1089 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1090 static_cast<
long long>(transformedPermissions.size()));
1103 const bool anySdkVersionSet =
1104 !options.harmonyOsCompatibleSdkVersion.isEmpty()
1105 || !options.harmonyOsTargetSdkVersion.isEmpty()
1106 || !options.harmonyOsCompileSdkVersion.isEmpty();
1108 const QString buildProfilePath =
1109 options.outputDirectory +
"/build-profile.json5"_L1;
1110 QFile buildProfileFile(buildProfilePath);
1111 if (anySdkVersionSet && buildProfileFile.exists()) {
1112 if (!buildProfileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1113 fprintf(stderr,
"Failed to open build-profile.json5 for reading\n");
1116 QString content = QString::fromUtf8(buildProfileFile.readAll());
1117 buildProfileFile.close();
1119 if (!options.harmonyOsCompatibleSdkVersion.isEmpty()) {
1122 "\"compatibleSdkVersion\":\\s*\"[^\"]*\""_L1),
1123 "\"compatibleSdkVersion\": \""_L1
1124 + jsonStringEscape(options.harmonyOsCompatibleSdkVersion)
1128 const QString targetSentinel =
"/* %%INSERT_TARGET_SDK_VERSION%% */"_L1;
1129 const QString targetReplacement =
1130 options.harmonyOsTargetSdkVersion.isEmpty()
1132 :
"\"targetSdkVersion\": \""_L1
1133 + jsonStringEscape(options.harmonyOsTargetSdkVersion)
1135 content.replace(targetSentinel, targetReplacement);
1137 const QString compileSentinel =
"/* %%INSERT_COMPILE_SDK_VERSION%% */"_L1;
1138 const QString compileReplacement =
1139 options.harmonyOsCompileSdkVersion.isEmpty()
1141 :
"\"compileSdkVersion\": \""_L1
1142 + jsonStringEscape(options.harmonyOsCompileSdkVersion)
1144 content.replace(compileSentinel, compileReplacement);
1146 if (!buildProfileFile.open(
1147 QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1148 fprintf(stderr,
"Failed to open build-profile.json5 for writing\n");
1151 buildProfileFile.write(content.toUtf8());
1152 buildProfileFile.close();
1156 " Updated build-profile.json5 SDK versions"
1157 " (compatible=%s, target=%s, compile=%s)\n",
1158 qPrintable(options.harmonyOsCompatibleSdkVersion),
1159 qPrintable(options.harmonyOsTargetSdkVersion),
1160 qPrintable(options.harmonyOsCompileSdkVersion));
1166 fprintf(stdout,
"Template customization completed\n");
1373 if (options.testBinariesDirectory.isEmpty()) {
1374 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1378 if (!QDir(options.testBinariesDirectory).exists()) {
1379 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1380 qPrintable(options.testBinariesDirectory));
1385 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1388 QStringList foundHelpers;
1389 QSet<QString> helperNames;
1393 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1394 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1395 found, foundHelpers, helperNames);
1397 if (found.isEmpty()) {
1398 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1399 qPrintable(options.testBinariesDirectory));
1404 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1405 if (!foundHelpers.isEmpty())
1406 fprintf(stdout,
"Found %lld test helper libraries\n",
1407 static_cast<
long long>(foundHelpers.size()));
1411 for (
const QString &arch : options.targetArchs) {
1412 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1413 QDir().mkpath(archLibPath);
1415 for (
const QString &testBinary : found) {
1416 QFileInfo testInfo(testBinary);
1417 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1419 if (options.verbose)
1420 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1422 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1423 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1428 for (
const QString &helperLib : foundHelpers) {
1429 QFileInfo helperInfo(helperLib);
1430 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1432 if (options.verbose)
1433 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1435 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1436 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1443 for (
const QString &testBinary : found) {
1444 QString fileName = QFileInfo(testBinary).fileName();
1445 if (!bundledBinaries.contains(fileName))
1446 bundledBinaries.append(fileName);
1450 if (!options.depFilePath.isEmpty()) {
1451 for (
const QString &testBinary : found)
1452 dependenciesForDepfile << testBinary;
1453 for (
const QString &helperLib : foundHelpers)
1454 dependenciesForDepfile << helperLib;
1492 if (options.qtLibsDirectory.isEmpty())
1495 if (!QDir(options.qtLibsDirectory).exists()) {
1497 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1498 qPrintable(options.qtLibsDirectory));
1504 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1506 QDir libsDir(options.qtLibsDirectory);
1507 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1509 for (
const QFileInfo &entry : entries) {
1510 for (
const QString &arch : options.targetArchs) {
1511 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1512 QDir().mkpath(QFileInfo(destPath).absolutePath());
1513 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1516 if (!options.depFilePath.isEmpty())
1517 dependenciesForDepfile << entry.filePath();
1521 for (
const QString &arch : options.targetArchs) {
1522 QString stdCppPath = findStdCppLibrary(options, arch);
1523 if (!stdCppPath.isEmpty()) {
1524 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1525 if (options.verbose)
1526 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1527 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1529 if (!options.depFilePath.isEmpty())
1530 dependenciesForDepfile << stdCppPath;
1535 for (
const QString &extraDir : options.extraLibsDirs) {
1537 if (!dir.exists()) {
1538 if (options.verbose)
1539 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1543 if (options.verbose)
1544 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1546 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1547 for (
const QFileInfo &entry : entries) {
1551 const QString soname = readElfSoname(options, entry.filePath());
1552 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1553 ? soname : entry.fileName();
1554 for (
const QString &arch : options.targetArchs) {
1555 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1556 QDir().mkpath(QFileInfo(destPath).absolutePath());
1557 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1560 if (!options.depFilePath.isEmpty())
1561 dependenciesForDepfile << entry.filePath();
2164 fprintf(stdout,
"Detecting Qt library dependencies\n");
2167 QStringList toProcess;
2168 toProcess.append(options.applicationBinary);
2172 for (
const QString &projectLib : options.projectLibraries)
2173 toProcess.append(projectLib);
2175 QList<QtDependency> qtDependencies;
2176 QStringList detectedQtModules;
2177 bool needsStdCpp =
false;
2179 while (!toProcess.isEmpty()) {
2180 QString currentLib = toProcess.takeFirst();
2182 if (processedLibs.contains(currentLib))
2185 processedLibs.insert(currentLib);
2188 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
2190 QStringList deps = readElfDependencies(options, currentLib);
2192 for (
const QString &dep : deps) {
2194 if (dep ==
"libc++_shared.so"_L1) {
2200 if (isSystemLibrary(dep))
2204 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
2206 QString extraDepPath = findExtraDepLibrary(options, dep);
2207 if (extraDepPath.isEmpty())
2212 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
2213 if (options.verbose)
2214 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
2215 QtDependency extraDep;
2216 extraDep.relativePath =
"lib/"_L1 + dep;
2217 extraDep.absolutePath = extraDepPath;
2218 qtDependencies.append(extraDep);
2219 toProcess.append(extraDepPath);
2225 if (dep.startsWith(
"libQt6"_L1)) {
2226 QString moduleName = dep.mid(6);
2227 if (moduleName.endsWith(
".so"_L1))
2229 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
2230 detectedQtModules.append(moduleName);
2233 QString depPath = findQtLibrary(options, dep);
2234 if (depPath.isEmpty()) {
2235 if (options.verbose)
2236 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2240 if (processedLibs.contains(depPath))
2243 if (options.verbose)
2244 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2247 qtDep.relativePath =
"lib/"_L1 + dep;
2248 qtDep.absolutePath = depPath;
2249 qtDependencies.append(std::move(qtDep));
2252 toProcess.append(std::move(depPath));
2257 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
2259 fprintf(stdout,
"C++ standard library required\n");
2264 for (
const QString &arch : options.targetArchs) {
2265 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
2266 QString stdCppPath = findStdCppLibrary(options, arch);
2267 if (stdCppPath.isEmpty()) {
2268 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
2270 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
2271 if (options.verbose)
2272 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
2273 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
2275 "Failed to copy C++ standard library to: %s\n",
2276 qPrintable(destPath));
2281 if (!options.depFilePath.isEmpty())
2282 dependenciesForDepfile << stdCppPath;
2291 for (
const QtDependency &dep : qtDependencies) {
2292 QFileInfo libInfo(dep.relativePath);
2293 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
2296 if (!options.depFilePath.isEmpty())
2297 dependenciesForDepfile << dep.absolutePath;
2525 QList<QmlImportInfo> imports;
2527 if (options.qmlRootPaths.isEmpty()) {
2530 "No QML root path specified, skipping QML import scanning\n");
2535 fprintf(stdout,
"Scanning for QML imports\n");
2538 QStringList searchPaths;
2539 if (!options.qtLibExecsDirectory.isEmpty())
2540 searchPaths.append(options.qtLibExecsDirectory);
2543 if (!options.qtHostDirectory.isEmpty())
2544 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2546 QString qmlImportScannerPath =
2547 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2550 if (qmlImportScannerPath.isEmpty()) {
2551 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2553 for (
int i = 0; i < 10; ++i) {
2554 qmlImportScannerPath = QStandardPaths::findExecutable(
2555 "qmlimportscanner"_L1,
2556 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2558 if (!qmlImportScannerPath.isEmpty())
2566 if (qmlImportScannerPath.isEmpty()) {
2569 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2574 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2575 qPrintable(qmlImportScannerPath));
2578 QStringList importPaths;
2581 for (
const QString &path : options.qmlImportPaths)
2582 if (QFile::exists(path))
2583 importPaths.append(path);
2588 QFileInfo appBinary(options.applicationBinary);
2589 QString appBuildDir = appBinary.absolutePath();
2590 if (QDir(appBuildDir).exists())
2591 importPaths.append(appBuildDir);
2594 if (!options.qtQmlDirectory.isEmpty() &&
2595 QDir(options.qtQmlDirectory).exists()) {
2596 importPaths.append(options.qtQmlDirectory);
2599 QFileInfo appBinary(options.applicationBinary);
2600 QDir qtDir(appBinary.absolutePath());
2601 for (
int i = 0; i < 10; ++i) {
2602 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2603 if (QDir(qmlDir).exists()) {
2604 importPaths.append(qmlDir);
2608 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2609 if (QDir(qmlDir).exists()) {
2610 importPaths.append(QDir::cleanPath(qmlDir));
2618 if (importPaths.isEmpty()) {
2619 fprintf(stderr,
"Warning: No QML import paths found\n");
2625 QStringList arguments;
2626 for (
const QString &rootPath : options.qmlRootPaths)
2627 arguments <<
"-rootPath"_L1 << rootPath;
2629 for (
const QString &importPath : importPaths)
2630 arguments <<
"-importPath"_L1 << importPath;
2633 fprintf(stdout,
" Root paths:\n");
2634 for (
const QString &rootPath : options.qmlRootPaths)
2635 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2636 fprintf(stdout,
" Import paths:\n");
2637 for (
const QString &path : importPaths)
2638 fprintf(stdout,
" %s\n", qPrintable(path));
2643 process.start(qmlImportScannerPath, arguments);
2645 if (!process.waitForFinished(30000)) {
2646 fprintf(stderr,
"Error: qmlimportscanner timed out\n");
2650 if (process.exitCode() != 0) {
2651 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2652 process.exitCode());
2653 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2658 QByteArray output = process.readAllStandardOutput();
2659 QJsonDocument doc = QJsonDocument::fromJson(output);
2661 if (!doc.isArray()) {
2662 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2666 QJsonArray array = doc.array();
2667 for (
const QJsonValue &value : array) {
2668 if (!value.isObject())
2671 QJsonObject obj = value.toObject();
2673 info.name = obj[
"name"_L1].toString();
2674 info.path = obj[
"path"_L1].toString();
2675 info.type = obj[
"type"_L1].toString();
2677 if (obj.contains(
"plugin"_L1))
2678 info.plugin = obj[
"plugin"_L1].toString();
2680 if (obj.contains(
"pluginIsOptional"_L1))
2681 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2683 if (obj.contains(
"prefer"_L1))
2684 info.prefer = obj[
"prefer"_L1].toString();
2687 if (obj.contains(
"components"_L1)) {
2688 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2689 for (
const QJsonValue &comp : componentsArray)
2690 info.components.append(comp.toString());
2694 if (obj.contains(
"scripts"_L1)) {
2695 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2696 for (
const QJsonValue &script : scriptsArray)
2697 info.scripts.append(script.toString());
2701 if (info.path.isEmpty()) {
2702 if (options.verbose)
2703 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2704 qPrintable(info.name));
2709 if (info.type !=
"module"_L1)
2712 if (options.verbose)
2713 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2714 qPrintable(info.path));
2716 imports.append(info);
2761 const QList<QmlImportInfo> &imports,
2762 QSet<QString> &processedLibs)
2764 if (imports.isEmpty())
2768 fprintf(stdout,
"Copying QML imports\n");
2772 QString qmlDestBase = hapQmlDir(options);
2773 QDir().mkpath(qmlDestBase);
2776 QStringList qmlPluginsToScan;
2778 for (
const QmlImportInfo &import : imports) {
2779 if (options.verbose)
2780 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2784 QString relativePath;
2787 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2789 relativePath = import.path.mid(options.qtQmlDirectory.length());
2790 if (relativePath.startsWith(
'/'_L1))
2791 relativePath = relativePath.mid(1);
2795 relativePath = import.name;
2796 relativePath.replace(
'.'_L1,
'/'_L1);
2799 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2800 QDir().mkpath(destModuleDir);
2803 QString qmldirSrc = import.path +
"/qmldir"_L1;
2804 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2805 if (QFile::exists(qmldirSrc)) {
2806 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2807 if (options.verbose)
2808 fprintf(stdout,
" Copied qmldir\n");
2811 if (!options.depFilePath.isEmpty())
2812 dependenciesForDepfile << qmldirSrc;
2814 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2815 qPrintable(import.name));
2822 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2824 if (!import.plugin.isEmpty()) {
2826 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2827 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2829 if (QFile::exists(pluginSrc)) {
2831 for (
const QString &arch : options.targetArchs) {
2832 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2833 +
"/"_L1 + pluginFileName;
2835 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2836 if (options.verbose)
2837 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2838 qPrintable(arch), qPrintable(pluginFileName));
2839 processedLibs.insert(pluginSrc);
2842 if (!qmlPluginsToScan.contains(pluginSrc))
2843 qmlPluginsToScan.append(pluginSrc);
2846 if (!options.depFilePath.isEmpty())
2847 dependenciesForDepfile << pluginSrc;
2848 }
else if (!import.pluginIsOptional) {
2849 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2850 qPrintable(pluginFileName));
2853 }
else if (!import.pluginIsOptional) {
2854 if (options.verbose)
2855 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2856 qPrintable(pluginFileName));
2861 if (!qmlFilesAreEmbedded) {
2862 for (
const QString &component : import.components) {
2863 QFileInfo compInfo(component);
2864 if (!compInfo.exists()) {
2865 if (options.verbose)
2866 fprintf(stdout,
" Warning: Component file not found: %s\n",
2867 qPrintable(component));
2871 QString relativePath = component.mid(import.path.length());
2872 if (relativePath.startsWith(
'/'_L1))
2873 relativePath = relativePath.mid(1);
2875 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2876 QFileInfo destInfo(destFile);
2877 QDir().mkpath(destInfo.absolutePath());
2879 if (copyFileIfNewer(component, destFile, options.verbose)) {
2880 if (options.verbose)
2881 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
2884 if (!options.depFilePath.isEmpty())
2885 dependenciesForDepfile << component;
2890 for (
const QString &script : import.scripts) {
2891 QFileInfo scriptInfo(script);
2892 if (!scriptInfo.exists())
2895 QString relativePath = script.mid(import.path.length());
2896 if (relativePath.startsWith(
'/'_L1))
2897 relativePath = relativePath.mid(1);
2899 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2900 QFileInfo destInfo(destFile);
2901 QDir().mkpath(destInfo.absolutePath());
2903 if (copyFileIfNewer(script, destFile, options.verbose)) {
2904 if (options.verbose)
2905 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
2908 if (!options.depFilePath.isEmpty())
2909 dependenciesForDepfile << script;
2913 if (options.verbose)
2914 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
2919 if (!qmlPluginsToScan.isEmpty()) {
2921 fprintf(stdout,
"Scanning QML plugin dependencies\n");
2923 QStringList toProcess = qmlPluginsToScan;
2924 while (!toProcess.isEmpty()) {
2925 QString pluginPath = toProcess.takeFirst();
2928 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
2930 QStringList deps = readElfDependencies(options, pluginPath);
2931 for (
const QString &dep : deps) {
2933 if (isSystemLibrary(dep))
2938 if (dep.startsWith(
"libQt6"_L1)) {
2939 depPath = findQtLibrary(options, dep);
2940 if (depPath.isEmpty()) {
2941 if (options.verbose)
2942 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2945 }
else if (!options.extraLibsDirs.isEmpty()) {
2946 depPath = findExtraDepLibrary(options, dep);
2947 if (depPath.isEmpty())
2953 if (processedLibs.contains(depPath))
2956 if (options.verbose)
2957 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2960 for (
const QString &arch : options.targetArchs) {
2961 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
2962 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
2963 if (options.verbose)
2964 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
2967 if (!options.depFilePath.isEmpty())
2968 dependenciesForDepfile << depPath;
2972 processedLibs.insert(depPath);
2974 toProcess.append(depPath);
2980 fprintf(stdout,
"QML imports copied successfully\n");
2992 const char *envName;
2993 const char *cliFlag;
2995 QByteArray envValue;
2997 QByteArray resolved()
const
2999 if (!cliValue.isEmpty())
3000 return cliValue.toUtf8();
3003 QString sourceLabel()
const
3005 return cliValue.isEmpty()
3006 ? QString::fromLatin1(envName)
3007 : QString::fromLatin1(cliFlag);
3011 Field required[] = {
3012 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path",
3013 options.signingCertPath, qgetenv(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
3014 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile",
3015 options.signingProfile, qgetenv(
"QT_HARMONYOS_SIGNING_PROFILE") },
3016 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file",
3017 options.signingStoreFile, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
3018 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias",
3019 options.signingKeyAlias, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
3020 {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
3021 options.signingKeyPassword, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
3022 {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
3023 options.signingStorePassword, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
3026 QByteArray signAlg = options.signingAlg.isEmpty()
3027 ? qgetenv(
"QT_HARMONYOS_SIGNING_ALG")
3028 : options.signingAlg.toUtf8();
3030 bool anySet = !signAlg.isEmpty();
3031 for (
const Field &f : required)
3032 anySet = anySet || !f.resolved().isEmpty();
3036 QStringList missing;
3037 for (
const Field &f : required) {
3038 if (f.resolved().isEmpty())
3039 missing << QString::fromLatin1(f.cliFlag) +
" / "_L1
3040 + QString::fromLatin1(f.envName);
3042 if (!missing.isEmpty()) {
3043 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
3044 " are missing (neither CLI flag nor env var was supplied):\n");
3045 for (
const QString &name : missing)
3046 fprintf(stderr,
" %s\n", qPrintable(name));
3050 if (signAlg.isEmpty())
3051 signAlg =
"SHA256withECDSA";
3053 const QString buildProfilePath = options.outputDirectory +
"/build-profile.json5"_L1;
3054 QFile profileFile(buildProfilePath);
3055 if (!profileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
3056 fprintf(stderr,
"Error: cannot open %s for reading: %s\n",
3057 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3060 QByteArray content = profileFile.readAll();
3061 profileFile.close();
3065 static const QByteArray needle =
"\"signingConfigs\": []";
3066 const int idx = content.indexOf(needle);
3068 fprintf(stderr,
"Error: '%s' not found in %s. The template may have been modified;\n"
3069 " cannot inject signing configuration safely.\n",
3070 needle.constData(), qPrintable(buildProfilePath));
3076 auto escapeJsonString = [](
const QByteArray &in) {
3078 out.reserve(in.size());
3080 if (c ==
'\\' || c ==
'"')
3087 QByteArray replacement;
3088 replacement.append(
"\"signingConfigs\": [\n");
3089 replacement.append(
" {\n");
3090 replacement.append(
" \"name\": \"default\",\n");
3091 replacement.append(
" \"type\": \"HarmonyOS\",\n");
3092 replacement.append(
" \"material\": {\n");
3093 auto appendField = [&](
const char *key,
const QByteArray &value,
bool last) {
3094 replacement.append(
" \"");
3095 replacement.append(key);
3096 replacement.append(
"\": \"");
3097 replacement.append(escapeJsonString(value));
3098 replacement.append(
'"');
3100 replacement.append(
',');
3101 replacement.append(
'\n');
3103 appendField(
"certpath", required[0].resolved(),
false);
3104 appendField(
"keyAlias", required[3].resolved(),
false);
3105 appendField(
"keyPassword", required[4].resolved(),
false);
3106 appendField(
"profile", required[1].resolved(),
false);
3107 appendField(
"signAlg", signAlg,
false);
3108 appendField(
"storeFile", required[2].resolved(),
false);
3109 appendField(
"storePassword", required[5].resolved(),
true);
3110 replacement.append(
" }\n");
3111 replacement.append(
" }\n");
3112 replacement.append(
" ]");
3114 content.replace(idx, needle.size(), replacement);
3116 if (!profileFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
3117 fprintf(stderr,
"Error: cannot open %s for writing: %s\n",
3118 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3121 if (profileFile.write(content) != content.size()) {
3122 fprintf(stderr,
"Error: failed to write full content to %s\n",
3123 qPrintable(buildProfilePath));
3124 profileFile.close();
3127 profileFile.close();
3130 fprintf(stdout,
"Injected HAP signingConfig into %s\n", qPrintable(buildProfilePath));
3131 fprintf(stdout,
" certpath: %s (from %s)\n",
3132 required[0].resolved().constData(), qPrintable(required[0].sourceLabel()));
3133 fprintf(stdout,
" profile: %s (from %s)\n",
3134 required[1].resolved().constData(), qPrintable(required[1].sourceLabel()));
3135 fprintf(stdout,
" storeFile: %s (from %s)\n",
3136 required[2].resolved().constData(), qPrintable(required[2].sourceLabel()));
3137 fprintf(stdout,
" keyAlias: %s (from %s)\n",
3138 required[3].resolved().constData(), qPrintable(required[3].sourceLabel()));
3139 fprintf(stdout,
" signAlg: %s\n", signAlg.constData());
3361 QCoreApplication app(argc, argv);
3362 QCoreApplication::setApplicationName(
"harmonydeployqt"_L1);
3363 QCoreApplication::setApplicationVersion(
"1.0"_L1);
3366 options.timer.start();
3368 if (!parseCommandLine(app.arguments(), &options))
3372 fprintf(stdout,
"Qt HarmonyOS Deployment Tool\n");
3373 fprintf(stdout,
"==============================\n\n");
3379 const QList<TestQmlModule> testQmlModules =
3380 options.testBundleMode ? findTestQmlModules(options) : QList<TestQmlModule>{};
3382 if (options.testBundleMode && !verifyUniqueTestQmlModuleDeployDirs(testQmlModules))
3386 fprintf(stdout,
"\nDeployment process started...\n");
3388 QString hapOutputPath;
3392 fprintf(stderr,
"Failed to copy template\n");
3398 fprintf(stderr,
"Failed to customize template\n");
3403 QStringList bundledBinaries;
3405 if (!copyTestBinaries(options, bundledBinaries)) {
3406 fprintf(stderr,
"Failed to copy test binaries\n");
3410 fprintf(stderr,
"Failed to copy Qt libraries\n");
3414 fprintf(stderr,
"Failed to copy Qt plugins\n");
3418 fprintf(stderr,
"Failed to copy QML modules\n");
3421 if (!copyTestQmlModules(testQmlModules, options)) {
3422 fprintf(stderr,
"Failed to copy test QML modules\n");
3427 fprintf(stderr,
"Failed to copy application binary\n");
3431 fprintf(stderr,
"Failed to copy project libraries\n");
3434 QSet<QString> processedLibs;
3435 if (!detectAndCopyDependencies(options, processedLibs)) {
3436 fprintf(stderr,
"Failed to detect and copy dependencies\n");
3439 QList<QmlImportInfo> qmlImports = scanQmlImports(options);
3441 fprintf(stderr,
"Failed to copy QML files\n");
3444 if (!copyQmlImports(options, qmlImports, processedLibs)) {
3445 fprintf(stderr,
"Failed to copy QML imports\n");
3448 if (!copyPlugins(options, processedLibs)) {
3449 fprintf(stderr,
"Failed to copy plugins\n");
3455 fprintf(stderr,
"Failed to copy extra plugins\n");
3460 fprintf(stderr,
"Failed to inject signing configuration\n");
3465 if (!buildHap(options, &hapOutputPath)) {
3466 fprintf(stderr,
"Failed to build HAP\n");
3472 if (!writeTestBinariesList(options, bundledBinaries)) {
3473 fprintf(stderr,
"Failed to write binaries.txt\n");
3480 if (!options.depFilePath.isEmpty()) {
3481 if (hapOutputPath.isEmpty()) {
3484 hapOutputPath = options.outputDirectory
3485 +
"/entry/build/default/outputs/default/autotests.hap"_L1;
3488 QString targetName = QFileInfo(options.applicationBinary).completeBaseName();
3489 if (targetName.startsWith(
"lib"_L1))
3490 targetName = targetName.mid(3);
3491 hapOutputPath = options.outputDirectory +
"/entry/build/default/outputs/default/"_L1
3492 + targetName +
".hap"_L1;
3495 if (!writeDepfile(options, hapOutputPath))
3496 fprintf(stderr,
"Warning: Failed to write dependency file\n");
3501 if (!installToDevice(options, hapOutputPath)) {
3502 fprintf(stderr,
"Failed to install to device\n");
3507 fprintf(stdout,
"\n==============================================\n");
3508 fprintf(stdout,
"Deployment completed successfully!\n");
3509 fprintf(stdout,
"==============================================\n");
3510 fprintf(stdout,
"Project location: %s\n", qPrintable(options.outputDirectory));
3512 if (!hapOutputPath.isEmpty())
3513 fprintf(stdout,
"HAP package: %s\n", qPrintable(hapOutputPath));
3516 fprintf(stdout,
"\nTotal time: %lld ms\n", options.timer.elapsed());