| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #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, |
| UnixBased = 0x00002, |
| WinRt = 0x00004, |
| // 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, |
| WinRtIntelMsvc = WindowsBased + WinRt + IntelBased + Msvc, |
| WinRtArmMsvc = WindowsBased + WinRt + ArmBased + Msvc, |
| Unix = UnixBased, |
| 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 = QPair<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"; |
| static const char unixSharedLibrarySuffix[] = ".so"; |
| |
| inline QString sharedLibrarySuffix(Platform platform) { return QLatin1String((platform & WindowsBased) ? windowsSharedLibrarySuffix : unixSharedLibrarySuffix); } |
| bool isBuildDirectory(Platform platform, const QString &dirName); |
| |
| bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage); |
| bool createDirectory(const QString &directory, QString *errorMessage); |
| QString findInPath(const QString &file); |
| |
| extern const char *qmakeInfixKey; // Fake key containing the libinfix |
| |
| QMap<QString, QString> queryQMakeAll(QString *errorMessage); |
| QString queryQMake(const QString &variable, 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 runElevatedBackgroundProcess(const QString &binary, const QStringList &args, Qt::HANDLE *processHandle); |
| |
| bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, |
| QStringList *dependentLibraries = 0, unsigned *wordSize = 0, |
| bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr); |
| bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage, |
| QStringList *dependentLibraries = 0, unsigned *wordSize = 0, |
| bool *isDebug = 0); |
| |
| inline bool readExecutable(const QString &executableFileName, Platform platform, |
| QString *errorMessage, QStringList *dependentLibraries = 0, |
| unsigned *wordSize = 0, bool *isDebug = 0, unsigned short *machineArch = nullptr) |
| { |
| return platform == Unix ? |
| readElfExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug) : |
| readPeExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug, |
| (platform == WindowsDesktopMinGW), machineArch); |
| } |
| |
| #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, Platform platform, QString *errorMessage) |
| { |
| QStringList result; |
| readExecutable(executableFileName, platform, errorMessage, &result); |
| return result; |
| } |
| |
| QString findD3dCompiler(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 + QLatin1Char('/') + 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(QLatin1Char('/'))) { |
| *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 + QLatin1Char('/') + relativeSource), sourceFileInfo.fileName(), errorMessage); |
| } // Source is symbolic link |
| |
| if (sourceFileInfo.isDir()) { |
| if ((flags & SkipQmlDesignerSpecificsDirectories) && sourceFileInfo.fileName() == QLatin1String("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 + QLatin1Char('/') + 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() == QLatin1String("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 |