diff options
Diffstat (limited to 'src/tools/windeployqt/utils.h')
-rw-r--r-- | src/tools/windeployqt/utils.h | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/src/tools/windeployqt/utils.h b/src/tools/windeployqt/utils.h new file mode 100644 index 0000000000..2c7cd79bf0 --- /dev/null +++ b/src/tools/windeployqt/utils.h @@ -0,0 +1,364 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef UTILS_H +#define UTILS_H + +#include <QStringList> +#include <QMap> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDateTime> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonDocument> + +#include <iostream> + +QT_BEGIN_NAMESPACE + +enum PlatformFlag { + // OS + WindowsBased = 0x00001, + // CPU + IntelBased = 0x00010, + ArmBased = 0x00020, + // Compiler + Msvc = 0x00100, + MinGW = 0x00200, + ClangMsvc = 0x00400, + ClangMinGW = 0x00800, + // Platforms + WindowsDesktopMsvc = WindowsBased + IntelBased + Msvc, + WindowsDesktopMinGW = WindowsBased + IntelBased + MinGW, + WindowsDesktopClangMsvc = WindowsBased + IntelBased + ClangMsvc, + WindowsDesktopClangMinGW = WindowsBased + IntelBased + ClangMinGW, + UnknownPlatform +}; + +Q_DECLARE_FLAGS(Platform, PlatformFlag) + +Q_DECLARE_OPERATORS_FOR_FLAGS(Platform) + +inline bool platformHasDebugSuffix(Platform p) // Uses 'd' debug suffix +{ + return p.testFlag(Msvc) || p.testFlag(ClangMsvc); +} + +enum ListOption { + ListNone = 0, + ListSource, + ListTarget, + ListRelative, + ListMapping +}; + +inline std::wostream &operator<<(std::wostream &str, const QString &s) +{ +#ifdef Q_OS_WIN + str << reinterpret_cast<const wchar_t *>(s.utf16()); +#else + str << s.toStdWString(); +#endif + return str; +} + +// Container class for JSON output +class JsonOutput +{ + using SourceTargetMapping = std::pair<QString, QString>; + using SourceTargetMappings = QList<SourceTargetMapping>; + +public: + void addFile(const QString &source, const QString &target) + { + m_files.append(SourceTargetMapping(source, target)); + } + + void removeTargetDirectory(const QString &targetDirectory) + { + for (int i = m_files.size() - 1; i >= 0; --i) { + if (m_files.at(i).second == targetDirectory) + m_files.removeAt(i); + } + } + + QByteArray toJson() const + { + QJsonObject document; + QJsonArray files; + for (const SourceTargetMapping &mapping : m_files) { + QJsonObject object; + object.insert(QStringLiteral("source"), QDir::toNativeSeparators(mapping.first)); + object.insert(QStringLiteral("target"), QDir::toNativeSeparators(mapping.second)); + files.append(object); + } + document.insert(QStringLiteral("files"), files); + return QJsonDocument(document).toJson(); + } + QByteArray toList(ListOption option, const QDir &base) const + { + QByteArray list; + for (const SourceTargetMapping &mapping : m_files) { + const QString source = QDir::toNativeSeparators(mapping.first); + const QString fileName = QFileInfo(mapping.first).fileName(); + const QString target = QDir::toNativeSeparators(mapping.second) + QDir::separator() + fileName; + switch (option) { + case ListNone: + break; + case ListSource: + list += source.toUtf8() + '\n'; + break; + case ListTarget: + list += target.toUtf8() + '\n'; + break; + case ListRelative: + list += QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + '\n'; + break; + case ListMapping: + list += '"' + source.toUtf8() + "\" \"" + QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + "\"\n"; + break; + } + } + return list; + } +private: + SourceTargetMappings m_files; +}; + +#ifdef Q_OS_WIN +QString normalizeFileName(const QString &name); +QString winErrorMessage(unsigned long error); +QString findSdkTool(const QString &tool); +#else // !Q_OS_WIN +inline QString normalizeFileName(const QString &name) { return name; } +#endif // !Q_OS_WIN + +static const char windowsSharedLibrarySuffix[] = ".dll"; + +inline QString sharedLibrarySuffix() { return QLatin1StringView(windowsSharedLibrarySuffix); } +bool isBuildDirectory(Platform platform, const QString &dirName); + +bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage); +bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun); +QString findInPath(const QString &file); + +extern const char *qmakeInfixKey; // Fake key containing the libinfix + +QMap<QString, QString> queryQtPaths(const QString &qmakeBinary, QString *errorMessage); + +enum DebugMatchMode { + MatchDebug, + MatchRelease, + MatchDebugOrRelease +}; + +QStringList findSharedLibraries(const QDir &directory, Platform platform, + DebugMatchMode debugMatchMode, + const QString &prefix = QString()); + +bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, + const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage); +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory = QString(), + unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0, + QString *errorMessage = 0); + +bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, + QStringList *dependentLibraries = 0, unsigned *wordSize = 0, + bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr); + +#ifdef Q_OS_WIN +# if !defined(IMAGE_FILE_MACHINE_ARM64) +# define IMAGE_FILE_MACHINE_ARM64 0xAA64 +# endif +QString getArchString (unsigned short machineArch); +#endif // Q_OS_WIN + +// Return dependent modules of executable files. + +inline QStringList findDependentLibraries(const QString &executableFileName, QString *errorMessage) +{ + QStringList result; + readPeExecutable(executableFileName, errorMessage, &result); + return result; +} + +QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize); +QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize); + +bool patchQtCore(const QString &path, QString *errorMessage); + +extern int optVerboseLevel; + +// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir +// to obtain the files. +enum UpdateFileFlag { + ForceUpdateFile = 0x1, + SkipUpdateFile = 0x2, + RemoveEmptyQmlDirectories = 0x4, + SkipQmlDesignerSpecificsDirectories = 0x8 +}; + +template <class DirectoryFileEntryFunction> +bool updateFile(const QString &sourceFileName, + DirectoryFileEntryFunction directoryFileEntryFunction, + const QString &targetDirectory, + unsigned flags, + JsonOutput *json, + QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + u'/' + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::wcout << "Checking " << sourceFileName << ", " << targetFileName << '\n'; + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isSymLink()) { + const QString sourcePath = sourceFileInfo.symLinkTarget(); + const QString relativeSource = QDir(sourceFileInfo.absolutePath()).relativeFilePath(sourcePath); + if (relativeSource.contains(u'/')) { + *errorMessage = QString::fromLatin1("Symbolic links across directories are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + // Update the linked-to file + if (!updateFile(sourcePath, directoryFileEntryFunction, targetDirectory, flags, json, errorMessage)) + return false; + + if (targetFileInfo.exists()) { + if (!targetFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a symbolic link.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a symlink + const QString relativeTarget = QDir(targetFileInfo.absolutePath()).relativeFilePath(targetFileInfo.symLinkTarget()); + if (relativeSource == relativeTarget) // Exists and points to same entry: happy. + return true; + QFile existingTargetFile(targetFileName); + if (!(flags & SkipUpdateFile) && !existingTargetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing symbolic link %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), existingTargetFile.errorString()); + return false; + } + } // target symbolic link exists + return createSymbolicLink(QFileInfo(targetDirectory + u'/' + relativeSource), sourceFileInfo.fileName(), errorMessage); + } // Source is symbolic link + + if (sourceFileInfo.isDir()) { + if ((flags & SkipQmlDesignerSpecificsDirectories) && sourceFileInfo.fileName() == QLatin1StringView("designer")) { + if (optVerboseLevel) + std::wcout << "Skipping " << QDir::toNativeSeparators(sourceFileName) << ".\n"; + return true; + } + bool created = false; + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + if (optVerboseLevel) + std::wcout << "Creating " << targetFileName << ".\n"; + if (!(flags & SkipUpdateFile)) { + created = d.mkdir(sourceFileInfo.fileName()); + if (!created) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + } + // Recurse into directory + QDir dir(sourceFileName); + + const QStringList allEntries = directoryFileEntryFunction(dir) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &entry : allEntries) + if (!updateFile(sourceFileName + u'/' + entry, directoryFileEntryFunction, targetFileName, flags, json, errorMessage)) + return false; + // Remove empty directories, for example QML import folders for which the filter did not match. + if (created && (flags & RemoveEmptyQmlDirectories)) { + QDir d(targetFileName); + const QStringList entries = d.entryList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + if (entries.isEmpty() || (entries.size() == 1 && entries.first() == QLatin1StringView("qmldir"))) { + if (!d.removeRecursively()) { + *errorMessage = QString::fromLatin1("Cannot remove empty directory %1.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } + if (json) + json->removeTargetDirectory(targetFileName); + } + } + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (!(flags & ForceUpdateFile) + && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::wcout << sourceFileInfo.fileName() << " is up to date.\n"; + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; + } + QFile targetFile(targetFileName); + if (!(flags & SkipUpdateFile) && !targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n"; + if (!(flags & SkipUpdateFile)) { + if (!file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + if (!(file.permissions() & QFile::WriteUser)) { // QTBUG-40152, clear inherited read-only attribute + QFile targetFile(targetFileName); + if (!targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser)) { + *errorMessage = QString::fromLatin1("Cannot set write permission on %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), file.errorString()); + return false; + } + } // Check permissions + } // !SkipUpdateFile + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; +} + +// Base class to filter files by name filters functions to be passed to updateFile(). +class NameFilterFileEntryFunction { +public: + explicit NameFilterFileEntryFunction(const QStringList &nameFilters) : m_nameFilters(nameFilters) {} + QStringList operator()(const QDir &dir) const { return dir.entryList(m_nameFilters, QDir::Files); } + +private: + const QStringList m_nameFilters; +}; + +// Convenience for all files. +inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage) +{ + return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, flags, json, errorMessage); +} + +QT_END_NAMESPACE + +#endif // UTILS_H |