1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "utils.h"
30 #include "elfreader.h"
31 
32 #include <QtCore/QString>
33 #include <QtCore/QDebug>
34 #include <QtCore/QDir>
35 #include <QtCore/QFile>
36 #include <QtCore/QFileInfo>
37 #include <QtCore/QTemporaryFile>
38 #include <QtCore/QScopedPointer>
39 #include <QtCore/QScopedArrayPointer>
40 #include <QtCore/QStandardPaths>
41 #if defined(Q_OS_WIN)
42 #  include <QtCore/qt_windows.h>
43 #  include <shlwapi.h>
44 #  include <delayimp.h>
45 #else // Q_OS_WIN
46 #  include <sys/wait.h>
47 #  include <sys/types.h>
48 #  include <sys/stat.h>
49 #  include <unistd.h>
50 #  include <stdlib.h>
51 #  include <string.h>
52 #  include <errno.h>
53 #  include <fcntl.h>
54 #endif  // !Q_OS_WIN
55 
56 QT_BEGIN_NAMESPACE
57 
58 int optVerboseLevel = 1;
59 
isBuildDirectory(Platform platform,const QString & dirName)60 bool isBuildDirectory(Platform platform, const QString &dirName)
61 {
62     return (platform.testFlag(Msvc) || platform.testFlag(ClangMsvc))
63         && (dirName == QLatin1String("debug") || dirName == QLatin1String("release"));
64 }
65 
66 // Create a symbolic link by changing to the source directory to make sure the
67 // link uses relative paths only (QFile::link() otherwise uses the absolute path).
createSymbolicLink(const QFileInfo & source,const QString & target,QString * errorMessage)68 bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage)
69 {
70     const QString oldDirectory = QDir::currentPath();
71     if (!QDir::setCurrent(source.absolutePath())) {
72         *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath()));
73         return false;
74     }
75     QFile file(source.fileName());
76     const bool success = file.link(target);
77     QDir::setCurrent(oldDirectory);
78     if (!success) {
79         *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3")
80                         .arg(QDir::toNativeSeparators(source.absoluteFilePath()),
81                              QDir::toNativeSeparators(target), file.errorString());
82         return false;
83     }
84     return true;
85 }
86 
createDirectory(const QString & directory,QString * errorMessage)87 bool createDirectory(const QString &directory, QString *errorMessage)
88 {
89     const QFileInfo fi(directory);
90     if (fi.isDir())
91         return true;
92     if (fi.exists()) {
93         *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.").
94                         arg(QDir::toNativeSeparators(directory));
95         return false;
96     }
97     if (optVerboseLevel)
98         std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n";
99     QDir dir;
100     if (!dir.mkpath(directory)) {
101         *errorMessage = QString::fromLatin1("Could not create directory %1.").
102                         arg(QDir::toNativeSeparators(directory));
103         return false;
104     }
105     return true;
106 }
107 
108 // Find shared libraries matching debug/Platform in a directory, return relative names.
findSharedLibraries(const QDir & directory,Platform platform,DebugMatchMode debugMatchMode,const QString & prefix)109 QStringList findSharedLibraries(const QDir &directory, Platform platform,
110                                 DebugMatchMode debugMatchMode,
111                                 const QString &prefix)
112 {
113     QString nameFilter = prefix;
114     if (nameFilter.isEmpty())
115         nameFilter += QLatin1Char('*');
116     if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform))
117         nameFilter += QLatin1Char('d');
118     nameFilter += sharedLibrarySuffix(platform);
119     QStringList result;
120     QString errorMessage;
121     const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files);
122     for (const QFileInfo &dllFi : dlls) {
123         const QString dllPath = dllFi.absoluteFilePath();
124         bool matches = true;
125         if (debugMatchMode != MatchDebugOrRelease && (platform & WindowsBased)) {
126             bool debugDll;
127             if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll,
128                                  (platform == WindowsDesktopMinGW))) {
129                 matches = debugDll == (debugMatchMode == MatchDebug);
130             } else {
131                 std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath)
132                            << ": " << errorMessage;
133             }
134         } // Windows
135         if (matches)
136             result += dllFi.fileName();
137     } // for
138     return result;
139 }
140 
141 #ifdef Q_OS_WIN
winErrorMessage(unsigned long error)142 QString winErrorMessage(unsigned long error)
143 {
144     QString rc = QString::fromLatin1("#%1: ").arg(error);
145     ushort *lpMsgBuf;
146 
147     const DWORD len = FormatMessage(
148             FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
149             NULL, error, 0, reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, NULL);
150     if (len) {
151         rc = QString::fromUtf16(lpMsgBuf, int(len));
152         LocalFree(lpMsgBuf);
153     } else {
154         rc += QString::fromLatin1("<unknown error>");
155     }
156     return rc;
157 }
158 
159 // Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW()
normalizeFileName(const QString & name)160 QString normalizeFileName(const QString &name)
161 {
162     wchar_t shortBuffer[MAX_PATH];
163     const QString nativeFileName = QDir::toNativeSeparators(name);
164     if (!GetShortPathNameW(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), shortBuffer, MAX_PATH))
165         return name;
166     wchar_t result[MAX_PATH];
167     if (!GetLongPathNameW(shortBuffer, result, MAX_PATH))
168         return name;
169     return QDir::fromNativeSeparators(QString::fromWCharArray(result));
170 }
171 
172 // Find a tool binary in the Windows SDK 8
findSdkTool(const QString & tool)173 QString findSdkTool(const QString &tool)
174 {
175     QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(QLatin1Char(';'));
176     const QByteArray sdkDir = qgetenv("WindowsSdkDir");
177     if (!sdkDir.isEmpty())
178         paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + QLatin1String("/Tools/x64"));
179     return QStandardPaths::findExecutable(tool, paths);
180 }
181 
182 // runProcess helper: Create a temporary file for stdout/stderr redirection.
createInheritableTemporaryFile()183 static HANDLE createInheritableTemporaryFile()
184 {
185     wchar_t path[MAX_PATH];
186     if (!GetTempPath(MAX_PATH, path))
187         return INVALID_HANDLE_VALUE;
188     wchar_t name[MAX_PATH];
189     if (!GetTempFileName(path, L"temp", 0, name)) // Creates file.
190         return INVALID_HANDLE_VALUE;
191     SECURITY_ATTRIBUTES securityAttributes;
192     ZeroMemory(&securityAttributes, sizeof(securityAttributes));
193     securityAttributes.nLength = sizeof(securityAttributes);
194     securityAttributes.bInheritHandle = TRUE;
195     return CreateFile(name, GENERIC_READ | GENERIC_WRITE,
196                       FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
197                       TRUNCATE_EXISTING,
198                       FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
199 }
200 
201 // runProcess helper: Rewind and read out a temporary file for stdout/stderr.
readTemporaryProcessFile(HANDLE handle,QByteArray * result)202 static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result)
203 {
204     if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF)
205         return;
206     char buf[1024];
207     DWORD bytesRead;
208     while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead)
209         result->append(buf, int(bytesRead));
210     CloseHandle(handle);
211 }
212 
appendToCommandLine(const QString & argument,QString * commandLine)213 static inline void appendToCommandLine(const QString &argument, QString *commandLine)
214 {
215     const bool needsQuote = argument.contains(QLatin1Char(' '));
216     if (!commandLine->isEmpty())
217         commandLine->append(QLatin1Char(' '));
218     if (needsQuote)
219         commandLine->append(QLatin1Char('"'));
220     commandLine->append(argument);
221     if (needsQuote)
222         commandLine->append(QLatin1Char('"'));
223 }
224 
225 // runProcess: Run a command line process (replacement for QProcess which
226 // does not exist in the bootstrap library).
runProcess(const QString & binary,const QStringList & args,const QString & workingDirectory,unsigned long * exitCode,QByteArray * stdOut,QByteArray * stdErr,QString * errorMessage)227 bool runProcess(const QString &binary, const QStringList &args,
228                 const QString &workingDirectory,
229                 unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
230                 QString *errorMessage)
231 {
232     if (exitCode)
233         *exitCode = 0;
234 
235     STARTUPINFO si;
236     ZeroMemory(&si, sizeof(si));
237     si.cb = sizeof(si);
238 
239     STARTUPINFO myInfo;
240     GetStartupInfo(&myInfo);
241     si.hStdInput = myInfo.hStdInput;
242     si.hStdOutput = myInfo.hStdOutput;
243     si.hStdError = myInfo.hStdError;
244 
245     PROCESS_INFORMATION pi;
246     ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
247     const QChar backSlash = QLatin1Char('\\');
248     QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ?  QDir::currentPath() : workingDirectory);
249     if (!nativeWorkingDir.endsWith(backSlash))
250         nativeWorkingDir += backSlash;
251 
252     if (stdOut) {
253         si.hStdOutput = createInheritableTemporaryFile();
254         if (si.hStdOutput == INVALID_HANDLE_VALUE) {
255             if (errorMessage)
256                 *errorMessage = QStringLiteral("Error creating stdout temporary file");
257             return false;
258         }
259         si.dwFlags |= STARTF_USESTDHANDLES;
260     }
261 
262     if (stdErr) {
263         si.hStdError = createInheritableTemporaryFile();
264         if (si.hStdError == INVALID_HANDLE_VALUE) {
265             if (errorMessage)
266                 *errorMessage = QStringLiteral("Error creating stderr temporary file");
267             return false;
268         }
269         si.dwFlags |= STARTF_USESTDHANDLES;
270     }
271 
272     // Create a copy of the command line which CreateProcessW can modify.
273     QString commandLine;
274     appendToCommandLine(binary, &commandLine);
275     for (const QString &a : args)
276         appendToCommandLine(a, &commandLine);
277     if (optVerboseLevel > 1)
278         std::wcout << "Running: " << commandLine << '\n';
279 
280     QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]);
281     commandLine.toWCharArray(commandLineW.data());
282     commandLineW[commandLine.size()] = 0;
283     if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0,
284                         reinterpret_cast<LPCWSTR>(nativeWorkingDir.utf16()), &si, &pi)) {
285         if (stdOut)
286             CloseHandle(si.hStdOutput);
287         if (stdErr)
288             CloseHandle(si.hStdError);
289         if (errorMessage)
290             *errorMessage = QStringLiteral("CreateProcessW failed: ") + winErrorMessage(GetLastError());
291         return false;
292     }
293 
294     WaitForSingleObject(pi.hProcess, INFINITE);
295     CloseHandle(pi.hThread);
296     if (exitCode)
297         GetExitCodeProcess(pi.hProcess, exitCode);
298     CloseHandle(pi.hProcess);
299 
300     if (stdOut)
301         readTemporaryProcessFile(si.hStdOutput, stdOut);
302     if (stdErr)
303         readTemporaryProcessFile(si.hStdError, stdErr);
304     return true;
305 }
306 
runElevatedBackgroundProcess(const QString & binary,const QStringList & args,Qt::HANDLE * processHandle)307 bool runElevatedBackgroundProcess(const QString &binary, const QStringList &args, Qt::HANDLE *processHandle)
308 {
309     QScopedArrayPointer<wchar_t> binaryW(new wchar_t[binary.size() + 1]);
310     binary.toWCharArray(binaryW.data());
311     binaryW[binary.size()] = 0;
312 
313     const QString arguments = args.join(QLatin1Char(' '));
314     QScopedArrayPointer<wchar_t> argumentsW(new wchar_t[arguments.size() + 1]);
315     arguments.toWCharArray(argumentsW.data());
316     argumentsW[arguments.size()] = 0;
317 
318     SHELLEXECUTEINFO shellExecute = {};
319     shellExecute.cbSize = sizeof(shellExecute);
320     shellExecute.fMask = SEE_MASK_NOCLOSEPROCESS;
321     shellExecute.hwnd = 0;
322     shellExecute.lpVerb = L"runas"; // causes elevation
323     shellExecute.lpFile = binaryW.data();
324     shellExecute.lpParameters = argumentsW.data();
325     shellExecute.lpDirectory = 0;
326     shellExecute.nShow = SW_SHOW;
327     shellExecute.hInstApp = 0;
328 
329     bool ret = ShellExecuteEx(&shellExecute);
330 
331     if (processHandle)
332         *processHandle = shellExecute.hProcess;
333 
334     return ret;
335 }
336 
337 #else // Q_OS_WIN
338 
encodeFileName(const QString & f)339 static inline char *encodeFileName(const QString &f)
340 {
341     const QByteArray encoded = QFile::encodeName(f);
342     char *result = new char[encoded.size() + 1];
343     strcpy(result, encoded.constData());
344     return result;
345 }
346 
tempFilePattern()347 static inline char *tempFilePattern()
348 {
349     QString path = QDir::tempPath();
350     if (!path.endsWith(QLatin1Char('/')))
351         path += QLatin1Char('/');
352     path += QStringLiteral("tmpXXXXXX");
353     return encodeFileName(path);
354 }
355 
readOutRedirectFile(int fd)356 static inline QByteArray readOutRedirectFile(int fd)
357 {
358     enum { bufSize = 256 };
359 
360     QByteArray result;
361     if (!lseek(fd, 0, 0)) {
362         char buf[bufSize];
363         while (true) {
364             const ssize_t rs = read(fd, buf, bufSize);
365             if (rs <= 0)
366                 break;
367             result.append(buf, int(rs));
368         }
369     }
370     close(fd);
371     return result;
372 }
373 
374 // runProcess: Run a command line process (replacement for QProcess which
375 // does not exist in the bootstrap library).
runProcess(const QString & binary,const QStringList & args,const QString & workingDirectory,unsigned long * exitCode,QByteArray * stdOut,QByteArray * stdErr,QString * errorMessage)376 bool runProcess(const QString &binary, const QStringList &args,
377                 const QString &workingDirectory,
378                 unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
379                 QString *errorMessage)
380 {
381     QScopedArrayPointer<char> stdOutFileName;
382     QScopedArrayPointer<char> stdErrFileName;
383 
384     int stdOutFile = 0;
385     if (stdOut) {
386         stdOutFileName.reset(tempFilePattern());
387         stdOutFile = mkstemp(stdOutFileName.data());
388         if (stdOutFile < 0) {
389             *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
390             return false;
391         }
392     }
393 
394     int stdErrFile = 0;
395     if (stdErr) {
396         stdErrFileName.reset(tempFilePattern());
397         stdErrFile = mkstemp(stdErrFileName.data());
398         if (stdErrFile < 0) {
399             *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
400             return false;
401         }
402     }
403 
404     const pid_t pID = fork();
405 
406     if (pID < 0) {
407         *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno));
408         return false;
409     }
410 
411     if (!pID) { // Child
412         if (stdOut) {
413             dup2(stdOutFile, STDOUT_FILENO);
414             close(stdOutFile);
415         }
416         if (stdErr) {
417             dup2(stdErrFile, STDERR_FILENO);
418             close(stdErrFile);
419         }
420 
421         if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) {
422             std::wcerr << "Failed to change working directory to " << workingDirectory << ".\n";
423             ::_exit(-1);
424         }
425 
426         char **argv  = new char *[args.size() + 2]; // Create argv.
427         char **ap = argv;
428         *ap++ = encodeFileName(binary);
429         for (const QString &a : qAsConst(args))
430             *ap++ = encodeFileName(a);
431         *ap = 0;
432 
433         execvp(argv[0], argv);
434         ::_exit(-1);
435     }
436 
437     int status;
438     pid_t waitResult;
439 
440     do {
441         waitResult = waitpid(pID, &status, 0);
442     } while (waitResult == -1 && errno == EINTR);
443 
444     if (stdOut) {
445         *stdOut = readOutRedirectFile(stdOutFile);
446         unlink(stdOutFileName.data());
447     }
448     if (stdErr) {
449         *stdErr = readOutRedirectFile(stdErrFile);
450         unlink(stdErrFileName.data());
451     }
452 
453     if (waitResult < 0) {
454         *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno));
455         return false;
456     }
457     if (!WIFEXITED(status)) {
458         *errorMessage = binary + QStringLiteral(" did not exit cleanly.");
459         return false;
460     }
461     if (exitCode)
462         *exitCode = WEXITSTATUS(status);
463     return true;
464 }
465 
runElevatedBackgroundProcess(const QString & binary,const QStringList & args,Qt::HANDLE * processHandle)466 bool runElevatedBackgroundProcess(const QString &binary, const QStringList &args, Qt::HANDLE *processHandle)
467 {
468     Q_UNUSED(binary);
469     Q_UNUSED(args);
470     Q_UNUSED(processHandle);
471     Q_UNIMPLEMENTED();
472     return false;
473 }
474 
475 #endif // !Q_OS_WIN
476 
477 // Find a file in the path using ShellAPI. This can be used to locate DLLs which
478 // QStandardPaths cannot do.
findInPath(const QString & file)479 QString findInPath(const QString &file)
480 {
481 #if defined(Q_OS_WIN)
482     if (file.size() < MAX_PATH -  1) {
483         wchar_t buffer[MAX_PATH];
484         file.toWCharArray(buffer);
485         buffer[file.size()] = 0;
486         if (PathFindOnPath(buffer, NULL))
487             return QDir::cleanPath(QString::fromWCharArray(buffer));
488     }
489     return QString();
490 #else // Q_OS_WIN
491     return QStandardPaths::findExecutable(file);
492 #endif // !Q_OS_WIN
493 }
494 
495 const char *qmakeInfixKey = "QT_INFIX";
496 
queryQMakeAll(QString * errorMessage)497 QMap<QString, QString> queryQMakeAll(QString *errorMessage)
498 {
499     QByteArray stdOut;
500     QByteArray stdErr;
501     unsigned long exitCode = 0;
502     const QString binary = QStringLiteral("qmake");
503     if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, &stdErr, errorMessage))
504         return QMap<QString, QString>();
505     if (exitCode) {
506         *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode)
507             + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
508         return QMap<QString, QString>();
509     }
510     const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(QLatin1Char('\r'));
511     QMap<QString, QString> result;
512     const int size = output.size();
513     for (int pos = 0; pos < size; ) {
514         const int colonPos = output.indexOf(QLatin1Char(':'), pos);
515         if (colonPos < 0)
516             break;
517         int endPos = output.indexOf(QLatin1Char('\n'), colonPos + 1);
518         if (endPos < 0)
519             endPos = size;
520         const QString key = output.mid(pos, colonPos - pos);
521         const QString value = output.mid(colonPos + 1, endPos - colonPos - 1);
522         result.insert(key, value);
523         pos = endPos + 1;
524     }
525     QFile qconfigPriFile(result.value(QStringLiteral("QT_HOST_DATA")) + QStringLiteral("/mkspecs/qconfig.pri"));
526     if (qconfigPriFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
527         while (true) {
528             const QByteArray line = qconfigPriFile.readLine();
529             if (line.isEmpty())
530                 break;
531             if (line.startsWith("QT_LIBINFIX")) {
532                 const int pos = line.indexOf('=');
533                 if (pos >= 0) {
534                     const QString infix = QString::fromUtf8(line.right(line.size() - pos - 1).trimmed());
535                     if (!infix.isEmpty())
536                         result.insert(QLatin1String(qmakeInfixKey), infix);
537                 }
538                 break;
539             }
540         }
541     } else {
542         std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName())
543             << ": " << qconfigPriFile.errorString()<< '\n';
544     }
545     return result;
546 }
547 
queryQMake(const QString & variable,QString * errorMessage)548 QString queryQMake(const QString &variable, QString *errorMessage)
549 {
550     QByteArray stdOut;
551     QByteArray stdErr;
552     unsigned long exitCode;
553     const QString binary = QStringLiteral("qmake");
554     QStringList args;
555     args << QStringLiteral("-query ") << variable;
556     if (!runProcess(binary, args, QString(), &exitCode, &stdOut, &stdErr, errorMessage))
557         return QString();
558     if (exitCode) {
559         *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode)
560             + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
561         return QString();
562     }
563     return QString::fromLocal8Bit(stdOut).trimmed();
564 }
565 
566 // Update a file or directory.
updateFile(const QString & sourceFileName,const QStringList & nameFilters,const QString & targetDirectory,unsigned flags,JsonOutput * json,QString * errorMessage)567 bool updateFile(const QString &sourceFileName, const QStringList &nameFilters,
568                 const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage)
569 {
570     const QFileInfo sourceFileInfo(sourceFileName);
571     const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName();
572     if (optVerboseLevel > 1)
573         std::wcout << "Checking " << sourceFileName << ", " << targetFileName<< '\n';
574 
575     if (!sourceFileInfo.exists()) {
576         *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName));
577         return false;
578     }
579 
580     if (sourceFileInfo.isSymLink()) {
581         *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).")
582                         .arg(QDir::toNativeSeparators(sourceFileName));
583         return false;
584     }
585 
586     const QFileInfo targetFileInfo(targetFileName);
587 
588     if (sourceFileInfo.isDir()) {
589         if (targetFileInfo.exists()) {
590             if (!targetFileInfo.isDir()) {
591                 *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.")
592                                 .arg(QDir::toNativeSeparators(targetFileName));
593                 return false;
594             } // Not a directory.
595         } else { // exists.
596             QDir d(targetDirectory);
597             if (optVerboseLevel)
598                 std::wcout << "Creating " << QDir::toNativeSeparators(targetFileName) << ".\n";
599             if (!(flags & SkipUpdateFile) && !d.mkdir(sourceFileInfo.fileName())) {
600                 *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.")
601                                 .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory));
602                 return false;
603             }
604         }
605         // Recurse into directory
606         QDir dir(sourceFileName);
607         const QFileInfoList allEntries = dir.entryInfoList(nameFilters, QDir::Files)
608             + dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
609         for (const QFileInfo &entryFi : allEntries) {
610             if (!updateFile(entryFi.absoluteFilePath(), nameFilters, targetFileName, flags, json, errorMessage))
611                 return false;
612         }
613         return true;
614     } // Source is directory.
615 
616     if (targetFileInfo.exists()) {
617         if (!(flags & ForceUpdateFile)
618             && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) {
619             if (optVerboseLevel)
620                 std::wcout << sourceFileInfo.fileName() << " is up to date.\n";
621             if (json)
622                 json->addFile(sourceFileName, targetDirectory);
623             return true;
624         }
625         QFile targetFile(targetFileName);
626         if (!(flags & SkipUpdateFile) && !targetFile.remove()) {
627             *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2")
628                             .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString());
629             return false;
630         }
631     } // target exists
632     QFile file(sourceFileName);
633     if (optVerboseLevel)
634         std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n";
635     if (!(flags & SkipUpdateFile) && !file.copy(targetFileName)) {
636         *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3")
637                 .arg(QDir::toNativeSeparators(sourceFileName),
638                      QDir::toNativeSeparators(targetFileName),
639                      file.errorString());
640         return false;
641     }
642     if (json)
643         json->addFile(sourceFileName, targetDirectory);
644     return true;
645 }
646 
readElfExecutable(const QString & elfExecutableFileName,QString * errorMessage,QStringList * dependentLibraries,unsigned * wordSize,bool * isDebug)647 bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage,
648                        QStringList *dependentLibraries, unsigned *wordSize,
649                        bool *isDebug)
650 {
651     ElfReader elfReader(elfExecutableFileName);
652     const ElfData data = elfReader.readHeaders();
653     if (data.sectionHeaders.isEmpty()) {
654         *errorMessage = QStringLiteral("Unable to read ELF binary \"")
655             + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ")
656             + elfReader.errorString();
657             return false;
658     }
659     if (wordSize)
660         *wordSize = data.elfclass == Elf_ELFCLASS64 ? 64 : 32;
661     if (dependentLibraries) {
662         dependentLibraries->clear();
663         const QList<QByteArray> libs = elfReader.dependencies();
664         if (libs.isEmpty()) {
665             *errorMessage = QStringLiteral("Unable to read dependenices of ELF binary \"")
666                 + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ")
667                 + elfReader.errorString();
668                 return false;
669         }
670         for (const QByteArray &l : libs)
671             dependentLibraries->push_back(QString::fromLocal8Bit(l));
672     }
673     if (isDebug)
674         *isDebug = data.symbolsType != UnknownSymbols && data.symbolsType != NoSymbols;
675     return true;
676 }
677 
678 #ifdef Q_OS_WIN
679 
stringFromRvaPtr(const void * rvaPtr)680 static inline QString stringFromRvaPtr(const void *rvaPtr)
681 {
682     return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr));
683 }
684 
685 // Helper for reading out PE executable files: Find a section header for an RVA
686 // (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
687 template <class ImageNtHeader>
findSectionHeader(DWORD rva,const ImageNtHeader * nTHeader)688 const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader)
689 {
690     const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
691     const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections;
692     for ( ; section < sectionEnd; ++section)
693         if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize))
694                 return section;
695     return 0;
696 }
697 
698 // Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
699 template <class ImageNtHeader>
rvaToPtr(DWORD rva,const ImageNtHeader * nTHeader,const void * imageBase)700 inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase)
701 {
702     const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
703     if (!sectionHdr)
704         return 0;
705     const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
706     return static_cast<const char *>(imageBase) + rva - delta;
707 }
708 
709 // Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32
710 template <class ImageNtHeader>
ntHeaderWordSize(const ImageNtHeader * header)711 inline unsigned ntHeaderWordSize(const ImageNtHeader *header)
712 {
713     // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
714     enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
715     if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
716         return 32;
717     if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
718         return 64;
719     return 0;
720 }
721 
722 // Helper for reading out PE executable files: Retrieve the NT image header of an
723 // executable via the legacy DOS header.
getNtHeader(void * fileMemory,QString * errorMessage)724 static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage)
725 {
726     IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
727     // Check DOS header consistency
728     if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
729         || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
730         *errorMessage = QString::fromLatin1("DOS header check failed.");
731         return 0;
732     }
733     // Retrieve NT header
734     char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
735     IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
736     // check NT header consistency
737     if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
738         || ntHeaders->Signature != IMAGE_NT_SIGNATURE
739         || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
740         *errorMessage = QString::fromLatin1("NT header check failed.");
741         return 0;
742     }
743     // Check magic
744     if (!ntHeaderWordSize(ntHeaders)) {
745         *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid.").
746                         arg(ntHeaders->OptionalHeader.Magic);
747         return 0;
748     }
749     // Check section headers
750     IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
751     if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
752         *errorMessage = QString::fromLatin1("NT header section header check failed.");
753         return 0;
754     }
755     return ntHeaders;
756 }
757 
758 // Helper for reading out PE executable files: Read out import sections from
759 // IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
760 template <class ImageNtHeader>
readImportSections(const ImageNtHeader * ntHeaders,const void * base,QString * errorMessage)761 inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage)
762 {
763     // Get import directory entry RVA and read out
764     const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
765     if (!importsStartRVA) {
766         *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.");
767         return QStringList();
768     }
769     const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, base));
770     if (!importDesc) {
771         *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry.");
772         return QStringList();
773     }
774     QStringList result;
775     for ( ; importDesc->Name; ++importDesc)
776         result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
777 
778     // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
779     // Check on grAttr bit 1 whether this is the format using RVA's > VS 6
780     if (const DWORD delayedImportsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) {
781         const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
782         for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc)
783             result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
784     }
785 
786     return result;
787 }
788 
789 // Check for MSCV runtime (MSVCP90D.dll/MSVCP90.dll, MSVCP120D.dll/MSVCP120.dll,
790 // VCRUNTIME140D.DLL/VCRUNTIME140.DLL (VS2015) or msvcp120d_app.dll/msvcp120_app.dll).
791 enum MsvcDebugRuntimeResult { MsvcDebugRuntime, MsvcReleaseRuntime, NoMsvcRuntime };
792 
checkMsvcDebugRuntime(const QStringList & dependentLibraries)793 static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &dependentLibraries)
794 {
795     for (const QString &lib : dependentLibraries) {
796         int pos = 0;
797         if (lib.startsWith(QLatin1String("MSVCR"), Qt::CaseInsensitive)
798             || lib.startsWith(QLatin1String("MSVCP"), Qt::CaseInsensitive)
799             || lib.startsWith(QLatin1String("VCRUNTIME"), Qt::CaseInsensitive)) {
800             int lastDotPos = lib.lastIndexOf(QLatin1Char('.'));
801             pos = -1 == lastDotPos ? 0 : lastDotPos - 1;
802         }
803 
804         if (pos > 0 && lib.contains(QLatin1String("_app"), Qt::CaseInsensitive))
805             pos -= 4;
806 
807         if (pos) {
808             return lib.at(pos).toLower() == QLatin1Char('d')
809                 ? MsvcDebugRuntime : MsvcReleaseRuntime;
810         }
811     }
812     return NoMsvcRuntime;
813 }
814 
815 template <class ImageNtHeader>
determineDebugAndDependentLibs(const ImageNtHeader * nth,const void * fileMemory,bool isMinGW,QStringList * dependentLibrariesIn,bool * isDebugIn,QString * errorMessage)816 inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
817                                            bool isMinGW,
818                                            QStringList *dependentLibrariesIn,
819                                            bool *isDebugIn, QString *errorMessage)
820 {
821     const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
822     QStringList dependentLibraries;
823     if (dependentLibrariesIn || (isDebugIn != nullptr && hasDebugEntry && !isMinGW))
824         dependentLibraries = readImportSections(nth, fileMemory, errorMessage);
825 
826     if (dependentLibrariesIn)
827         *dependentLibrariesIn = dependentLibraries;
828     if (isDebugIn != nullptr) {
829         if (isMinGW) {
830             // Use logic that's used e.g. in objdump / pfd library
831             *isDebugIn = !(nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED);
832         } else {
833             // When an MSVC debug entry is present, check whether the debug runtime
834             // is actually used to detect -release / -force-debug-info builds.
835             *isDebugIn = hasDebugEntry && checkMsvcDebugRuntime(dependentLibraries) != MsvcReleaseRuntime;
836         }
837     }
838 }
839 
840 // Read a PE executable and determine dependent libraries, word size
841 // and debug flags.
readPeExecutable(const QString & peExecutableFileName,QString * errorMessage,QStringList * dependentLibrariesIn,unsigned * wordSizeIn,bool * isDebugIn,bool isMinGW,unsigned short * machineArchIn)842 bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage,
843                       QStringList *dependentLibrariesIn, unsigned *wordSizeIn,
844                       bool *isDebugIn, bool isMinGW, unsigned short *machineArchIn)
845 {
846     bool result = false;
847     HANDLE hFile = NULL;
848     HANDLE hFileMap = NULL;
849     void *fileMemory = 0;
850 
851     if (dependentLibrariesIn)
852         dependentLibrariesIn->clear();
853     if (wordSizeIn)
854         *wordSizeIn = 0;
855     if (isDebugIn)
856         *isDebugIn = false;
857 
858     do {
859         // Create a memory mapping of the file
860         hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL,
861                              OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
862         if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
863             *errorMessage = QString::fromLatin1("Cannot open '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
864             break;
865         }
866 
867         hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
868         if (hFileMap == NULL) {
869             *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
870             break;
871         }
872 
873         fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
874         if (!fileMemory) {
875             *errorMessage = QString::fromLatin1("Cannot map '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
876             break;
877         }
878 
879         const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
880         if (!ntHeaders)
881             break;
882 
883         const unsigned wordSize = ntHeaderWordSize(ntHeaders);
884         if (wordSizeIn)
885             *wordSizeIn = wordSize;
886         if (wordSize == 32) {
887             determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
888                                            fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage);
889         } else {
890             determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
891                                            fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage);
892         }
893 
894         if (machineArchIn)
895             *machineArchIn = ntHeaders->FileHeader.Machine;
896 
897         result = true;
898         if (optVerboseLevel > 1) {
899             std::wcout << __FUNCTION__ << ": " << QDir::toNativeSeparators(peExecutableFileName)
900                 << ' ' << wordSize << " bit";
901             if (isMinGW)
902                 std::wcout << ", MinGW";
903             if (dependentLibrariesIn) {
904                 std::wcout << ", dependent libraries: ";
905                 if (optVerboseLevel > 2)
906                     std::wcout << dependentLibrariesIn->join(QLatin1Char(' '));
907                 else
908                     std::wcout << dependentLibrariesIn->size();
909             }
910             if (isDebugIn)
911                 std::wcout << (*isDebugIn ? ", debug" : ", release");
912             std::wcout << '\n';
913         }
914     } while (false);
915 
916     if (fileMemory)
917         UnmapViewOfFile(fileMemory);
918 
919     if (hFileMap != NULL)
920         CloseHandle(hFileMap);
921 
922     if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
923         CloseHandle(hFile);
924 
925     return result;
926 }
927 
findD3dCompiler(Platform platform,const QString & qtBinDir,unsigned wordSize)928 QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize)
929 {
930     const QString prefix = QStringLiteral("D3Dcompiler_");
931     const QString suffix = QLatin1String(windowsSharedLibrarySuffix);
932     // Get the DLL from Kit 8.0 onwards
933     const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir"));
934     if (!kitDir.isEmpty()) {
935         QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/");
936         if (platform.testFlag(ArmBased)) {
937             redistDirPath += QStringLiteral("arm");
938         } else {
939             redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64");
940         }
941         QDir redistDir(redistDirPath);
942         if (redistDir.exists()) {
943             const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + QLatin1Char('*') + suffix), QDir::Files);
944             if (!files.isEmpty())
945                 return files.front().absoluteFilePath();
946         }
947     }
948     QStringList candidateVersions;
949     for (int i = 47 ; i >= 40 ; --i)
950         candidateVersions.append(prefix + QString::number(i) + suffix);
951     // Check the bin directory of the Qt SDK (in case it is shadowed by the
952     // Windows system directory in PATH).
953     for (const QString &candidate : qAsConst(candidateVersions)) {
954         const QFileInfo fi(qtBinDir + QLatin1Char('/') + candidate);
955         if (fi.isFile())
956             return fi.absoluteFilePath();
957     }
958     // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47).
959     if (platform.testFlag(IntelBased)) {
960         QString errorMessage;
961         unsigned detectedWordSize;
962         for (const QString &candidate : qAsConst(candidateVersions)) {
963             const QString dll = findInPath(candidate);
964             if (!dll.isEmpty()
965                 && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0)
966                 && detectedWordSize == wordSize) {
967                 return dll;
968             }
969         }
970     }
971     return QString();
972 }
973 
974 #else // Q_OS_WIN
975 
readPeExecutable(const QString &,QString * errorMessage,QStringList *,unsigned *,bool *,bool,unsigned short *)976 bool readPeExecutable(const QString &, QString *errorMessage,
977                       QStringList *, unsigned *, bool *, bool, unsigned short *)
978 {
979     *errorMessage = QStringLiteral("Not implemented.");
980     return false;
981 }
982 
findD3dCompiler(Platform,const QString &,unsigned)983 QString findD3dCompiler(Platform, const QString &, unsigned)
984 {
985     return QString();
986 }
987 
988 #endif // !Q_OS_WIN
989 
990 // Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=."
patchQtCore(const QString & path,QString * errorMessage)991 bool patchQtCore(const QString &path, QString *errorMessage)
992 {
993     if (optVerboseLevel)
994         std::wcout << "Patching " << QFileInfo(path).fileName() << "...\n";
995 
996     QFile file(path);
997     if (!file.open(QIODevice::ReadOnly)) {
998         *errorMessage = QString::fromLatin1("Unable to patch %1: %2").arg(
999                     QDir::toNativeSeparators(path), file.errorString());
1000         return false;
1001     }
1002     const QByteArray oldContent = file.readAll();
1003 
1004     if (oldContent.isEmpty()) {
1005         *errorMessage = QString::fromLatin1("Unable to patch %1: Could not read file content").arg(
1006                     QDir::toNativeSeparators(path));
1007         return false;
1008     }
1009     file.close();
1010 
1011     QByteArray content = oldContent;
1012 
1013     QByteArray prfxpath("qt_prfxpath=");
1014     int startPos = content.indexOf(prfxpath);
1015     if (startPos == -1) {
1016         *errorMessage = QString::fromLatin1(
1017                     "Unable to patch %1: Could not locate pattern \"qt_prfxpath=\"").arg(
1018                     QDir::toNativeSeparators(path));
1019         return false;
1020     }
1021     startPos += prfxpath.length();
1022     int endPos = content.indexOf(char(0), startPos);
1023     if (endPos == -1) {
1024         *errorMessage = QString::fromLatin1("Unable to patch %1: Internal error").arg(
1025                     QDir::toNativeSeparators(path));
1026         return false;
1027     }
1028 
1029     QByteArray replacement = QByteArray(endPos - startPos, char(0));
1030     replacement[0] = '.';
1031     content.replace(startPos, endPos - startPos, replacement);
1032     if (content == oldContent)
1033         return true;
1034 
1035     if (!file.open(QIODevice::WriteOnly)
1036         || (file.write(content) != content.size())) {
1037         *errorMessage = QString::fromLatin1("Unable to patch %1: Could not write to file: %2").arg(
1038                     QDir::toNativeSeparators(path), file.errorString());
1039         return false;
1040     }
1041     return true;
1042 }
1043 
1044 #ifdef Q_OS_WIN
getArchString(unsigned short machineArch)1045 QString getArchString(unsigned short machineArch)
1046 {
1047     switch (machineArch) {
1048         case IMAGE_FILE_MACHINE_I386:
1049             return QStringLiteral("x86");
1050         case IMAGE_FILE_MACHINE_ARM:
1051             return QStringLiteral("arm");
1052         case IMAGE_FILE_MACHINE_AMD64:
1053             return QStringLiteral("x64");
1054         case IMAGE_FILE_MACHINE_ARM64:
1055             return QStringLiteral("arm64");
1056         default:
1057             break;
1058     }
1059     return QString();
1060 }
1061 #endif // Q_OS_WIN
1062 
1063 QT_END_NAMESPACE
1064