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 if (options->applicationBinary.isEmpty()) {
359 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
367 if (!QFile::exists(options->applicationBinary)) {
368 fprintf(stderr,
"Error: application binary does not exist: %s\n",
369 qPrintable(options->applicationBinary));
370 fprintf(stderr,
" Build the project before running harmonydeployqt.\n");
377 if (options->harmonyOsAppBundleName.isEmpty())
378 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
379 if (options->harmonyOsAppName.isEmpty())
380 options->harmonyOsAppName =
"QtAutoTests"_L1;
384 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
389 searchPath = QDir::cleanPath(options->qtLibsDirectory);
390 }
else if (!options->applicationBinary.isEmpty()) {
391 QFileInfo appInfo(options->applicationBinary);
392 searchPath = QDir::cleanPath(appInfo.absolutePath());
395 if (searchPath.isEmpty()) {
396 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
397 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
402 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
405 for (
int i = 0; i < 10; ++i) {
407 QString templatePath = searchPath +
"/share/qt6/src/harmonyos/templates"_L1;
409 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
410 QDir(templatePath).exists() ?
"FOUND" :
"not found");
412 if (QDir(templatePath).exists()) {
413 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
418 templatePath = searchPath +
"/src/harmonyos/templates"_L1;
420 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
421 QDir(templatePath).exists() ?
"FOUND" :
"not found");
423 if (QDir(templatePath).exists()) {
424 options->harmonyOsPackageSourceDirectory = std::move(templatePath);
429 const auto lastSlash = searchPath.lastIndexOf(
'/'_L1);
430 if (lastSlash <= 0) {
432 fprintf(stdout,
" Reached root directory\n");
435 searchPath.resize(lastSlash);
438 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
439 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
440 fprintf(stderr,
" and could not auto-detect template location\n");
441 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
444 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
448 if (options->harmonyOsAppName.isEmpty()) {
449 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
453 if (options->harmonyOsAppBundleName.isEmpty()) {
454 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
459 if (options->outputDirectory.isEmpty()) {
461 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
463 QFileInfo appInfo(options->applicationBinary);
464 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
465 appInfo.completeBaseName() +
"-harmonyos"_L1;
470 fprintf(stdout,
"Configuration loaded:\n");
472 fprintf(stdout,
" Mode: test bundle\n");
473 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
474 if (!options->testExcludeList.isEmpty())
475 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
477 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
479 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
480 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
481 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
482 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
483 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
723 fprintf(stdout,
"Customizing template files\n");
726 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
727 QFile qtAppConstantsFile(qtAppConstantsPath);
729 if (qtAppConstantsFile.exists()) {
730 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
731 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
735 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
736 qtAppConstantsFile.close();
742 appLibName =
"libtst_placeholder.so"_L1;
744 QFileInfo appInfo(options.applicationBinary);
745 appLibName = appInfo.fileName();
748 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
749 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
751 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
752 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
756 qtAppConstantsFile.write(content.toUtf8());
757 qtAppConstantsFile.close();
760 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
770 QString iconValue = options.harmonyOsAppIcon;
771 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
772 QFileInfo iconInfo(iconValue);
773 if (!iconInfo.exists() || !iconInfo.isFile()) {
774 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
777 QString safeStem = iconInfo.completeBaseName();
778 for (QChar &c : safeStem) {
779 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
780 c = QLatin1Char(
'_');
782 const QString destFileName = iconInfo.suffix().isEmpty()
784 : safeStem +
"."_L1 + iconInfo.suffix();
785 const QStringList destDirs = {
786 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
787 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
789 for (
const QString &destDir : destDirs) {
790 QDir().mkpath(destDir);
791 const QString destPath = destDir +
"/"_L1 + destFileName;
792 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
793 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
797 iconValue =
"$media:"_L1 + safeStem;
804 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
805 QFile appJsonFile(appJsonPath);
807 if (appJsonFile.exists()) {
808 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
809 fprintf(stderr,
"Failed to open app.json5 for reading\n");
812 QString content = QString::fromUtf8(appJsonFile.readAll());
815 auto replaceStringField =
816 [&content](QLatin1StringView key,
const QString &value) {
820 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
821 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
824 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
825 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
826 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
833 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
834 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
835 replaceStringField(
"icon"_L1, iconValue);
839 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
840 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
843 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
844 fprintf(stderr,
"Failed to open app.json5 for writing\n");
847 appJsonFile.write(content.toUtf8());
851 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
852 qPrintable(options.harmonyOsAppBundleName));
860 const QString displayLabel =
861 (!options.harmonyOsAppLabel.isEmpty()
862 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
863 ? options.harmonyOsAppLabel
864 : options.harmonyOsAppName;
869 QJsonArray transformedPermissions;
870 QList<PromotedReason> promotedReasons;
871 QSet<QString> seenPromotedIds;
872 for (
const QJsonValue &value : std::as_const(options.permissions)) {
873 if (!value.isObject()) {
874 transformedPermissions.append(value);
877 QJsonObject entry = value.toObject();
878 if (entry.contains(
"reason"_L1)) {
879 const QString reason = entry[
"reason"_L1].toString();
880 if (reasonNeedsPromotion(reason)) {
881 const QString permName = entry[
"name"_L1].toString();
882 const QString stringId = synthesizePermissionReasonId(permName);
883 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
884 if (!seenPromotedIds.contains(stringId)) {
885 promotedReasons.append({stringId, reason});
886 seenPromotedIds.insert(stringId);
890 transformedPermissions.append(entry);
896 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
898 for (
const QString &locale : locales) {
899 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
900 locale +
"/element/string.json"_L1;
901 QFile stringJsonFile(stringJsonPath);
903 if (!stringJsonFile.exists())
906 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
907 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
911 const QByteArray bytes = stringJsonFile.readAll();
912 stringJsonFile.close();
914 QJsonParseError parseErr;
915 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
916 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
917 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
918 qPrintable(locale), qPrintable(parseErr.errorString()));
921 QJsonObject root = doc.object();
922 QJsonArray strings = root[
"string"_L1].toArray();
925 QSet<QString> existingNames;
926 for (qsizetype i = 0; i < strings.size(); ++i) {
927 QJsonObject e = strings[i].toObject();
928 const QString name = e[
"name"_L1].toString();
929 existingNames.insert(name);
930 if (name ==
"QAbility_label"_L1) {
931 e[
"value"_L1] = displayLabel;
937 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
938 if (existingNames.contains(p.id))
942 e[
"value"_L1] = p.value;
944 existingNames.insert(p.id);
946 root[
"string"_L1] = strings;
949 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
950 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
954 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
955 stringJsonFile.close();
957 if (options.verbose) {
959 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
960 qPrintable(locale), qPrintable(displayLabel),
961 static_cast<
long long>(promotedReasons.size()));
966 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
967 QFile appScopeStringFile(appScopeStringPath);
969 if (appScopeStringFile.exists()) {
970 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
971 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
975 QString content = QString::fromUtf8(appScopeStringFile.readAll());
976 appScopeStringFile.close();
979 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
980 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
982 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
983 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
987 appScopeStringFile.write(content.toUtf8());
988 appScopeStringFile.close();
991 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
992 qPrintable(displayLabel));
997 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
998 QFile moduleJsonFile(moduleJsonPath);
1000 if (moduleJsonFile.exists()) {
1001 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1002 fprintf(stderr,
"Failed to open module.json5 for reading\n");
1006 QString content = QString::fromUtf8(moduleJsonFile.readAll());
1007 moduleJsonFile.close();
1013 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
1014 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
1015 ?
"$string:module_desc"_L1
1016 : jsonStringEscape(options.harmonyOsModuleDescription);
1017 content.replace(descriptionSentinel, descriptionValue);
1020 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
1021 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
1022 if (deviceTypes.isEmpty())
1023 deviceTypes = QStringList{
"phone"_L1,
"tablet"_L1,
"2in1"_L1 };
1024 QStringList quotedDeviceTypes;
1025 quotedDeviceTypes.reserve(deviceTypes.size());
1026 for (
const QString &dt : std::as_const(deviceTypes))
1027 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
1028 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
1037 const QString orientationSentinelLine =
1038 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
1039 QString orientationReplacement;
1040 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
1041 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
1042 orientationReplacement =
" \"orientation\": \""_L1
1043 + options.harmonyOsAbilityOrientation
1047 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
1048 qPrintable(options.harmonyOsAbilityOrientation));
1051 content.replace(orientationSentinelLine, orientationReplacement);
1057 if (!iconValue.isEmpty()) {
1059 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
1060 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1067 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1068 QString permissionsFragment;
1069 if (!transformedPermissions.isEmpty()) {
1070 QStringList entryStrings;
1071 entryStrings.reserve(transformedPermissions.size());
1072 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1073 if (!value.isObject())
1075 const QJsonObject entry = value.toObject();
1079 const QByteArray pretty =
1080 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1081 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1082 QStringList indented;
1083 indented.reserve(lines.size());
1084 for (
const QString &line : lines)
1085 indented.append(
" "_L1 + line);
1086 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1088 if (!entryStrings.isEmpty()) {
1089 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1092 content.replace(sentinel, permissionsFragment);
1094 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1095 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1099 moduleJsonFile.write(content.toUtf8());
1100 moduleJsonFile.close();
1103 fprintf(stdout,
" Updated module.json5 description\n");
1104 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1105 static_cast<
long long>(transformedPermissions.size()));
1118 const bool anySdkVersionSet =
1119 !options.harmonyOsCompatibleSdkVersion.isEmpty()
1120 || !options.harmonyOsTargetSdkVersion.isEmpty()
1121 || !options.harmonyOsCompileSdkVersion.isEmpty();
1123 const QString buildProfilePath =
1124 options.outputDirectory +
"/build-profile.json5"_L1;
1125 QFile buildProfileFile(buildProfilePath);
1126 if (anySdkVersionSet && buildProfileFile.exists()) {
1127 if (!buildProfileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1128 fprintf(stderr,
"Failed to open build-profile.json5 for reading\n");
1131 QString content = QString::fromUtf8(buildProfileFile.readAll());
1132 buildProfileFile.close();
1134 if (!options.harmonyOsCompatibleSdkVersion.isEmpty()) {
1137 "\"compatibleSdkVersion\":\\s*\"[^\"]*\""_L1),
1138 "\"compatibleSdkVersion\": \""_L1
1139 + jsonStringEscape(options.harmonyOsCompatibleSdkVersion)
1143 const QString targetSentinel =
"/* %%INSERT_TARGET_SDK_VERSION%% */"_L1;
1144 const QString targetReplacement =
1145 options.harmonyOsTargetSdkVersion.isEmpty()
1147 :
"\"targetSdkVersion\": \""_L1
1148 + jsonStringEscape(options.harmonyOsTargetSdkVersion)
1150 content.replace(targetSentinel, targetReplacement);
1152 const QString compileSentinel =
"/* %%INSERT_COMPILE_SDK_VERSION%% */"_L1;
1153 const QString compileReplacement =
1154 options.harmonyOsCompileSdkVersion.isEmpty()
1156 :
"\"compileSdkVersion\": \""_L1
1157 + jsonStringEscape(options.harmonyOsCompileSdkVersion)
1159 content.replace(compileSentinel, compileReplacement);
1161 if (!buildProfileFile.open(
1162 QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1163 fprintf(stderr,
"Failed to open build-profile.json5 for writing\n");
1166 buildProfileFile.write(content.toUtf8());
1167 buildProfileFile.close();
1171 " Updated build-profile.json5 SDK versions"
1172 " (compatible=%s, target=%s, compile=%s)\n",
1173 qPrintable(options.harmonyOsCompatibleSdkVersion),
1174 qPrintable(options.harmonyOsTargetSdkVersion),
1175 qPrintable(options.harmonyOsCompileSdkVersion));
1181 fprintf(stdout,
"Template customization completed\n");
1388 if (options.testBinariesDirectory.isEmpty()) {
1389 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1393 if (!QDir(options.testBinariesDirectory).exists()) {
1394 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1395 qPrintable(options.testBinariesDirectory));
1400 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1403 QStringList foundHelpers;
1404 QSet<QString> helperNames;
1408 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1409 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1410 found, foundHelpers, helperNames);
1412 if (found.isEmpty()) {
1413 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1414 qPrintable(options.testBinariesDirectory));
1419 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1420 if (!foundHelpers.isEmpty())
1421 fprintf(stdout,
"Found %lld test helper libraries\n",
1422 static_cast<
long long>(foundHelpers.size()));
1426 for (
const QString &arch : options.targetArchs) {
1427 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1428 QDir().mkpath(archLibPath);
1430 for (
const QString &testBinary : found) {
1431 QFileInfo testInfo(testBinary);
1432 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1434 if (options.verbose)
1435 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1437 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1438 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1443 for (
const QString &helperLib : foundHelpers) {
1444 QFileInfo helperInfo(helperLib);
1445 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1447 if (options.verbose)
1448 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1450 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1451 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1458 for (
const QString &testBinary : found) {
1459 QString fileName = QFileInfo(testBinary).fileName();
1460 if (!bundledBinaries.contains(fileName))
1461 bundledBinaries.append(fileName);
1465 if (!options.depFilePath.isEmpty()) {
1466 for (
const QString &testBinary : found)
1467 dependenciesForDepfile << testBinary;
1468 for (
const QString &helperLib : foundHelpers)
1469 dependenciesForDepfile << helperLib;
1507 if (options.qtLibsDirectory.isEmpty())
1510 if (!QDir(options.qtLibsDirectory).exists()) {
1512 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1513 qPrintable(options.qtLibsDirectory));
1519 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1521 QDir libsDir(options.qtLibsDirectory);
1522 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1524 for (
const QFileInfo &entry : entries) {
1525 for (
const QString &arch : options.targetArchs) {
1526 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1527 QDir().mkpath(QFileInfo(destPath).absolutePath());
1528 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1531 if (!options.depFilePath.isEmpty())
1532 dependenciesForDepfile << entry.filePath();
1536 for (
const QString &arch : options.targetArchs) {
1537 QString stdCppPath = findStdCppLibrary(options, arch);
1538 if (!stdCppPath.isEmpty()) {
1539 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1540 if (options.verbose)
1541 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1542 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1544 if (!options.depFilePath.isEmpty())
1545 dependenciesForDepfile << stdCppPath;
1550 for (
const QString &extraDir : options.extraLibsDirs) {
1552 if (!dir.exists()) {
1553 if (options.verbose)
1554 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1558 if (options.verbose)
1559 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1561 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1562 for (
const QFileInfo &entry : entries) {
1566 const QString soname = readElfSoname(options, entry.filePath());
1567 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1568 ? soname : entry.fileName();
1569 for (
const QString &arch : options.targetArchs) {
1570 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1571 QDir().mkpath(QFileInfo(destPath).absolutePath());
1572 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1575 if (!options.depFilePath.isEmpty())
1576 dependenciesForDepfile << entry.filePath();
2179 fprintf(stdout,
"Detecting Qt library dependencies\n");
2182 QStringList toProcess;
2183 toProcess.append(options.applicationBinary);
2187 for (
const QString &projectLib : options.projectLibraries)
2188 toProcess.append(projectLib);
2190 QList<QtDependency> qtDependencies;
2191 QStringList detectedQtModules;
2192 bool needsStdCpp =
false;
2194 while (!toProcess.isEmpty()) {
2195 QString currentLib = toProcess.takeFirst();
2197 if (processedLibs.contains(currentLib))
2200 processedLibs.insert(currentLib);
2203 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
2205 QStringList deps = readElfDependencies(options, currentLib);
2207 for (
const QString &dep : deps) {
2209 if (dep ==
"libc++_shared.so"_L1) {
2215 if (isSystemLibrary(dep))
2219 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
2221 QString extraDepPath = findExtraDepLibrary(options, dep);
2222 if (extraDepPath.isEmpty())
2227 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
2228 if (options.verbose)
2229 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
2230 QtDependency extraDep;
2231 extraDep.relativePath =
"lib/"_L1 + dep;
2232 extraDep.absolutePath = extraDepPath;
2233 qtDependencies.append(extraDep);
2234 toProcess.append(extraDepPath);
2240 if (dep.startsWith(
"libQt6"_L1)) {
2241 QString moduleName = dep.mid(6);
2242 if (moduleName.endsWith(
".so"_L1))
2244 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
2245 detectedQtModules.append(moduleName);
2248 QString depPath = findQtLibrary(options, dep);
2249 if (depPath.isEmpty()) {
2250 if (options.verbose)
2251 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2255 if (processedLibs.contains(depPath))
2258 if (options.verbose)
2259 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2262 qtDep.relativePath =
"lib/"_L1 + dep;
2263 qtDep.absolutePath = depPath;
2264 qtDependencies.append(std::move(qtDep));
2267 toProcess.append(std::move(depPath));
2272 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
2274 fprintf(stdout,
"C++ standard library required\n");
2279 for (
const QString &arch : options.targetArchs) {
2280 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
2281 QString stdCppPath = findStdCppLibrary(options, arch);
2282 if (stdCppPath.isEmpty()) {
2283 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
2285 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
2286 if (options.verbose)
2287 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
2288 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
2290 "Failed to copy C++ standard library to: %s\n",
2291 qPrintable(destPath));
2296 if (!options.depFilePath.isEmpty())
2297 dependenciesForDepfile << stdCppPath;
2306 for (
const QtDependency &dep : qtDependencies) {
2307 QFileInfo libInfo(dep.relativePath);
2308 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
2311 if (!options.depFilePath.isEmpty())
2312 dependenciesForDepfile << dep.absolutePath;
2540 QList<QmlImportInfo> imports;
2542 if (options.qmlRootPaths.isEmpty()) {
2545 "No QML root path specified, skipping QML import scanning\n");
2550 fprintf(stdout,
"Scanning for QML imports\n");
2553 QStringList searchPaths;
2554 if (!options.qtLibExecsDirectory.isEmpty())
2555 searchPaths.append(options.qtLibExecsDirectory);
2558 if (!options.qtHostDirectory.isEmpty())
2559 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2561 QString qmlImportScannerPath =
2562 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2565 if (qmlImportScannerPath.isEmpty()) {
2566 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2568 for (
int i = 0; i < 10; ++i) {
2569 qmlImportScannerPath = QStandardPaths::findExecutable(
2570 "qmlimportscanner"_L1,
2571 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2573 if (!qmlImportScannerPath.isEmpty())
2581 if (qmlImportScannerPath.isEmpty()) {
2584 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2589 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2590 qPrintable(qmlImportScannerPath));
2593 QStringList importPaths;
2596 for (
const QString &path : options.qmlImportPaths)
2597 if (QFile::exists(path))
2598 importPaths.append(path);
2603 QFileInfo appBinary(options.applicationBinary);
2604 QString appBuildDir = appBinary.absolutePath();
2605 if (QDir(appBuildDir).exists())
2606 importPaths.append(appBuildDir);
2609 if (!options.qtQmlDirectory.isEmpty() &&
2610 QDir(options.qtQmlDirectory).exists()) {
2611 importPaths.append(options.qtQmlDirectory);
2614 QFileInfo appBinary(options.applicationBinary);
2615 QDir qtDir(appBinary.absolutePath());
2616 for (
int i = 0; i < 10; ++i) {
2617 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2618 if (QDir(qmlDir).exists()) {
2619 importPaths.append(qmlDir);
2623 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2624 if (QDir(qmlDir).exists()) {
2625 importPaths.append(QDir::cleanPath(qmlDir));
2633 if (importPaths.isEmpty()) {
2634 fprintf(stderr,
"Warning: No QML import paths found\n");
2640 QStringList arguments;
2641 for (
const QString &rootPath : options.qmlRootPaths)
2642 arguments <<
"-rootPath"_L1 << rootPath;
2644 for (
const QString &importPath : importPaths)
2645 arguments <<
"-importPath"_L1 << importPath;
2648 fprintf(stdout,
" Root paths:\n");
2649 for (
const QString &rootPath : options.qmlRootPaths)
2650 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2651 fprintf(stdout,
" Import paths:\n");
2652 for (
const QString &path : importPaths)
2653 fprintf(stdout,
" %s\n", qPrintable(path));
2658 process.start(qmlImportScannerPath, arguments);
2660 if (!process.waitForFinished(30000)) {
2661 fprintf(stderr,
"Error: qmlimportscanner timed out\n");
2665 if (process.exitCode() != 0) {
2666 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2667 process.exitCode());
2668 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2673 QByteArray output = process.readAllStandardOutput();
2674 QJsonDocument doc = QJsonDocument::fromJson(output);
2676 if (!doc.isArray()) {
2677 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2681 QJsonArray array = doc.array();
2682 for (
const QJsonValue &value : array) {
2683 if (!value.isObject())
2686 QJsonObject obj = value.toObject();
2688 info.name = obj[
"name"_L1].toString();
2689 info.path = obj[
"path"_L1].toString();
2690 info.type = obj[
"type"_L1].toString();
2692 if (obj.contains(
"plugin"_L1))
2693 info.plugin = obj[
"plugin"_L1].toString();
2695 if (obj.contains(
"pluginIsOptional"_L1))
2696 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2698 if (obj.contains(
"prefer"_L1))
2699 info.prefer = obj[
"prefer"_L1].toString();
2702 if (obj.contains(
"components"_L1)) {
2703 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2704 for (
const QJsonValue &comp : componentsArray)
2705 info.components.append(comp.toString());
2709 if (obj.contains(
"scripts"_L1)) {
2710 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2711 for (
const QJsonValue &script : scriptsArray)
2712 info.scripts.append(script.toString());
2716 if (info.path.isEmpty()) {
2717 if (options.verbose)
2718 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2719 qPrintable(info.name));
2724 if (info.type !=
"module"_L1)
2727 if (options.verbose)
2728 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2729 qPrintable(info.path));
2731 imports.append(info);
2776 const QList<QmlImportInfo> &imports,
2777 QSet<QString> &processedLibs)
2779 if (imports.isEmpty())
2783 fprintf(stdout,
"Copying QML imports\n");
2787 QString qmlDestBase = hapQmlDir(options);
2788 QDir().mkpath(qmlDestBase);
2791 QStringList qmlPluginsToScan;
2793 for (
const QmlImportInfo &import : imports) {
2794 if (options.verbose)
2795 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2799 QString relativePath;
2802 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2804 relativePath = import.path.mid(options.qtQmlDirectory.length());
2805 if (relativePath.startsWith(
'/'_L1))
2806 relativePath = relativePath.mid(1);
2810 relativePath = import.name;
2811 relativePath.replace(
'.'_L1,
'/'_L1);
2814 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2815 QDir().mkpath(destModuleDir);
2818 QString qmldirSrc = import.path +
"/qmldir"_L1;
2819 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2820 if (QFile::exists(qmldirSrc)) {
2821 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2822 if (options.verbose)
2823 fprintf(stdout,
" Copied qmldir\n");
2826 if (!options.depFilePath.isEmpty())
2827 dependenciesForDepfile << qmldirSrc;
2829 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2830 qPrintable(import.name));
2837 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2839 if (!import.plugin.isEmpty()) {
2841 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2842 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2844 if (QFile::exists(pluginSrc)) {
2846 for (
const QString &arch : options.targetArchs) {
2847 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2848 +
"/"_L1 + pluginFileName;
2850 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2851 if (options.verbose)
2852 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2853 qPrintable(arch), qPrintable(pluginFileName));
2854 processedLibs.insert(pluginSrc);
2857 if (!qmlPluginsToScan.contains(pluginSrc))
2858 qmlPluginsToScan.append(pluginSrc);
2861 if (!options.depFilePath.isEmpty())
2862 dependenciesForDepfile << pluginSrc;
2863 }
else if (!import.pluginIsOptional) {
2864 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2865 qPrintable(pluginFileName));
2868 }
else if (!import.pluginIsOptional) {
2869 if (options.verbose)
2870 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2871 qPrintable(pluginFileName));
2876 if (!qmlFilesAreEmbedded) {
2877 for (
const QString &component : import.components) {
2878 QFileInfo compInfo(component);
2879 if (!compInfo.exists()) {
2880 if (options.verbose)
2881 fprintf(stdout,
" Warning: Component file not found: %s\n",
2882 qPrintable(component));
2886 QString relativePath = component.mid(import.path.length());
2887 if (relativePath.startsWith(
'/'_L1))
2888 relativePath = relativePath.mid(1);
2890 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2891 QFileInfo destInfo(destFile);
2892 QDir().mkpath(destInfo.absolutePath());
2894 if (copyFileIfNewer(component, destFile, options.verbose)) {
2895 if (options.verbose)
2896 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
2899 if (!options.depFilePath.isEmpty())
2900 dependenciesForDepfile << component;
2905 for (
const QString &script : import.scripts) {
2906 QFileInfo scriptInfo(script);
2907 if (!scriptInfo.exists())
2910 QString relativePath = script.mid(import.path.length());
2911 if (relativePath.startsWith(
'/'_L1))
2912 relativePath = relativePath.mid(1);
2914 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2915 QFileInfo destInfo(destFile);
2916 QDir().mkpath(destInfo.absolutePath());
2918 if (copyFileIfNewer(script, destFile, options.verbose)) {
2919 if (options.verbose)
2920 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
2923 if (!options.depFilePath.isEmpty())
2924 dependenciesForDepfile << script;
2928 if (options.verbose)
2929 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
2934 if (!qmlPluginsToScan.isEmpty()) {
2936 fprintf(stdout,
"Scanning QML plugin dependencies\n");
2938 QStringList toProcess = qmlPluginsToScan;
2939 while (!toProcess.isEmpty()) {
2940 QString pluginPath = toProcess.takeFirst();
2943 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
2945 QStringList deps = readElfDependencies(options, pluginPath);
2946 for (
const QString &dep : deps) {
2948 if (isSystemLibrary(dep))
2953 if (dep.startsWith(
"libQt6"_L1)) {
2954 depPath = findQtLibrary(options, dep);
2955 if (depPath.isEmpty()) {
2956 if (options.verbose)
2957 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2960 }
else if (!options.extraLibsDirs.isEmpty()) {
2961 depPath = findExtraDepLibrary(options, dep);
2962 if (depPath.isEmpty())
2968 if (processedLibs.contains(depPath))
2971 if (options.verbose)
2972 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2975 for (
const QString &arch : options.targetArchs) {
2976 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
2977 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
2978 if (options.verbose)
2979 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
2982 if (!options.depFilePath.isEmpty())
2983 dependenciesForDepfile << depPath;
2987 processedLibs.insert(depPath);
2989 toProcess.append(depPath);
2995 fprintf(stdout,
"QML imports copied successfully\n");
3007 const char *envName;
3008 const char *cliFlag;
3010 QByteArray envValue;
3012 QByteArray resolved()
const
3014 if (!cliValue.isEmpty())
3015 return cliValue.toUtf8();
3018 QString sourceLabel()
const
3020 return cliValue.isEmpty()
3021 ? QString::fromLatin1(envName)
3022 : QString::fromLatin1(cliFlag);
3026 Field required[] = {
3027 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path",
3028 options.signingCertPath, qgetenv(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
3029 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile",
3030 options.signingProfile, qgetenv(
"QT_HARMONYOS_SIGNING_PROFILE") },
3031 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file",
3032 options.signingStoreFile, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
3033 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias",
3034 options.signingKeyAlias, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
3035 {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
3036 options.signingKeyPassword, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
3037 {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
3038 options.signingStorePassword, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
3041 QByteArray signAlg = options.signingAlg.isEmpty()
3042 ? qgetenv(
"QT_HARMONYOS_SIGNING_ALG")
3043 : options.signingAlg.toUtf8();
3045 bool anySet = !signAlg.isEmpty();
3046 for (
const Field &f : required)
3047 anySet = anySet || !f.resolved().isEmpty();
3051 QStringList missing;
3052 for (
const Field &f : required) {
3053 if (f.resolved().isEmpty())
3054 missing << QString::fromLatin1(f.cliFlag) +
" / "_L1
3055 + QString::fromLatin1(f.envName);
3057 if (!missing.isEmpty()) {
3058 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
3059 " are missing (neither CLI flag nor env var was supplied):\n");
3060 for (
const QString &name : missing)
3061 fprintf(stderr,
" %s\n", qPrintable(name));
3065 if (signAlg.isEmpty())
3066 signAlg =
"SHA256withECDSA";
3068 const QString buildProfilePath = options.outputDirectory +
"/build-profile.json5"_L1;
3069 QFile profileFile(buildProfilePath);
3070 if (!profileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
3071 fprintf(stderr,
"Error: cannot open %s for reading: %s\n",
3072 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3075 QByteArray content = profileFile.readAll();
3076 profileFile.close();
3080 static const QByteArray needle =
"\"signingConfigs\": []";
3081 const int idx = content.indexOf(needle);
3083 fprintf(stderr,
"Error: '%s' not found in %s. The template may have been modified;\n"
3084 " cannot inject signing configuration safely.\n",
3085 needle.constData(), qPrintable(buildProfilePath));
3091 auto escapeJsonString = [](
const QByteArray &in) {
3093 out.reserve(in.size());
3095 if (c ==
'\\' || c ==
'"')
3102 QByteArray replacement;
3103 replacement.append(
"\"signingConfigs\": [\n");
3104 replacement.append(
" {\n");
3105 replacement.append(
" \"name\": \"default\",\n");
3106 replacement.append(
" \"type\": \"HarmonyOS\",\n");
3107 replacement.append(
" \"material\": {\n");
3108 auto appendField = [&](
const char *key,
const QByteArray &value,
bool last) {
3109 replacement.append(
" \"");
3110 replacement.append(key);
3111 replacement.append(
"\": \"");
3112 replacement.append(escapeJsonString(value));
3113 replacement.append(
'"');
3115 replacement.append(
',');
3116 replacement.append(
'\n');
3118 appendField(
"certpath", required[0].resolved(),
false);
3119 appendField(
"keyAlias", required[3].resolved(),
false);
3120 appendField(
"keyPassword", required[4].resolved(),
false);
3121 appendField(
"profile", required[1].resolved(),
false);
3122 appendField(
"signAlg", signAlg,
false);
3123 appendField(
"storeFile", required[2].resolved(),
false);
3124 appendField(
"storePassword", required[5].resolved(),
true);
3125 replacement.append(
" }\n");
3126 replacement.append(
" }\n");
3127 replacement.append(
" ]");
3129 content.replace(idx, needle.size(), replacement);
3131 if (!profileFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
3132 fprintf(stderr,
"Error: cannot open %s for writing: %s\n",
3133 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
3136 if (profileFile.write(content) != content.size()) {
3137 fprintf(stderr,
"Error: failed to write full content to %s\n",
3138 qPrintable(buildProfilePath));
3139 profileFile.close();
3142 profileFile.close();
3145 fprintf(stdout,
"Injected HAP signingConfig into %s\n", qPrintable(buildProfilePath));
3146 fprintf(stdout,
" certpath: %s (from %s)\n",
3147 required[0].resolved().constData(), qPrintable(required[0].sourceLabel()));
3148 fprintf(stdout,
" profile: %s (from %s)\n",
3149 required[1].resolved().constData(), qPrintable(required[1].sourceLabel()));
3150 fprintf(stdout,
" storeFile: %s (from %s)\n",
3151 required[2].resolved().constData(), qPrintable(required[2].sourceLabel()));
3152 fprintf(stdout,
" keyAlias: %s (from %s)\n",
3153 required[3].resolved().constData(), qPrintable(required[3].sourceLabel()));
3154 fprintf(stdout,
" signAlg: %s\n", signAlg.constData());
3378 QCoreApplication app(argc, argv);
3379 QCoreApplication::setApplicationName(
"harmonydeployqt"_L1);
3380 QCoreApplication::setApplicationVersion(
"1.0"_L1);
3383 options.timer.start();
3385 if (!parseCommandLine(app.arguments(), &options))
3389 fprintf(stdout,
"Qt HarmonyOS Deployment Tool\n");
3390 fprintf(stdout,
"==============================\n\n");
3396 const QList<TestQmlModule> testQmlModules =
3397 options.testBundleMode ? findTestQmlModules(options) : QList<TestQmlModule>{};
3399 if (options.testBundleMode && !verifyUniqueTestQmlModuleDeployDirs(testQmlModules))
3403 fprintf(stdout,
"\nDeployment process started...\n");
3405 QString hapOutputPath;
3409 fprintf(stderr,
"Failed to copy template\n");
3415 fprintf(stderr,
"Failed to customize template\n");
3420 QStringList bundledBinaries;
3422 if (!copyTestBinaries(options, bundledBinaries)) {
3423 fprintf(stderr,
"Failed to copy test binaries\n");
3427 fprintf(stderr,
"Failed to copy Qt libraries\n");
3431 fprintf(stderr,
"Failed to copy Qt plugins\n");
3435 fprintf(stderr,
"Failed to copy QML modules\n");
3438 if (!copyTestQmlModules(testQmlModules, options)) {
3439 fprintf(stderr,
"Failed to copy test QML modules\n");
3444 fprintf(stderr,
"Failed to copy application binary\n");
3448 fprintf(stderr,
"Failed to copy project libraries\n");
3451 QSet<QString> processedLibs;
3452 if (!detectAndCopyDependencies(options, processedLibs)) {
3453 fprintf(stderr,
"Failed to detect and copy dependencies\n");
3456 QList<QmlImportInfo> qmlImports = scanQmlImports(options);
3458 fprintf(stderr,
"Failed to copy QML files\n");
3461 if (!copyQmlImports(options, qmlImports, processedLibs)) {
3462 fprintf(stderr,
"Failed to copy QML imports\n");
3465 if (!copyPlugins(options, processedLibs)) {
3466 fprintf(stderr,
"Failed to copy plugins\n");
3472 fprintf(stderr,
"Failed to copy extra plugins\n");
3477 fprintf(stderr,
"Failed to inject signing configuration\n");
3482 if (!buildHap(options, &hapOutputPath)) {
3483 fprintf(stderr,
"Failed to build HAP\n");
3489 if (!writeTestBinariesList(options, bundledBinaries)) {
3490 fprintf(stderr,
"Failed to write binaries.txt\n");
3497 if (!options.depFilePath.isEmpty()) {
3498 if (hapOutputPath.isEmpty()) {
3501 hapOutputPath = options.outputDirectory
3502 +
"/entry/build/default/outputs/default/autotests.hap"_L1;
3505 QString targetName = QFileInfo(options.applicationBinary).completeBaseName();
3506 if (targetName.startsWith(
"lib"_L1))
3507 targetName = targetName.mid(3);
3508 hapOutputPath = options.outputDirectory +
"/entry/build/default/outputs/default/"_L1
3509 + targetName +
".hap"_L1;
3512 if (!writeDepfile(options, hapOutputPath))
3513 fprintf(stderr,
"Warning: Failed to write dependency file\n");
3518 if (!installToDevice(options, hapOutputPath)) {
3519 fprintf(stderr,
"Failed to install to device\n");
3524 fprintf(stdout,
"\n==============================================\n");
3525 fprintf(stdout,
"Deployment completed successfully!\n");
3526 fprintf(stdout,
"==============================================\n");
3527 fprintf(stdout,
"Project location: %s\n", qPrintable(options.outputDirectory));
3529 if (!hapOutputPath.isEmpty())
3530 fprintf(stdout,
"HAP package: %s\n", qPrintable(hapOutputPath));
3533 fprintf(stdout,
"\nTotal time: %lld ms\n", options.timer.elapsed());