| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "utils.h" |
| #include "elfreader.h" |
| |
| #include <QtCore/QString> |
| #include <QtCore/QDebug> |
| #include <QtCore/QDir> |
| #include <QtCore/QFile> |
| #include <QtCore/QFileInfo> |
| #include <QtCore/QTemporaryFile> |
| #include <QtCore/QScopedPointer> |
| #include <QtCore/QScopedArrayPointer> |
| #include <QtCore/QStandardPaths> |
| #if defined(Q_OS_WIN) |
| # include <QtCore/qt_windows.h> |
| # include <shlwapi.h> |
| # include <delayimp.h> |
| #else // Q_OS_WIN |
| # include <sys/wait.h> |
| # include <sys/types.h> |
| # include <sys/stat.h> |
| # include <unistd.h> |
| # include <stdlib.h> |
| # include <string.h> |
| # include <errno.h> |
| # include <fcntl.h> |
| #endif // !Q_OS_WIN |
| |
| QT_BEGIN_NAMESPACE |
| |
| int optVerboseLevel = 1; |
| |
| bool isBuildDirectory(Platform platform, const QString &dirName) |
| { |
| return (platform.testFlag(Msvc) || platform.testFlag(ClangMsvc)) |
| && (dirName == QLatin1String("debug") || dirName == QLatin1String("release")); |
| } |
| |
| // Create a symbolic link by changing to the source directory to make sure the |
| // link uses relative paths only (QFile::link() otherwise uses the absolute path). |
| bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage) |
| { |
| const QString oldDirectory = QDir::currentPath(); |
| if (!QDir::setCurrent(source.absolutePath())) { |
| *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath())); |
| return false; |
| } |
| QFile file(source.fileName()); |
| const bool success = file.link(target); |
| QDir::setCurrent(oldDirectory); |
| if (!success) { |
| *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3") |
| .arg(QDir::toNativeSeparators(source.absoluteFilePath()), |
| QDir::toNativeSeparators(target), file.errorString()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool createDirectory(const QString &directory, QString *errorMessage) |
| { |
| const QFileInfo fi(directory); |
| if (fi.isDir()) |
| return true; |
| if (fi.exists()) { |
| *errorMessage = QString::fromLatin1("%1 already exists and is not a directory."). |
| arg(QDir::toNativeSeparators(directory)); |
| return false; |
| } |
| if (optVerboseLevel) |
| std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n"; |
| QDir dir; |
| if (!dir.mkpath(directory)) { |
| *errorMessage = QString::fromLatin1("Could not create directory %1."). |
| arg(QDir::toNativeSeparators(directory)); |
| return false; |
| } |
| return true; |
| } |
| |
| // Find shared libraries matching debug/Platform in a directory, return relative names. |
| QStringList findSharedLibraries(const QDir &directory, Platform platform, |
| DebugMatchMode debugMatchMode, |
| const QString &prefix) |
| { |
| QString nameFilter = prefix; |
| if (nameFilter.isEmpty()) |
| nameFilter += QLatin1Char('*'); |
| if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform)) |
| nameFilter += QLatin1Char('d'); |
| nameFilter += sharedLibrarySuffix(platform); |
| QStringList result; |
| QString errorMessage; |
| const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files); |
| for (const QFileInfo &dllFi : dlls) { |
| const QString dllPath = dllFi.absoluteFilePath(); |
| bool matches = true; |
| if (debugMatchMode != MatchDebugOrRelease && (platform & WindowsBased)) { |
| bool debugDll; |
| if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll, |
| (platform == WindowsDesktopMinGW))) { |
| matches = debugDll == (debugMatchMode == MatchDebug); |
| } else { |
| std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath) |
| << ": " << errorMessage; |
| } |
| } // Windows |
| if (matches) |
| result += dllFi.fileName(); |
| } // for |
| return result; |
| } |
| |
| #ifdef Q_OS_WIN |
| QString winErrorMessage(unsigned long error) |
| { |
| QString rc = QString::fromLatin1("#%1: ").arg(error); |
| ushort *lpMsgBuf; |
| |
| const DWORD len = FormatMessage( |
| FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| NULL, error, 0, reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, NULL); |
| if (len) { |
| rc = QString::fromUtf16(lpMsgBuf, int(len)); |
| LocalFree(lpMsgBuf); |
| } else { |
| rc += QString::fromLatin1("<unknown error>"); |
| } |
| return rc; |
| } |
| |
| // Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW() |
| QString normalizeFileName(const QString &name) |
| { |
| wchar_t shortBuffer[MAX_PATH]; |
| const QString nativeFileName = QDir::toNativeSeparators(name); |
| if (!GetShortPathNameW(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), shortBuffer, MAX_PATH)) |
| return name; |
| wchar_t result[MAX_PATH]; |
| if (!GetLongPathNameW(shortBuffer, result, MAX_PATH)) |
| return name; |
| return QDir::fromNativeSeparators(QString::fromWCharArray(result)); |
| } |
| |
| // Find a tool binary in the Windows SDK 8 |
| QString findSdkTool(const QString &tool) |
| { |
| QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(QLatin1Char(';')); |
| const QByteArray sdkDir = qgetenv("WindowsSdkDir"); |
| if (!sdkDir.isEmpty()) |
| paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + QLatin1String("/Tools/x64")); |
| return QStandardPaths::findExecutable(tool, paths); |
| } |
| |
| // runProcess helper: Create a temporary file for stdout/stderr redirection. |
| static HANDLE createInheritableTemporaryFile() |
| { |
| wchar_t path[MAX_PATH]; |
| if (!GetTempPath(MAX_PATH, path)) |
| return INVALID_HANDLE_VALUE; |
| wchar_t name[MAX_PATH]; |
| if (!GetTempFileName(path, L"temp", 0, name)) // Creates file. |
| return INVALID_HANDLE_VALUE; |
| SECURITY_ATTRIBUTES securityAttributes; |
| ZeroMemory(&securityAttributes, sizeof(securityAttributes)); |
| securityAttributes.nLength = sizeof(securityAttributes); |
| securityAttributes.bInheritHandle = TRUE; |
| return CreateFile(name, GENERIC_READ | GENERIC_WRITE, |
| FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes, |
| TRUNCATE_EXISTING, |
| FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); |
| } |
| |
| // runProcess helper: Rewind and read out a temporary file for stdout/stderr. |
| static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result) |
| { |
| if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF) |
| return; |
| char buf[1024]; |
| DWORD bytesRead; |
| while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead) |
| result->append(buf, int(bytesRead)); |
| CloseHandle(handle); |
| } |
| |
| static inline void appendToCommandLine(const QString &argument, QString *commandLine) |
| { |
| const bool needsQuote = argument.contains(QLatin1Char(' ')); |
| if (!commandLine->isEmpty()) |
| commandLine->append(QLatin1Char(' ')); |
| if (needsQuote) |
| commandLine->append(QLatin1Char('"')); |
| commandLine->append(argument); |
| if (needsQuote) |
| commandLine->append(QLatin1Char('"')); |
| } |
| |
| // runProcess: Run a command line process (replacement for QProcess which |
| // does not exist in the bootstrap library). |
| bool runProcess(const QString &binary, const QStringList &args, |
| const QString &workingDirectory, |
| unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, |
| QString *errorMessage) |
| { |
| if (exitCode) |
| *exitCode = 0; |
| |
| STARTUPINFO si; |
| ZeroMemory(&si, sizeof(si)); |
| si.cb = sizeof(si); |
| |
| STARTUPINFO myInfo; |
| GetStartupInfo(&myInfo); |
| si.hStdInput = myInfo.hStdInput; |
| si.hStdOutput = myInfo.hStdOutput; |
| si.hStdError = myInfo.hStdError; |
| |
| PROCESS_INFORMATION pi; |
| ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); |
| const QChar backSlash = QLatin1Char('\\'); |
| QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ? QDir::currentPath() : workingDirectory); |
| if (!nativeWorkingDir.endsWith(backSlash)) |
| nativeWorkingDir += backSlash; |
| |
| if (stdOut) { |
| si.hStdOutput = createInheritableTemporaryFile(); |
| if (si.hStdOutput == INVALID_HANDLE_VALUE) { |
| if (errorMessage) |
| *errorMessage = QStringLiteral("Error creating stdout temporary file"); |
| return false; |
| } |
| si.dwFlags |= STARTF_USESTDHANDLES; |
| } |
| |
| if (stdErr) { |
| si.hStdError = createInheritableTemporaryFile(); |
| if (si.hStdError == INVALID_HANDLE_VALUE) { |
| if (errorMessage) |
| *errorMessage = QStringLiteral("Error creating stderr temporary file"); |
| return false; |
| } |
| si.dwFlags |= STARTF_USESTDHANDLES; |
| } |
| |
| // Create a copy of the command line which CreateProcessW can modify. |
| QString commandLine; |
| appendToCommandLine(binary, &commandLine); |
| for (const QString &a : args) |
| appendToCommandLine(a, &commandLine); |
| if (optVerboseLevel > 1) |
| std::wcout << "Running: " << commandLine << '\n'; |
| |
| QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]); |
| commandLine.toWCharArray(commandLineW.data()); |
| commandLineW[commandLine.size()] = 0; |
| if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0, |
| reinterpret_cast<LPCWSTR>(nativeWorkingDir.utf16()), &si, &pi)) { |
| if (stdOut) |
| CloseHandle(si.hStdOutput); |
| if (stdErr) |
| CloseHandle(si.hStdError); |
| if (errorMessage) |
| *errorMessage = QStringLiteral("CreateProcessW failed: ") + winErrorMessage(GetLastError()); |
| return false; |
| } |
| |
| WaitForSingleObject(pi.hProcess, INFINITE); |
| CloseHandle(pi.hThread); |
| if (exitCode) |
| GetExitCodeProcess(pi.hProcess, exitCode); |
| CloseHandle(pi.hProcess); |
| |
| if (stdOut) |
| readTemporaryProcessFile(si.hStdOutput, stdOut); |
| if (stdErr) |
| readTemporaryProcessFile(si.hStdError, stdErr); |
| return true; |
| } |
| |
| bool runElevatedBackgroundProcess(const QString &binary, const QStringList &args, Qt::HANDLE *processHandle) |
| { |
| QScopedArrayPointer<wchar_t> binaryW(new wchar_t[binary.size() + 1]); |
| binary.toWCharArray(binaryW.data()); |
| binaryW[binary.size()] = 0; |
| |
| const QString arguments = args.join(QLatin1Char(' ')); |
| QScopedArrayPointer<wchar_t> argumentsW(new wchar_t[arguments.size() + 1]); |
| arguments.toWCharArray(argumentsW.data()); |
| argumentsW[arguments.size()] = 0; |
| |
| SHELLEXECUTEINFO shellExecute = {}; |
| shellExecute.cbSize = sizeof(shellExecute); |
| shellExecute.fMask = SEE_MASK_NOCLOSEPROCESS; |
| shellExecute.hwnd = 0; |
| shellExecute.lpVerb = L"runas"; // causes elevation |
| shellExecute.lpFile = binaryW.data(); |
| shellExecute.lpParameters = argumentsW.data(); |
| shellExecute.lpDirectory = 0; |
| shellExecute.nShow = SW_SHOW; |
| shellExecute.hInstApp = 0; |
| |
| bool ret = ShellExecuteEx(&shellExecute); |
| |
| if (processHandle) |
| *processHandle = shellExecute.hProcess; |
| |
| return ret; |
| } |
| |
| #else // Q_OS_WIN |
| |
| static inline char *encodeFileName(const QString &f) |
| { |
| const QByteArray encoded = QFile::encodeName(f); |
| char *result = new char[encoded.size() + 1]; |
| strcpy(result, encoded.constData()); |
| return result; |
| } |
| |
| static inline char *tempFilePattern() |
| { |
| QString path = QDir::tempPath(); |
| if (!path.endsWith(QLatin1Char('/'))) |
| path += QLatin1Char('/'); |
| path += QStringLiteral("tmpXXXXXX"); |
| return encodeFileName(path); |
| } |
| |
| static inline QByteArray readOutRedirectFile(int fd) |
| { |
| enum { bufSize = 256 }; |
| |
| QByteArray result; |
| if (!lseek(fd, 0, 0)) { |
| char buf[bufSize]; |
| while (true) { |
| const ssize_t rs = read(fd, buf, bufSize); |
| if (rs <= 0) |
| break; |
| result.append(buf, int(rs)); |
| } |
| } |
| close(fd); |
| return result; |
| } |
| |
| // runProcess: Run a command line process (replacement for QProcess which |
| // does not exist in the bootstrap library). |
| bool runProcess(const QString &binary, const QStringList &args, |
| const QString &workingDirectory, |
| unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, |
| QString *errorMessage) |
| { |
| QScopedArrayPointer<char> stdOutFileName; |
| QScopedArrayPointer<char> stdErrFileName; |
| |
| int stdOutFile = 0; |
| if (stdOut) { |
| stdOutFileName.reset(tempFilePattern()); |
| stdOutFile = mkstemp(stdOutFileName.data()); |
| if (stdOutFile < 0) { |
| *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); |
| return false; |
| } |
| } |
| |
| int stdErrFile = 0; |
| if (stdErr) { |
| stdErrFileName.reset(tempFilePattern()); |
| stdErrFile = mkstemp(stdErrFileName.data()); |
| if (stdErrFile < 0) { |
| *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); |
| return false; |
| } |
| } |
| |
| const pid_t pID = fork(); |
| |
| if (pID < 0) { |
| *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno)); |
| return false; |
| } |
| |
| if (!pID) { // Child |
| if (stdOut) { |
| dup2(stdOutFile, STDOUT_FILENO); |
| close(stdOutFile); |
| } |
| if (stdErr) { |
| dup2(stdErrFile, STDERR_FILENO); |
| close(stdErrFile); |
| } |
| |
| if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) { |
| std::wcerr << "Failed to change working directory to " << workingDirectory << ".\n"; |
| ::_exit(-1); |
| } |
| |
| char **argv = new char *[args.size() + 2]; // Create argv. |
| char **ap = argv; |
| *ap++ = encodeFileName(binary); |
| for (const QString &a : qAsConst(args)) |
| *ap++ = encodeFileName(a); |
| *ap = 0; |
| |
| execvp(argv[0], argv); |
| ::_exit(-1); |
| } |
| |
| int status; |
| pid_t waitResult; |
| |
| do { |
| waitResult = waitpid(pID, &status, 0); |
| } while (waitResult == -1 && errno == EINTR); |
| |
| if (stdOut) { |
| *stdOut = readOutRedirectFile(stdOutFile); |
| unlink(stdOutFileName.data()); |
| } |
| if (stdErr) { |
| *stdErr = readOutRedirectFile(stdErrFile); |
| unlink(stdErrFileName.data()); |
| } |
| |
| if (waitResult < 0) { |
| *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno)); |
| return false; |
| } |
| if (!WIFEXITED(status)) { |
| *errorMessage = binary + QStringLiteral(" did not exit cleanly."); |
| return false; |
| } |
| if (exitCode) |
| *exitCode = WEXITSTATUS(status); |
| return true; |
| } |
| |
| bool runElevatedBackgroundProcess(const QString &binary, const QStringList &args, Qt::HANDLE *processHandle) |
| { |
| Q_UNUSED(binary); |
| Q_UNUSED(args); |
| Q_UNUSED(processHandle); |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| #endif // !Q_OS_WIN |
| |
| // Find a file in the path using ShellAPI. This can be used to locate DLLs which |
| // QStandardPaths cannot do. |
| QString findInPath(const QString &file) |
| { |
| #if defined(Q_OS_WIN) |
| if (file.size() < MAX_PATH - 1) { |
| wchar_t buffer[MAX_PATH]; |
| file.toWCharArray(buffer); |
| buffer[file.size()] = 0; |
| if (PathFindOnPath(buffer, NULL)) |
| return QDir::cleanPath(QString::fromWCharArray(buffer)); |
| } |
| return QString(); |
| #else // Q_OS_WIN |
| return QStandardPaths::findExecutable(file); |
| #endif // !Q_OS_WIN |
| } |
| |
| const char *qmakeInfixKey = "QT_INFIX"; |
| |
| QMap<QString, QString> queryQMakeAll(QString *errorMessage) |
| { |
| QByteArray stdOut; |
| QByteArray stdErr; |
| unsigned long exitCode = 0; |
| const QString binary = QStringLiteral("qmake"); |
| if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, &stdErr, errorMessage)) |
| return QMap<QString, QString>(); |
| if (exitCode) { |
| *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) |
| + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); |
| return QMap<QString, QString>(); |
| } |
| const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(QLatin1Char('\r')); |
| QMap<QString, QString> result; |
| const int size = output.size(); |
| for (int pos = 0; pos < size; ) { |
| const int colonPos = output.indexOf(QLatin1Char(':'), pos); |
| if (colonPos < 0) |
| break; |
| int endPos = output.indexOf(QLatin1Char('\n'), colonPos + 1); |
| if (endPos < 0) |
| endPos = size; |
| const QString key = output.mid(pos, colonPos - pos); |
| const QString value = output.mid(colonPos + 1, endPos - colonPos - 1); |
| result.insert(key, value); |
| pos = endPos + 1; |
| } |
| QFile qconfigPriFile(result.value(QStringLiteral("QT_HOST_DATA")) + QStringLiteral("/mkspecs/qconfig.pri")); |
| if (qconfigPriFile.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| while (true) { |
| const QByteArray line = qconfigPriFile.readLine(); |
| if (line.isEmpty()) |
| break; |
| if (line.startsWith("QT_LIBINFIX")) { |
| const int pos = line.indexOf('='); |
| if (pos >= 0) { |
| const QString infix = QString::fromUtf8(line.right(line.size() - pos - 1).trimmed()); |
| if (!infix.isEmpty()) |
| result.insert(QLatin1String(qmakeInfixKey), infix); |
| } |
| break; |
| } |
| } |
| } else { |
| std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName()) |
| << ": " << qconfigPriFile.errorString()<< '\n'; |
| } |
| return result; |
| } |
| |
| QString queryQMake(const QString &variable, QString *errorMessage) |
| { |
| QByteArray stdOut; |
| QByteArray stdErr; |
| unsigned long exitCode; |
| const QString binary = QStringLiteral("qmake"); |
| QStringList args; |
| args << QStringLiteral("-query ") << variable; |
| if (!runProcess(binary, args, QString(), &exitCode, &stdOut, &stdErr, errorMessage)) |
| return QString(); |
| if (exitCode) { |
| *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) |
| + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); |
| return QString(); |
| } |
| return QString::fromLocal8Bit(stdOut).trimmed(); |
| } |
| |
| // Update a file or directory. |
| bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, |
| 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; |
| } |
| |
| if (sourceFileInfo.isSymLink()) { |
| *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).") |
| .arg(QDir::toNativeSeparators(sourceFileName)); |
| return false; |
| } |
| |
| const QFileInfo targetFileInfo(targetFileName); |
| |
| if (sourceFileInfo.isDir()) { |
| 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 " << QDir::toNativeSeparators(targetFileName) << ".\n"; |
| if (!(flags & SkipUpdateFile) && !d.mkdir(sourceFileInfo.fileName())) { |
| *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") |
| .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); |
| return false; |
| } |
| } |
| // Recurse into directory |
| QDir dir(sourceFileName); |
| const QFileInfoList allEntries = dir.entryInfoList(nameFilters, QDir::Files) |
| + dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); |
| for (const QFileInfo &entryFi : allEntries) { |
| if (!updateFile(entryFi.absoluteFilePath(), nameFilters, targetFileName, flags, json, errorMessage)) |
| return false; |
| } |
| 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) && !file.copy(targetFileName)) { |
| *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") |
| .arg(QDir::toNativeSeparators(sourceFileName), |
| QDir::toNativeSeparators(targetFileName), |
| file.errorString()); |
| return false; |
| } |
| if (json) |
| json->addFile(sourceFileName, targetDirectory); |
| return true; |
| } |
| |
| bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage, |
| QStringList *dependentLibraries, unsigned *wordSize, |
| bool *isDebug) |
| { |
| ElfReader elfReader(elfExecutableFileName); |
| const ElfData data = elfReader.readHeaders(); |
| if (data.sectionHeaders.isEmpty()) { |
| *errorMessage = QStringLiteral("Unable to read ELF binary \"") |
| + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ") |
| + elfReader.errorString(); |
| return false; |
| } |
| if (wordSize) |
| *wordSize = data.elfclass == Elf_ELFCLASS64 ? 64 : 32; |
| if (dependentLibraries) { |
| dependentLibraries->clear(); |
| const QList<QByteArray> libs = elfReader.dependencies(); |
| if (libs.isEmpty()) { |
| *errorMessage = QStringLiteral("Unable to read dependenices of ELF binary \"") |
| + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ") |
| + elfReader.errorString(); |
| return false; |
| } |
| for (const QByteArray &l : libs) |
| dependentLibraries->push_back(QString::fromLocal8Bit(l)); |
| } |
| if (isDebug) |
| *isDebug = data.symbolsType != UnknownSymbols && data.symbolsType != NoSymbols; |
| return true; |
| } |
| |
| #ifdef Q_OS_WIN |
| |
| static inline QString stringFromRvaPtr(const void *rvaPtr) |
| { |
| return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr)); |
| } |
| |
| // Helper for reading out PE executable files: Find a section header for an RVA |
| // (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). |
| template <class ImageNtHeader> |
| const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader) |
| { |
| const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader); |
| const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections; |
| for ( ; section < sectionEnd; ++section) |
| if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize)) |
| return section; |
| return 0; |
| } |
| |
| // Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). |
| template <class ImageNtHeader> |
| inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase) |
| { |
| const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader); |
| if (!sectionHdr) |
| return 0; |
| const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData; |
| return static_cast<const char *>(imageBase) + rva - delta; |
| } |
| |
| // Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32 |
| template <class ImageNtHeader> |
| inline unsigned ntHeaderWordSize(const ImageNtHeader *header) |
| { |
| // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC |
| enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b }; |
| if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic) |
| return 32; |
| if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic) |
| return 64; |
| return 0; |
| } |
| |
| // Helper for reading out PE executable files: Retrieve the NT image header of an |
| // executable via the legacy DOS header. |
| static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage) |
| { |
| IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory); |
| // Check DOS header consistency |
| if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) |
| || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { |
| *errorMessage = QString::fromLatin1("DOS header check failed."); |
| return 0; |
| } |
| // Retrieve NT header |
| char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew; |
| IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC); |
| // check NT header consistency |
| if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) |
| || ntHeaders->Signature != IMAGE_NT_SIGNATURE |
| || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) { |
| *errorMessage = QString::fromLatin1("NT header check failed."); |
| return 0; |
| } |
| // Check magic |
| if (!ntHeaderWordSize(ntHeaders)) { |
| *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid."). |
| arg(ntHeaders->OptionalHeader.Magic); |
| return 0; |
| } |
| // Check section headers |
| IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders); |
| if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) { |
| *errorMessage = QString::fromLatin1("NT header section header check failed."); |
| return 0; |
| } |
| return ntHeaders; |
| } |
| |
| // Helper for reading out PE executable files: Read out import sections from |
| // IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32. |
| template <class ImageNtHeader> |
| inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage) |
| { |
| // Get import directory entry RVA and read out |
| const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; |
| if (!importsStartRVA) { |
| *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry."); |
| return QStringList(); |
| } |
| const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, base)); |
| if (!importDesc) { |
| *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry."); |
| return QStringList(); |
| } |
| QStringList result; |
| for ( ; importDesc->Name; ++importDesc) |
| result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base))); |
| |
| // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx . |
| // Check on grAttr bit 1 whether this is the format using RVA's > VS 6 |
| if (const DWORD delayedImportsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) { |
| const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, base)); |
| for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc) |
| result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base))); |
| } |
| |
| return result; |
| } |
| |
| // Check for MSCV runtime (MSVCP90D.dll/MSVCP90.dll, MSVCP120D.dll/MSVCP120.dll, |
| // VCRUNTIME140D.DLL/VCRUNTIME140.DLL (VS2015) or msvcp120d_app.dll/msvcp120_app.dll). |
| enum MsvcDebugRuntimeResult { MsvcDebugRuntime, MsvcReleaseRuntime, NoMsvcRuntime }; |
| |
| static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &dependentLibraries) |
| { |
| for (const QString &lib : dependentLibraries) { |
| int pos = 0; |
| if (lib.startsWith(QLatin1String("MSVCR"), Qt::CaseInsensitive) |
| || lib.startsWith(QLatin1String("MSVCP"), Qt::CaseInsensitive) |
| || lib.startsWith(QLatin1String("VCRUNTIME"), Qt::CaseInsensitive)) { |
| int lastDotPos = lib.lastIndexOf(QLatin1Char('.')); |
| pos = -1 == lastDotPos ? 0 : lastDotPos - 1; |
| } |
| |
| if (pos > 0 && lib.contains(QLatin1String("_app"), Qt::CaseInsensitive)) |
| pos -= 4; |
| |
| if (pos) { |
| return lib.at(pos).toLower() == QLatin1Char('d') |
| ? MsvcDebugRuntime : MsvcReleaseRuntime; |
| } |
| } |
| return NoMsvcRuntime; |
| } |
| |
| template <class ImageNtHeader> |
| inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory, |
| bool isMinGW, |
| QStringList *dependentLibrariesIn, |
| bool *isDebugIn, QString *errorMessage) |
| { |
| const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; |
| QStringList dependentLibraries; |
| if (dependentLibrariesIn || (isDebugIn != nullptr && hasDebugEntry && !isMinGW)) |
| dependentLibraries = readImportSections(nth, fileMemory, errorMessage); |
| |
| if (dependentLibrariesIn) |
| *dependentLibrariesIn = dependentLibraries; |
| if (isDebugIn != nullptr) { |
| if (isMinGW) { |
| // Use logic that's used e.g. in objdump / pfd library |
| *isDebugIn = !(nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED); |
| } else { |
| // When an MSVC debug entry is present, check whether the debug runtime |
| // is actually used to detect -release / -force-debug-info builds. |
| *isDebugIn = hasDebugEntry && checkMsvcDebugRuntime(dependentLibraries) != MsvcReleaseRuntime; |
| } |
| } |
| } |
| |
| // Read a PE executable and determine dependent libraries, word size |
| // and debug flags. |
| bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, |
| QStringList *dependentLibrariesIn, unsigned *wordSizeIn, |
| bool *isDebugIn, bool isMinGW, unsigned short *machineArchIn) |
| { |
| bool result = false; |
| HANDLE hFile = NULL; |
| HANDLE hFileMap = NULL; |
| void *fileMemory = 0; |
| |
| if (dependentLibrariesIn) |
| dependentLibrariesIn->clear(); |
| if (wordSizeIn) |
| *wordSizeIn = 0; |
| if (isDebugIn) |
| *isDebugIn = false; |
| |
| do { |
| // Create a memory mapping of the file |
| hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) { |
| *errorMessage = QString::fromLatin1("Cannot open '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); |
| break; |
| } |
| |
| hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); |
| if (hFileMap == NULL) { |
| *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); |
| break; |
| } |
| |
| fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0); |
| if (!fileMemory) { |
| *errorMessage = QString::fromLatin1("Cannot map '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); |
| break; |
| } |
| |
| const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage); |
| if (!ntHeaders) |
| break; |
| |
| const unsigned wordSize = ntHeaderWordSize(ntHeaders); |
| if (wordSizeIn) |
| *wordSizeIn = wordSize; |
| if (wordSize == 32) { |
| determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders), |
| fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage); |
| } else { |
| determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders), |
| fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage); |
| } |
| |
| if (machineArchIn) |
| *machineArchIn = ntHeaders->FileHeader.Machine; |
| |
| result = true; |
| if (optVerboseLevel > 1) { |
| std::wcout << __FUNCTION__ << ": " << QDir::toNativeSeparators(peExecutableFileName) |
| << ' ' << wordSize << " bit"; |
| if (isMinGW) |
| std::wcout << ", MinGW"; |
| if (dependentLibrariesIn) { |
| std::wcout << ", dependent libraries: "; |
| if (optVerboseLevel > 2) |
| std::wcout << dependentLibrariesIn->join(QLatin1Char(' ')); |
| else |
| std::wcout << dependentLibrariesIn->size(); |
| } |
| if (isDebugIn) |
| std::wcout << (*isDebugIn ? ", debug" : ", release"); |
| std::wcout << '\n'; |
| } |
| } while (false); |
| |
| if (fileMemory) |
| UnmapViewOfFile(fileMemory); |
| |
| if (hFileMap != NULL) |
| CloseHandle(hFileMap); |
| |
| if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) |
| CloseHandle(hFile); |
| |
| return result; |
| } |
| |
| QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize) |
| { |
| const QString prefix = QStringLiteral("D3Dcompiler_"); |
| const QString suffix = QLatin1String(windowsSharedLibrarySuffix); |
| // Get the DLL from Kit 8.0 onwards |
| const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir")); |
| if (!kitDir.isEmpty()) { |
| QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/"); |
| if (platform.testFlag(ArmBased)) { |
| redistDirPath += QStringLiteral("arm"); |
| } else { |
| redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64"); |
| } |
| QDir redistDir(redistDirPath); |
| if (redistDir.exists()) { |
| const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + QLatin1Char('*') + suffix), QDir::Files); |
| if (!files.isEmpty()) |
| return files.front().absoluteFilePath(); |
| } |
| } |
| QStringList candidateVersions; |
| for (int i = 47 ; i >= 40 ; --i) |
| candidateVersions.append(prefix + QString::number(i) + suffix); |
| // Check the bin directory of the Qt SDK (in case it is shadowed by the |
| // Windows system directory in PATH). |
| for (const QString &candidate : qAsConst(candidateVersions)) { |
| const QFileInfo fi(qtBinDir + QLatin1Char('/') + candidate); |
| if (fi.isFile()) |
| return fi.absoluteFilePath(); |
| } |
| // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47). |
| if (platform.testFlag(IntelBased)) { |
| QString errorMessage; |
| unsigned detectedWordSize; |
| for (const QString &candidate : qAsConst(candidateVersions)) { |
| const QString dll = findInPath(candidate); |
| if (!dll.isEmpty() |
| && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0) |
| && detectedWordSize == wordSize) { |
| return dll; |
| } |
| } |
| } |
| return QString(); |
| } |
| |
| #else // Q_OS_WIN |
| |
| bool readPeExecutable(const QString &, QString *errorMessage, |
| QStringList *, unsigned *, bool *, bool, unsigned short *) |
| { |
| *errorMessage = QStringLiteral("Not implemented."); |
| return false; |
| } |
| |
| QString findD3dCompiler(Platform, const QString &, unsigned) |
| { |
| return QString(); |
| } |
| |
| #endif // !Q_OS_WIN |
| |
| // Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=." |
| bool patchQtCore(const QString &path, QString *errorMessage) |
| { |
| if (optVerboseLevel) |
| std::wcout << "Patching " << QFileInfo(path).fileName() << "...\n"; |
| |
| QFile file(path); |
| if (!file.open(QIODevice::ReadOnly)) { |
| *errorMessage = QString::fromLatin1("Unable to patch %1: %2").arg( |
| QDir::toNativeSeparators(path), file.errorString()); |
| return false; |
| } |
| const QByteArray oldContent = file.readAll(); |
| |
| if (oldContent.isEmpty()) { |
| *errorMessage = QString::fromLatin1("Unable to patch %1: Could not read file content").arg( |
| QDir::toNativeSeparators(path)); |
| return false; |
| } |
| file.close(); |
| |
| QByteArray content = oldContent; |
| |
| QByteArray prfxpath("qt_prfxpath="); |
| int startPos = content.indexOf(prfxpath); |
| if (startPos == -1) { |
| *errorMessage = QString::fromLatin1( |
| "Unable to patch %1: Could not locate pattern \"qt_prfxpath=\"").arg( |
| QDir::toNativeSeparators(path)); |
| return false; |
| } |
| startPos += prfxpath.length(); |
| int endPos = content.indexOf(char(0), startPos); |
| if (endPos == -1) { |
| *errorMessage = QString::fromLatin1("Unable to patch %1: Internal error").arg( |
| QDir::toNativeSeparators(path)); |
| return false; |
| } |
| |
| QByteArray replacement = QByteArray(endPos - startPos, char(0)); |
| replacement[0] = '.'; |
| content.replace(startPos, endPos - startPos, replacement); |
| if (content == oldContent) |
| return true; |
| |
| if (!file.open(QIODevice::WriteOnly) |
| || (file.write(content) != content.size())) { |
| *errorMessage = QString::fromLatin1("Unable to patch %1: Could not write to file: %2").arg( |
| QDir::toNativeSeparators(path), file.errorString()); |
| return false; |
| } |
| return true; |
| } |
| |
| #ifdef Q_OS_WIN |
| QString getArchString(unsigned short machineArch) |
| { |
| switch (machineArch) { |
| case IMAGE_FILE_MACHINE_I386: |
| return QStringLiteral("x86"); |
| case IMAGE_FILE_MACHINE_ARM: |
| return QStringLiteral("arm"); |
| case IMAGE_FILE_MACHINE_AMD64: |
| return QStringLiteral("x64"); |
| case IMAGE_FILE_MACHINE_ARM64: |
| return QStringLiteral("arm64"); |
| default: |
| break; |
| } |
| return QString(); |
| } |
| #endif // Q_OS_WIN |
| |
| QT_END_NAMESPACE |