blob: b15ca01cff2c3a8c6480a5d73169d43a11ce6a03 [file] [log] [blame]
/****************************************************************************
**
** 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