Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
utils.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "utils.h"
5
6#include <QtCore/QString>
7#include <QtCore/QDebug>
8#include <QtCore/QDir>
9#include <QtCore/QFile>
10#include <QtCore/QFileInfo>
11#include <QtCore/QTemporaryFile>
12#include <QtCore/QScopedPointer>
13#include <QtCore/QScopedArrayPointer>
14#include <QtCore/QStandardPaths>
15#if defined(Q_OS_WIN)
16# include <QtCore/qt_windows.h>
17# include <QtCore/private/qsystemerror_p.h>
18# include <shlwapi.h>
19# include <delayimp.h>
20#else // Q_OS_WIN
21# include <sys/wait.h>
22# include <sys/types.h>
23# include <sys/stat.h>
24# include <unistd.h>
25# include <stdlib.h>
26# include <string.h>
27# include <errno.h>
28# include <fcntl.h>
29#endif // !Q_OS_WIN
30
32
33using namespace Qt::StringLiterals;
34
36
38{
39 return (platform.testFlag(Msvc) || platform.testFlag(ClangMsvc))
40 && (dirName == "debug"_L1 || dirName == "release"_L1);
41}
42
43// Create a symbolic link by changing to the source directory to make sure the
44// link uses relative paths only (QFile::link() otherwise uses the absolute path).
46{
47 const QString oldDirectory = QDir::currentPath();
48 if (!QDir::setCurrent(source.absolutePath())) {
49 *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath()));
50 return false;
51 }
52 QFile file(source.fileName());
53 const bool success = file.link(target);
54 QDir::setCurrent(oldDirectory);
55 if (!success) {
56 *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3")
57 .arg(QDir::toNativeSeparators(source.absoluteFilePath()),
59 return false;
60 }
61 return true;
62}
63
65{
66 const QFileInfo fi(directory);
67 if (fi.isDir())
68 return true;
69 if (fi.exists()) {
70 *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.").
72 return false;
73 }
75 std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n";
76 if (!dryRun) {
77 QDir dir;
78 if (!dir.mkpath(directory)) {
79 *errorMessage = QString::fromLatin1("Could not create directory %1.")
81 return false;
82 }
83 }
84 return true;
85}
86
87// Find shared libraries matching debug/Platform in a directory, return relative names.
89 DebugMatchMode debugMatchMode,
90 const QString &prefix)
91{
92 QString nameFilter = prefix;
93 if (nameFilter.isEmpty())
94 nameFilter += u'*';
95 if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform))
96 nameFilter += u'd';
97 nameFilter += sharedLibrarySuffix();
100 const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files);
101 for (const QFileInfo &dllFi : dlls) {
102 const QString dllPath = dllFi.absoluteFilePath();
103 bool matches = true;
104 if (debugMatchMode != MatchDebugOrRelease && (platform & WindowsBased)) {
105 bool debugDll;
106 if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll,
108 matches = debugDll == (debugMatchMode == MatchDebug);
109 } else {
110 std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath)
111 << ": " << errorMessage;
112 }
113 } // Windows
114 if (matches)
115 result += dllFi.fileName();
116 } // for
117 return result;
118}
119
120#ifdef Q_OS_WIN
121
122// Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW()
124{
125 wchar_t shortBuffer[MAX_PATH];
126 const QString nativeFileName = QDir::toNativeSeparators(name);
127 if (!GetShortPathNameW(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), shortBuffer, MAX_PATH))
128 return name;
129 wchar_t result[MAX_PATH];
130 if (!GetLongPathNameW(shortBuffer, result, MAX_PATH))
131 return name;
133}
134
135// Find a tool binary in the Windows SDK 8
136QString findSdkTool(const QString &tool)
137{
138 QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(u';');
139 const QByteArray sdkDir = qgetenv("WindowsSdkDir");
140 if (!sdkDir.isEmpty())
141 paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + "/Tools/x64"_L1);
143}
144
145// runProcess helper: Create a temporary file for stdout/stderr redirection.
146static HANDLE createInheritableTemporaryFile()
147{
148 wchar_t path[MAX_PATH];
149 if (!GetTempPath(MAX_PATH, path))
150 return INVALID_HANDLE_VALUE;
151 wchar_t name[MAX_PATH];
152 if (!GetTempFileName(path, L"temp", 0, name)) // Creates file.
153 return INVALID_HANDLE_VALUE;
154 SECURITY_ATTRIBUTES securityAttributes;
155 ZeroMemory(&securityAttributes, sizeof(securityAttributes));
156 securityAttributes.nLength = sizeof(securityAttributes);
157 securityAttributes.bInheritHandle = TRUE;
158 return CreateFile(name, GENERIC_READ | GENERIC_WRITE,
159 FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
160 TRUNCATE_EXISTING,
161 FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
162}
163
164// runProcess helper: Rewind and read out a temporary file for stdout/stderr.
165static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result)
166{
167 if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF)
168 return;
169 char buf[1024];
170 DWORD bytesRead;
171 while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead)
172 result->append(buf, int(bytesRead));
173 CloseHandle(handle);
174}
175
176static inline void appendToCommandLine(const QString &argument, QString *commandLine)
177{
178 const bool needsQuote = argument.contains(u' ');
179 if (!commandLine->isEmpty())
180 commandLine->append(u' ');
181 if (needsQuote)
182 commandLine->append(u'"');
183 commandLine->append(argument);
184 if (needsQuote)
185 commandLine->append(u'"');
186}
187
188// runProcess: Run a command line process (replacement for QProcess which
189// does not exist in the bootstrap library).
190bool runProcess(const QString &binary, const QStringList &args,
191 const QString &workingDirectory,
192 unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
194{
195 if (exitCode)
196 *exitCode = 0;
197
198 STARTUPINFO si;
199 ZeroMemory(&si, sizeof(si));
200 si.cb = sizeof(si);
201
202 STARTUPINFO myInfo;
203 GetStartupInfo(&myInfo);
204 si.hStdInput = myInfo.hStdInput;
205 si.hStdOutput = myInfo.hStdOutput;
206 si.hStdError = myInfo.hStdError;
207
208 PROCESS_INFORMATION pi;
209 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
210 const QChar backSlash = u'\\';
211 QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ? QDir::currentPath() : workingDirectory);
212 if (!nativeWorkingDir.endsWith(backSlash))
213 nativeWorkingDir += backSlash;
214
215 if (stdOut) {
216 si.hStdOutput = createInheritableTemporaryFile();
217 if (si.hStdOutput == INVALID_HANDLE_VALUE) {
218 if (errorMessage)
219 *errorMessage = QStringLiteral("Error creating stdout temporary file");
220 return false;
221 }
222 si.dwFlags |= STARTF_USESTDHANDLES;
223 }
224
225 if (stdErr) {
226 si.hStdError = createInheritableTemporaryFile();
227 if (si.hStdError == INVALID_HANDLE_VALUE) {
228 if (errorMessage)
229 *errorMessage = QStringLiteral("Error creating stderr temporary file");
230 return false;
231 }
232 si.dwFlags |= STARTF_USESTDHANDLES;
233 }
234
235 // Create a copy of the command line which CreateProcessW can modify.
236 QString commandLine;
237 appendToCommandLine(binary, &commandLine);
238 for (const QString &a : args)
239 appendToCommandLine(a, &commandLine);
240 if (optVerboseLevel > 1)
241 std::wcout << "Running: " << commandLine << '\n';
242
243 QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]);
244 commandLine.toWCharArray(commandLineW.data());
245 commandLineW[commandLine.size()] = 0;
246 if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0,
247 reinterpret_cast<LPCWSTR>(nativeWorkingDir.utf16()), &si, &pi)) {
248 if (stdOut)
249 CloseHandle(si.hStdOutput);
250 if (stdErr)
251 CloseHandle(si.hStdError);
252 if (errorMessage) {
253 *errorMessage = QStringLiteral("CreateProcessW failed: ")
254 + QSystemError::windowsString();
255 }
256 return false;
257 }
258
259 WaitForSingleObject(pi.hProcess, INFINITE);
260 CloseHandle(pi.hThread);
261 if (exitCode)
262 GetExitCodeProcess(pi.hProcess, exitCode);
263 CloseHandle(pi.hProcess);
264
265 if (stdOut)
266 readTemporaryProcessFile(si.hStdOutput, stdOut);
267 if (stdErr)
268 readTemporaryProcessFile(si.hStdError, stdErr);
269 return true;
270}
271
272#else // Q_OS_WIN
273
274static inline char *encodeFileName(const QString &f)
275{
277 char *result = new char[encoded.size() + 1];
278 strcpy(result, encoded.constData());
279 return result;
280}
281
282static inline char *tempFilePattern()
283{
285 if (!path.endsWith(u'/'))
286 path += u'/';
287 path += QStringLiteral("tmpXXXXXX");
288 return encodeFileName(path);
289}
290
292{
293 enum { bufSize = 256 };
294
296 if (!lseek(fd, 0, 0)) {
297 char buf[bufSize];
298 while (true) {
299 const ssize_t rs = read(fd, buf, bufSize);
300 if (rs <= 0)
301 break;
302 result.append(buf, int(rs));
303 }
304 }
305 close(fd);
306 return result;
307}
308
309// runProcess: Run a command line process (replacement for QProcess which
310// does not exist in the bootstrap library).
312 const QString &workingDirectory,
313 unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
315{
316 QScopedArrayPointer<char> stdOutFileName;
317 QScopedArrayPointer<char> stdErrFileName;
318
319 int stdOutFile = 0;
320 if (stdOut) {
321 stdOutFileName.reset(tempFilePattern());
322 stdOutFile = mkstemp(stdOutFileName.data());
323 if (stdOutFile < 0) {
324 *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
325 return false;
326 }
327 }
328
329 int stdErrFile = 0;
330 if (stdErr) {
331 stdErrFileName.reset(tempFilePattern());
332 stdErrFile = mkstemp(stdErrFileName.data());
333 if (stdErrFile < 0) {
334 *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
335 return false;
336 }
337 }
338
339 const pid_t pID = fork();
340
341 if (pID < 0) {
342 *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno));
343 return false;
344 }
345
346 if (!pID) { // Child
347 if (stdOut) {
348 dup2(stdOutFile, STDOUT_FILENO);
349 close(stdOutFile);
350 }
351 if (stdErr) {
352 dup2(stdErrFile, STDERR_FILENO);
353 close(stdErrFile);
354 }
355
356 if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) {
357 std::wcerr << "Failed to change working directory to " << workingDirectory << ".\n";
358 ::_exit(-1);
359 }
360
361 char **argv = new char *[args.size() + 2]; // Create argv.
362 char **ap = argv;
363 *ap++ = encodeFileName(binary);
364 for (const QString &a : std::as_const(args))
365 *ap++ = encodeFileName(a);
366 *ap = 0;
367
368 execvp(argv[0], argv);
369 ::_exit(-1);
370 }
371
372 int status;
373 pid_t waitResult;
374
375 do {
376 waitResult = waitpid(pID, &status, 0);
377 } while (waitResult == -1 && errno == EINTR);
378
379 if (stdOut) {
380 *stdOut = readOutRedirectFile(stdOutFile);
381 unlink(stdOutFileName.data());
382 }
383 if (stdErr) {
384 *stdErr = readOutRedirectFile(stdErrFile);
385 unlink(stdErrFileName.data());
386 }
387
388 if (waitResult < 0) {
389 *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno));
390 return false;
391 }
392 if (!WIFEXITED(status)) {
393 *errorMessage = binary + QStringLiteral(" did not exit cleanly.");
394 return false;
395 }
396 if (exitCode)
397 *exitCode = WEXITSTATUS(status);
398 return true;
399}
400
401#endif // !Q_OS_WIN
402
403// Find a file in the path using ShellAPI. This can be used to locate DLLs which
404// QStandardPaths cannot do.
406{
407#if defined(Q_OS_WIN)
408 if (file.size() < MAX_PATH - 1) {
409 wchar_t buffer[MAX_PATH];
410 file.toWCharArray(buffer);
411 buffer[file.size()] = 0;
412 if (PathFindOnPath(buffer, NULL))
414 }
415 return QString();
416#else // Q_OS_WIN
418#endif // !Q_OS_WIN
419}
420
421const char *qmakeInfixKey = "QT_INFIX";
422
423QMap<QString, QString> queryQtPaths(const QString &qtpathsBinary, QString *errorMessage)
424{
425 const QString binary = !qtpathsBinary.isEmpty() ? qtpathsBinary : QStringLiteral("qtpaths");
426 const QString colonSpace = QStringLiteral(": ");
427 QByteArray stdOut;
428 QByteArray stdErr;
429 unsigned long exitCode = 0;
430 if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut,
431 &stdErr, errorMessage)) {
432 *errorMessage = QStringLiteral("Error running binary ") + binary + colonSpace + *errorMessage;
433 return QMap<QString, QString>();
434 }
435 if (exitCode) {
436 *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode)
437 + colonSpace + QString::fromLocal8Bit(stdErr);
438 return QMap<QString, QString>();
439 }
440 const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(u'\r');
441 QMap<QString, QString> result;
442 const qsizetype size = output.size();
443 for (qsizetype pos = 0; pos < size; ) {
444 const qsizetype colonPos = output.indexOf(u':', pos);
445 if (colonPos < 0)
446 break;
447 qsizetype endPos = output.indexOf(u'\n', colonPos + 1);
448 if (endPos < 0)
449 endPos = size;
450 const QString key = output.mid(pos, colonPos - pos);
451 const QString value = output.mid(colonPos + 1, endPos - colonPos - 1);
452 result.insert(key, value);
453 pos = endPos + 1;
454 }
455 QFile qconfigPriFile(result.value(QStringLiteral("QT_HOST_DATA")) + QStringLiteral("/mkspecs/qconfig.pri"));
456 if (qconfigPriFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
457 while (true) {
458 const QByteArray line = qconfigPriFile.readLine();
459 if (line.isEmpty())
460 break;
461 if (line.startsWith("QT_LIBINFIX")) {
462 const int pos = line.indexOf('=');
463 if (pos >= 0) {
464 const QString infix = QString::fromUtf8(line.right(line.size() - pos - 1).trimmed());
465 if (!infix.isEmpty())
466 result.insert(QLatin1StringView(qmakeInfixKey), infix);
467 }
468 break;
469 }
470 }
471 } else {
472 std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName())
473 << colonSpace << qconfigPriFile.errorString()<< '\n';
474 }
475 return result;
476}
477
478// Update a file or directory.
479bool updateFile(const QString &sourceFileName, const QStringList &nameFilters,
480 const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage)
481{
482 const QFileInfo sourceFileInfo(sourceFileName);
483 const QString targetFileName = targetDirectory + u'/' + sourceFileInfo.fileName();
484 if (optVerboseLevel > 1)
485 std::wcout << "Checking " << sourceFileName << ", " << targetFileName<< '\n';
486
487 if (!sourceFileInfo.exists()) {
488 *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName));
489 return false;
490 }
491
492 if (sourceFileInfo.isSymLink()) {
493 *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).")
494 .arg(QDir::toNativeSeparators(sourceFileName));
495 return false;
496 }
497
498 const QFileInfo targetFileInfo(targetFileName);
499
500 if (sourceFileInfo.isDir()) {
501 if (targetFileInfo.exists()) {
502 if (!targetFileInfo.isDir()) {
503 *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.")
504 .arg(QDir::toNativeSeparators(targetFileName));
505 return false;
506 } // Not a directory.
507 } else { // exists.
508 QDir d(targetDirectory);
509 if (optVerboseLevel)
510 std::wcout << "Creating " << QDir::toNativeSeparators(targetFileName) << ".\n";
511 if (!(flags & SkipUpdateFile) && !d.mkdir(sourceFileInfo.fileName())) {
512 *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.")
513 .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory));
514 return false;
515 }
516 }
517 // Recurse into directory
518 QDir dir(sourceFileName);
519 const QFileInfoList allEntries = dir.entryInfoList(nameFilters, QDir::Files)
520 + dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
521 for (const QFileInfo &entryFi : allEntries) {
522 if (!updateFile(entryFi.absoluteFilePath(), nameFilters, targetFileName, flags, json, errorMessage))
523 return false;
524 }
525 return true;
526 } // Source is directory.
527
528 if (targetFileInfo.exists()) {
529 if (!(flags & ForceUpdateFile)
530 && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) {
531 if (optVerboseLevel)
532 std::wcout << sourceFileInfo.fileName() << " is up to date.\n";
533 if (json)
534 json->addFile(sourceFileName, targetDirectory);
535 return true;
536 }
537 QFile targetFile(targetFileName);
538 if (!(flags & SkipUpdateFile) && !targetFile.remove()) {
539 *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2")
540 .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString());
541 return false;
542 }
543 } // target exists
544 QFile file(sourceFileName);
545 if (optVerboseLevel)
546 std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n";
547 if (!(flags & SkipUpdateFile) && !file.copy(targetFileName)) {
548 *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3")
549 .arg(QDir::toNativeSeparators(sourceFileName),
550 QDir::toNativeSeparators(targetFileName),
551 file.errorString());
552 return false;
553 }
554 if (json)
555 json->addFile(sourceFileName, targetDirectory);
556 return true;
557}
558
559#ifdef Q_OS_WIN
560
561static inline QString stringFromRvaPtr(const void *rvaPtr)
562{
563 return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr));
564}
565
566// Helper for reading out PE executable files: Find a section header for an RVA
567// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
568template <class ImageNtHeader>
569const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader)
570{
571 const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
572 const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections;
573 for ( ; section < sectionEnd; ++section)
574 if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize))
575 return section;
576 return 0;
577}
578
579// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
580template <class ImageNtHeader>
581inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase)
582{
583 const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
584 if (!sectionHdr)
585 return 0;
586 const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
587 return static_cast<const char *>(imageBase) + rva - delta;
588}
589
590// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32
591template <class ImageNtHeader>
592inline unsigned ntHeaderWordSize(const ImageNtHeader *header)
593{
594 // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
595 enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
596 if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
597 return 32;
598 if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
599 return 64;
600 return 0;
601}
602
603// Helper for reading out PE executable files: Retrieve the NT image header of an
604// executable via the legacy DOS header.
605static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage)
606{
607 IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
608 // Check DOS header consistency
609 if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
610 || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
611 *errorMessage = QString::fromLatin1("DOS header check failed.");
612 return 0;
613 }
614 // Retrieve NT header
615 char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
616 IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
617 // check NT header consistency
618 if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
619 || ntHeaders->Signature != IMAGE_NT_SIGNATURE
620 || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
621 *errorMessage = QString::fromLatin1("NT header check failed.");
622 return 0;
623 }
624 // Check magic
625 if (!ntHeaderWordSize(ntHeaders)) {
626 *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid.").
627 arg(ntHeaders->OptionalHeader.Magic);
628 return 0;
629 }
630 // Check section headers
631 IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
632 if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
633 *errorMessage = QString::fromLatin1("NT header section header check failed.");
634 return 0;
635 }
636 return ntHeaders;
637}
638
639// Helper for reading out PE executable files: Read out import sections from
640// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
641template <class ImageNtHeader>
642inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage)
643{
644 // Get import directory entry RVA and read out
645 const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
646 if (!importsStartRVA) {
647 *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.");
648 return QStringList();
649 }
650 const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, base));
651 if (!importDesc) {
652 *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry.");
653 return QStringList();
654 }
656 for ( ; importDesc->Name; ++importDesc)
657 result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
658
659 // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
660 // Check on grAttr bit 1 whether this is the format using RVA's > VS 6
661 if (const DWORD delayedImportsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) {
662 const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
663 for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc)
664 result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
665 }
666
667 return result;
668}
669
670// Check for MSCV runtime (MSVCP90D.dll/MSVCP90.dll, MSVCP120D.dll/MSVCP120.dll,
671// VCRUNTIME140D.DLL/VCRUNTIME140.DLL (VS2015) or msvcp120d_app.dll/msvcp120_app.dll).
672enum MsvcDebugRuntimeResult { MsvcDebugRuntime, MsvcReleaseRuntime, NoMsvcRuntime };
673
674static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &dependentLibraries)
675{
676 for (const QString &lib : dependentLibraries) {
677 qsizetype pos = 0;
678 if (lib.startsWith("MSVCR"_L1, Qt::CaseInsensitive)
679 || lib.startsWith("MSVCP"_L1, Qt::CaseInsensitive)
680 || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive)
681 || lib.startsWith("VCCORLIB"_L1, Qt::CaseInsensitive)
682 || lib.startsWith("CONCRT"_L1, Qt::CaseInsensitive)
683 || lib.startsWith("UCRTBASE"_L1, Qt::CaseInsensitive)) {
684 qsizetype lastDotPos = lib.lastIndexOf(u'.');
685 pos = -1 == lastDotPos ? 0 : lastDotPos - 1;
686 }
687
688 if (pos > 0) {
689 const auto removeExtraSuffix = [&lib, &pos](const QString &suffix) -> void {
690 if (lib.contains(suffix, Qt::CaseInsensitive))
691 pos -= suffix.size();
692 };
693 removeExtraSuffix("_app"_L1);
694 removeExtraSuffix("_atomic_wait"_L1);
695 removeExtraSuffix("_codecvt_ids"_L1);
696 }
697
698 if (pos)
699 return lib.at(pos).toLower() == u'd' ? MsvcDebugRuntime : MsvcReleaseRuntime;
700 }
701 return NoMsvcRuntime;
702}
703
704template <class ImageNtHeader>
705inline QStringList determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
707{
708 return readImportSections(nth, fileMemory, errorMessage);
709}
710
711template <class ImageNtHeader>
712inline bool determineDebug(const ImageNtHeader *nth, const void *fileMemory,
713 QStringList *dependentLibrariesIn, QString *errorMessage)
714{
715 if (nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED)
716 return false;
717
718 const QStringList dependentLibraries = dependentLibrariesIn != nullptr ?
719 *dependentLibrariesIn :
720 determineDependentLibs(nth, fileMemory, errorMessage);
721
722 const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
723 // When an MSVC debug entry is present, check whether the debug runtime
724 // is actually used to detect -release / -force-debug-info builds.
725 const MsvcDebugRuntimeResult msvcrt = checkMsvcDebugRuntime(dependentLibraries);
726 if (msvcrt == NoMsvcRuntime)
727 return hasDebugEntry;
728 else
729 return hasDebugEntry && msvcrt == MsvcDebugRuntime;
730}
731
732template <class ImageNtHeader>
733inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
734 QStringList *dependentLibrariesIn,
735 bool *isDebugIn, QString *errorMessage)
736{
737 if (dependentLibrariesIn)
738 *dependentLibrariesIn = determineDependentLibs(nth, fileMemory, errorMessage);
739
740 if (isDebugIn)
741 *isDebugIn = determineDebug(nth, fileMemory, dependentLibrariesIn, errorMessage);
742}
743
744// Read a PE executable and determine dependent libraries, word size
745// and debug flags.
746bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage,
747 QStringList *dependentLibrariesIn, unsigned *wordSizeIn,
748 bool *isDebugIn, bool isMinGW, unsigned short *machineArchIn)
749{
750 bool result = false;
751 HANDLE hFile = NULL;
752 HANDLE hFileMap = NULL;
753 void *fileMemory = 0;
754
755 if (dependentLibrariesIn)
756 dependentLibrariesIn->clear();
757 if (wordSizeIn)
758 *wordSizeIn = 0;
759 if (isDebugIn)
760 *isDebugIn = false;
761
762 do {
763 // Create a memory mapping of the file
764 hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL,
765 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
766 if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
767 *errorMessage = QString::fromLatin1("Cannot open '%1': %2")
768 .arg(peExecutableFileName, QSystemError::windowsString());
769 break;
770 }
771
772 hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
773 if (hFileMap == NULL) {
774 *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2")
775 .arg(peExecutableFileName, QSystemError::windowsString());
776 break;
777 }
778
779 fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
780 if (!fileMemory) {
781 *errorMessage = QString::fromLatin1("Cannot map '%1': %2")
782 .arg(peExecutableFileName, QSystemError::windowsString());
783 break;
784 }
785
786 const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
787 if (!ntHeaders)
788 break;
789
790 const unsigned wordSize = ntHeaderWordSize(ntHeaders);
791 if (wordSizeIn)
792 *wordSizeIn = wordSize;
793 if (wordSize == 32) {
794 determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
795 fileMemory, dependentLibrariesIn, isDebugIn, errorMessage);
796 } else {
797 determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
798 fileMemory, dependentLibrariesIn, isDebugIn, errorMessage);
799 }
800
801 if (machineArchIn)
802 *machineArchIn = ntHeaders->FileHeader.Machine;
803
804 result = true;
805 if (optVerboseLevel > 1) {
806 std::wcout << __FUNCTION__ << ": " << QDir::toNativeSeparators(peExecutableFileName)
807 << ' ' << wordSize << " bit";
808 if (isMinGW)
809 std::wcout << ", MinGW";
810 if (dependentLibrariesIn) {
811 std::wcout << ", dependent libraries: ";
812 if (optVerboseLevel > 2)
813 std::wcout << dependentLibrariesIn->join(u' ');
814 else
815 std::wcout << dependentLibrariesIn->size();
816 }
817 if (isDebugIn)
818 std::wcout << (*isDebugIn ? ", debug" : ", release");
819 std::wcout << '\n';
820 }
821 } while (false);
822
823 if (fileMemory)
824 UnmapViewOfFile(fileMemory);
825
826 if (hFileMap != NULL)
827 CloseHandle(hFileMap);
828
829 if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
830 CloseHandle(hFile);
831
832 return result;
833}
834
835QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize)
836{
837 const QString prefix = QStringLiteral("D3Dcompiler_");
839 // Get the DLL from Kit 8.0 onwards
840 const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir"));
841 if (!kitDir.isEmpty()) {
842 QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/");
843 if (platform.testFlag(ArmBased)) {
844 redistDirPath += QStringLiteral("arm");
845 } else {
846 redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64");
847 }
848 QDir redistDir(redistDirPath);
849 if (redistDir.exists()) {
850 const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + u'*' + suffix), QDir::Files);
851 if (!files.isEmpty())
852 return files.front().absoluteFilePath();
853 }
854 }
855 QStringList candidateVersions;
856 for (int i = 47 ; i >= 40 ; --i)
857 candidateVersions.append(prefix + QString::number(i) + suffix);
858 // Check the bin directory of the Qt SDK (in case it is shadowed by the
859 // Windows system directory in PATH).
860 for (const QString &candidate : std::as_const(candidateVersions)) {
861 const QFileInfo fi(qtBinDir + u'/' + candidate);
862 if (fi.isFile())
863 return fi.absoluteFilePath();
864 }
865 // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47).
866 if (platform.testFlag(IntelBased)) {
868 unsigned detectedWordSize;
869 for (const QString &candidate : std::as_const(candidateVersions)) {
870 const QString dll = findInPath(candidate);
871 if (!dll.isEmpty()
872 && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0)
873 && detectedWordSize == wordSize) {
874 return dll;
875 }
876 }
877 }
878 return QString();
879}
880
881QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize)
882{
884 const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir"));
886 for (QString prefix : { QStringLiteral("dxcompiler"), QStringLiteral("dxil") }) {
887 QString name = prefix + suffix;
888 if (!kitDir.isEmpty()) {
889 QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/");
890 if (platform.testFlag(ArmBased)) {
891 redistDirPath += wordSize == 32 ? QStringLiteral("arm") : QStringLiteral("arm64");
892 } else {
893 redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64");
894 }
895 QDir redistDir(redistDirPath);
896 if (redistDir.exists()) {
897 const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + u'*' + suffix), QDir::Files);
898 if (!files.isEmpty()) {
899 results.append(files.front().absoluteFilePath());
900 continue;
901 }
902 }
903 }
904 // Check the bin directory of the Qt SDK (in case it is shadowed by the
905 // Windows system directory in PATH).
906 const QFileInfo fi(qtBinDir + u'/' + name);
907 if (fi.isFile()) {
908 results.append(fi.absoluteFilePath());
909 continue;
910 }
911 // Try to find it in the PATH (e.g. the Vulkan SDK ships these, even if Windows itself doesn't).
912 if (platform.testFlag(IntelBased)) {
914 unsigned detectedWordSize;
915 const QString dll = findInPath(name);
916 if (!dll.isEmpty()
917 && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0)
918 && detectedWordSize == wordSize)
919 {
920 results.append(dll);
921 continue;
922 }
923 }
924 }
925 return results;
926}
927
928#else // Q_OS_WIN
929
931 QStringList *, unsigned *, bool *, bool, unsigned short *)
932{
933 *errorMessage = QStringLiteral("Not implemented.");
934 return false;
935}
936
938{
939 return QString();
940}
941
943{
944 return QStringList();
945}
946
947#endif // !Q_OS_WIN
948
949// Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=."
951{
952 if (optVerboseLevel)
953 std::wcout << "Patching " << QFileInfo(path).fileName() << "...\n";
954
955 QFile file(path);
957 *errorMessage = QString::fromLatin1("Unable to patch %1: %2").arg(
959 return false;
960 }
961 const QByteArray oldContent = file.readAll();
962
963 if (oldContent.isEmpty()) {
964 *errorMessage = QString::fromLatin1("Unable to patch %1: Could not read file content").arg(
966 return false;
967 }
968 file.close();
969
970 QByteArray content = oldContent;
971
972 QByteArray prfxpath("qt_prfxpath=");
973 int startPos = content.indexOf(prfxpath);
974 if (startPos == -1) {
976 "Unable to patch %1: Could not locate pattern \"qt_prfxpath=\"").arg(
978 return false;
979 }
980 startPos += prfxpath.length();
981 int endPos = content.indexOf(char(0), startPos);
982 if (endPos == -1) {
983 *errorMessage = QString::fromLatin1("Unable to patch %1: Internal error").arg(
985 return false;
986 }
987
988 QByteArray replacement = QByteArray(endPos - startPos, char(0));
989 replacement[0] = '.';
990 content.replace(startPos, endPos - startPos, replacement);
991 if (content == oldContent)
992 return true;
993
995 || (file.write(content) != content.size())) {
996 *errorMessage = QString::fromLatin1("Unable to patch %1: Could not write to file: %2").arg(
998 return false;
999 }
1000 return true;
1001}
1002
1003#ifdef Q_OS_WIN
1004QString getArchString(unsigned short machineArch)
1005{
1006 switch (machineArch) {
1007 case IMAGE_FILE_MACHINE_I386:
1008 return QStringLiteral("x86");
1009 case IMAGE_FILE_MACHINE_ARM:
1010 return QStringLiteral("arm");
1011 case IMAGE_FILE_MACHINE_AMD64:
1012 return QStringLiteral("x64");
1014 return QStringLiteral("arm64");
1015 default:
1016 break;
1017 }
1018 return QString();
1019}
1020#endif // Q_OS_WIN
1021
void addFile(const QString &source, const QString &target)
Definition utils.h:75
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
qsizetype indexOf(char c, qsizetype from=0) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray & replace(qsizetype index, qsizetype len, const char *s, qsizetype alen)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:339
\inmodule QtCore
\inmodule QtCore
Definition qdir.h:20
static QString fromNativeSeparators(const QString &pathName)
Definition qdir.cpp:962
static bool setCurrent(const QString &path)
Sets the application's current working directory to path.
Definition qdir.cpp:2030
static QString tempPath()
Returns the absolute canonical path of the system's temporary directory.
Definition qdir.cpp:2133
static QString cleanPath(const QString &path)
Returns path with directory separators normalized (that is, platform-native separators converted to "...
Definition qdir.cpp:2398
static QString toNativeSeparators(const QString &pathName)
Definition qdir.cpp:929
static QString currentPath()
Returns the absolute path of the application's current directory.
Definition qdir.cpp:2054
@ Files
Definition qdir.h:23
@ NoDotAndDotDot
Definition qdir.h:44
@ Dirs
Definition qdir.h:22
void close() override
Calls QFileDevice::flush() and closes the file.
QString fileName() const
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool link(const QString &newName)
Creates a link named linkName that points to the file currently specified by fileName().
Definition qfile.cpp:720
bool copy(const QString &newName)
Copies the file named fileName() to newName.
Definition qfile.cpp:765
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
qint64 size() const override
\reimp
Definition qfile.cpp:1179
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
QString errorString() const
Returns a human-readable description of the last device error that occurred.
qsizetype size() const noexcept
Definition qlist.h:397
void append(parameter_type t)
Definition qlist.h:458
static QString findExecutable(const QString &executableName, const QStringList &paths=QStringList())
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5949
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QString right(qsizetype n) const &
Definition qstring.h:375
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString & append(QChar c)
Definition qstring.cpp:3252
QString trimmed() const &
Definition qstring.h:447
Combined button and popup list for selecting options.
void * HANDLE
@ CaseInsensitive
QList< QString > QStringList
Constructs a string list that contains the given string, str.
static QString header(const QString &name)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static bool matches(const QJsonObject &object, const QString &osName, const QVersionNumber &kernelVersion, const QString &osRelease, const QOpenGLConfig::Gpu &gpu)
Definition qopengl.cpp:270
GLsizei GLsizei GLenum void * binary
GLuint64 GLenum void * handle
GLuint64 key
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLfloat GLfloat f
GLenum GLuint buffer
GLsizei const GLuint * paths
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLbitfield flags
GLuint64 GLenum GLint fd
GLuint name
GLsizei bufSize
GLsizei GLsizei GLchar * source
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
#define MAX_PATH
SSL_CTX int void * arg
#define QStringLiteral(str)
#define IMAGE_FILE_MACHINE_ARM64
Definition main.cpp:24
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
ptrdiff_t qsizetype
Definition qtypes.h:165
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
Definition qurl.cpp:3517
static const uint base
Definition qurlidna.cpp:20
ReturnedValue read(const char *data)
QT_BEGIN_NAMESPACE typedef uchar * output
QT_BEGIN_NAMESPACE Platform platform()
Platform
QFile file
[0]
QString dir
[11]
QStringList files
[8]
QDBusArgument argument
QJSValueList args
static QByteArray readOutRedirectFile(int fd)
Definition utils.cpp:291
QStringList findDxc(Platform, const QString &, unsigned)
Definition utils.cpp:942
bool readPeExecutable(const QString &, QString *errorMessage, QStringList *, unsigned *, bool *, bool, unsigned short *)
Definition utils.cpp:930
int optVerboseLevel
Definition utils.cpp:35
QString findInPath(const QString &file)
Definition utils.cpp:405
static char * encodeFileName(const QString &f)
Definition utils.cpp:274
QMap< QString, QString > queryQtPaths(const QString &qtpathsBinary, QString *errorMessage)
Definition utils.cpp:423
bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage)
Definition utils.cpp:45
static char * tempFilePattern()
Definition utils.cpp:282
QString findD3dCompiler(Platform, const QString &, unsigned)
Definition utils.cpp:937
bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun)
Definition utils.cpp:64
QStringList findSharedLibraries(const QDir &directory, Platform platform, DebugMatchMode debugMatchMode, const QString &prefix)
Definition utils.cpp:88
bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage)
Definition utils.cpp:479
bool runProcess(const QString &binary, const QStringList &args, const QString &workingDirectory, unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, QString *errorMessage)
Definition utils.cpp:311
bool isBuildDirectory(Platform platform, const QString &dirName)
Definition utils.cpp:37
bool patchQtCore(const QString &path, QString *errorMessage)
Definition utils.cpp:950
const char * qmakeInfixKey
Definition utils.cpp:421
@ ClangMsvc
Definition utils.h:29
@ WindowsBased
Definition utils.h:22
@ Msvc
Definition utils.h:27
@ WindowsDesktopMinGW
Definition utils.h:35
@ ArmBased
Definition utils.h:25
@ IntelBased
Definition utils.h:24
bool platformHasDebugSuffix(Platform p)
Definition utils.h:45
@ SkipUpdateFile
Definition utils.h:200
@ ForceUpdateFile
Definition utils.h:199
static const char windowsSharedLibrarySuffix[]
Definition utils.h:139
DebugMatchMode
Definition utils.h:152
@ MatchDebug
Definition utils.h:153
@ MatchDebugOrRelease
Definition utils.h:155
QString sharedLibrarySuffix()
Definition utils.h:141
QString normalizeFileName(const QString &name)
Definition utils.h:136