summaryrefslogtreecommitdiffstats
path: root/src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/androiddeployqt/main.cpp745
-rw-r--r--src/tools/bootstrap/CMakeLists.txt6
-rw-r--r--src/tools/moc/CMakeLists.txt10
-rw-r--r--src/tools/moc/generator.cpp2
-rw-r--r--src/tools/moc/main.cpp69
-rw-r--r--src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp2
-rw-r--r--src/tools/qlalr/cppgenerator.cpp5
-rw-r--r--src/tools/rcc/rcc.cpp18
-rw-r--r--src/tools/rcc/rcc.h4
-rw-r--r--src/tools/syncqt/CMakeLists.txt2
-rw-r--r--src/tools/syncqt/main.cpp15
-rw-r--r--src/tools/uic/cpp/cppwriteinitialization.cpp61
-rw-r--r--src/tools/uic/ui4.cpp3
-rw-r--r--src/tools/uic/uic.cpp4
-rw-r--r--src/tools/windeployqt/main.cpp22
15 files changed, 773 insertions, 195 deletions
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index 6125b405b5..f9a645aadb 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -20,6 +20,7 @@
#include <QHash>
#include <QSet>
#include <QMap>
+#include <QProcess>
#include <depfile_shared.h>
#include <shellquote_shared.h>
@@ -44,15 +45,16 @@ static const bool mustReadOutputAnyway = true; // pclose seems to return the wro
static QStringList dependenciesForDepfile;
-FILE *openProcess(const QString &command)
+auto openProcess(const QString &command)
{
#if defined(Q_OS_WIN32)
QString processedCommand = u'\"' + command + u'\"';
#else
const QString& processedCommand = command;
#endif
-
- return popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ);
+ struct Closer { void operator()(FILE *proc) const { if (proc) (void)pclose(proc); } };
+ using UP = std::unique_ptr<FILE, Closer>;
+ return UP{popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ)};
}
struct QtDependency
@@ -104,6 +106,9 @@ struct Options
, installApk(false)
, uninstallApk(false)
, qmlImportScannerBinaryPath()
+ , buildAar(false)
+ , qmlDomBinaryPath()
+ , generateJavaQmlComponents(false)
{}
enum DeploymentMechanism
@@ -163,7 +168,7 @@ struct Options
// Versioning
QString versionName;
QString versionCode;
- QByteArray minSdkVersion{"23"};
+ QByteArray minSdkVersion{"28"};
QByteArray targetSdkVersion{"34"};
// lib c++ path
@@ -239,6 +244,10 @@ struct Options
// Override qml import scanner path
QString qmlImportScannerBinaryPath;
bool qmlSkipImportScanning = false;
+ bool buildAar;
+ QString qmlDomBinaryPath;
+ bool generateJavaQmlComponents;
+ QSet<QString> selectedJavaQmlComponents;
};
static const QHash<QByteArray, QByteArray> elfArchitectures = {
@@ -310,23 +319,21 @@ QString fileArchitecture(const Options &options, const QString &path)
readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(path));
- FILE *readElfCommand = openProcess(readElf);
+ auto readElfCommand = openProcess(readElf);
if (!readElfCommand) {
fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
return {};
}
char buffer[512];
- while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
+ while (fgets(buffer, sizeof(buffer), readElfCommand.get()) != nullptr) {
QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
line = line.trimmed();
if (line.startsWith("Arch: ")) {
auto it = elfArchitectures.find(line.mid(6));
- pclose(readElfCommand);
return it != elfArchitectures.constEnd() ? QString::fromLatin1(it.value()) : QString{};
}
}
- pclose(readElfCommand);
return {};
}
@@ -527,6 +534,8 @@ Options parseOptions()
options.protectedAuthenticationPath = true;
} else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
options.auxMode = true;
+ } else if (argument.compare("--build-aar"_L1, Qt::CaseInsensitive) == 0) {
+ options.buildAar = true;
} else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
} else if (argument.compare("--no-rcc-bundle-cleanup"_L1,
@@ -538,6 +547,23 @@ Options parseOptions()
}
}
+ if (options.buildAar) {
+ if (options.installApk || options.uninstallApk) {
+ fprintf(stderr, "Warning: Skipping %s, AAR packages are not installable.\n",
+ options.uninstallApk ? "--reinstall" : "--install");
+ options.installApk = false;
+ options.uninstallApk = false;
+ }
+ if (options.buildAAB) {
+ fprintf(stderr, "Warning: Skipping -aab as --build-aar is present.\n");
+ options.buildAAB = false;
+ }
+ if (!options.keyStore.isEmpty()) {
+ fprintf(stderr, "Warning: Skipping --sign, signing AAR packages is not supported.\n");
+ options.keyStore.clear();
+ }
+ }
+
if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
options.helpRequested = true;
@@ -615,7 +641,7 @@ Optional arguments:
--tsa <url>: Location of the Time Stamping Authority.
--tsacert <alias>: Public key certificate for TSA.
--internalsf: Include the .SF file inside the signature block.
- --sectionsonly: Don't compute hash of entire manifest.
+ --sectionsonly: Do not compute hash of entire manifest.
--protected: Keystore has protected authentication path.
--jarsigner: Deprecated, ignored.
@@ -645,6 +671,9 @@ Optional arguments:
--apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
+ --build-aar: Build an AAR package. This option skips --aab, --install,
+ --reinstall, and --sign options if they are provided.
+
--qml-importscanner-binary <path/to/qmlimportscanner>: Override the
default qmlimportscanner binary path. By default the
qmlimportscanner binary is located using the Qt directory
@@ -658,7 +687,7 @@ Optional arguments:
--no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
running androiddeployqt. This option simplifies debugging of
the resource bundle content, but it should not be used when deploying
- a project, since it litters the 'assets' directory.
+ a project, since it litters the "assets" directory.
--copy-dependencies-only: resolve application dependencies and stop
deploying process after all libraries and resources that the
@@ -732,18 +761,72 @@ bool copyFileIfNewer(const QString &sourceFileName,
return true;
}
-QString cleanPackageName(QString packageName)
+struct GradleBuildConfigs {
+ QString appNamespace;
+ bool setsLegacyPackaging = false;
+ bool usesIntegerCompileSdkVersion = false;
+};
+
+GradleBuildConfigs gradleBuildConfigs(const QString &path)
+{
+ GradleBuildConfigs configs;
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ return configs;
+
+ auto isComment = [](const QByteArray &trimmed) {
+ return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
+ };
+
+ auto extractValue = [](const QByteArray &trimmed) {
+ int idx = trimmed.indexOf('=');
+
+ if (idx == -1)
+ idx = trimmed.indexOf(' ');
+
+ if (idx > -1)
+ return trimmed.mid(idx + 1).trimmed();
+
+ return QByteArray();
+ };
+
+ const auto lines = file.readAll().split('\n');
+ for (const auto &line : lines) {
+ const QByteArray trimmedLine = line.trimmed();
+ if (isComment(trimmedLine))
+ continue;
+ if (trimmedLine.contains("useLegacyPackaging")) {
+ configs.setsLegacyPackaging = true;
+ } else if (trimmedLine.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) {
+ configs.usesIntegerCompileSdkVersion = true;
+ } else if (trimmedLine.contains("namespace")) {
+ configs.appNamespace = QString::fromUtf8(extractValue(trimmedLine));
+ }
+ }
+
+ return configs;
+}
+
+QString cleanPackageName(QString packageName, bool *cleaned = nullptr)
{
auto isLegalChar = [] (QChar c) -> bool {
ushort ch = c.unicode();
return (ch >= '0' && ch <= '9') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
- ch == '.';
+ ch == '.' || ch == '_';
};
+
+ if (cleaned)
+ *cleaned = false;
+
for (QChar &c : packageName) {
- if (!isLegalChar(c))
+ if (!isLegalChar(c)) {
c = u'_';
+ if (cleaned)
+ *cleaned = true;
+ }
}
static QStringList keywords;
@@ -778,12 +861,16 @@ QString cleanPackageName(QString packageName)
QChar c = word[0];
if ((c >= u'0' && c <= u'9') || c == u'_') {
packageName.insert(index + 1, u'a');
+ if (cleaned)
+ *cleaned = true;
index = next + 1;
continue;
}
}
if (keywords.contains(word)) {
packageName.insert(next, "_"_L1);
+ if (cleaned)
+ *cleaned = true;
index = next + 1;
} else {
index = next;
@@ -813,18 +900,31 @@ QString detectLatestAndroidPlatform(const QString &sdkPath)
return latestPlatform.baseName();
}
-QString packageNameFromAndroidManifest(const QString &androidManifestPath)
+QString extractPackageName(Options *options)
{
- QFile androidManifestXml(androidManifestPath);
+ {
+ const QString gradleBuildFile = options->androidSourceDirectory + "/build.gradle"_L1;
+ QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace;
+
+ if (!packageName.isEmpty() && packageName != "androidPackageName"_L1)
+ return packageName;
+ }
+
+ QFile androidManifestXml(options->androidSourceDirectory + "/AndroidManifest.xml"_L1);
if (androidManifestXml.open(QIODevice::ReadOnly)) {
QXmlStreamReader reader(&androidManifestXml);
while (!reader.atEnd()) {
reader.readNext();
- if (reader.isStartElement() && reader.name() == "manifest"_L1)
- return cleanPackageName(reader.attributes().value("package"_L1).toString());
+ if (reader.isStartElement() && reader.name() == "manifest"_L1) {
+ QString packageName = reader.attributes().value("package"_L1).toString();
+ if (!packageName.isEmpty() && packageName != "org.qtproject.example"_L1)
+ return packageName;
+ break;
+ }
}
}
- return {};
+
+ return QString();
}
bool parseCmakeBoolean(const QJsonValue &value)
@@ -1202,6 +1302,40 @@ bool readInputFile(Options *options)
}
{
+ const QJsonValue genJavaQmlComponents = jsonObject.value("generate-java-qml-components"_L1);
+ if (!genJavaQmlComponents.isUndefined() && genJavaQmlComponents.isBool()) {
+ options->generateJavaQmlComponents = genJavaQmlComponents.toBool(false);
+ if (options->generateJavaQmlComponents && !options->buildAar) {
+ fprintf(stderr,
+ "Warning: Skipping the generation of Java components from QML as it can be "
+ "enabled only for an AAR target.\n");
+ options->generateJavaQmlComponents = false;
+ }
+ }
+ }
+
+ {
+ const QJsonValue qmlDomBinaryPath = jsonObject.value("qml-dom-binary"_L1);
+ if (!qmlDomBinaryPath.isUndefined()) {
+ options->qmlDomBinaryPath = qmlDomBinaryPath.toString();
+ } else if (options->generateJavaQmlComponents) {
+ fprintf(stderr,
+ "No qmldom binary defined in json file which is required when "
+ "building with QT_ANDROID_GENERATE_JAVA_QML_COMPONENTS flag.\n");
+ return false;
+ }
+ }
+
+ {
+ const QJsonValue qmlFiles = jsonObject.value("qml-files-for-code-generator"_L1);
+ if (!qmlFiles.isUndefined() && qmlFiles.isArray()) {
+ const QJsonArray jArray = qmlFiles.toArray();
+ for (auto &item : jArray)
+ options->selectedJavaQmlComponents << item.toString();
+ }
+ }
+
+ {
const QJsonValue applicationBinary = jsonObject.value("application-binary"_L1);
if (applicationBinary.isUndefined()) {
fprintf(stderr, "No application binary defined in json file.\n");
@@ -1222,6 +1356,24 @@ bool readInputFile(Options *options)
}
{
+ const QJsonValue androidPackageName = jsonObject.value("android-package-name"_L1);
+ const QString extractedPackageName = extractPackageName(options);
+ if (!extractedPackageName.isEmpty())
+ options->packageName = extractedPackageName;
+ else if (!androidPackageName.isUndefined())
+ options->packageName = androidPackageName.toString();
+ else
+ options->packageName = "org.qtproject.example.%1"_L1.arg(options->applicationBinary);
+
+ bool cleaned;
+ options->packageName = cleanPackageName(options->packageName, &cleaned);
+ if (cleaned) {
+ fprintf(stderr, "Warning: Package name contained illegal characters and was cleaned "
+ "to \"%s\"\n", qPrintable(options->packageName));
+ }
+ }
+
+ {
using ItFlag = QDirListing::IteratorFlag;
const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1);
if (!deploymentDependencies.isUndefined()) {
@@ -1278,9 +1430,6 @@ bool readInputFile(Options *options)
options->isZstdCompressionEnabled = zstdCompressionFlag.toBool();
}
}
- options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + "/AndroidManifest.xml"_L1);
- if (options->packageName.isEmpty())
- options->packageName = cleanPackageName("org.qtproject.example.%1"_L1.arg(options->applicationBinary));
return true;
}
@@ -1380,6 +1529,9 @@ bool copyAndroidTemplate(const Options &options)
if (!copyAndroidTemplate(options, "/src/android/templates"_L1))
return false;
+ if (options.buildAar)
+ return copyAndroidTemplate(options, "/src/android/templates_aar"_L1);
+
return true;
}
@@ -1735,16 +1887,10 @@ bool updateAndroidManifest(Options &options)
reader.readNext();
if (reader.isStartElement()) {
- if (reader.name() == "manifest"_L1) {
- if (!reader.attributes().hasAttribute("package"_L1)) {
- fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath));
- return false;
- }
- options.packageName = reader.attributes().value("package"_L1).toString();
- } else if (reader.name() == "uses-sdk"_L1) {
+ if (reader.name() == "uses-sdk"_L1) {
if (reader.attributes().hasAttribute("android:minSdkVersion"_L1))
- if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 23) {
- fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 23\n");
+ if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 28) {
+ fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 28\n");
return false;
}
} else if ((reader.name() == "application"_L1 ||
@@ -2014,7 +2160,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
- FILE *readElfCommand = openProcess(readElf);
+ auto readElfCommand = openProcess(readElf);
if (!readElfCommand) {
fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
return QStringList();
@@ -2024,7 +2170,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
bool readLibs = false;
char buffer[512];
- while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
+ while (fgets(buffer, sizeof(buffer), readElfCommand.get()) != nullptr) {
QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
QString library;
line = line.trimmed();
@@ -2034,7 +2180,6 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
if (options.verbose)
fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
- pclose(readElfCommand);
return {};
}
}
@@ -2049,8 +2194,6 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
ret += libraryName;
}
- pclose(readElfCommand);
-
return ret;
}
@@ -2188,7 +2331,7 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
qmlImportScanner.toLocal8Bit().constData());
}
- FILE *qmlImportScannerCommand = popen(qmlImportScanner.toLocal8Bit().constData(), QT_POPEN_READ);
+ auto qmlImportScannerCommand = openProcess(qmlImportScanner);
if (qmlImportScannerCommand == 0) {
fprintf(stderr, "Couldn't run qmlimportscanner.\n");
return false;
@@ -2196,13 +2339,12 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
QByteArray output;
char buffer[512];
- while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand) != 0)
+ while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand.get()) != nullptr)
output += QByteArray(buffer, qstrlen(buffer));
QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
if (jsonDocument.isNull()) {
fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
- pclose(qmlImportScannerCommand);
return false;
}
@@ -2211,7 +2353,6 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
QJsonValue value = jsonArray.at(i);
if (!value.isObject()) {
fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
- pclose(qmlImportScannerCommand);
return false;
}
@@ -2257,7 +2398,6 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
if (importPathOfThisImport.isEmpty()) {
fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
- pclose(qmlImportScannerCommand);
return false;
}
@@ -2325,7 +2465,6 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
}
}
- pclose(qmlImportScannerCommand);
return true;
}
@@ -2344,17 +2483,17 @@ bool runCommand(const Options &options, const QString &command)
if (options.verbose)
fprintf(stdout, "Running command '%s'\n", qPrintable(command));
- FILE *runCommand = openProcess(command);
+ auto runCommand = openProcess(command);
if (runCommand == nullptr) {
fprintf(stderr, "Cannot run command '%s'\n", qPrintable(command));
return false;
}
char buffer[4096];
- while (fgets(buffer, sizeof(buffer), runCommand) != nullptr) {
+ while (fgets(buffer, sizeof(buffer), runCommand.get()) != nullptr) {
if (options.verbose)
fprintf(stdout, "%s", buffer);
}
- pclose(runCommand);
+ runCommand.reset();
fflush(stdout);
fflush(stderr);
return true;
@@ -2505,7 +2644,8 @@ bool containsApplicationBinary(Options *options)
return true;
}
-FILE *runAdb(const Options &options, const QString &arguments)
+auto runAdb(const Options &options, const QString &arguments)
+ -> decltype(openProcess({}))
{
QString adb = execSuffixAppended(options.sdkPath + "/platform-tools/adb"_L1);
if (!QFile::exists(adb)) {
@@ -2521,7 +2661,7 @@ FILE *runAdb(const Options &options, const QString &arguments)
if (options.verbose)
fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
- FILE *adbCommand = openProcess(adb);
+ auto adbCommand = openProcess(adb);
if (adbCommand == 0) {
fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
return 0;
@@ -2745,38 +2885,6 @@ void checkAndWarnGradleLongPaths(const QString &outputDirectory)
}
#endif
-struct GradleFlags {
- bool setsLegacyPackaging = false;
- bool usesIntegerCompileSdkVersion = false;
-};
-
-GradleFlags gradleBuildFlags(const QString &path)
-{
- GradleFlags flags;
-
- QFile file(path);
- if (!file.open(QIODevice::ReadOnly))
- return flags;
-
- auto isComment = [](const QByteArray &line) {
- const auto trimmed = line.trimmed();
- return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
- };
-
- const auto lines = file.readAll().split('\n');
- for (const auto &line : lines) {
- if (isComment(line))
- continue;
- if (line.contains("useLegacyPackaging")) {
- flags.setsLegacyPackaging = true;
- } else if (line.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) {
- flags.usesIntegerCompileSdkVersion = true;
- }
- }
-
- return flags;
-}
-
bool buildAndroidProject(const Options &options)
{
GradleProperties localProperties;
@@ -2789,8 +2897,8 @@ bool buildAndroidProject(const Options &options)
GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1;
- GradleFlags gradleFlags = gradleBuildFlags(gradleBuildFilePath);
- if (!gradleFlags.setsLegacyPackaging)
+ GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
+ if (!gradleConfigs.setsLegacyPackaging)
gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
gradleProperties["buildDir"] = "build";
@@ -2809,7 +2917,7 @@ bool buildAndroidProject(const Options &options)
QByteArray sdkPlatformVersion;
// Provide the integer version only if build.gradle explicitly converts to Integer,
// to avoid regression to existing projects that build for sdk platform of form android-xx.
- if (gradleFlags.usesIntegerCompileSdkVersion) {
+ if (gradleConfigs.usesIntegerCompileSdkVersion) {
const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit();
bool ok;
tmp.toInt(&ok);
@@ -2824,6 +2932,7 @@ bool buildAndroidProject(const Options &options)
if (sdkPlatformVersion.isEmpty())
sdkPlatformVersion = options.androidPlatform.toLocal8Bit();
+ gradleProperties["androidPackageName"] = options.packageName.toLocal8Bit();
gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion;
gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
@@ -2839,6 +2948,9 @@ bool buildAndroidProject(const Options &options)
abiList.append(it.key());
}
gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
+ gradleProperties["qtGradlePluginType"] = options.buildAar
+ ? "com.android.library"
+ : "com.android.application";
if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
return false;
@@ -2864,19 +2976,19 @@ bool buildAndroidProject(const Options &options)
if (options.verbose)
commandLine += " --info"_L1;
- FILE *gradleCommand = openProcess(commandLine);
+ auto gradleCommand = openProcess(commandLine);
if (gradleCommand == 0) {
fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine));
return false;
}
char buffer[512];
- while (fgets(buffer, sizeof(buffer), gradleCommand) != 0) {
+ while (fgets(buffer, sizeof(buffer), gradleCommand.get()) != nullptr) {
fprintf(stdout, "%s", buffer);
fflush(stdout);
}
- int errorCode = pclose(gradleCommand);
+ const int errorCode = pclose(gradleCommand.release());
if (errorCode != 0) {
fprintf(stderr, "Building the android package failed!\n");
if (!options.verbose)
@@ -2902,18 +3014,18 @@ bool uninstallApk(const Options &options)
fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
- FILE *adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName));
+ auto adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName));
if (adbCommand == 0)
return false;
if (options.verbose || mustReadOutputAnyway) {
char buffer[512];
- while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
+ while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
if (options.verbose)
fprintf(stdout, "%s", buffer);
}
- int returnCode = pclose(adbCommand);
+ const int returnCode = pclose(adbCommand.release());
if (returnCode != 0) {
fprintf(stderr, "Warning: Uninstall failed!\n");
if (!options.verbose)
@@ -2926,39 +3038,43 @@ bool uninstallApk(const Options &options)
enum PackageType {
AAB,
+ AAR,
UnsignedAPK,
SignedAPK
};
-QString packagePath(const Options &options, PackageType pt)
-{
- QString path(options.outputDirectory);
- path += "/build/outputs/%1/"_L1.arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle"));
- QString buildType(options.releasePackage ? "release/"_L1 : "debug/"_L1);
- if (QDir(path + buildType).exists())
- path += buildType;
- path += QDir(options.outputDirectory).dirName() + u'-';
- if (options.releasePackage) {
- path += "release-"_L1;
- if (pt >= UnsignedAPK) {
- if (pt == UnsignedAPK)
- path += "un"_L1;
- path += "signed.apk"_L1;
- } else {
- path.chop(1);
- path += ".aab"_L1;
- }
- } else {
- path += "debug"_L1;
- if (pt >= UnsignedAPK) {
- if (pt == SignedAPK)
- path += "-signed"_L1;
- path += ".apk"_L1;
- } else {
- path += ".aab"_L1;
- }
- }
- return path;
+QString packagePath(const Options &options, PackageType packageType)
+{
+ // The package type is always AAR if option.buildAar has been set
+ if (options.buildAar)
+ packageType = AAR;
+
+ static const QHash<PackageType, QLatin1StringView> packageTypeToPath{
+ { AAB, "bundle"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
+ };
+ static const QHash<PackageType, QLatin1StringView> packageTypeToExtension{
+ { AAB, "aab"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
+ };
+
+ const QString buildType(options.releasePackage ? "release"_L1 : "debug"_L1);
+ QString signedSuffix;
+ if (packageType == SignedAPK)
+ signedSuffix = "-signed"_L1;
+ else if (packageType == UnsignedAPK && options.releasePackage)
+ signedSuffix = "-unsigned"_L1;
+
+ QString dirPath(options.outputDirectory);
+ dirPath += "/build/outputs/%1/"_L1.arg(packageTypeToPath[packageType]);
+ if (QDir(dirPath + buildType).exists())
+ dirPath += buildType;
+
+ const QString fileName = "/%1-%2%3.%4"_L1.arg(
+ QDir(options.outputDirectory).dirName(),
+ buildType,
+ signedSuffix,
+ packageTypeToExtension[packageType]);
+
+ return dirPath + fileName;
}
bool installApk(const Options &options)
@@ -2971,20 +3087,20 @@ bool installApk(const Options &options)
if (options.verbose)
fprintf(stdout, "Installing Android package to device.\n");
- FILE *adbCommand = runAdb(options, " install -r "_L1
- + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
- : SignedAPK));
+ auto adbCommand = runAdb(options, " install -r "_L1
+ + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
+ : SignedAPK));
if (adbCommand == 0)
return false;
if (options.verbose || mustReadOutputAnyway) {
char buffer[512];
- while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
+ while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
if (options.verbose)
fprintf(stdout, "%s", buffer);
}
- int returnCode = pclose(adbCommand);
+ const int returnCode = pclose(adbCommand.release());
if (returnCode != 0) {
fprintf(stderr, "Installing to device failed!\n");
if (!options.verbose)
@@ -3102,7 +3218,7 @@ bool signAAB(const Options &options)
QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file))
.arg(shellQuote(options.keyStoreAlias));
- FILE *jarSignerCommand = openProcess(command);
+ auto jarSignerCommand = openProcess(command);
if (jarSignerCommand == 0) {
fprintf(stderr, "Couldn't run jarsigner.\n");
return false;
@@ -3110,11 +3226,11 @@ bool signAAB(const Options &options)
if (options.verbose) {
char buffer[512];
- while (fgets(buffer, sizeof(buffer), jarSignerCommand) != 0)
+ while (fgets(buffer, sizeof(buffer), jarSignerCommand.get()) != nullptr)
fprintf(stdout, "%s", buffer);
}
- int errorCode = pclose(jarSignerCommand);
+ const int errorCode = pclose(jarSignerCommand.release());
if (errorCode != 0) {
fprintf(stderr, "jarsigner command failed.\n");
if (!options.verbose)
@@ -3142,17 +3258,17 @@ bool signPackage(const Options &options)
return false;
auto zipalignRunner = [](const QString &zipAlignCommandLine) {
- FILE *zipAlignCommand = openProcess(zipAlignCommandLine);
+ auto zipAlignCommand = openProcess(zipAlignCommandLine);
if (zipAlignCommand == 0) {
fprintf(stderr, "Couldn't run zipalign.\n");
return false;
}
char buffer[512];
- while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0)
+ while (fgets(buffer, sizeof(buffer), zipAlignCommand.get()) != nullptr)
fprintf(stdout, "%s", buffer);
- return pclose(zipAlignCommand) == 0;
+ return pclose(zipAlignCommand.release()) == 0;
};
const QString verifyZipAlignCommandLine =
@@ -3209,17 +3325,17 @@ bool signPackage(const Options &options)
apkSignCommand += " %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
auto apkSignerRunner = [](const QString &command, bool verbose) {
- FILE *apkSigner = openProcess(command);
+ auto apkSigner = openProcess(command);
if (apkSigner == 0) {
fprintf(stderr, "Couldn't run apksigner.\n");
return false;
}
char buffer[512];
- while (fgets(buffer, sizeof(buffer), apkSigner) != 0)
+ while (fgets(buffer, sizeof(buffer), apkSigner.get()) != nullptr)
fprintf(stdout, "%s", buffer);
- int errorCode = pclose(apkSigner);
+ const int errorCode = pclose(apkSigner.release());
if (errorCode != 0) {
fprintf(stderr, "apksigner command failed.\n");
if (!verbose)
@@ -3263,7 +3379,8 @@ enum ErrorCode
CannotInstallApk = 16,
CannotCopyAndroidExtraResources = 19,
CannotCopyApk = 20,
- CannotCreateRcc = 21
+ CannotCreateRcc = 21,
+ CannotGenerateJavaQmlComponents = 22
};
bool writeDependencyFile(const Options &options)
@@ -3296,6 +3413,362 @@ bool writeDependencyFile(const Options &options)
return true;
}
+int generateJavaQmlComponents(const Options &options)
+{
+ // TODO QTBUG-125892: Current method of path discovery are to be improved
+ // For instance, it does not discover statically linked **inner** QML modules.
+ const auto getImportPaths = [](const QString &buildPath, const QString &libName,
+ QStringList &appImports, QStringList &externalImports) -> bool {
+ QFile confRspFile("%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
+ if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
+ return false;
+ QTextStream rspStream(&confRspFile);
+ while (!rspStream.atEnd()) {
+ QString currentLine = rspStream.readLine();
+ if (currentLine.compare("-importPath"_L1) == 0) {
+ currentLine = rspStream.readLine();
+ if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
+ appImports << currentLine;
+ else
+ externalImports << currentLine;
+ }
+ }
+ return appImports.count() + externalImports.count();
+ };
+
+ struct ComponentInfo {
+ QString name;
+ QString path;
+ };
+
+ struct ModuleInfo
+ {
+ QString moduleName;
+ QString preferPath;
+ QList<ComponentInfo> qmlComponents;
+ bool isValid() { return qmlComponents.size() && moduleName.size(); }
+ };
+
+ const auto getModuleInfo = [](const QString &qmldirPath) -> ModuleInfo {
+ QFile qmlDirFile(qmldirPath + "/qmldir"_L1);
+ if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
+ return ModuleInfo();
+ ModuleInfo moduleInfo;
+ QTextStream qmldirStream(&qmlDirFile);
+ while (!qmldirStream.atEnd()) {
+ const QString currentLine = qmldirStream.readLine();
+ if (currentLine.size() && currentLine[0].isLower()) {
+ // TODO QTBUG-125891: Handling of QML modules with dotted URI
+ if (currentLine.startsWith("module "_L1))
+ moduleInfo.moduleName = currentLine.split(" "_L1)[1];
+ else if (currentLine.startsWith("prefer "_L1))
+ moduleInfo.preferPath = currentLine.split(" "_L1)[1];
+ } else if (currentLine.size()
+ && (currentLine[0].isUpper() || currentLine.startsWith("singleton"_L1))) {
+ const QStringList parts = currentLine.split(" "_L1);
+ if (parts.size() > 2)
+ moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
+ }
+ }
+ return moduleInfo;
+ };
+
+ const auto extractDomInfo = [](const QString &qmlDomExecPath, const QString &qmldirPath,
+ const QString &qmlFile,
+ const QStringList &otherImportPaths) -> QJsonObject {
+ QByteArray domInfo;
+ QString importFlags;
+ for (auto &importPath : otherImportPaths)
+ importFlags.append(" -i %1"_L1.arg(shellQuote(importPath)));
+
+ const QString qmlDomCmd = "%1 -d -D required -f +:propertyInfos %2 %3"_L1.arg(
+ shellQuote(qmlDomExecPath), importFlags,
+ shellQuote("%1/%2"_L1.arg(qmldirPath, qmlFile)));
+ const QStringList qmlDomCmdParts = QProcess::splitCommand(qmlDomCmd);
+ QProcess process;
+ process.start(qmlDomCmdParts.first(), qmlDomCmdParts.sliced(1));
+ if (!process.waitForStarted()) {
+ fprintf(stderr, "Cannot execute command %s\n", qPrintable(qmlDomCmd));
+ return QJsonObject();
+ }
+ // Wait, maximum 30 seconds
+ if (!process.waitForFinished(30000)) {
+ fprintf(stderr, "Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
+ return QJsonObject();
+ }
+ domInfo = process.readAllStandardOutput();
+
+ QJsonParseError jsonError;
+ const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ fprintf(stderr, "Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
+ return jsonDoc.object();
+ };
+
+ const auto getComponent = [](const QJsonObject &dom) -> QJsonObject {
+ if (dom.isEmpty())
+ return QJsonObject();
+
+ const QJsonObject currentItem = dom.value("currentItem"_L1).toObject();
+ if (!currentItem.value("isValid"_L1).toBool(false))
+ return QJsonObject();
+
+ const QJsonArray components =
+ currentItem.value("components"_L1).toObject().value(""_L1).toArray();
+ if (components.isEmpty())
+ return QJsonObject();
+ return components.constBegin()->toObject();
+ };
+
+ const auto getProperties = [](const QJsonObject &component) -> QJsonArray {
+ QJsonArray properties;
+ const QJsonArray objects = component.value("objects"_L1).toArray();
+ if (objects.isEmpty())
+ return QJsonArray();
+ const QJsonObject propertiesObject =
+ objects[0].toObject().value("propertyInfos"_L1).toObject();
+ for (const auto &jsonProperty : propertiesObject) {
+ const QJsonArray propertyDefs =
+ jsonProperty.toObject().value("propertyDefs"_L1).toArray();
+ if (propertyDefs.isEmpty())
+ continue;
+
+ properties.append(propertyDefs[0].toObject());
+ }
+ return properties;
+ };
+
+ const auto getMethods = [](const QJsonObject &component) -> QJsonArray {
+ QJsonArray methods;
+ const QJsonArray objects = component.value("objects"_L1).toArray();
+ if (objects.isEmpty())
+ return QJsonArray();
+ const QJsonObject methodsObject = objects[0].toObject().value("methods"_L1).toObject();
+ for (const auto &jsonMethod : methodsObject) {
+ const QJsonArray overloads = jsonMethod.toArray();
+ for (const auto &m : overloads)
+ methods.append(m);
+ }
+ return methods;
+ };
+
+ const static QHash<QString, QString> qmlToJavaType = {
+ { "qreal"_L1, "Double"_L1 }, { "double"_L1, "Double"_L1 }, { "int"_L1, "Integer"_L1 },
+ { "float"_L1, "Float"_L1 }, { "bool"_L1, "Boolean"_L1 }, { "string"_L1, "String"_L1 },
+ { "void"_L1, "Void"_L1 }
+ };
+
+ const auto endBlock = [](QTextStream &stream, int indentWidth = 0) {
+ stream << QString(indentWidth, u' ') << "}\n";
+ };
+
+ const auto createHeaderBlock = [](QTextStream &stream, const QString &javaPackage) {
+ stream << "/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
+ << "package %1;\n\n"_L1.arg(javaPackage)
+ << "import org.qtproject.qt.android.QtSignalListener;\n"
+ << "import org.qtproject.qt.android.QtQmlComponent;\n\n";
+ };
+
+ const auto beginLibraryBlock = [](QTextStream &stream, const QString &libName) {
+ stream << QLatin1StringView("public final class %1 {\n").arg(libName);
+ };
+
+ const auto beginModuleBlock = [](QTextStream &stream, const QString &moduleName,
+ bool topLevel = false, int indentWidth = 4) {
+ const QString indent(indentWidth, u' ');
+ stream << indent
+ << "public final%1 class %2 {\n"_L1.arg(topLevel ? ""_L1 : " static"_L1, moduleName);
+ };
+
+ const auto beginComponentBlock = [](QTextStream &stream, const QString &libName,
+ const QString &moduleName, const QString &preferPath,
+ const ComponentInfo &componentInfo, int indentWidth = 8) {
+ const QString indent(indentWidth, u' ');
+
+ stream << indent
+ << "public final static class %1 extends QtQmlComponent {\n"_L1
+ .arg(componentInfo.name)
+ << indent << " @Override public String getLibraryName() {\n"_L1
+ << indent << " return \"%1\";\n"_L1.arg(libName)
+ << indent << " }\n"_L1
+ << indent << " @Override public String getModuleName() {\n"_L1
+ << indent << " return \"%1\";\n"_L1.arg(moduleName)
+ << indent << " }\n"_L1
+ << indent << " @Override public String getFilePath() {\n"_L1
+ << indent << " return \"qrc%1%2\";\n"_L1.arg(preferPath)
+ .arg(componentInfo.path)
+ << indent << " }\n"_L1;
+ };
+
+ const auto beginPropertyBlock = [](QTextStream &stream, const QJsonObject &propertyData,
+ int indentWidth = 8) {
+ const QString indent(indentWidth, u' ');
+ const QString propertyName = propertyData["name"_L1].toString();
+ if (propertyName.isEmpty())
+ return;
+ const QString upperPropertyName =
+ propertyName[0].toUpper() + propertyName.last(propertyName.size() - 1);
+ const QString typeName = propertyData["typeName"_L1].toString();
+ const bool isReadyonly = propertyData["isReadonly"_L1].toBool();
+
+ const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
+
+ if (!isReadyonly) {
+ stream << indent
+ << "public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
+ upperPropertyName, javaTypeName, propertyName);
+ }
+
+ stream << indent
+ << "public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
+ .arg(upperPropertyName, javaTypeName, propertyName)
+ << indent
+ << "public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
+ .arg(upperPropertyName, javaTypeName)
+ << indent
+ << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
+ propertyName, javaTypeName)
+ << indent << "}\n";
+ };
+
+ const auto beginSignalBlock = [](QTextStream &stream, const QJsonObject &methodData,
+ int indentWidth = 8) {
+ const QString indent(indentWidth, u' ');
+ if (methodData["methodType"_L1] != 0)
+ return;
+ const QJsonArray parameters = methodData["parameters"_L1].toArray();
+ if (parameters.size() > 1)
+ return;
+
+ const QString methodName = methodData["name"_L1].toString();
+ if (methodName.isEmpty())
+ return;
+ const QString upperMethodName =
+ methodName[0].toUpper() + methodName.last(methodName.size() - 1);
+ const QString typeName = !parameters.isEmpty()
+ ? parameters[0].toObject()["typeName"_L1].toString()
+ : "void"_L1;
+
+ const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
+ stream << indent
+ << "public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1.arg(
+ upperMethodName, javaTypeName)
+ << indent
+ << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
+ methodName, javaTypeName)
+ << indent << "}\n";
+ };
+
+ const QString libName(options.applicationBinary);
+ const QString libClassname = libName[0].toUpper() + libName.last(libName.size() - 1);
+ const QString javaPackage = options.packageName;
+ const QString outputDir = "%1/src/%2"_L1.arg(options.outputDirectory,
+ QString(javaPackage).replace(u'.', u'/'));
+ const QString buildPath(QDir(options.buildDirectory).absolutePath());
+ const QString domBinaryPath(options.qmlDomBinaryPath);
+ const bool leafEqualsLibname = javaPackage.endsWith(".%1"_L1.arg(libName));
+
+ fprintf(stdout, "Generating Java QML Components in %s directory.\n", qPrintable(outputDir));
+ if (!QDir().current().mkpath(outputDir)) {
+ fprintf(stderr, "Cannot create %s directory\n", qPrintable(outputDir));
+ return false;
+ }
+
+ QStringList appImports;
+ QStringList externalImports;
+ if (!getImportPaths(buildPath, libName, appImports, externalImports))
+ return false;
+
+ QTextStream outputStream;
+ std::unique_ptr<QFile> outputFile;
+
+ if (!leafEqualsLibname) {
+ outputFile.reset(new QFile("%1/%2.java"_L1.arg(outputDir, libClassname)));
+ if (outputFile->exists())
+ outputFile->remove();
+ if (!outputFile->open(QFile::ReadWrite)) {
+ fprintf(stderr, "Cannot open %s file to write.\n",
+ qPrintable(outputFile->fileName()));
+ return false;
+ }
+ outputStream.setDevice(outputFile.get());
+ createHeaderBlock(outputStream, javaPackage);
+ beginLibraryBlock(outputStream, libClassname);
+ }
+
+ int generatedComponents = 0;
+ for (const auto &importPath : appImports) {
+ ModuleInfo moduleInfo = getModuleInfo(importPath);
+ if (!moduleInfo.isValid())
+ continue;
+
+ const QString moduleClassname = moduleInfo.moduleName[0].toUpper()
+ + moduleInfo.moduleName.last(moduleInfo.moduleName.size() - 1);
+
+ int indentBase = 4;
+ if (leafEqualsLibname) {
+ indentBase = 0;
+ QIODevice *outputStreamDevice = outputStream.device();
+ if (outputStreamDevice) {
+ outputStream.flush();
+ outputStream.reset();
+ outputStreamDevice->close();
+ }
+
+ outputFile.reset(new QFile("%1/%2.java"_L1.arg(outputDir,moduleClassname)));
+ if (outputFile->exists() && !outputFile->remove())
+ return false;
+ if (!outputFile->open(QFile::ReadWrite)) {
+ fprintf(stderr, "Cannot open %s file to write.\n", qPrintable(outputFile->fileName()));
+ return false;
+ }
+
+ outputStream.setDevice(outputFile.get());
+ createHeaderBlock(outputStream, javaPackage);
+ }
+
+ beginModuleBlock(outputStream, moduleClassname, leafEqualsLibname, indentBase);
+ indentBase += 4;
+
+ for (const auto &qmlComponent : moduleInfo.qmlComponents) {
+ const bool isSelected = options.selectedJavaQmlComponents.contains(
+ "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
+ if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
+ continue;
+
+ QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
+ externalImports + appImports);
+ QJsonObject component = getComponent(domInfo);
+ if (component.isEmpty())
+ continue;
+
+ beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
+ qmlComponent, indentBase);
+ indentBase += 4;
+
+ const QJsonArray properties = getProperties(component);
+ for (const QJsonValue &p : std::as_const(properties))
+ beginPropertyBlock(outputStream, p.toObject(), indentBase);
+
+ const QJsonArray methods = getMethods(component);
+ for (const QJsonValue &m : std::as_const(methods))
+ beginSignalBlock(outputStream, m.toObject(), indentBase);
+
+ indentBase -= 4;
+ endBlock(outputStream, indentBase);
+ generatedComponents++;
+ }
+ indentBase -= 4;
+ endBlock(outputStream, indentBase);
+ }
+ if (!leafEqualsLibname)
+ endBlock(outputStream, 0);
+
+ outputStream.flush();
+ outputStream.device()->close();
+ return generatedComponents;
+}
+
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
@@ -3383,6 +3856,16 @@ int main(int argc, char *argv[])
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
+ if (options.generateJavaQmlComponents) {
+ if (!generateJavaQmlComponents(options))
+ return CannotGenerateJavaQmlComponents;
+ }
+
+ if (Q_UNLIKELY(options.timing)) {
+ fprintf(stdout, "[TIMING] %lld ns: Generate Java QtQmlComponents.\n",
+ options.timer.nsecsElapsed());
+ }
+
// If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK
if (options.deploymentMechanism == Options::Unbundled) {
QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1.
diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt
index 93e826fa22..ad95d77d00 100644
--- a/src/tools/bootstrap/CMakeLists.txt
+++ b/src/tools/bootstrap/CMakeLists.txt
@@ -8,6 +8,10 @@
# The bootstrap library has a few manual tweaks compared to other
# libraries.
qt_add_library(Bootstrap STATIC)
+qt_internal_add_sbom(Bootstrap
+ TYPE QT_MODULE
+ NO_INSTALL
+)
qt_internal_add_sync_header_dependencies(Bootstrap Core)
@@ -199,6 +203,8 @@ qt_internal_extend_target(Bootstrap CONDITION CMAKE_CROSSCOMPILING OR NOT QT_FEA
PCRE2_DISABLE_JIT
PUBLIC_INCLUDE_DIRECTORIES
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/pcre2/src>
+ ATTRIBUTION_FILE_DIR_PATHS
+ ../../3rdparty/pcre2
)
qt_internal_extend_target(Bootstrap
diff --git a/src/tools/moc/CMakeLists.txt b/src/tools/moc/CMakeLists.txt
index b98b7ab4e9..14fc9762a5 100644
--- a/src/tools/moc/CMakeLists.txt
+++ b/src/tools/moc/CMakeLists.txt
@@ -38,6 +38,16 @@ qt_internal_add_tool(${target_name}
)
qt_internal_return_unless_building_tools()
+qt_internal_extend_sbom(${target_name}
+ COPYRIGHTS
+ "Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>"
+ "Copyright (C) 2014 Olivier Goffart <ogoffart@woboq.org>"
+ "Copyright (C) 2016 Intel Corporation."
+ "Copyright (C) 2018 Intel Corporation."
+ "Copyright (C) 2018 The Qt Company Ltd."
+ "Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com>"
+)
+
## Scopes:
#####################################################################
diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp
index 02e9ef178a..1c6604a96e 100644
--- a/src/tools/moc/generator.cpp
+++ b/src/tools/moc/generator.cpp
@@ -833,7 +833,7 @@ void Generator::generateProperties()
//
if (cdef->propertyList.size())
- fprintf(out, "\n // properties: name, type, flags\n");
+ fprintf(out, "\n // properties: name, type, flags, notifyId, revision\n");
for (const PropertyDef &p : std::as_const(cdef->propertyList)) {
uint flags = Invalid;
if (!isBuiltinType(p.type))
diff --git a/src/tools/moc/main.cpp b/src/tools/moc/main.cpp
index bb51352519..89fb367ca7 100644
--- a/src/tools/moc/main.cpp
+++ b/src/tools/moc/main.cpp
@@ -19,7 +19,8 @@
#include <qcoreapplication.h>
#include <qcommandlineoption.h>
#include <qcommandlineparser.h>
-#include <qscopedpointer.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
@@ -58,10 +59,21 @@ void error(const char *msg = "Invalid argument")
fprintf(stderr, "moc: %s\n", msg);
}
-struct ScopedPointerFileCloser
+static auto openFileForWriting(const QString &name)
{
- static inline void cleanup(FILE *handle) { if (handle) fclose(handle); }
-};
+ struct Closer { void operator()(FILE *handle) const { fclose(handle); } };
+ using R = std::unique_ptr<FILE, Closer>;
+
+#ifdef _MSC_VER
+ FILE *file;
+ if (_wfopen_s(&file, reinterpret_cast<const wchar_t *>(name.utf16()), L"w") != 0)
+ return R{};
+ return R{file};
+#else
+ return R{fopen(QFile::encodeName(name).constData(), "w")};
+#endif
+}
+using File = decltype(openFileForWriting({}));
static inline bool hasNext(const Symbols &symbols, int i)
{ return (i < symbols.size()); }
@@ -178,7 +190,7 @@ int runMoc(int argc, char **argv)
QString filename;
QString output;
QFile in;
- FILE *out = nullptr;
+ File out;
// Note that moc isn't translated.
// If you use this code as an example for a translated app, make sure to translate the strings.
@@ -528,16 +540,12 @@ int runMoc(int argc, char **argv)
// 3. and output meta object code
- QScopedPointer<FILE, ScopedPointerFileCloser> jsonOutput;
+ File jsonOutput;
bool outputToFile = true;
if (output.size()) { // output file specified
-#if defined(_MSC_VER)
- if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
-#else
- out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
+ out = openFileForWriting(output);
if (!out)
-#endif
{
const auto fopen_errno = errno;
fprintf(stderr, "moc: Cannot create %s. Error: %s\n",
@@ -548,37 +556,29 @@ int runMoc(int argc, char **argv)
if (parser.isSet(jsonOption)) {
const QString jsonOutputFileName = output + ".json"_L1;
- FILE *f;
-#if defined(_MSC_VER)
- if (_wfopen_s(&f, reinterpret_cast<const wchar_t *>(jsonOutputFileName.utf16()), L"w") != 0)
-#else
- f = fopen(QFile::encodeName(jsonOutputFileName).constData(), "w");
- if (!f)
-#endif
- {
+ jsonOutput = openFileForWriting(jsonOutputFileName);
+ if (!jsonOutput) {
const auto fopen_errno = errno;
fprintf(stderr, "moc: Cannot create JSON output file %s. Error: %s\n",
QFile::encodeName(jsonOutputFileName).constData(),
strerror(fopen_errno));
}
- jsonOutput.reset(f);
}
} else { // use stdout
- out = stdout;
+ out.reset(stdout);
outputToFile = false;
}
if (pp.preprocessOnly) {
- fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
+ fprintf(out.get(), "%s\n", composePreprocessorOutput(moc.symbols).constData());
} else {
if (moc.classList.isEmpty())
moc.note("No relevant classes found. No output generated.");
else
- moc.generate(out, jsonOutput.data());
+ moc.generate(out.get(), jsonOutput.get());
}
- if (output.size())
- fclose(out);
+ out.reset();
if (parser.isSet(depFileOption)) {
// 4. write a Make-style dependency file (can also be consumed by Ninja).
@@ -596,26 +596,17 @@ int runMoc(int argc, char **argv)
fprintf(stderr, "moc: Writing to stdout, but no depfile path specified.\n");
}
- QScopedPointer<FILE, ScopedPointerFileCloser> depFileHandle;
- FILE *depFileHandleRaw;
-#if defined(_MSC_VER)
- if (_wfopen_s(&depFileHandleRaw,
- reinterpret_cast<const wchar_t *>(depOutputFileName.utf16()), L"w") != 0)
-#else
- depFileHandleRaw = fopen(QFile::encodeName(depOutputFileName).constData(), "w");
- if (!depFileHandleRaw)
-#endif
- {
+ File depFileHandle = openFileForWriting(depOutputFileName);
+ if (!depFileHandle) {
const auto fopen_errno = errno;
fprintf(stderr, "moc: Cannot create dep output file '%s'. Error: %s\n",
QFile::encodeName(depOutputFileName).constData(),
strerror(fopen_errno));
}
- depFileHandle.reset(depFileHandleRaw);
- if (!depFileHandle.isNull()) {
+ if (depFileHandle) {
// First line is the path to the generated file.
- fprintf(depFileHandle.data(), "%s: ",
+ fprintf(depFileHandle.get(), "%s: ",
escapeAndEncodeDependencyPath(depRuleName).constData());
QByteArrayList dependencies;
@@ -647,7 +638,7 @@ int runMoc(int argc, char **argv)
// Join dependencies, output them, and output a final new line.
const auto dependenciesJoined = dependencies.join(QByteArrayLiteral(" \\\n "));
- fprintf(depFileHandle.data(), "%s\n", dependenciesJoined.constData());
+ fprintf(depFileHandle.get(), "%s\n", dependenciesJoined.constData());
}
}
diff --git a/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp b/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp
index 579604286c..d637854d2b 100644
--- a/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp
+++ b/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp
@@ -276,7 +276,7 @@ QTextStream &QDBusXmlToCpp::writeHeader(QTextStream &ts, bool changesWillBeLost)
{
ts << "/*\n"
" * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION "\n"
- " * Command line was: " << commandLine << "\n"
+ " * Source file was " << QFileInfo(inputFile).fileName() << "\n"
" *\n"
" * " PROGRAMNAME " is " PROGRAMCOPYRIGHT "\n"
" *\n"
diff --git a/src/tools/qlalr/cppgenerator.cpp b/src/tools/qlalr/cppgenerator.cpp
index fd56de106d..f12917f0eb 100644
--- a/src/tools/qlalr/cppgenerator.cpp
+++ b/src/tools/qlalr/cppgenerator.cpp
@@ -1,5 +1,7 @@
+// REUSE-IgnoreStart
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// REUSE-IgnoreEnd
#include "cppgenerator.h"
@@ -39,7 +41,7 @@ void generateList(const QList<int> &list, QTextStream &out)
}
}
-
+// REUSE-IgnoreStart
QString CppGenerator::copyrightHeader() const
{
return
@@ -47,6 +49,7 @@ QString CppGenerator::copyrightHeader() const
"// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0\n"
"\n"_L1;
}
+// REUSE-IgnoreEnd
QString CppGenerator::privateCopyrightHeader() const
{
diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp
index 2bda7f2977..a1089914fd 100644
--- a/src/tools/rcc/rcc.cpp
+++ b/src/tools/rcc/rcc.cpp
@@ -21,7 +21,7 @@
# include <zstd.h>
#endif
-// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp)
+// Note: A copy of this file is used in Qt Widgets Designer (qttools/src/designer/src/lib/shared/rcc.cpp)
QT_BEGIN_NAMESPACE
@@ -512,6 +512,12 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
reader.raiseError("expected <RCC> tag"_L1);
else
tokens.push(RccTag);
+ } else if (reader.name() == m_strings.TAG_LEGAL) {
+ if (tokens.isEmpty() || tokens.top() != RccTag) {
+ reader.raiseError("unexpected <legal> tag"_L1);
+ } else {
+ m_legal = reader.readElementText().trimmed();
+ }
} else if (reader.name() == m_strings.TAG_RESOURCE) {
if (tokens.isEmpty() || tokens.top() != RccTag) {
reader.raiseError("unexpected <RESOURCE> tag"_L1);
@@ -1087,11 +1093,20 @@ void RCCResourceLibrary::writeNumber8(quint64 number)
bool RCCResourceLibrary::writeHeader()
{
+ auto writeCopyright = [this](QByteArrayView prefix) {
+ const QStringList lines = m_legal.split(u'\n', Qt::SkipEmptyParts);
+ for (const QString &line : lines) {
+ write(prefix.data(), prefix.size());
+ writeString(line.toUtf8().trimmed());
+ writeChar('\n');
+ }
+ };
switch (m_format) {
case C_Code:
case Pass1:
writeString("/****************************************************************************\n");
writeString("** Resource object code\n");
+ writeCopyright("** ");
writeString("**\n");
writeString("** Created by: The Resource Compiler for Qt version ");
writeByteArray(QT_VERSION_STR);
@@ -1105,6 +1120,7 @@ bool RCCResourceLibrary::writeHeader()
break;
case Python_Code:
writeString("# Resource object code (Python 3)\n");
+ writeCopyright("# ");
writeString("# Created by: object code\n");
writeString("# Created by: The Resource Compiler for Qt version ");
writeByteArray(QT_VERSION_STR);
diff --git a/src/tools/rcc/rcc.h b/src/tools/rcc/rcc.h
index 60af1c67cf..e2ae8c5240 100644
--- a/src/tools/rcc/rcc.h
+++ b/src/tools/rcc/rcc.h
@@ -2,7 +2,7 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc_p.h)
+// Note: A copy of this file is used in Qt Widgets Designer (qttools/src/designer/src/lib/shared/rcc_p.h)
#ifndef RCC_H
#define RCC_H
@@ -93,6 +93,7 @@ private:
const QString TAG_RCC;
const QString TAG_RESOURCE;
const QString TAG_FILE;
+ const QString TAG_LEGAL = QLatin1StringView("legal");
const QString ATTRIBUTE_LANG;
const QString ATTRIBUTE_PREFIX;
const QString ATTRIBUTE_ALIAS;
@@ -134,6 +135,7 @@ private:
QString m_resourceRoot;
QString m_initName;
QString m_outputName;
+ QString m_legal;
Format m_format;
bool m_verbose;
CompressionAlgorithm m_compressionAlgo;
diff --git a/src/tools/syncqt/CMakeLists.txt b/src/tools/syncqt/CMakeLists.txt
index b3ab091aa4..b54dd72774 100644
--- a/src/tools/syncqt/CMakeLists.txt
+++ b/src/tools/syncqt/CMakeLists.txt
@@ -1,3 +1,5 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_INTERNAL_AVOID_OVERRIDING_SYNCQT_CONFIG)
qt_internal_get_configs_for_flag_manipulation(configs)
qt_internal_remove_known_optimization_flags(LANGUAGES CXX CONFIGS ${configs})
diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp
index 5df7b03fd5..ab646e6cd1 100644
--- a/src/tools/syncqt/main.cpp
+++ b/src/tools/syncqt/main.cpp
@@ -1428,13 +1428,12 @@ public:
// - <class|stuct> StructName
// - template <> class ClassName
// - class ClassName : [public|protected|private] BaseClassName
- // - class ClassName [final|Q_DECL_FINAL|sealed]
+ // - class ClassName [QT_TEXT_STREAM_FINAL|Q_DECL_FINAL|final|sealed]
// And possible combinations of the above variants.
static const std::regex ClassRegex(
- "^ *(template *<.*> *)?(class|struct) +([^ <>]* "
- "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> "
- "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? "
- "*.*)? *$");
+ "^ *(template *<.*> *)?(class|struct +)([^<>:]*\\s+)?" // Preceding part
+ "((?!Q[A-Z_0-9]*_FINAL|final|sealed)Q[a-zA-Z0-9_]+)" // Actual symbol
+ "(\\s+Q[A-Z_0-9]*_FINAL|\\s+final|\\s+sealed)?\\s*(:|$).*"); // Trailing part
// This regex checks if line contains function pointer typedef declaration like:
// - typedef void (* QFunctionPointerType)(int, char);
@@ -1445,10 +1444,6 @@ public:
// - typedef AnySymbol<char> QAnySymbolType;
static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$");
- // This regex checks if symbols is the Qt public symbol. Assume that Qt public symbols start
- // with the capital 'Q'.
- static const std::regex QtClassRegex("^Q\\w+$");
-
std::smatch match;
if (std::regex_match(line, match, FunctionPointerRegex)) {
symbol = match[1].str();
@@ -1456,8 +1451,6 @@ public:
symbol = match[2].str();
} else if (std::regex_match(line, match, ClassRegex)) {
symbol = match[4].str();
- if (!std::regex_match(symbol, QtClassRegex))
- symbol.clear();
} else {
return false;
}
diff --git a/src/tools/uic/cpp/cppwriteinitialization.cpp b/src/tools/uic/cpp/cppwriteinitialization.cpp
index 205d6a50a9..14ec778d65 100644
--- a/src/tools/uic/cpp/cppwriteinitialization.cpp
+++ b/src/tools/uic/cpp/cppwriteinitialization.cpp
@@ -127,16 +127,63 @@ namespace {
return iconHasStatePixmaps(i) || !i->attributeTheme().isEmpty();
}
+ // Checks on property names
+ bool isIdentifier(QChar c) { return c.isLetterOrNumber() || c == u'_'; }
+
+ bool checkPropertyName(const QString &name)
+ {
+ return !name.isEmpty() && name.at(0).isLetter()
+ && std::all_of(name.cbegin(), name.cend(), isIdentifier);
+ }
+
+ // Basic checks on enum/flag values
+ static bool isValidEnumValue(QChar c)
+ {
+ if (c.isLetterOrNumber())
+ return true;
+ switch (c.unicode()) {
+ case '|':
+ case ' ':
+ case ':':
+ case '_':
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ bool checkEnumValue(const QString &value)
+ {
+ return std::all_of(value.cbegin(), value.cend(), isValidEnumValue);
+ }
+
+ QString msgInvalidValue(const QString &name, const QString &value)
+ {
+ return "uic: Invalid property value: \""_L1 + name + "\": \""_L1 + value + u'"';
+ }
+
// Check on properties. Filter out empty legacy pixmap/icon properties
// as Designer pre 4.4 used to remove missing resource references.
// This can no longer be handled by the code as we have 'setIcon(QIcon())' as well as 'QIcon icon'
static bool checkProperty(const CustomWidgetsInfo *customWidgetsInfo,
const QString &fileName, const QString &className,
const DomProperty *p) {
+
+ const QString &name = p->attributeName();
+ if (!checkPropertyName(name)) {
+ qWarning("uic: Invalid property name: \"%s\".", qPrintable(name));
+ return false;
+ }
+
switch (p->kind()) {
// ### fixme Qt 7 remove this: Exclude deprecated properties of Qt 5.
case DomProperty::Set:
- if (p->attributeName() == u"features"
+ if (!checkEnumValue(p->elementSet())) {
+ qWarning("%s", qPrintable(msgInvalidValue(name, p->elementSet())));
+ return false;
+ }
+ if (name == u"features"
&& customWidgetsInfo->extends(className, "QDockWidget")
&& p->elementSet() == u"QDockWidget::AllDockWidgetFeatures") {
const QString msg = fileName + ": Warning: Deprecated enum value QDockWidget::AllDockWidgetFeatures was encountered."_L1;
@@ -145,7 +192,11 @@ namespace {
}
break;
case DomProperty::Enum:
- if (p->attributeName() == u"sizeAdjustPolicy"
+ if (!checkEnumValue(p->elementEnum())) {
+ qWarning("%s", qPrintable(msgInvalidValue(name, p->elementEnum())));
+ return false;
+ }
+ if (name == u"sizeAdjustPolicy"
&& customWidgetsInfo->extends(className, "QComboBox")
&& p->elementEnum() == u"QComboBox::AdjustToMinimumContentsLength") {
const QString msg = fileName + ": Warning: Deprecated enum value QComboBox::AdjustToMinimumContentsLength was encountered."_L1;
@@ -158,7 +209,7 @@ namespace {
if (!isIconFormat44(dri)) {
if (dri->text().isEmpty()) {
const QString msg = QString::fromLatin1("%1: Warning: An invalid icon property '%2' was encountered.")
- .arg(fileName, p->attributeName());
+ .arg(fileName, name);
qWarning("%s", qPrintable(msg));
return false;
}
@@ -169,7 +220,7 @@ namespace {
if (const DomResourcePixmap *drp = p->elementPixmap())
if (drp->text().isEmpty()) {
const QString msg = QString::fromUtf8("%1: Warning: An invalid pixmap property '%2' was encountered.")
- .arg(fileName, p->attributeName());
+ .arg(fileName, name);
qWarning("%s", qPrintable(msg));
return false;
}
@@ -2155,7 +2206,7 @@ QString WriteInitialization::pixCall(const DomProperty *p) const
s = p->elementPixmap()->text();
break;
default:
- qWarning("%s: Warning: Unknown icon format encountered. The ui-file was generated with a too-recent version of Designer.",
+ qWarning("%s: Warning: Unknown icon format encountered. The ui-file was generated with a too-recent version of Qt Widgets Designer.",
qPrintable(m_option.messagePrefix()));
return "QIcon()"_L1;
break;
diff --git a/src/tools/uic/ui4.cpp b/src/tools/uic/ui4.cpp
index d65fc4a8c3..b6a8f4eb4b 100644
--- a/src/tools/uic/ui4.cpp
+++ b/src/tools/uic/ui4.cpp
@@ -77,7 +77,8 @@ void DomUI::read(QXmlStreamReader &reader)
setElementAuthor(reader.readElementText());
continue;
}
- if (!tag.compare(u"comment"_s, Qt::CaseInsensitive)) {
+ if (!tag.compare(u"comment"_s, Qt::CaseInsensitive)
+ || !tag.compare(u"legal"_s, Qt::CaseInsensitive)) {
setElementComment(reader.readElementText());
continue;
}
diff --git a/src/tools/uic/uic.cpp b/src/tools/uic/uic.cpp
index fb0a37d21d..1b10e1d722 100644
--- a/src/tools/uic/uic.cpp
+++ b/src/tools/uic/uic.cpp
@@ -165,7 +165,7 @@ DomUI *Uic::parseUiFile(QXmlStreamReader &reader)
&& !ui) {
const double version = versionFromUiAttribute(reader);
if (version < 4.0) {
- const QString msg = QString::fromLatin1("uic: File generated with too old version of Qt Designer (%1)").arg(version);
+ const QString msg = QString::fromLatin1("uic: File generated with too old version of Qt Widgets Designer (%1)").arg(version);
fprintf(stderr, "%s\n", qPrintable(msg));
return nullptr;
}
@@ -202,7 +202,7 @@ bool Uic::write(QIODevice *in)
double version = ui->attributeVersion().toDouble();
if (version < 4.0) {
- fprintf(stderr, "uic: File generated with too old version of Qt Designer\n");
+ fprintf(stderr, "uic: File generated with too old version of Qt Widgets Designer\n");
return false;
}
diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp
index 084345a4d8..dca9132e15 100644
--- a/src/tools/windeployqt/main.cpp
+++ b/src/tools/windeployqt/main.cpp
@@ -139,8 +139,10 @@ static Platform platformFromMkSpec(const QString &xSpec)
return WindowsDesktopClangMsvc;
if (xSpec.contains("arm"_L1))
return WindowsDesktopMsvcArm;
+ if (xSpec.contains("G++"_L1))
+ return WindowsDesktopMinGW;
- return xSpec.contains("g++"_L1) ? WindowsDesktopMinGW : WindowsDesktopMsvcIntel;
+ return WindowsDesktopMsvc;
}
return UnknownPlatform;
}
@@ -1928,6 +1930,24 @@ int main(int argc, char **argv)
}
options.platform = platformFromMkSpec(xSpec);
+ // We are on MSVC and not crosscompiling. We need the host arch
+ if (options.platform == WindowsDesktopMsvc) {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ switch (si.wProcessorArchitecture) {
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ case PROCESSOR_ARCHITECTURE_IA64:
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ options.platform |= IntelBased;
+ break;
+ case PROCESSOR_ARCHITECTURE_ARM:
+ case PROCESSOR_ARCHITECTURE_ARM64:
+ options.platform |= ArmBased;
+ break;
+ default:
+ options.platform = UnknownPlatform;
+ }
+ }
if (options.platform == UnknownPlatform) {
std::wcerr << "Unsupported platform " << xSpec << '\n';
return 1;