152 QCommandLineParser parser;
153 parser.setApplicationDescription(
"Qt HarmonyOS Deployment Tool"_L1);
155 QCommandLineOption inputOption(
"input"_L1,
"JSON configuration file"_L1,
"file"_L1);
156 QCommandLineOption outputOption(
"output"_L1,
"Output directory"_L1,
"dir"_L1);
157 QCommandLineOption hvigorOption(
"hvigor"_L1,
"Path to hvigorw"_L1,
"path"_L1);
158 QCommandLineOption installOption(
"install"_L1,
"Install to device"_L1);
159 QCommandLineOption releaseOption(
"release"_L1,
"Build release configuration"_L1);
160 QCommandLineOption verboseOption(
"verbose"_L1,
"Verbose output"_L1);
161 QCommandLineOption noBuildOption(
"no-build"_L1,
"Skip building"_L1);
162 QCommandLineOption testBundleOption(
"test-bundle"_L1,
"Enable test bundle mode"_L1);
163 QCommandLineOption depfileOption(
"depfile"_L1,
"Dependency file output"_L1,
"path"_L1);
164 QCommandLineOption depfileBaseOption(
"depfile-base"_L1,
"Base directory for depfile paths"_L1,
"dir"_L1);
165 QCommandLineOption signingCertPathOption(
"signing-cert-path"_L1,
166 "Path to the .cer file"_L1,
"path"_L1);
167 QCommandLineOption signingProfileOption(
"signing-profile"_L1,
168 "Path to the .p7b profile"_L1,
"path"_L1);
169 QCommandLineOption signingStoreFileOption(
"signing-store-file"_L1,
170 "Path to the .p12 keystore"_L1,
"path"_L1);
171 QCommandLineOption signingKeyAliasOption(
"signing-key-alias"_L1,
172 "Key alias inside the keystore"_L1,
"alias"_L1);
173 QCommandLineOption signingKeyPasswordOption(
"signing-key-password"_L1,
174 "Encrypted key password"_L1,
"pwd"_L1);
175 QCommandLineOption signingStorePasswordOption(
"signing-store-password"_L1,
176 "Encrypted keystore password"_L1,
"pwd"_L1);
177 QCommandLineOption signingAlgOption(
"signing-alg"_L1,
178 "Signature algorithm (default SHA256withECDSA)"_L1,
"alg"_L1);
179 QCommandLineOption helpOption(
"help"_L1,
"Show help"_L1);
181 parser.addOption(inputOption);
182 parser.addOption(outputOption);
183 parser.addOption(hvigorOption);
184 parser.addOption(installOption);
185 parser.addOption(releaseOption);
186 parser.addOption(verboseOption);
187 parser.addOption(noBuildOption);
188 parser.addOption(testBundleOption);
189 parser.addOption(depfileOption);
190 parser.addOption(depfileBaseOption);
191 parser.addOption(signingCertPathOption);
192 parser.addOption(signingProfileOption);
193 parser.addOption(signingStoreFileOption);
194 parser.addOption(signingKeyAliasOption);
195 parser.addOption(signingKeyPasswordOption);
196 parser.addOption(signingStorePasswordOption);
197 parser.addOption(signingAlgOption);
198 parser.addOption(helpOption);
200 if (!parser.parse(arguments)) {
201 fprintf(stderr,
"%s\n", qPrintable(parser.errorText()));
205 if (parser.isSet(helpOption)) {
210 if (!parser.isSet(inputOption)) {
211 fprintf(stderr,
"Error: --input option is required\n");
216 options->inputFile = parser.value(inputOption);
217 options->outputDirectory = parser.value(outputOption);
218 options->hvigorPath = parser.value(hvigorOption);
221 options
->verbose = parser.isSet(verboseOption);
224 options->depFilePath = parser.value(depfileOption);
225 options->depFileBase = parser.value(depfileBaseOption);
226 options->signingCertPath = parser.value(signingCertPathOption);
227 options->signingProfile = parser.value(signingProfileOption);
228 options->signingStoreFile = parser.value(signingStoreFileOption);
229 options->signingKeyAlias = parser.value(signingKeyAliasOption);
230 options->signingKeyPassword = parser.value(signingKeyPasswordOption);
231 options->signingStorePassword = parser.value(signingStorePasswordOption);
232 options->signingAlg = parser.value(signingAlgOption);
239 QFile inputFile(options->inputFile);
240 if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
241 fprintf(stderr,
"Failed to open input file: %s\n", qPrintable(options->inputFile));
245 QJsonParseError parseError;
246 QJsonDocument doc = QJsonDocument::fromJson(inputFile.readAll(), &parseError);
248 fprintf(stderr,
"Failed to parse JSON: %s\n", qPrintable(parseError.errorString()));
252 QJsonObject obj = doc.object();
254 options->applicationBinary = obj[
"application-binary"_L1].toString();
255 options->harmonyOsPackageSourceDirectory = obj[
"harmonyos-package-source-directory"_L1].toString();
256 options->harmonyOsAppName = obj[
"harmonyos-app-name"_L1].toString();
257 options->harmonyOsAppBundleName = obj[
"harmonyos-app-bundle-name"_L1].toString();
258 options->sdkRoot = obj[
"sdk-root"_L1].toString();
259 options->ndkRoot = obj[
"ndk-root"_L1].toString();
262 const QJsonValue rootPathValue = obj[
"qml-root-path"_L1];
263 if (rootPathValue.isArray()) {
264 for (
const QJsonValue &v : rootPathValue.toArray()) {
265 const QString s = v.toString();
267 options->qmlRootPaths.append(s);
270 const QString s = rootPathValue.toString();
272 options->qmlRootPaths.append(s);
277 options->qtLibsDirectory = obj[
"qtLibsDirectory"_L1].toString();
278 options->qtPluginsDirectory = obj[
"qtPluginsDirectory"_L1].toString();
279 options->qtQmlDirectory = obj[
"qtQmlDirectory"_L1].toString();
280 options->qtLibExecsDirectory = obj[
"qtLibExecsDirectory"_L1].toString();
281 options->qtHostDirectory = obj[
"qtHostDirectory"_L1].toString();
283 QJsonArray extraLibsDirsArray = obj[
"extra-libs-dirs"_L1].toArray();
284 for (
const QJsonValue &value : extraLibsDirsArray)
285 options->extraLibsDirs.append(value.toString());
288 if (obj[
"test-bundle"_L1].toBool())
290 options->testBinariesDirectory = obj[
"test-binaries-directory"_L1].toString();
291 QJsonArray excludeArray = obj[
"test-exclude-list"_L1].toArray();
292 for (
const QJsonValue &value : excludeArray)
293 options->testExcludeList.append(value.toString());
296 QJsonArray projectLibsArray = obj[
"project-libraries"_L1].toArray();
297 for (
const QJsonValue &value : projectLibsArray)
298 options->projectLibraries.append(value.toString());
301 QJsonArray importPathsArray = obj[
"qml-import-paths"_L1].toArray();
302 for (
const QJsonValue &value : importPathsArray)
303 options->qmlImportPaths.append(value.toString());
306 QJsonArray pluginsImportPathsArray = obj[
"plugins-import-paths"_L1].toArray();
307 for (
const QJsonValue &value : pluginsImportPathsArray)
308 options->pluginsImportPaths.append(value.toString());
311 QJsonArray archArray = obj[
"harmonyos-target-arch"_L1].toArray();
312 for (
const QJsonValue &value : archArray)
313 options->targetArchs.append(value.toString());
314 if (options->targetArchs.isEmpty())
315 options->targetArchs.append(
"arm64-v8a"_L1);
318 options->permissions = obj[
"permissions"_L1].toArray();
321 options->harmonyOsAppVendor = obj[
"harmonyos-app-vendor"_L1].toString();
322 options->harmonyOsAppVersionCode = obj[
"harmonyos-app-version-code"_L1].toInt();
323 options->harmonyOsAppVersionName = obj[
"harmonyos-app-version-name"_L1].toString();
324 options->harmonyOsAppLabel = obj[
"harmonyos-app-label"_L1].toString();
325 options->harmonyOsAppIcon = obj[
"harmonyos-app-icon"_L1].toString();
328 options->harmonyOsCompatibleSdkVersion =
329 obj[
"harmonyos-compatible-sdk-version"_L1].toString();
330 options->harmonyOsTargetSdkVersion =
331 obj[
"harmonyos-target-sdk-version"_L1].toString();
332 options->harmonyOsCompileSdkVersion =
333 obj[
"harmonyos-compile-sdk-version"_L1].toString();
338 const QJsonArray extraPluginsArray =
339 obj[
"harmonyos-extra-plugins"_L1].toArray();
340 for (
const QJsonValue &v : extraPluginsArray) {
341 const QString s = v.toString();
343 options->extraPlugins.append(s);
348 options->harmonyOsModuleDescription = obj[
"harmonyos-module-description"_L1].toString();
349 const QJsonArray deviceTypesArray = obj[
"harmonyos-module-device-types"_L1].toArray();
350 for (
const QJsonValue &value : deviceTypesArray)
351 options->harmonyOsModuleDeviceTypes.append(value.toString());
352 options->harmonyOsAbilityOrientation = obj[
"harmonyos-ability-orientation"_L1].toString();
356 fprintf(stderr,
"Error: 'application-binary' not specified in JSON\n");
362 if (options->harmonyOsAppBundleName.isEmpty())
363 options->harmonyOsAppBundleName =
"org.qtproject.autotests"_L1;
364 if (options->harmonyOsAppName.isEmpty())
365 options->harmonyOsAppName =
"QtAutoTests"_L1;
369 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
374 searchPath = QDir::cleanPath(options->qtLibsDirectory);
375 }
else if (!options->applicationBinary.isEmpty()) {
376 QFileInfo appInfo(options->applicationBinary);
377 searchPath = QDir::cleanPath(appInfo.absolutePath());
380 if (searchPath.isEmpty()) {
381 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
382 fprintf(stderr,
" and could not auto-detect template location (no search path)\n");
387 fprintf(stdout,
"Searching for template starting from: %s\n", qPrintable(searchPath));
390 QString currentPath = searchPath;
391 for (
int i = 0; i < 10; ++i) {
393 QString templatePath = currentPath +
"/share/qt6/src/harmonyos/templates"_L1;
395 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
396 QDir(templatePath).exists() ?
"FOUND" :
"not found");
398 if (QDir(templatePath).exists()) {
399 options->harmonyOsPackageSourceDirectory = templatePath;
404 templatePath = currentPath +
"/src/harmonyos/templates"_L1;
406 fprintf(stdout,
" Checking: %s ... %s\n", qPrintable(templatePath),
407 QDir(templatePath).exists() ?
"FOUND" :
"not found");
409 if (QDir(templatePath).exists()) {
410 options->harmonyOsPackageSourceDirectory = templatePath;
415 int lastSlash = currentPath.lastIndexOf(
'/'_L1);
416 if (lastSlash <= 0) {
418 fprintf(stdout,
" Reached root directory\n");
421 currentPath = currentPath.left(lastSlash);
424 if (options->harmonyOsPackageSourceDirectory.isEmpty()) {
425 fprintf(stderr,
"Error: 'harmonyos-package-source-directory' not specified in JSON\n");
426 fprintf(stderr,
" and could not auto-detect template location\n");
427 fprintf(stderr,
" Please specify the path to the HarmonyOS application template\n");
430 fprintf(stdout,
"Auto-detected template: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
434 if (options->harmonyOsAppName.isEmpty()) {
435 fprintf(stderr,
"Error: 'harmonyos-app-name' not specified in JSON\n");
439 if (options->harmonyOsAppBundleName.isEmpty()) {
440 fprintf(stderr,
"Error: 'harmonyos-app-bundle-name' not specified in JSON\n");
445 if (options->outputDirectory.isEmpty()) {
447 options->outputDirectory = QDir::currentPath() +
"/harmonyos-tests-bundle"_L1;
449 QFileInfo appInfo(options->applicationBinary);
450 options->outputDirectory = QDir::currentPath() +
"/"_L1 +
451 appInfo.completeBaseName() +
"-harmonyos"_L1;
456 fprintf(stdout,
"Configuration loaded:\n");
458 fprintf(stdout,
" Mode: test bundle\n");
459 fprintf(stdout,
" Test binaries directory: %s\n", qPrintable(options->testBinariesDirectory));
460 if (!options->testExcludeList.isEmpty())
461 fprintf(stdout,
" Exclude list: %s\n", qPrintable(options->testExcludeList.join(
", "_L1)));
463 fprintf(stdout,
" Application binary: %s\n", qPrintable(options->applicationBinary));
465 fprintf(stdout,
" Template directory: %s\n", qPrintable(options->harmonyOsPackageSourceDirectory));
466 fprintf(stdout,
" App name: %s\n", qPrintable(options->harmonyOsAppName));
467 fprintf(stdout,
" Bundle name: %s\n", qPrintable(options->harmonyOsAppBundleName));
468 fprintf(stdout,
" Output directory: %s\n", qPrintable(options->outputDirectory));
469 fprintf(stdout,
" Target architectures: %s\n", qPrintable(options->targetArchs.join(
", "_L1)));
707 fprintf(stdout,
"Customizing template files\n");
710 QString qtAppConstantsPath = options.outputDirectory +
"/entry/src/main/ets/common/QtAppConstants.ets"_L1;
711 QFile qtAppConstantsFile(qtAppConstantsPath);
713 if (qtAppConstantsFile.exists()) {
714 if (!qtAppConstantsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
715 fprintf(stderr,
"Failed to open QtAppConstants.ets for reading\n");
719 QString content = QString::fromUtf8(qtAppConstantsFile.readAll());
720 qtAppConstantsFile.close();
726 appLibName =
"libtst_placeholder.so"_L1;
728 QFileInfo appInfo(options.applicationBinary);
729 appLibName = appInfo.fileName();
732 content.replace(QRegularExpression(
"APP_LIBRARY_NAME = '[^']*'"_L1),
733 "APP_LIBRARY_NAME = '"_L1 + appLibName +
"'"_L1);
735 if (!qtAppConstantsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
736 fprintf(stderr,
"Failed to open QtAppConstants.ets for writing\n");
740 qtAppConstantsFile.write(content.toUtf8());
741 qtAppConstantsFile.close();
744 fprintf(stdout,
" Updated QtAppConstants.ets with app name: %s\n", qPrintable(appLibName));
754 QString iconValue = options.harmonyOsAppIcon;
755 if (!iconValue.isEmpty() && !iconValue.startsWith(
"$media:"_L1)) {
756 QFileInfo iconInfo(iconValue);
757 if (!iconInfo.exists() || !iconInfo.isFile()) {
758 fprintf(stderr,
"App icon does not exist: %s\n", qPrintable(iconValue));
761 QString safeStem = iconInfo.completeBaseName();
762 for (QChar &c : safeStem) {
763 if (!c.isLetterOrNumber() && c != QLatin1Char(
'_'))
764 c = QLatin1Char(
'_');
766 const QString destFileName = iconInfo.suffix().isEmpty()
768 : safeStem +
"."_L1 + iconInfo.suffix();
769 const QStringList destDirs = {
770 options.outputDirectory +
"/AppScope/resources/base/media"_L1,
771 options.outputDirectory +
"/entry/src/main/resources/base/media"_L1,
773 for (
const QString &destDir : destDirs) {
774 QDir().mkpath(destDir);
775 const QString destPath = destDir +
"/"_L1 + destFileName;
776 if (!copyFileIfNewer(iconValue, destPath, options.verbose)) {
777 fprintf(stderr,
"Failed to copy app icon to: %s\n", qPrintable(destPath));
781 iconValue =
"$media:"_L1 + safeStem;
788 QString appJsonPath = options.outputDirectory +
"/AppScope/app.json5"_L1;
789 QFile appJsonFile(appJsonPath);
791 if (appJsonFile.exists()) {
792 if (!appJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
793 fprintf(stderr,
"Failed to open app.json5 for reading\n");
796 QString content = QString::fromUtf8(appJsonFile.readAll());
799 auto replaceStringField =
800 [&content](QLatin1StringView key,
const QString &value) {
804 QRegularExpression(
"\""_L1 + key +
"\":\\s*\"[^\"]*\""_L1),
805 "\""_L1 + key +
"\": \""_L1 + jsonStringEscape(value) +
"\""_L1);
808 replaceStringField(
"bundleName"_L1, options.harmonyOsAppBundleName);
809 replaceStringField(
"vendor"_L1, options.harmonyOsAppVendor);
810 replaceStringField(
"versionName"_L1, options.harmonyOsAppVersionName);
817 if (options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
818 replaceStringField(
"label"_L1, options.harmonyOsAppLabel);
819 replaceStringField(
"icon"_L1, iconValue);
823 QRegularExpression(
"\"versionCode\":\\s*\\d+"_L1),
824 "\"versionCode\": "_L1 + QString::number(options.harmonyOsAppVersionCode));
827 if (!appJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
828 fprintf(stderr,
"Failed to open app.json5 for writing\n");
831 appJsonFile.write(content.toUtf8());
835 fprintf(stdout,
" Updated app.json5 (bundle: %s)\n",
836 qPrintable(options.harmonyOsAppBundleName));
844 const QString displayLabel =
845 (!options.harmonyOsAppLabel.isEmpty()
846 && !options.harmonyOsAppLabel.startsWith(
"$string:"_L1))
847 ? options.harmonyOsAppLabel
848 : options.harmonyOsAppName;
853 QJsonArray transformedPermissions;
854 QList<PromotedReason> promotedReasons;
855 QSet<QString> seenPromotedIds;
856 for (
const QJsonValue &value : std::as_const(options.permissions)) {
857 if (!value.isObject()) {
858 transformedPermissions.append(value);
861 QJsonObject entry = value.toObject();
862 if (entry.contains(
"reason"_L1)) {
863 const QString reason = entry[
"reason"_L1].toString();
864 if (reasonNeedsPromotion(reason)) {
865 const QString permName = entry[
"name"_L1].toString();
866 const QString stringId = synthesizePermissionReasonId(permName);
867 entry[
"reason"_L1] = QString(
"$string:"_L1 + stringId);
868 if (!seenPromotedIds.contains(stringId)) {
869 promotedReasons.append({stringId, reason});
870 seenPromotedIds.insert(stringId);
874 transformedPermissions.append(entry);
880 QStringList locales = QStringList() <<
"base"_L1 <<
"en_US"_L1 <<
"zh_CN"_L1;
882 for (
const QString &locale : locales) {
883 QString stringJsonPath = options.outputDirectory +
"/entry/src/main/resources/"_L1 +
884 locale +
"/element/string.json"_L1;
885 QFile stringJsonFile(stringJsonPath);
887 if (!stringJsonFile.exists())
890 if (!stringJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
891 fprintf(stderr,
"Failed to open %s string.json for reading\n", qPrintable(locale));
895 const QByteArray bytes = stringJsonFile.readAll();
896 stringJsonFile.close();
898 QJsonParseError parseErr;
899 QJsonDocument doc = QJsonDocument::fromJson(bytes, &parseErr);
900 if (parseErr.error != QJsonParseError::NoError || !doc.isObject()) {
901 fprintf(stderr,
"Failed to parse %s string.json: %s\n",
902 qPrintable(locale), qPrintable(parseErr.errorString()));
905 QJsonObject root = doc.object();
906 QJsonArray strings = root[
"string"_L1].toArray();
909 QSet<QString> existingNames;
910 for (qsizetype i = 0; i < strings.size(); ++i) {
911 QJsonObject e = strings[i].toObject();
912 const QString name = e[
"name"_L1].toString();
913 existingNames.insert(name);
914 if (name ==
"QAbility_label"_L1) {
915 e[
"value"_L1] = displayLabel;
921 for (
const PromotedReason &p : std::as_const(promotedReasons)) {
922 if (existingNames.contains(p.id))
926 e[
"value"_L1] = p.value;
928 existingNames.insert(p.id);
930 root[
"string"_L1] = strings;
933 if (!stringJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
934 fprintf(stderr,
"Failed to open %s string.json for writing\n", qPrintable(locale));
938 stringJsonFile.write(doc.toJson(QJsonDocument::Indented));
939 stringJsonFile.close();
941 if (options.verbose) {
943 " Updated %s string.json (label: %s, +%lld promoted permission reasons)\n",
944 qPrintable(locale), qPrintable(displayLabel),
945 static_cast<
long long>(promotedReasons.size()));
950 QString appScopeStringPath = options.outputDirectory +
"/AppScope/resources/base/element/string.json"_L1;
951 QFile appScopeStringFile(appScopeStringPath);
953 if (appScopeStringFile.exists()) {
954 if (!appScopeStringFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
955 fprintf(stderr,
"Failed to open AppScope string.json for reading\n");
959 QString content = QString::fromUtf8(appScopeStringFile.readAll());
960 appScopeStringFile.close();
963 QRegularExpression appNameRegex(
"(\"name\":\\s*\"app_name\"[^}]*\"value\":\\s*)\"[^\"]*\""_L1);
964 content.replace(appNameRegex,
"\\1\""_L1 + jsonStringEscape(displayLabel) +
"\""_L1);
966 if (!appScopeStringFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
967 fprintf(stderr,
"Failed to open AppScope string.json for writing\n");
971 appScopeStringFile.write(content.toUtf8());
972 appScopeStringFile.close();
975 fprintf(stdout,
" Updated AppScope string.json with app name: %s\n",
976 qPrintable(displayLabel));
981 QString moduleJsonPath = options.outputDirectory +
"/entry/src/main/module.json5"_L1;
982 QFile moduleJsonFile(moduleJsonPath);
984 if (moduleJsonFile.exists()) {
985 if (!moduleJsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
986 fprintf(stderr,
"Failed to open module.json5 for reading\n");
990 QString content = QString::fromUtf8(moduleJsonFile.readAll());
991 moduleJsonFile.close();
997 const QString descriptionSentinel =
"%%INSERT_MODULE_DESCRIPTION%%"_L1;
998 const QString descriptionValue = options.harmonyOsModuleDescription.isEmpty()
999 ?
"$string:module_desc"_L1
1000 : jsonStringEscape(options.harmonyOsModuleDescription);
1001 content.replace(descriptionSentinel, descriptionValue);
1004 const QString deviceTypesSentinel =
"/* %%INSERT_DEVICE_TYPES%% */"_L1;
1005 QStringList deviceTypes = options.harmonyOsModuleDeviceTypes;
1006 if (deviceTypes.isEmpty())
1007 deviceTypes = QStringList{
"phone"_L1,
"tablet"_L1,
"2in1"_L1 };
1008 QStringList quotedDeviceTypes;
1009 quotedDeviceTypes.reserve(deviceTypes.size());
1010 for (
const QString &dt : std::as_const(deviceTypes))
1011 quotedDeviceTypes.append(
"\""_L1 + dt +
"\""_L1);
1012 content.replace(deviceTypesSentinel, quotedDeviceTypes.join(
", "_L1));
1021 const QString orientationSentinelLine =
1022 " /* %%INSERT_ABILITY_ORIENTATION%% */\n"_L1;
1023 QString orientationReplacement;
1024 if (!options.harmonyOsAbilityOrientation.isEmpty()) {
1025 if (isValidHarmonyOsAbilityOrientation(options.harmonyOsAbilityOrientation)) {
1026 orientationReplacement =
" \"orientation\": \""_L1
1027 + options.harmonyOsAbilityOrientation
1031 "Warning: Ignoring unknown harmonyos-ability-orientation value '%s'\n",
1032 qPrintable(options.harmonyOsAbilityOrientation));
1035 content.replace(orientationSentinelLine, orientationReplacement);
1041 if (!iconValue.isEmpty()) {
1043 QRegularExpression(
"\"icon\":\\s*\"\\$media:layered_image\""_L1),
1044 "\"icon\": \""_L1 + iconValue +
"\""_L1);
1051 const QString sentinel =
"/* %%INSERT_PERMISSIONS%% */"_L1;
1052 QString permissionsFragment;
1053 if (!transformedPermissions.isEmpty()) {
1054 QStringList entryStrings;
1055 entryStrings.reserve(transformedPermissions.size());
1056 for (
const QJsonValue &value : std::as_const(transformedPermissions)) {
1057 if (!value.isObject())
1059 const QJsonObject entry = value.toObject();
1063 const QByteArray pretty =
1064 QJsonDocument(entry).toJson(QJsonDocument::Indented).trimmed();
1065 const QStringList lines = QString::fromUtf8(pretty).split(QLatin1Char(
'\n'));
1066 QStringList indented;
1067 indented.reserve(lines.size());
1068 for (
const QString &line : lines)
1069 indented.append(
" "_L1 + line);
1070 entryStrings.append(indented.join(QLatin1Char(
'\n')));
1072 if (!entryStrings.isEmpty()) {
1073 permissionsFragment =
"\n"_L1 + entryStrings.join(
",\n"_L1) +
"\n "_L1;
1076 content.replace(sentinel, permissionsFragment);
1078 if (!moduleJsonFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1079 fprintf(stderr,
"Failed to open module.json5 for writing\n");
1083 moduleJsonFile.write(content.toUtf8());
1084 moduleJsonFile.close();
1087 fprintf(stdout,
" Updated module.json5 description\n");
1088 fprintf(stdout,
" Injected %lld permissions into module.json5\n",
1089 static_cast<
long long>(transformedPermissions.size()));
1102 const bool anySdkVersionSet =
1103 !options.harmonyOsCompatibleSdkVersion.isEmpty()
1104 || !options.harmonyOsTargetSdkVersion.isEmpty()
1105 || !options.harmonyOsCompileSdkVersion.isEmpty();
1107 const QString buildProfilePath =
1108 options.outputDirectory +
"/build-profile.json5"_L1;
1109 QFile buildProfileFile(buildProfilePath);
1110 if (anySdkVersionSet && buildProfileFile.exists()) {
1111 if (!buildProfileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1112 fprintf(stderr,
"Failed to open build-profile.json5 for reading\n");
1115 QString content = QString::fromUtf8(buildProfileFile.readAll());
1116 buildProfileFile.close();
1118 if (!options.harmonyOsCompatibleSdkVersion.isEmpty()) {
1121 "\"compatibleSdkVersion\":\\s*\"[^\"]*\""_L1),
1122 "\"compatibleSdkVersion\": \""_L1
1123 + jsonStringEscape(options.harmonyOsCompatibleSdkVersion)
1127 const QString targetSentinel =
"/* %%INSERT_TARGET_SDK_VERSION%% */"_L1;
1128 const QString targetReplacement =
1129 options.harmonyOsTargetSdkVersion.isEmpty()
1131 :
"\"targetSdkVersion\": \""_L1
1132 + jsonStringEscape(options.harmonyOsTargetSdkVersion)
1134 content.replace(targetSentinel, targetReplacement);
1136 const QString compileSentinel =
"/* %%INSERT_COMPILE_SDK_VERSION%% */"_L1;
1137 const QString compileReplacement =
1138 options.harmonyOsCompileSdkVersion.isEmpty()
1140 :
"\"compileSdkVersion\": \""_L1
1141 + jsonStringEscape(options.harmonyOsCompileSdkVersion)
1143 content.replace(compileSentinel, compileReplacement);
1145 if (!buildProfileFile.open(
1146 QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1147 fprintf(stderr,
"Failed to open build-profile.json5 for writing\n");
1150 buildProfileFile.write(content.toUtf8());
1151 buildProfileFile.close();
1155 " Updated build-profile.json5 SDK versions"
1156 " (compatible=%s, target=%s, compile=%s)\n",
1157 qPrintable(options.harmonyOsCompatibleSdkVersion),
1158 qPrintable(options.harmonyOsTargetSdkVersion),
1159 qPrintable(options.harmonyOsCompileSdkVersion));
1165 fprintf(stdout,
"Template customization completed\n");
1372 if (options.testBinariesDirectory.isEmpty()) {
1373 fprintf(stderr,
"Error: 'test-binaries-directory' not specified for test bundle mode\n");
1377 if (!QDir(options.testBinariesDirectory).exists()) {
1378 fprintf(stderr,
"Error: test-binaries-directory does not exist: %s\n",
1379 qPrintable(options.testBinariesDirectory));
1384 fprintf(stdout,
"Scanning for test binaries in %s\n", qPrintable(options.testBinariesDirectory));
1387 QStringList foundHelpers;
1388 QSet<QString> helperNames;
1392 const QStringList excludeDirs = { QFileInfo(options.outputDirectory).absoluteFilePath() };
1393 scanTestBinariesDir(options.testBinariesDirectory, options.testExcludeList, excludeDirs,
1394 found, foundHelpers, helperNames);
1396 if (found.isEmpty()) {
1397 fprintf(stderr,
"Warning: No test binaries (libtst_*.so) found in %s\n",
1398 qPrintable(options.testBinariesDirectory));
1403 fprintf(stdout,
"Found %lld test binaries\n",
static_cast<
long long>(found.size()));
1404 if (!foundHelpers.isEmpty())
1405 fprintf(stdout,
"Found %lld test helper libraries\n",
1406 static_cast<
long long>(foundHelpers.size()));
1410 for (
const QString &arch : options.targetArchs) {
1411 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
1412 QDir().mkpath(archLibPath);
1414 for (
const QString &testBinary : found) {
1415 QFileInfo testInfo(testBinary);
1416 QString destPath = archLibPath +
"/"_L1 + testInfo.fileName();
1418 if (options.verbose)
1419 fprintf(stdout,
" Copying test binary: %s\n", qPrintable(testInfo.fileName()));
1421 if (!copyFileIfNewer(testBinary, destPath, options.verbose)) {
1422 fprintf(stderr,
"Failed to copy test binary: %s\n", qPrintable(testBinary));
1427 for (
const QString &helperLib : foundHelpers) {
1428 QFileInfo helperInfo(helperLib);
1429 QString destPath = archLibPath +
"/"_L1 + helperInfo.fileName();
1431 if (options.verbose)
1432 fprintf(stdout,
" Copying test helper lib: %s\n", qPrintable(helperInfo.fileName()));
1434 if (!copyFileIfNewer(helperLib, destPath, options.verbose)) {
1435 fprintf(stderr,
"Failed to copy test helper lib: %s\n", qPrintable(helperLib));
1442 for (
const QString &testBinary : found) {
1443 QString fileName = QFileInfo(testBinary).fileName();
1444 if (!bundledBinaries.contains(fileName))
1445 bundledBinaries.append(fileName);
1449 if (!options.depFilePath.isEmpty()) {
1450 for (
const QString &testBinary : found)
1451 dependenciesForDepfile << testBinary;
1452 for (
const QString &helperLib : foundHelpers)
1453 dependenciesForDepfile << helperLib;
1491 if (options.qtLibsDirectory.isEmpty())
1494 if (!QDir(options.qtLibsDirectory).exists()) {
1496 fprintf(stdout,
"Qt libs directory not found, skipping: %s\n",
1497 qPrintable(options.qtLibsDirectory));
1503 fprintf(stdout,
"Copying all Qt libraries from %s\n", qPrintable(options.qtLibsDirectory));
1505 QDir libsDir(options.qtLibsDirectory);
1506 const QFileInfoList entries = libsDir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1508 for (
const QFileInfo &entry : entries) {
1509 for (
const QString &arch : options.targetArchs) {
1510 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + entry.fileName();
1511 QDir().mkpath(QFileInfo(destPath).absolutePath());
1512 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1515 if (!options.depFilePath.isEmpty())
1516 dependenciesForDepfile << entry.filePath();
1520 for (
const QString &arch : options.targetArchs) {
1521 QString stdCppPath = findStdCppLibrary(options, arch);
1522 if (!stdCppPath.isEmpty()) {
1523 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/libc++_shared.so"_L1;
1524 if (options.verbose)
1525 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
1526 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose))
1528 if (!options.depFilePath.isEmpty())
1529 dependenciesForDepfile << stdCppPath;
1534 for (
const QString &extraDir : options.extraLibsDirs) {
1536 if (!dir.exists()) {
1537 if (options.verbose)
1538 fprintf(stdout,
"Extra libs dir not found, skipping: %s\n", qPrintable(extraDir));
1542 if (options.verbose)
1543 fprintf(stdout,
"Copying extra libraries from %s\n", qPrintable(extraDir));
1545 const QFileInfoList entries = dir.entryInfoList({
"*.so"_L1,
"*.so.*"_L1}, QDir::Files);
1546 for (
const QFileInfo &entry : entries) {
1550 const QString soname = readElfSoname(options, entry.filePath());
1551 const QString deployName = (!soname.isEmpty() && soname != entry.fileName())
1552 ? soname : entry.fileName();
1553 for (
const QString &arch : options.targetArchs) {
1554 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + deployName;
1555 QDir().mkpath(QFileInfo(destPath).absolutePath());
1556 if (!copyFileIfNewer(entry.filePath(), destPath, options.verbose))
1559 if (!options.depFilePath.isEmpty())
1560 dependenciesForDepfile << entry.filePath();
1939 fprintf(stdout,
"Detecting Qt library dependencies\n");
1942 QStringList toProcess;
1943 toProcess.append(options.applicationBinary);
1947 for (
const QString &projectLib : options.projectLibraries)
1948 toProcess.append(projectLib);
1950 QList<QtDependency> qtDependencies;
1951 QStringList detectedQtModules;
1952 bool needsStdCpp =
false;
1954 while (!toProcess.isEmpty()) {
1955 QString currentLib = toProcess.takeFirst();
1957 if (processedLibs.contains(currentLib))
1960 processedLibs.insert(currentLib);
1963 fprintf(stdout,
" Analyzing: %s\n", qPrintable(QFileInfo(currentLib).fileName()));
1965 QStringList deps = readElfDependencies(options, currentLib);
1967 for (
const QString &dep : deps) {
1969 if (dep ==
"libc++_shared.so"_L1) {
1975 if (isSystemLibrary(dep))
1979 if (!dep.startsWith(
"libQt6"_L1) && !dep.startsWith(
"libqohos"_L1)) {
1981 QString extraDepPath = findExtraDepLibrary(options, dep);
1982 if (extraDepPath.isEmpty())
1987 if (!processedLibs.contains(extraDepPath) && !toProcess.contains(extraDepPath)) {
1988 if (options.verbose)
1989 fprintf(stdout,
" Found extra dep: %s\n", qPrintable(dep));
1990 QtDependency extraDep;
1991 extraDep.relativePath =
"lib/"_L1 + dep;
1992 extraDep.absolutePath = extraDepPath;
1993 qtDependencies.append(extraDep);
1994 toProcess.append(extraDepPath);
2000 if (dep.startsWith(
"libQt6"_L1)) {
2001 QString moduleName = dep.mid(6);
2002 if (moduleName.endsWith(
".so"_L1))
2004 if (!moduleName.isEmpty() && !detectedQtModules.contains(moduleName))
2005 detectedQtModules.append(moduleName);
2008 QString depPath = findQtLibrary(options, dep);
2009 if (depPath.isEmpty()) {
2010 if (options.verbose)
2011 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2015 if (processedLibs.contains(depPath))
2018 if (options.verbose)
2019 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2022 qtDep.relativePath =
"lib/"_L1 + dep;
2023 qtDep.absolutePath = depPath;
2024 qtDependencies.append(qtDep);
2027 toProcess.append(depPath);
2032 fprintf(stdout,
"Found %lld Qt library dependencies\n",
static_cast<
long long>(qtDependencies.size()));
2034 fprintf(stdout,
"C++ standard library required\n");
2039 for (
const QString &arch : options.targetArchs) {
2040 QString archLibPath = options.outputDirectory +
"/entry/libs/"_L1 + arch;
2041 QString stdCppPath = findStdCppLibrary(options, arch);
2042 if (stdCppPath.isEmpty()) {
2043 fprintf(stderr,
"Warning: Could not find C++ standard library for %s\n", qPrintable(arch));
2045 QString destPath = archLibPath +
"/libc++_shared.so"_L1;
2046 if (options.verbose)
2047 fprintf(stdout,
" Copying C++ standard library for %s\n", qPrintable(arch));
2048 if (!copyFileIfNewer(stdCppPath, destPath, options.verbose)) {
2050 "Failed to copy C++ standard library to: %s\n",
2051 qPrintable(destPath));
2056 if (!options.depFilePath.isEmpty())
2057 dependenciesForDepfile << stdCppPath;
2066 for (
const QtDependency &dep : qtDependencies) {
2067 QFileInfo libInfo(dep.relativePath);
2068 if (!copyFileToArchitectures(options, dep.absolutePath, libInfo.fileName(),
false))
2071 if (!options.depFilePath.isEmpty())
2072 dependenciesForDepfile << dep.absolutePath;
2300 QList<QmlImportInfo> imports;
2302 if (options.qmlRootPaths.isEmpty()) {
2305 "No QML root path specified, skipping QML import scanning\n");
2310 fprintf(stdout,
"Scanning for QML imports\n");
2313 QStringList searchPaths;
2314 if (!options.qtLibExecsDirectory.isEmpty())
2315 searchPaths.append(options.qtLibExecsDirectory);
2318 if (!options.qtHostDirectory.isEmpty())
2319 searchPaths.append(options.qtHostDirectory +
"/bin"_L1);
2321 QString qmlImportScannerPath =
2322 QStandardPaths::findExecutable(
"qmlimportscanner"_L1, searchPaths);
2325 if (qmlImportScannerPath.isEmpty()) {
2326 QDir dir(QFileInfo(options.applicationBinary).absolutePath());
2328 for (
int i = 0; i < 10; ++i) {
2329 qmlImportScannerPath = QStandardPaths::findExecutable(
2330 "qmlimportscanner"_L1,
2331 {dir.absoluteFilePath(
"libexec"_L1), dir.absoluteFilePath(
"bin"_L1)});
2333 if (!qmlImportScannerPath.isEmpty())
2341 if (qmlImportScannerPath.isEmpty()) {
2344 "Warning: qmlimportscanner not found, skipping QML import scanning\n");
2349 fprintf(stdout,
" Using qmlimportscanner: %s\n",
2350 qPrintable(qmlImportScannerPath));
2353 QStringList importPaths;
2356 for (
const QString &path : options.qmlImportPaths)
2357 if (QFile::exists(path))
2358 importPaths.append(path);
2363 QFileInfo appBinary(options.applicationBinary);
2364 QString appBuildDir = appBinary.absolutePath();
2365 if (QDir(appBuildDir).exists())
2366 importPaths.append(appBuildDir);
2369 if (!options.qtQmlDirectory.isEmpty() &&
2370 QDir(options.qtQmlDirectory).exists()) {
2371 importPaths.append(options.qtQmlDirectory);
2374 QFileInfo appBinary(options.applicationBinary);
2375 QDir qtDir(appBinary.absolutePath());
2376 for (
int i = 0; i < 10; ++i) {
2377 QString qmlDir = qtDir.absoluteFilePath(
"qml"_L1);
2378 if (QDir(qmlDir).exists()) {
2379 importPaths.append(qmlDir);
2383 qmlDir = qtDir.absoluteFilePath(
"qtbase/../qml"_L1);
2384 if (QDir(qmlDir).exists()) {
2385 importPaths.append(QDir::cleanPath(qmlDir));
2393 if (importPaths.isEmpty()) {
2394 fprintf(stderr,
"Warning: No QML import paths found\n");
2400 QStringList arguments;
2401 for (
const QString &rootPath : options.qmlRootPaths)
2402 arguments <<
"-rootPath"_L1 << rootPath;
2404 for (
const QString &importPath : importPaths)
2405 arguments <<
"-importPath"_L1 << importPath;
2408 fprintf(stdout,
" Root paths:\n");
2409 for (
const QString &rootPath : options.qmlRootPaths)
2410 fprintf(stdout,
" %s\n", qPrintable(rootPath));
2411 fprintf(stdout,
" Import paths:\n");
2412 for (
const QString &path : importPaths)
2413 fprintf(stdout,
" %s\n", qPrintable(path));
2418 process.start(qmlImportScannerPath, arguments);
2420 if (!process.waitForFinished(30000)) {
2421 fprintf(stderr,
"Error: qmlimportscanner timed out\n");
2425 if (process.exitCode() != 0) {
2426 fprintf(stderr,
"Error: qmlimportscanner failed with exit code %d\n",
2427 process.exitCode());
2428 fprintf(stderr,
"%s\n", process.readAllStandardError().constData());
2433 QByteArray output = process.readAllStandardOutput();
2434 QJsonDocument doc = QJsonDocument::fromJson(output);
2436 if (!doc.isArray()) {
2437 fprintf(stderr,
"Error: Invalid JSON output from qmlimportscanner\n");
2441 QJsonArray array = doc.array();
2442 for (
const QJsonValue &value : array) {
2443 if (!value.isObject())
2446 QJsonObject obj = value.toObject();
2448 info.name = obj[
"name"_L1].toString();
2449 info.path = obj[
"path"_L1].toString();
2450 info.type = obj[
"type"_L1].toString();
2452 if (obj.contains(
"plugin"_L1))
2453 info.plugin = obj[
"plugin"_L1].toString();
2455 if (obj.contains(
"pluginIsOptional"_L1))
2456 info.pluginIsOptional = obj[
"pluginIsOptional"_L1].toBool();
2458 if (obj.contains(
"prefer"_L1))
2459 info.prefer = obj[
"prefer"_L1].toString();
2462 if (obj.contains(
"components"_L1)) {
2463 QJsonArray componentsArray = obj[
"components"_L1].toArray();
2464 for (
const QJsonValue &comp : componentsArray)
2465 info.components.append(comp.toString());
2469 if (obj.contains(
"scripts"_L1)) {
2470 QJsonArray scriptsArray = obj[
"scripts"_L1].toArray();
2471 for (
const QJsonValue &script : scriptsArray)
2472 info.scripts.append(script.toString());
2476 if (info.path.isEmpty()) {
2477 if (options.verbose)
2478 fprintf(stdout,
" Warning: Could not resolve QML import: %s\n",
2479 qPrintable(info.name));
2484 if (info.type !=
"module"_L1)
2487 if (options.verbose)
2488 fprintf(stdout,
" Found QML import: %s at %s\n", qPrintable(info.name),
2489 qPrintable(info.path));
2491 imports.append(info);
2536 const QList<QmlImportInfo> &imports,
2537 QSet<QString> &processedLibs)
2539 if (imports.isEmpty())
2543 fprintf(stdout,
"Copying QML imports\n");
2547 QString qmlDestBase = options.outputDirectory +
"/entry/src/main/resources/resfile/qml"_L1;
2548 QDir().mkpath(qmlDestBase);
2551 QStringList qmlPluginsToScan;
2553 for (
const QmlImportInfo &import : imports) {
2554 if (options.verbose)
2555 fprintf(stdout,
" Copying QML module: %s\n", qPrintable(import.name));
2559 QString relativePath;
2562 if (!options.qtQmlDirectory.isEmpty() && import.path.startsWith(options.qtQmlDirectory)) {
2564 relativePath = import.path.mid(options.qtQmlDirectory.length());
2565 if (relativePath.startsWith(
'/'_L1))
2566 relativePath = relativePath.mid(1);
2570 relativePath = import.name;
2571 relativePath.replace(
'.'_L1,
'/'_L1);
2574 QString destModuleDir = qmlDestBase +
"/"_L1 + relativePath;
2575 QDir().mkpath(destModuleDir);
2578 QString qmldirSrc = import.path +
"/qmldir"_L1;
2579 QString qmldirDest = destModuleDir +
"/qmldir"_L1;
2580 if (QFile::exists(qmldirSrc)) {
2581 if (copyFileIfNewer(qmldirSrc, qmldirDest, options.verbose)) {
2582 if (options.verbose)
2583 fprintf(stdout,
" Copied qmldir\n");
2586 if (!options.depFilePath.isEmpty())
2587 dependenciesForDepfile << qmldirSrc;
2589 fprintf(stderr,
"Warning: Failed to copy qmldir for %s\n",
2590 qPrintable(import.name));
2597 bool qmlFilesAreEmbedded = import.prefer.startsWith(
":/"_L1);
2599 if (!import.plugin.isEmpty()) {
2601 QString pluginFileName =
"lib"_L1 + import.plugin +
".so"_L1;
2602 QString pluginSrc = import.path +
"/"_L1 + pluginFileName;
2604 if (QFile::exists(pluginSrc)) {
2606 for (
const QString &arch : options.targetArchs) {
2607 QString pluginDest = options.outputDirectory +
"/entry/libs/"_L1 + arch
2608 +
"/"_L1 + pluginFileName;
2610 if (copyFileIfNewer(pluginSrc, pluginDest, options.verbose)) {
2611 if (options.verbose)
2612 fprintf(stdout,
" Copied plugin to libs/%s: %s\n",
2613 qPrintable(arch), qPrintable(pluginFileName));
2614 processedLibs.insert(pluginSrc);
2617 if (!qmlPluginsToScan.contains(pluginSrc))
2618 qmlPluginsToScan.append(pluginSrc);
2621 if (!options.depFilePath.isEmpty())
2622 dependenciesForDepfile << pluginSrc;
2623 }
else if (!import.pluginIsOptional) {
2624 fprintf(stderr,
"Warning: Failed to copy required plugin: %s\n",
2625 qPrintable(pluginFileName));
2628 }
else if (!import.pluginIsOptional) {
2629 if (options.verbose)
2630 fprintf(stdout,
" Warning: Required plugin not found: %s\n",
2631 qPrintable(pluginFileName));
2636 if (!qmlFilesAreEmbedded) {
2637 for (
const QString &component : import.components) {
2638 QFileInfo compInfo(component);
2639 if (!compInfo.exists()) {
2640 if (options.verbose)
2641 fprintf(stdout,
" Warning: Component file not found: %s\n",
2642 qPrintable(component));
2646 QString relativePath = component.mid(import.path.length());
2647 if (relativePath.startsWith(
'/'_L1))
2648 relativePath = relativePath.mid(1);
2650 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2651 QFileInfo destInfo(destFile);
2652 QDir().mkpath(destInfo.absolutePath());
2654 if (copyFileIfNewer(component, destFile, options.verbose)) {
2655 if (options.verbose)
2656 fprintf(stdout,
" Copied component: %s\n", qPrintable(compInfo.fileName()));
2659 if (!options.depFilePath.isEmpty())
2660 dependenciesForDepfile << component;
2665 for (
const QString &script : import.scripts) {
2666 QFileInfo scriptInfo(script);
2667 if (!scriptInfo.exists())
2670 QString relativePath = script.mid(import.path.length());
2671 if (relativePath.startsWith(
'/'_L1))
2672 relativePath = relativePath.mid(1);
2674 QString destFile = destModuleDir +
"/"_L1 + relativePath;
2675 QFileInfo destInfo(destFile);
2676 QDir().mkpath(destInfo.absolutePath());
2678 if (copyFileIfNewer(script, destFile, options.verbose)) {
2679 if (options.verbose)
2680 fprintf(stdout,
" Copied script: %s\n", qPrintable(scriptInfo.fileName()));
2683 if (!options.depFilePath.isEmpty())
2684 dependenciesForDepfile << script;
2688 if (options.verbose)
2689 fprintf(stdout,
" Skipping QML files (embedded in resources)\n");
2694 if (!qmlPluginsToScan.isEmpty()) {
2696 fprintf(stdout,
"Scanning QML plugin dependencies\n");
2698 QStringList toProcess = qmlPluginsToScan;
2699 while (!toProcess.isEmpty()) {
2700 QString pluginPath = toProcess.takeFirst();
2703 fprintf(stdout,
" Scanning plugin: %s\n", qPrintable(QFileInfo(pluginPath).fileName()));
2705 QStringList deps = readElfDependencies(options, pluginPath);
2706 for (
const QString &dep : deps) {
2708 if (isSystemLibrary(dep))
2713 if (dep.startsWith(
"libQt6"_L1)) {
2714 depPath = findQtLibrary(options, dep);
2715 if (depPath.isEmpty()) {
2716 if (options.verbose)
2717 fprintf(stdout,
" Warning: Could not find Qt library: %s\n", qPrintable(dep));
2720 }
else if (!options.extraLibsDirs.isEmpty()) {
2721 depPath = findExtraDepLibrary(options, dep);
2722 if (depPath.isEmpty())
2728 if (processedLibs.contains(depPath))
2731 if (options.verbose)
2732 fprintf(stdout,
" Found dependency: %s\n", qPrintable(dep));
2735 for (
const QString &arch : options.targetArchs) {
2736 QString destPath = options.outputDirectory +
"/entry/libs/"_L1 + arch +
"/"_L1 + dep;
2737 if (copyFileIfNewer(depPath, destPath, options.verbose)) {
2738 if (options.verbose)
2739 fprintf(stdout,
" Copied %s to libs/%s\n", qPrintable(dep), qPrintable(arch));
2742 if (!options.depFilePath.isEmpty())
2743 dependenciesForDepfile << depPath;
2747 processedLibs.insert(depPath);
2749 toProcess.append(depPath);
2755 fprintf(stdout,
"QML imports copied successfully\n");
2767 const char *envName;
2768 const char *cliFlag;
2770 QByteArray envValue;
2772 QByteArray resolved()
const
2774 if (!cliValue.isEmpty())
2775 return cliValue.toUtf8();
2778 QString sourceLabel()
const
2780 return cliValue.isEmpty()
2781 ? QString::fromLatin1(envName)
2782 : QString::fromLatin1(cliFlag);
2786 Field required[] = {
2787 {
"QT_HARMONYOS_SIGNING_CERT_PATH",
"--signing-cert-path",
2788 options.signingCertPath, qgetenv(
"QT_HARMONYOS_SIGNING_CERT_PATH") },
2789 {
"QT_HARMONYOS_SIGNING_PROFILE",
"--signing-profile",
2790 options.signingProfile, qgetenv(
"QT_HARMONYOS_SIGNING_PROFILE") },
2791 {
"QT_HARMONYOS_SIGNING_STORE_FILE",
"--signing-store-file",
2792 options.signingStoreFile, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_FILE") },
2793 {
"QT_HARMONYOS_SIGNING_KEY_ALIAS",
"--signing-key-alias",
2794 options.signingKeyAlias, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_ALIAS") },
2795 {
"QT_HARMONYOS_SIGNING_KEY_PASSWORD",
"--signing-key-password",
2796 options.signingKeyPassword, qgetenv(
"QT_HARMONYOS_SIGNING_KEY_PASSWORD") },
2797 {
"QT_HARMONYOS_SIGNING_STORE_PASSWORD",
"--signing-store-password",
2798 options.signingStorePassword, qgetenv(
"QT_HARMONYOS_SIGNING_STORE_PASSWORD") },
2801 QByteArray signAlg = options.signingAlg.isEmpty()
2802 ? qgetenv(
"QT_HARMONYOS_SIGNING_ALG")
2803 : options.signingAlg.toUtf8();
2805 bool anySet = !signAlg.isEmpty();
2806 for (
const Field &f : required)
2807 anySet = anySet || !f.resolved().isEmpty();
2811 QStringList missing;
2812 for (
const Field &f : required) {
2813 if (f.resolved().isEmpty())
2814 missing << QString::fromLatin1(f.cliFlag) +
" / "_L1
2815 + QString::fromLatin1(f.envName);
2817 if (!missing.isEmpty()) {
2818 fprintf(stderr,
"Error: HAP signing requested, but the following required input(s)\n"
2819 " are missing (neither CLI flag nor env var was supplied):\n");
2820 for (
const QString &name : missing)
2821 fprintf(stderr,
" %s\n", qPrintable(name));
2825 if (signAlg.isEmpty())
2826 signAlg =
"SHA256withECDSA";
2828 const QString buildProfilePath = options.outputDirectory +
"/build-profile.json5"_L1;
2829 QFile profileFile(buildProfilePath);
2830 if (!profileFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2831 fprintf(stderr,
"Error: cannot open %s for reading: %s\n",
2832 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
2835 QByteArray content = profileFile.readAll();
2836 profileFile.close();
2840 static const QByteArray needle =
"\"signingConfigs\": []";
2841 const int idx = content.indexOf(needle);
2843 fprintf(stderr,
"Error: '%s' not found in %s. The template may have been modified;\n"
2844 " cannot inject signing configuration safely.\n",
2845 needle.constData(), qPrintable(buildProfilePath));
2851 auto escapeJsonString = [](
const QByteArray &in) {
2853 out.reserve(in.size());
2855 if (c ==
'\\' || c ==
'"')
2862 QByteArray replacement;
2863 replacement.append(
"\"signingConfigs\": [\n");
2864 replacement.append(
" {\n");
2865 replacement.append(
" \"name\": \"default\",\n");
2866 replacement.append(
" \"type\": \"HarmonyOS\",\n");
2867 replacement.append(
" \"material\": {\n");
2868 auto appendField = [&](
const char *key,
const QByteArray &value,
bool last) {
2869 replacement.append(
" \"");
2870 replacement.append(key);
2871 replacement.append(
"\": \"");
2872 replacement.append(escapeJsonString(value));
2873 replacement.append(
'"');
2875 replacement.append(
',');
2876 replacement.append(
'\n');
2878 appendField(
"certpath", required[0].resolved(),
false);
2879 appendField(
"keyAlias", required[3].resolved(),
false);
2880 appendField(
"keyPassword", required[4].resolved(),
false);
2881 appendField(
"profile", required[1].resolved(),
false);
2882 appendField(
"signAlg", signAlg,
false);
2883 appendField(
"storeFile", required[2].resolved(),
false);
2884 appendField(
"storePassword", required[5].resolved(),
true);
2885 replacement.append(
" }\n");
2886 replacement.append(
" }\n");
2887 replacement.append(
" ]");
2889 content.replace(idx, needle.size(), replacement);
2891 if (!profileFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
2892 fprintf(stderr,
"Error: cannot open %s for writing: %s\n",
2893 qPrintable(buildProfilePath), qPrintable(profileFile.errorString()));
2896 if (profileFile.write(content) != content.size()) {
2897 fprintf(stderr,
"Error: failed to write full content to %s\n",
2898 qPrintable(buildProfilePath));
2899 profileFile.close();
2902 profileFile.close();
2905 fprintf(stdout,
"Injected HAP signingConfig into %s\n", qPrintable(buildProfilePath));
2906 fprintf(stdout,
" certpath: %s (from %s)\n",
2907 required[0].resolved().constData(), qPrintable(required[0].sourceLabel()));
2908 fprintf(stdout,
" profile: %s (from %s)\n",
2909 required[1].resolved().constData(), qPrintable(required[1].sourceLabel()));
2910 fprintf(stdout,
" storeFile: %s (from %s)\n",
2911 required[2].resolved().constData(), qPrintable(required[2].sourceLabel()));
2912 fprintf(stdout,
" keyAlias: %s (from %s)\n",
2913 required[3].resolved().constData(), qPrintable(required[3].sourceLabel()));
2914 fprintf(stdout,
" signAlg: %s\n", signAlg.constData());