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 "qmlutils.h"
31 
32 #include <QtCore/QDir>
33 #include <QtCore/QFileInfo>
34 #include <QtCore/QCoreApplication>
35 #include <QtCore/QJsonDocument>
36 #include <QtCore/QJsonObject>
37 #include <QtCore/QJsonArray>
38 #include <QtCore/QCommandLineParser>
39 #include <QtCore/QCommandLineOption>
40 #include <QtCore/QOperatingSystemVersion>
41 #include <QtCore/QSharedPointer>
42 #include <QtCore/QVector>
43 
44 #ifdef Q_OS_WIN
45 #include <QtCore/qt_windows.h>
46 #else
47 #define IMAGE_FILE_MACHINE_ARM64 0xaa64
48 #endif
49 
50 #include <algorithm>
51 #include <iostream>
52 #include <cstdio>
53 
54 QT_BEGIN_NAMESPACE
55 
56 enum QtModule
57 #if defined(Q_COMPILER_CLASS_ENUM) || defined(Q_CC_MSVC)
58     : quint64
59 #endif
60 {
61     QtBluetoothModule         = 0x0000000000000001,
62     QtConcurrentModule        = 0x0000000000000002,
63     QtCoreModule              = 0x0000000000000004,
64     QtDeclarativeModule       = 0x0000000000000008,
65     QtDesignerComponents      = 0x0000000000000010,
66     QtDesignerModule          = 0x0000000000000020,
67     QtGuiModule               = 0x0000000000000040,
68     QtHelpModule              = 0x0000000000000080,
69     QtMultimediaModule        = 0x0000000000000100,
70     QtMultimediaWidgetsModule = 0x0000000000000200,
71     QtMultimediaQuickModule   = 0x0000000000000400,
72     QtNetworkModule           = 0x0000000000000800,
73     QtNfcModule               = 0x0000000000001000,
74     QtOpenGLModule            = 0x0000000000002000,
75     QtPositioningModule       = 0x0000000000004000,
76     QtPrintSupportModule      = 0x0000000000008000,
77     QtQmlModule               = 0x0000000000010000,
78     QtQuickModule             = 0x0000000000020000,
79     QtQuickParticlesModule    = 0x0000000000040000,
80     QtScriptModule            = 0x0000000000080000,
81     QtScriptToolsModule       = 0x0000000000100000,
82     QtSensorsModule           = 0x0000000000200000,
83     QtSerialPortModule        = 0x0000000000400000,
84     QtSqlModule               = 0x0000000000800000,
85     QtSvgModule               = 0x0000000001000000,
86     QtTestModule              = 0x0000000002000000,
87     QtWidgetsModule           = 0x0000000004000000,
88     QtWinExtrasModule         = 0x0000000008000000,
89     QtXmlModule               = 0x0000000010000000,
90     QtXmlPatternsModule       = 0x0000000020000000,
91     QtWebKitModule            = 0x0000000040000000,
92     QtWebKitWidgetsModule     = 0x0000000080000000,
93     QtQuickWidgetsModule      = 0x0000000100000000,
94     QtWebSocketsModule        = 0x0000000200000000,
95     QtEnginioModule           = 0x0000000400000000,
96     QtWebEngineCoreModule     = 0x0000000800000000,
97     QtWebEngineModule         = 0x0000001000000000,
98     QtWebEngineWidgetsModule  = 0x0000002000000000,
99     QtQmlToolingModule        = 0x0000004000000000,
100     Qt3DCoreModule            = 0x0000008000000000,
101     Qt3DRendererModule        = 0x0000010000000000,
102     Qt3DQuickModule           = 0x0000020000000000,
103     Qt3DQuickRendererModule   = 0x0000040000000000,
104     Qt3DInputModule           = 0x0000080000000000,
105     QtLocationModule          = 0x0000100000000000,
106     QtWebChannelModule        = 0x0000200000000000,
107     QtTextToSpeechModule      = 0x0000400000000000,
108     QtSerialBusModule         = 0x0000800000000000,
109     QtGamePadModule           = 0x0001000000000000,
110     Qt3DAnimationModule       = 0x0002000000000000,
111     QtWebViewModule           = 0x0004000000000000,
112     Qt3DExtrasModule          = 0x0008000000000000
113 };
114 
115 struct QtModuleEntry {
116     quint64 module;
117     const char *option;
118     const char *libraryName;
119     const char *translation;
120 };
121 
122 static QtModuleEntry qtModuleEntries[] = {
123     { QtBluetoothModule, "bluetooth", "Qt5Bluetooth", nullptr },
124     { QtConcurrentModule, "concurrent", "Qt5Concurrent", "qtbase" },
125     { QtCoreModule, "core", "Qt5Core", "qtbase" },
126     { QtDeclarativeModule, "declarative", "Qt5Declarative", "qtquick1" },
127     { QtDesignerModule, "designer", "Qt5Designer", nullptr },
128     { QtDesignerComponents, "designercomponents", "Qt5DesignerComponents", nullptr },
129     { QtEnginioModule, "enginio", "Enginio", nullptr },
130     { QtGamePadModule, "gamepad", "Qt5Gamepad", nullptr },
131     { QtGuiModule, "gui", "Qt5Gui", "qtbase" },
132     { QtHelpModule, "qthelp", "Qt5Help", "qt_help" },
133     { QtMultimediaModule, "multimedia", "Qt5Multimedia", "qtmultimedia" },
134     { QtMultimediaWidgetsModule, "multimediawidgets", "Qt5MultimediaWidgets", "qtmultimedia" },
135     { QtMultimediaQuickModule, "multimediaquick", "Qt5MultimediaQuick_p", "qtmultimedia" },
136     { QtNetworkModule, "network", "Qt5Network", "qtbase" },
137     { QtNfcModule, "nfc", "Qt5Nfc", nullptr },
138     { QtOpenGLModule, "opengl", "Qt5OpenGL", nullptr },
139     { QtPositioningModule, "positioning", "Qt5Positioning", nullptr },
140     { QtPrintSupportModule, "printsupport", "Qt5PrintSupport", nullptr },
141     { QtQmlModule, "qml", "Qt5Qml", "qtdeclarative" },
142     { QtQmlToolingModule, "qmltooling", "qmltooling", nullptr },
143     { QtQuickModule, "quick", "Qt5Quick", "qtdeclarative" },
144     { QtQuickParticlesModule, "quickparticles", "Qt5QuickParticles", nullptr },
145     { QtQuickWidgetsModule, "quickwidgets", "Qt5QuickWidgets", nullptr },
146     { QtScriptModule, "script", "Qt5Script", "qtscript" },
147     { QtScriptToolsModule, "scripttools", "Qt5ScriptTools", "qtscript" },
148     { QtSensorsModule, "sensors", "Qt5Sensors", nullptr },
149     { QtSerialPortModule, "serialport", "Qt5SerialPort", "qtserialport" },
150     { QtSqlModule, "sql", "Qt5Sql", "qtbase" },
151     { QtSvgModule, "svg", "Qt5Svg", nullptr },
152     { QtTestModule, "test", "Qt5Test", "qtbase" },
153     { QtWebKitModule, "webkit", "Qt5WebKit", nullptr },
154     { QtWebKitWidgetsModule, "webkitwidgets", "Qt5WebKitWidgets", nullptr },
155     { QtWebSocketsModule, "websockets", "Qt5WebSockets", nullptr },
156     { QtWidgetsModule, "widgets", "Qt5Widgets", "qtbase" },
157     { QtWinExtrasModule, "winextras", "Qt5WinExtras", nullptr },
158     { QtXmlModule, "xml", "Qt5Xml", "qtbase" },
159     { QtXmlPatternsModule, "xmlpatterns", "Qt5XmlPatterns", "qtxmlpatterns" },
160     { QtWebEngineCoreModule, "webenginecore", "Qt5WebEngineCore", nullptr },
161     { QtWebEngineModule, "webengine", "Qt5WebEngine", "qtwebengine" },
162     { QtWebEngineWidgetsModule, "webenginewidgets", "Qt5WebEngineWidgets", nullptr },
163     { Qt3DCoreModule, "3dcore", "Qt53DCore", nullptr },
164     { Qt3DRendererModule, "3drenderer", "Qt53DRender", nullptr },
165     { Qt3DQuickModule, "3dquick", "Qt53DQuick", nullptr },
166     { Qt3DQuickRendererModule, "3dquickrenderer", "Qt53DQuickRender", nullptr },
167     { Qt3DInputModule, "3dinput", "Qt53DInput", nullptr },
168     { Qt3DAnimationModule, "3danimation", "Qt53DAnimation", nullptr },
169     { Qt3DExtrasModule, "3dextras", "Qt53DExtras", nullptr },
170     { QtLocationModule, "geoservices", "Qt5Location", nullptr },
171     { QtWebChannelModule, "webchannel", "Qt5WebChannel", nullptr },
172     { QtTextToSpeechModule, "texttospeech", "Qt5TextToSpeech", nullptr },
173     { QtSerialBusModule, "serialbus", "Qt5SerialBus", nullptr },
174     { QtWebViewModule, "webview", "Qt5WebView", nullptr }
175 };
176 
177 enum QtPlugin {
178     QtVirtualKeyboardPlugin = 0x1
179 };
180 
181 static const char webKitProcessC[] = "QtWebProcess";
182 static const char webEngineProcessC[] = "QtWebEngineProcess";
183 
webProcessBinary(const char * binaryName,Platform p)184 static inline QString webProcessBinary(const char *binaryName, Platform p)
185 {
186     const QString webProcess = QLatin1String(binaryName);
187     return (p & WindowsBased) ? webProcess + QStringLiteral(".exe") : webProcess;
188 }
189 
formatQtModules(quint64 mask,bool option=false)190 static QByteArray formatQtModules(quint64 mask, bool option = false)
191 {
192     QByteArray result;
193     for (const auto &qtModule : qtModuleEntries) {
194         if (mask & qtModule.module) {
195             if (!result.isEmpty())
196                 result.append(' ');
197             result.append(option ? qtModule.option : qtModule.libraryName);
198         }
199     }
200     return result;
201 }
202 
platformFromMkSpec(const QString & xSpec)203 static Platform platformFromMkSpec(const QString &xSpec)
204 {
205     if (xSpec == QLatin1String("linux-g++"))
206         return Unix;
207     if (xSpec.startsWith(QLatin1String("win32-"))) {
208         if (xSpec.contains(QLatin1String("clang-g++")))
209             return WindowsDesktopClangMinGW;
210         if (xSpec.contains(QLatin1String("clang-msvc++")))
211             return WindowsDesktopClangMsvc;
212         return xSpec.contains(QLatin1String("g++")) ? WindowsDesktopMinGW : WindowsDesktopMsvc;
213     }
214     if (xSpec.startsWith(QLatin1String("winrt-x")))
215         return WinRtIntelMsvc;
216     if (xSpec.startsWith(QLatin1String("winrt-arm")))
217         return WinRtArmMsvc;
218     return UnknownPlatform;
219 }
220 
221 // Helpers for exclusive options, "-foo", "--no-foo"
222 enum ExlusiveOptionValue {
223     OptionAuto,
224     OptionEnabled,
225     OptionDisabled
226 };
227 
parseExclusiveOptions(const QCommandLineParser * parser,const QCommandLineOption & enableOption,const QCommandLineOption & disableOption)228 static ExlusiveOptionValue parseExclusiveOptions(const QCommandLineParser *parser,
229                                                  const QCommandLineOption &enableOption,
230                                                  const QCommandLineOption &disableOption)
231 {
232     const bool enabled = parser->isSet(enableOption);
233     const bool disabled = parser->isSet(disableOption);
234     if (enabled) {
235         if (disabled) {
236             std::wcerr << "Warning: both -" << enableOption.names().first()
237                 << " and -" << disableOption.names().first() << " were specified, defaulting to -"
238                 << enableOption.names().first() << ".\n";
239         }
240         return OptionEnabled;
241     }
242     return disabled ? OptionDisabled : OptionAuto;
243 }
244 
245 static ExlusiveOptionValue optWebKit2 = OptionAuto;
246 
247 struct Options {
248     enum DebugDetection {
249         DebugDetectionAuto,
250         DebugDetectionForceDebug,
251         DebugDetectionForceRelease
252     };
253 
254     enum AngleDetection {
255         AngleDetectionAuto,
256         AngleDetectionForceOn,
257         AngleDetectionForceOff
258     };
259 
260     bool plugins = true;
261     bool libraries = true;
262     bool quickImports = true;
263     bool translations = true;
264     bool systemD3dCompiler = true;
265     bool compilerRunTime = false;
266     unsigned disabledPlugins = 0;
267     AngleDetection angleDetection = AngleDetectionAuto;
268     bool softwareRasterizer = true;
269     Platform platform = WindowsDesktopMsvc;
270     quint64 additionalLibraries = 0;
271     quint64 disabledLibraries = 0;
272     unsigned updateFileFlags = 0;
273     QStringList qmlDirectories; // Project's QML files.
274     QStringList qmlImportPaths; // Custom QML module locations.
275     QString directory;
276     QString translationsDirectory; // Translations target directory
277     QStringList languages;
278     QString libraryDirectory;
279     QString pluginDirectory;
280     QStringList binaries;
281     JsonOutput *json = nullptr;
282     ListOption list = ListNone;
283     DebugDetection debugDetection = DebugDetectionAuto;
284     bool deployPdb = false;
285     bool dryRun = false;
286     bool patchQt = true;
287     bool ignoreLibraryErrors = false;
288 
isWinRtOptions289     inline bool isWinRt() const { return platform.testFlag(WinRt); }
290 };
291 
292 // Return binary from folder
findBinary(const QString & directory,Platform platform)293 static inline QString findBinary(const QString &directory, Platform platform)
294 {
295     const QStringList nameFilters = (platform & WindowsBased) ?
296         QStringList(QStringLiteral("*.exe")) : QStringList();
297     const QFileInfoList &binaries =
298         QDir(QDir::cleanPath(directory)).entryInfoList(nameFilters, QDir::Files | QDir::Executable);
299     for (const QFileInfo &binaryFi : binaries) {
300         const QString binary = binaryFi.fileName();
301         if (!binary.contains(QLatin1String(webKitProcessC), Qt::CaseInsensitive)
302             && !binary.contains(QLatin1String(webEngineProcessC), Qt::CaseInsensitive)) {
303             return binaryFi.absoluteFilePath();
304         }
305     }
306     return QString();
307 }
308 
msgFileDoesNotExist(const QString & file)309 static QString msgFileDoesNotExist(const QString & file)
310 {
311     return QLatin1Char('"') + QDir::toNativeSeparators(file)
312         + QStringLiteral("\" does not exist.");
313 }
314 
315 enum CommandLineParseFlag {
316     CommandLineParseError = 0x1,
317     CommandLineParseHelpRequested = 0x2
318 };
319 
parseArguments(const QStringList & arguments,QCommandLineParser * parser,Options * options,QString * errorMessage)320 static inline int parseArguments(const QStringList &arguments, QCommandLineParser *parser,
321                                  Options *options, QString *errorMessage)
322 {
323     using CommandLineOptionPtr = QSharedPointer<QCommandLineOption>;
324     using OptionPtrVector = QVector<CommandLineOptionPtr>;
325 
326     parser->setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
327     parser->setApplicationDescription(QStringLiteral("Qt Deploy Tool ") + QLatin1String(QT_VERSION_STR)
328         + QLatin1String("\n\nThe simplest way to use windeployqt is to add the bin directory of your Qt\n"
329         "installation (e.g. <QT_DIR\\bin>) to the PATH variable and then run:\n  windeployqt <path-to-app-binary>\n"
330         "If ICU, ANGLE, etc. are not in the bin directory, they need to be in the PATH\nvariable. "
331         "If your application uses Qt Quick, run:\n  windeployqt --qmldir <path-to-app-qml-files> <path-to-app-binary>"));
332     const QCommandLineOption helpOption = parser->addHelpOption();
333     parser->addVersionOption();
334 
335     QCommandLineOption dirOption(QStringLiteral("dir"),
336                                  QStringLiteral("Use directory instead of binary directory."),
337                                  QStringLiteral("directory"));
338     parser->addOption(dirOption);
339 
340     QCommandLineOption libDirOption(QStringLiteral("libdir"),
341                                     QStringLiteral("Copy libraries to path."),
342                                     QStringLiteral("path"));
343     parser->addOption(libDirOption);
344 
345     QCommandLineOption pluginDirOption(QStringLiteral("plugindir"),
346                                        QStringLiteral("Copy plugins to path."),
347                                        QStringLiteral("path"));
348     parser->addOption(pluginDirOption);
349 
350     QCommandLineOption debugOption(QStringLiteral("debug"),
351                                    QStringLiteral("Assume debug binaries."));
352     parser->addOption(debugOption);
353     QCommandLineOption releaseOption(QStringLiteral("release"),
354                                    QStringLiteral("Assume release binaries."));
355     parser->addOption(releaseOption);
356     QCommandLineOption releaseWithDebugInfoOption(QStringLiteral("release-with-debug-info"),
357                                                   QStringLiteral("Assume release binaries with debug information."));
358     releaseWithDebugInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); // Deprecated by improved debug detection.
359     parser->addOption(releaseWithDebugInfoOption);
360 
361     QCommandLineOption deployPdbOption(QStringLiteral("pdb"),
362                                        QStringLiteral("Deploy .pdb files (MSVC)."));
363     parser->addOption(deployPdbOption);
364 
365     QCommandLineOption forceOption(QStringLiteral("force"),
366                                     QStringLiteral("Force updating files."));
367     parser->addOption(forceOption);
368 
369     QCommandLineOption dryRunOption(QStringLiteral("dry-run"),
370                                     QStringLiteral("Simulation mode. Behave normally, but do not copy/update any files."));
371     parser->addOption(dryRunOption);
372 
373     QCommandLineOption noPatchQtOption(QStringLiteral("no-patchqt"),
374                                        QStringLiteral("Do not patch the Qt5Core library."));
375     parser->addOption(noPatchQtOption);
376 
377     QCommandLineOption ignoreErrorOption(QStringLiteral("ignore-library-errors"),
378                                          QStringLiteral("Ignore errors when libraries cannot be found."));
379     parser->addOption(ignoreErrorOption);
380 
381     QCommandLineOption noPluginsOption(QStringLiteral("no-plugins"),
382                                        QStringLiteral("Skip plugin deployment."));
383     parser->addOption(noPluginsOption);
384 
385     QCommandLineOption noLibraryOption(QStringLiteral("no-libraries"),
386                                        QStringLiteral("Skip library deployment."));
387     parser->addOption(noLibraryOption);
388 
389     QCommandLineOption qmlDirOption(QStringLiteral("qmldir"),
390                                     QStringLiteral("Scan for QML-imports starting from directory."),
391                                     QStringLiteral("directory"));
392     parser->addOption(qmlDirOption);
393 
394     QCommandLineOption qmlImportOption(QStringLiteral("qmlimport"),
395                                        QStringLiteral("Add the given path to the QML module search locations."),
396                                        QStringLiteral("directory"));
397     parser->addOption(qmlImportOption);
398 
399     QCommandLineOption noQuickImportOption(QStringLiteral("no-quick-import"),
400                                            QStringLiteral("Skip deployment of Qt Quick imports."));
401     parser->addOption(noQuickImportOption);
402 
403 
404     QCommandLineOption translationOption(QStringLiteral("translations"),
405                                          QStringLiteral("A comma-separated list of languages to deploy (de,fi)."),
406                                          QStringLiteral("languages"));
407     parser->addOption(translationOption);
408 
409     QCommandLineOption noTranslationOption(QStringLiteral("no-translations"),
410                                            QStringLiteral("Skip deployment of translations."));
411     parser->addOption(noTranslationOption);
412 
413     QCommandLineOption noSystemD3DCompilerOption(QStringLiteral("no-system-d3d-compiler"),
414                                                  QStringLiteral("Skip deployment of the system D3D compiler."));
415     parser->addOption(noSystemD3DCompilerOption);
416 
417 
418     QCommandLineOption compilerRunTimeOption(QStringLiteral("compiler-runtime"),
419                                              QStringLiteral("Deploy compiler runtime (Desktop only)."));
420     parser->addOption(compilerRunTimeOption);
421 
422     QCommandLineOption noVirtualKeyboardOption(QStringLiteral("no-virtualkeyboard"),
423                                                QStringLiteral("Disable deployment of the Virtual Keyboard."));
424     parser->addOption(noVirtualKeyboardOption);
425 
426     QCommandLineOption noCompilerRunTimeOption(QStringLiteral("no-compiler-runtime"),
427                                              QStringLiteral("Do not deploy compiler runtime (Desktop only)."));
428     parser->addOption(noCompilerRunTimeOption);
429 
430     QCommandLineOption webKitOption(QStringLiteral("webkit2"),
431                                     QStringLiteral("Deployment of WebKit2 (web process)."));
432     parser->addOption(webKitOption);
433 
434     QCommandLineOption noWebKitOption(QStringLiteral("no-webkit2"),
435                                       QStringLiteral("Skip deployment of WebKit2."));
436     parser->addOption(noWebKitOption);
437 
438     QCommandLineOption jsonOption(QStringLiteral("json"),
439                                   QStringLiteral("Print to stdout in JSON format."));
440     parser->addOption(jsonOption);
441 
442     QCommandLineOption angleOption(QStringLiteral("angle"),
443                                    QStringLiteral("Force deployment of ANGLE."));
444     parser->addOption(angleOption);
445 
446     QCommandLineOption noAngleOption(QStringLiteral("no-angle"),
447                                      QStringLiteral("Disable deployment of ANGLE."));
448     parser->addOption(noAngleOption);
449 
450     QCommandLineOption suppressSoftwareRasterizerOption(QStringLiteral("no-opengl-sw"),
451                                                         QStringLiteral("Do not deploy the software rasterizer library."));
452     parser->addOption(suppressSoftwareRasterizerOption);
453 
454     QCommandLineOption listOption(QStringLiteral("list"),
455                                   QLatin1String("Print only the names of the files copied.\n"
456                                                 "Available options:\n"
457                                                 "  source:   absolute path of the source files\n"
458                                                 "  target:   absolute path of the target files\n"
459                                                 "  relative: paths of the target files, relative\n"
460                                                 "            to the target directory\n"
461                                                 "  mapping:  outputs the source and the relative\n"
462                                                 "            target, suitable for use within an\n"
463                                                 "            Appx mapping file"),
464                                   QStringLiteral("option"));
465     parser->addOption(listOption);
466 
467     QCommandLineOption verboseOption(QStringLiteral("verbose"),
468                                      QStringLiteral("Verbose level (0-2)."),
469                                      QStringLiteral("level"));
470     parser->addOption(verboseOption);
471 
472     parser->addPositionalArgument(QStringLiteral("[files]"),
473                                   QStringLiteral("Binaries or directory containing the binary."));
474 
475     OptionPtrVector enabledModuleOptions;
476     OptionPtrVector disabledModuleOptions;
477     const int qtModulesCount = int(sizeof(qtModuleEntries) / sizeof(QtModuleEntry));
478     enabledModuleOptions.reserve(qtModulesCount);
479     disabledModuleOptions.reserve(qtModulesCount);
480     for (int i = 0; i < qtModulesCount; ++i) {
481         const QString option = QLatin1String(qtModuleEntries[i].option);
482         const QString name = QLatin1String(qtModuleEntries[i].libraryName);
483         const QString enabledDescription = QStringLiteral("Add ") + name + QStringLiteral(" module.");
484         CommandLineOptionPtr enabledOption(new QCommandLineOption(option, enabledDescription));
485         parser->addOption(*enabledOption.data());
486         enabledModuleOptions.append(enabledOption);
487         const QString disabledDescription = QStringLiteral("Remove ") + name + QStringLiteral(" module.");
488         CommandLineOptionPtr disabledOption(new QCommandLineOption(QStringLiteral("no-") + option,
489                                                                    disabledDescription));
490         disabledModuleOptions.append(disabledOption);
491         parser->addOption(*disabledOption.data());
492     }
493 
494     const bool success = parser->parse(arguments);
495     if (parser->isSet(helpOption))
496         return CommandLineParseHelpRequested;
497     if (!success) {
498         *errorMessage = parser->errorText();
499         return CommandLineParseError;
500     }
501 
502     options->libraryDirectory = parser->value(libDirOption);
503     options->pluginDirectory = parser->value(pluginDirOption);
504     options->plugins = !parser->isSet(noPluginsOption);
505     options->libraries = !parser->isSet(noLibraryOption);
506     options->translations = !parser->isSet(noTranslationOption);
507     if (parser->isSet(translationOption))
508         options->languages = parser->value(translationOption).split(QLatin1Char(','));
509     options->systemD3dCompiler = !parser->isSet(noSystemD3DCompilerOption);
510     options->quickImports = !parser->isSet(noQuickImportOption);
511 
512     if (parser->isSet(compilerRunTimeOption))
513         options->compilerRunTime = true;
514     else if (parser->isSet(noCompilerRunTimeOption))
515         options->compilerRunTime = false;
516 
517     if (options->compilerRunTime && options->platform != WindowsDesktopMinGW && options->platform != WindowsDesktopMsvc) {
518         *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only.");
519         return CommandLineParseError;
520     }
521 
522     if (parser->isSet(noVirtualKeyboardOption))
523         options->disabledPlugins |= QtVirtualKeyboardPlugin;
524 
525     if (parser->isSet(releaseWithDebugInfoOption))
526         std::wcerr << "Warning: " << releaseWithDebugInfoOption.names().first() << " is obsolete.";
527 
528     switch (parseExclusiveOptions(parser, debugOption, releaseOption)) {
529     case OptionAuto:
530         break;
531     case OptionEnabled:
532         options->debugDetection = Options::DebugDetectionForceDebug;
533         break;
534     case OptionDisabled:
535         options->debugDetection = Options::DebugDetectionForceRelease;
536         break;
537     }
538 
539     if (parser->isSet(deployPdbOption)) {
540         if (options->platform.testFlag(WindowsBased) && !options->platform.testFlag(MinGW))
541             options->deployPdb = true;
542         else
543             std::wcerr << "Warning: --" << deployPdbOption.names().first() << " is not supported on this platform.\n";
544     }
545 
546     switch (parseExclusiveOptions(parser, angleOption, noAngleOption)) {
547     case OptionAuto:
548         break;
549     case OptionEnabled:
550         options->angleDetection = Options::AngleDetectionForceOn;
551         break;
552     case OptionDisabled:
553         options->angleDetection = Options::AngleDetectionForceOff;
554         break;
555     }
556 
557     if (parser->isSet(suppressSoftwareRasterizerOption))
558         options->softwareRasterizer = false;
559 
560     optWebKit2 = parseExclusiveOptions(parser, webKitOption, noWebKitOption);
561 
562     if (parser->isSet(forceOption))
563         options->updateFileFlags |= ForceUpdateFile;
564     if (parser->isSet(dryRunOption)) {
565         options->dryRun = true;
566         options->updateFileFlags |= SkipUpdateFile;
567     }
568 
569     options->patchQt = !parser->isSet(noPatchQtOption);
570     options->ignoreLibraryErrors = parser->isSet(ignoreErrorOption);
571 
572     for (int i = 0; i < qtModulesCount; ++i) {
573         if (parser->isSet(*enabledModuleOptions.at(i)))
574             options->additionalLibraries |= qtModuleEntries[i].module;
575         if (parser->isSet(*disabledModuleOptions.at(i)))
576             options->disabledLibraries |= qtModuleEntries[i].module;
577     }
578 
579     // Add some dependencies
580     if (options->additionalLibraries & QtQuickModule)
581         options->additionalLibraries |= QtQmlModule;
582     if (options->additionalLibraries & QtDesignerComponents)
583         options->additionalLibraries |= QtDesignerModule;
584 
585     if (parser->isSet(listOption)) {
586         const QString value = parser->value(listOption);
587         if (value == QStringLiteral("source")) {
588             options->list = ListSource;
589         } else if (value == QStringLiteral("target")) {
590             options->list = ListTarget;
591         } else if (value == QStringLiteral("relative")) {
592             options->list = ListRelative;
593         } else if (value == QStringLiteral("mapping")) {
594             options->list = ListMapping;
595         } else {
596             *errorMessage = QStringLiteral("Please specify a valid option for -list (source, target, relative, mapping).");
597             return CommandLineParseError;
598         }
599     }
600 
601     if (parser->isSet(jsonOption) || options->list) {
602         optVerboseLevel = 0;
603         options->json = new JsonOutput;
604     } else {
605         if (parser->isSet(verboseOption)) {
606             bool ok;
607             const QString value = parser->value(verboseOption);
608             optVerboseLevel = value.toInt(&ok);
609             if (!ok || optVerboseLevel < 0) {
610                 *errorMessage = QStringLiteral("Invalid value \"%1\" passed for verbose level.").arg(value);
611                 return CommandLineParseError;
612             }
613         }
614     }
615 
616     const QStringList posArgs = parser->positionalArguments();
617     if (posArgs.isEmpty()) {
618         *errorMessage = QStringLiteral("Please specify the binary or folder.");
619         return CommandLineParseError | CommandLineParseHelpRequested;
620     }
621 
622     if (parser->isSet(dirOption))
623         options->directory = parser->value(dirOption);
624 
625     if (parser->isSet(qmlDirOption))
626         options->qmlDirectories = parser->values(qmlDirOption);
627 
628     if (parser->isSet(qmlImportOption))
629         options->qmlImportPaths = parser->values(qmlImportOption);
630 
631     const QString &file = posArgs.front();
632     const QFileInfo fi(QDir::cleanPath(file));
633     if (!fi.exists()) {
634         *errorMessage = msgFileDoesNotExist(file);
635         return CommandLineParseError;
636     }
637 
638     if (!options->directory.isEmpty() && !fi.isFile()) { // -dir was specified - expecting file.
639         *errorMessage = QLatin1Char('"') + file + QStringLiteral("\" is not an executable file.");
640         return CommandLineParseError;
641     }
642 
643     if (fi.isFile()) {
644         options->binaries.append(fi.absoluteFilePath());
645         if (options->directory.isEmpty())
646             options->directory = fi.absolutePath();
647     } else {
648         const QString binary = findBinary(fi.absoluteFilePath(), options->platform);
649         if (binary.isEmpty()) {
650             *errorMessage = QStringLiteral("Unable to find binary in \"") + file + QLatin1Char('"');
651             return CommandLineParseError;
652         }
653         options->directory = fi.absoluteFilePath();
654         options->binaries.append(binary);
655     } // directory.
656 
657     // Remaining files or plugin directories
658     for (int i = 1; i < posArgs.size(); ++i) {
659         const QFileInfo fi(QDir::cleanPath(posArgs.at(i)));
660         const QString path = fi.absoluteFilePath();
661         if (!fi.exists()) {
662             *errorMessage = msgFileDoesNotExist(path);
663             return CommandLineParseError;
664         }
665         if (fi.isDir()) {
666             const QStringList libraries =
667                 findSharedLibraries(QDir(path), options->platform, MatchDebugOrRelease, QString());
668             for (const QString &library : libraries)
669                 options->binaries.append(path + QLatin1Char('/') + library);
670         } else {
671             options->binaries.append(path);
672         }
673     }
674     options->translationsDirectory = options->directory + QLatin1String("/translations");
675     return 0;
676 }
677 
678 // Simple line wrapping at 80 character boundaries.
lineBreak(QString s)679 static inline QString lineBreak(QString s)
680 {
681     for (int i = 80; i < s.size(); i += 80) {
682         const int lastBlank = s.lastIndexOf(QLatin1Char(' '), i);
683         if (lastBlank >= 0) {
684             s[lastBlank] = QLatin1Char('\n');
685             i = lastBlank + 1;
686         }
687     }
688     return s;
689 }
690 
helpText(const QCommandLineParser & p)691 static inline QString helpText(const QCommandLineParser &p)
692 {
693     QString result = p.helpText();
694     // Replace the default-generated text which is too long by a short summary
695     // explaining how to enable single libraries.
696     const int moduleStart = result.indexOf(QLatin1String("\n  --bluetooth"));
697     const int argumentsStart = result.lastIndexOf(QLatin1String("\nArguments:"));
698     if (moduleStart >= argumentsStart)
699         return result;
700     QString moduleHelp = QLatin1String(
701         "\n\nQt libraries can be added by passing their name (-xml) or removed by passing\n"
702         "the name prepended by --no- (--no-xml). Available libraries:\n");
703     moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(0xFFFFFFFFFFFFFFFFull, true)));
704     moduleHelp += QLatin1Char('\n');
705     result.replace(moduleStart, argumentsStart - moduleStart, moduleHelp);
706     return result;
707 }
708 
isQtModule(const QString & libName)709 static inline bool isQtModule(const QString &libName)
710 {
711     // Match Standard modules named Qt5XX.dll
712     if (libName.size() < 3 || !libName.startsWith(QLatin1String("Qt"), Qt::CaseInsensitive))
713         return false;
714     const QChar version = libName.at(2);
715     return version.isDigit() && (version.toLatin1() - '0') == QT_VERSION_MAJOR;
716 }
717 
718 // Helper for recursively finding all dependent Qt libraries.
findDependentQtLibraries(const QString & qtBinDir,const QString & binary,Platform platform,QString * errorMessage,QStringList * result,unsigned * wordSize=nullptr,bool * isDebug=nullptr,unsigned short * machineArch=nullptr,int * directDependencyCount=nullptr,int recursionDepth=0)719 static bool findDependentQtLibraries(const QString &qtBinDir, const QString &binary, Platform platform,
720                                      QString *errorMessage, QStringList *result,
721                                      unsigned *wordSize = nullptr, bool *isDebug = nullptr,
722                                      unsigned short *machineArch = nullptr,
723                                      int *directDependencyCount = nullptr, int recursionDepth = 0)
724 {
725     QStringList dependentLibs;
726     if (directDependencyCount)
727         *directDependencyCount = 0;
728     if (!readExecutable(binary, platform, errorMessage, &dependentLibs, wordSize, isDebug, machineArch)) {
729         errorMessage->prepend(QLatin1String("Unable to find dependent libraries of ") +
730                               QDir::toNativeSeparators(binary) + QLatin1String(" :"));
731         return false;
732     }
733     // Filter out the Qt libraries. Note that depends.exe finds libs from optDirectory if we
734     // are run the 2nd time (updating). We want to check against the Qt bin dir libraries
735     const int start = result->size();
736     for (const QString &lib : qAsConst(dependentLibs)) {
737         if (isQtModule(lib)) {
738             const QString path = normalizeFileName(qtBinDir + QLatin1Char('/') + QFileInfo(lib).fileName());
739             if (!result->contains(path))
740                 result->append(path);
741         }
742     }
743     const int end = result->size();
744     if (directDependencyCount)
745         *directDependencyCount = end - start;
746     // Recurse
747     for (int i = start; i < end; ++i)
748         if (!findDependentQtLibraries(qtBinDir, result->at(i), platform, errorMessage, result,
749                                       nullptr, nullptr, nullptr, nullptr, recursionDepth + 1))
750             return false;
751     return true;
752 }
753 
754 // Base class to filter debug/release Windows DLLs for functions to be passed to updateFile().
755 // Tries to pre-filter by namefilter and does check via PE.
756 class DllDirectoryFileEntryFunction {
757 public:
DllDirectoryFileEntryFunction(Platform platform,DebugMatchMode debugMatchMode,const QString & prefix=QString ())758     explicit DllDirectoryFileEntryFunction(Platform platform,
759                                            DebugMatchMode debugMatchMode, const QString &prefix = QString()) :
760         m_platform(platform), m_debugMatchMode(debugMatchMode), m_prefix(prefix) {}
761 
operator ()(const QDir & dir) const762     QStringList operator()(const QDir &dir) const
763         { return findSharedLibraries(dir, m_platform, m_debugMatchMode, m_prefix); }
764 
765 private:
766     const Platform m_platform;
767     const DebugMatchMode m_debugMatchMode;
768     const QString m_prefix;
769 };
770 
pdbFileName(QString libraryFileName)771 static QString pdbFileName(QString libraryFileName)
772 {
773     const int lastDot = libraryFileName.lastIndexOf(QLatin1Char('.')) + 1;
774     if (lastDot <= 0)
775         return QString();
776     libraryFileName.replace(lastDot, libraryFileName.size() - lastDot, QLatin1String("pdb"));
777     return libraryFileName;
778 }
qmlCacheFileFilters()779 static inline QStringList qmlCacheFileFilters()
780 {
781     return QStringList() << QStringLiteral("*.jsc") << QStringLiteral("*.qmlc");
782 }
783 
784 // File entry filter function for updateFile() that returns a list of files for
785 // QML import trees: DLLs (matching debgug) and .qml/,js, etc.
786 class QmlDirectoryFileEntryFunction {
787 public:
788     enum Flags {
789         DeployPdb = 0x1,
790         SkipSources = 0x2
791     };
792 
QmlDirectoryFileEntryFunction(Platform platform,DebugMatchMode debugMatchMode,unsigned flags)793     explicit QmlDirectoryFileEntryFunction(Platform platform, DebugMatchMode debugMatchMode, unsigned flags)
794         : m_flags(flags), m_qmlNameFilter(QmlDirectoryFileEntryFunction::qmlNameFilters(flags))
795         , m_dllFilter(platform, debugMatchMode)
796     {}
797 
operator ()(const QDir & dir) const798     QStringList operator()(const QDir &dir) const
799     {
800         QStringList result;
801         const QStringList &libraries = m_dllFilter(dir);
802         for (const QString &library : libraries) {
803             result.append(library);
804             if (m_flags & DeployPdb) {
805                 const QString pdb = pdbFileName(library);
806                 if (QFileInfo(dir.absoluteFilePath(pdb)).isFile())
807                     result.append(pdb);
808             }
809         }
810         result.append(m_qmlNameFilter(dir));
811         return result;
812     }
813 
814 private:
qmlNameFilters(unsigned flags)815     static inline QStringList qmlNameFilters(unsigned flags)
816     {
817         QStringList result;
818         result << QStringLiteral("qmldir") << QStringLiteral("*.qmltypes")
819            << QStringLiteral("*.frag") << QStringLiteral("*.vert") // Shaders
820            << QStringLiteral("*.ttf");
821         if (!(flags & SkipSources)) {
822             result << QStringLiteral("*.js") << QStringLiteral("*.qml") << QStringLiteral("*.png");
823             result.append(qmlCacheFileFilters());
824         }
825         return result;
826     }
827 
828     const unsigned m_flags;
829     NameFilterFileEntryFunction m_qmlNameFilter;
830     DllDirectoryFileEntryFunction m_dllFilter;
831 };
832 
833 struct PluginModuleMapping
834 {
835     const char *directoryName;
836     quint64 module;
837 };
838 
839 static const PluginModuleMapping pluginModuleMappings[] =
840 {
841     {"qml1tooling", QtDeclarativeModule},
842     {"gamepads", QtGamePadModule},
843     {"accessible", QtGuiModule},
844     {"iconengines", QtGuiModule},
845     {"imageformats", QtGuiModule},
846     {"platforms", QtGuiModule},
847     {"platforminputcontexts", QtGuiModule},
848     {"virtualkeyboard", QtGuiModule},
849     {"geoservices", QtLocationModule},
850     {"audio", QtMultimediaModule},
851     {"mediaservice", QtMultimediaModule},
852     {"playlistformats", QtMultimediaModule},
853     {"bearer", QtNetworkModule},
854     {"position", QtPositioningModule},
855     {"printsupport", QtPrintSupportModule},
856     {"scenegraph", QtQuickModule},
857     {"qmltooling", QtQuickModule | QtQmlToolingModule},
858     {"sensors", QtSensorsModule},
859     {"sensorgestures", QtSensorsModule},
860     {"canbus", QtSerialBusModule},
861     {"sqldrivers", QtSqlModule},
862     {"texttospeech", QtTextToSpeechModule},
863     {"qtwebengine", QtWebEngineModule | QtWebEngineCoreModule | QtWebEngineWidgetsModule},
864     {"styles", QtWidgetsModule},
865     {"sceneparsers", Qt3DRendererModule},
866     {"renderers", Qt3DRendererModule},
867     {"renderplugins", Qt3DRendererModule},
868     {"geometryloaders", Qt3DRendererModule},
869     {"webview", QtWebViewModule}
870 };
871 
qtModuleForPlugin(const QString & subDirName)872 static inline quint64 qtModuleForPlugin(const QString &subDirName)
873 {
874     const auto end = std::end(pluginModuleMappings);
875     const auto result =
876         std::find_if(std::begin(pluginModuleMappings), end,
877                      [&subDirName] (const PluginModuleMapping &m) { return subDirName == QLatin1String(m.directoryName); });
878     return result != end ? result->module : 0; // "designer"
879 }
880 
qtModule(QString module,const QString & infix)881 static quint64 qtModule(QString module, const QString &infix)
882 {
883     // Match needle 'path/Qt5Core<infix><d>.dll' or 'path/libQt5Core<infix>.so.5.0'
884     const int lastSlashPos = module.lastIndexOf(QLatin1Char('/'));
885     if (lastSlashPos > 0)
886         module.remove(0, lastSlashPos + 1);
887     if (module.startsWith(QLatin1String("lib")))
888         module.remove(0, 3);
889     int endPos = infix.isEmpty() ? -1 : module.lastIndexOf(infix);
890     if (endPos == -1)
891         endPos = module.indexOf(QLatin1Char('.')); // strip suffixes '.so.5.0'.
892     if (endPos > 0)
893         module.truncate(endPos);
894     // That should leave us with 'Qt5Core<d>'.
895     for (const auto &qtModule : qtModuleEntries) {
896         const QLatin1String libraryName(qtModule.libraryName);
897         if (module == libraryName
898             || (module.size() == libraryName.size() + 1 && module.startsWith(libraryName))) {
899             return qtModule.module;
900         }
901     }
902     return 0;
903 }
904 
findQtPlugins(quint64 * usedQtModules,quint64 disabledQtModules,unsigned disabledPlugins,const QString & qtPluginsDirName,const QString & libraryLocation,const QString & infix,DebugMatchMode debugMatchModeIn,Platform platform,QString * platformPlugin)905 QStringList findQtPlugins(quint64 *usedQtModules, quint64 disabledQtModules,
906                           unsigned disabledPlugins,
907                           const QString &qtPluginsDirName, const QString &libraryLocation,
908                           const QString &infix,
909                           DebugMatchMode debugMatchModeIn, Platform platform, QString *platformPlugin)
910 {
911     QString errorMessage;
912     if (qtPluginsDirName.isEmpty())
913         return QStringList();
914     QDir pluginsDir(qtPluginsDirName);
915     QStringList result;
916     const QFileInfoList &pluginDirs = pluginsDir.entryInfoList(QStringList(QLatin1String("*")), QDir::Dirs | QDir::NoDotAndDotDot);
917     for (const QFileInfo &subDirFi : pluginDirs) {
918         const QString subDirName = subDirFi.fileName();
919         const quint64 module = qtModuleForPlugin(subDirName);
920         if (module & *usedQtModules) {
921             const DebugMatchMode debugMatchMode = (module & QtWebEngineCoreModule)
922                 ? MatchDebugOrRelease // QTBUG-44331: Debug detection does not work for webengine, deploy all.
923                 : debugMatchModeIn;
924             QDir subDir(subDirFi.absoluteFilePath());
925             // Filter out disabled plugins
926             if ((disabledPlugins & QtVirtualKeyboardPlugin) && subDirName == QLatin1String("virtualkeyboard"))
927                 continue;
928             if (disabledQtModules & QtQmlToolingModule && subDirName == QLatin1String("qmltooling"))
929                 continue;
930             // Filter for platform or any.
931             QString filter;
932             const bool isPlatformPlugin = subDirName == QLatin1String("platforms");
933             if (isPlatformPlugin) {
934                 switch (platform) {
935                 case WindowsDesktopMsvc:
936                 case WindowsDesktopMinGW:
937                     filter = QStringLiteral("qwindows");
938                     break;
939                 case WinRtIntelMsvc:
940                 case WinRtArmMsvc:
941                     filter = QStringLiteral("qwinrt");
942                     break;
943                 case Unix:
944                     filter = QStringLiteral("libqxcb");
945                     break;
946                 case UnknownPlatform:
947                     break;
948                 }
949             } else {
950                 filter  = QLatin1String("*");
951             }
952             const QStringList plugins = findSharedLibraries(subDir, platform, debugMatchMode, filter);
953             for (const QString &plugin : plugins) {
954                 // Filter out disabled plugins
955                 if ((disabledPlugins & QtVirtualKeyboardPlugin)
956                     && plugin.startsWith(QLatin1String("qtvirtualkeyboardplugin"))) {
957                     continue;
958                 }
959                 const QString pluginPath = subDir.absoluteFilePath(plugin);
960                 if (isPlatformPlugin)
961                     *platformPlugin = pluginPath;
962                 QStringList dependentQtLibs;
963                 quint64 neededModules = 0;
964                 if (findDependentQtLibraries(libraryLocation, pluginPath, platform, &errorMessage, &dependentQtLibs)) {
965                     for (int d = 0; d < dependentQtLibs.size(); ++ d)
966                         neededModules |= qtModule(dependentQtLibs.at(d), infix);
967                 } else {
968                     std::wcerr << "Warning: Cannot determine dependencies of "
969                         << QDir::toNativeSeparators(pluginPath) << ": " << errorMessage << '\n';
970                 }
971                 if (const quint64 missingModules = neededModules & disabledQtModules) {
972                     if (optVerboseLevel) {
973                         std::wcout << "Skipping plugin " << plugin
974                             << " due to disabled dependencies ("
975                             << formatQtModules(missingModules).constData() << ").\n";
976                     }
977                 } else {
978                     if (const quint64 missingModules = (neededModules & ~*usedQtModules)) {
979                         *usedQtModules |= missingModules;
980                         if (optVerboseLevel)
981                             std::wcout << "Adding " << formatQtModules(missingModules).constData() << " for " << plugin << '\n';
982                     }
983                     result.append(pluginPath);
984                 }
985             } // for filter
986         } // type matches
987     } // for plugin folder
988     return result;
989 }
990 
translationNameFilters(quint64 modules,const QString & prefix)991 static QStringList translationNameFilters(quint64 modules, const QString &prefix)
992 {
993     QStringList result;
994     for (const auto &qtModule : qtModuleEntries) {
995         if ((qtModule.module & modules) && qtModule.translation) {
996             const QString name = QLatin1String(qtModule.translation) +
997                                  QLatin1Char('_') +  prefix + QStringLiteral(".qm");
998             if (!result.contains(name))
999                 result.push_back(name);
1000         }
1001     }
1002     return result;
1003 }
1004 
deployTranslations(const QString & sourcePath,quint64 usedQtModules,const QString & target,const Options & options,QString * errorMessage)1005 static bool deployTranslations(const QString &sourcePath, quint64 usedQtModules,
1006                                const QString &target, const Options &options,
1007                                QString *errorMessage)
1008 {
1009     // Find available languages prefixes by checking on qtbase.
1010     QStringList prefixes;
1011     QDir sourceDir(sourcePath);
1012     const QStringList qmFilter = QStringList(QStringLiteral("qtbase_*.qm"));
1013     const QFileInfoList &qmFiles = sourceDir.entryInfoList(qmFilter);
1014     for (const QFileInfo &qmFi : qmFiles) {
1015         const QString prefix = qmFi.baseName().mid(7);
1016         if (options.languages.isEmpty() || options.languages.contains(prefix))
1017             prefixes.append(prefix);
1018     }
1019     if (prefixes.isEmpty()) {
1020         std::wcerr << "Warning: Could not find any translations in "
1021                    << QDir::toNativeSeparators(sourcePath) << " (developer build?)\n.";
1022         return true;
1023     }
1024     // Run lconvert to concatenate all files into a single named "qt_<prefix>.qm" in the application folder
1025     // Use QT_INSTALL_TRANSLATIONS as working directory to keep the command line short.
1026     const QString absoluteTarget = QFileInfo(target).absoluteFilePath();
1027     const QString binary = QStringLiteral("lconvert");
1028     QStringList arguments;
1029     for (const QString &prefix : qAsConst(prefixes)) {
1030         arguments.clear();
1031         const QString targetFile = QStringLiteral("qt_") + prefix + QStringLiteral(".qm");
1032         arguments.append(QStringLiteral("-o"));
1033         const QString targetFilePath = absoluteTarget + QLatin1Char('/') + targetFile;
1034         if (options.json)
1035             options.json->addFile(sourcePath +  QLatin1Char('/') + targetFile, absoluteTarget);
1036         arguments.append(QDir::toNativeSeparators(targetFilePath));
1037         const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationNameFilters(usedQtModules, prefix));
1038         for (const QFileInfo &langQmFileFi : langQmFiles) {
1039             // winrt relies on a proper list of deployed files. We cannot cheat an mention files we do not ship here.
1040             if (options.json && !options.isWinRt()) {
1041                 options.json->addFile(langQmFileFi.absoluteFilePath(),
1042                                       absoluteTarget);
1043             }
1044             arguments.append(langQmFileFi.fileName());
1045         }
1046         if (optVerboseLevel)
1047             std::wcout << "Creating " << targetFile << "...\n";
1048         unsigned long exitCode;
1049         if ((options.updateFileFlags & SkipUpdateFile) == 0
1050             && (!runProcess(binary, arguments, sourcePath, &exitCode, nullptr, nullptr, errorMessage)
1051                 || exitCode)) {
1052             return false;
1053         }
1054     } // for prefixes.
1055     return true;
1056 }
1057 
1058 struct DeployResult
1059 {
operator boolDeployResult1060     operator bool() const { return success; }
1061 
1062     bool success = false;
1063     bool isDebug = false;
1064     quint64 directlyUsedQtLibraries = 0;
1065     quint64 usedQtLibraries = 0;
1066     quint64 deployedQtLibraries = 0;
1067 };
1068 
libraryPath(const QString & libraryLocation,const char * name,const QString & qtLibInfix,Platform platform,bool debug)1069 static QString libraryPath(const QString &libraryLocation, const char *name,
1070                            const QString &qtLibInfix, Platform platform, bool debug)
1071 {
1072     QString result = libraryLocation + QLatin1Char('/');
1073     if (platform & WindowsBased) {
1074         result += QLatin1String(name);
1075         result += qtLibInfix;
1076         if (debug && platformHasDebugSuffix(platform))
1077             result += QLatin1Char('d');
1078     } else if (platform.testFlag(UnixBased)) {
1079         result += QStringLiteral("lib");
1080         result += QLatin1String(name);
1081         result += qtLibInfix;
1082     }
1083     result += sharedLibrarySuffix(platform);
1084     return result;
1085 }
1086 
vcDebugRedistDir()1087 static QString vcDebugRedistDir() { return QStringLiteral("Debug_NonRedist"); }
1088 
vcRedistDir()1089 static QString vcRedistDir()
1090 {
1091     const char vcDirVar[] = "VCINSTALLDIR";
1092     const QChar slash(QLatin1Char('/'));
1093     QString vcRedistDirName = QDir::cleanPath(QFile::decodeName(qgetenv(vcDirVar)));
1094     if (vcRedistDirName.isEmpty()) {
1095         std::wcerr << "Warning: Cannot find Visual Studio installation directory, " << vcDirVar
1096             << " is not set.\n";
1097         return QString();
1098     }
1099     if (!vcRedistDirName.endsWith(slash))
1100         vcRedistDirName.append(slash);
1101     vcRedistDirName.append(QStringLiteral("redist"));
1102     if (!QFileInfo(vcRedistDirName).isDir()) {
1103         std::wcerr << "Warning: Cannot find Visual Studio redist directory, "
1104             << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n";
1105         return QString();
1106     }
1107     const QString vc2017RedistDirName = vcRedistDirName + QStringLiteral("/MSVC");
1108     if (!QFileInfo(vc2017RedistDirName).isDir())
1109         return vcRedistDirName; // pre 2017
1110     // Look in reverse order for folder containing the debug redist folder
1111     const QFileInfoList subDirs =
1112         QDir(vc2017RedistDirName).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed);
1113     const bool isWindows10 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10;
1114     for (const QFileInfo &f : subDirs) {
1115         QString path = f.absoluteFilePath();
1116         if (QFileInfo(path + slash + vcDebugRedistDir()).isDir())
1117             return path;
1118         if (isWindows10) {
1119             path += QStringLiteral("/onecore");
1120             if (QFileInfo(path + slash + vcDebugRedistDir()).isDir())
1121                 return path;
1122         }
1123     }
1124     std::wcerr << "Warning: Cannot find Visual Studio redist directory under "
1125         << QDir::toNativeSeparators(vc2017RedistDirName).toStdWString() << ".\n";
1126     return QString();
1127 }
1128 
compilerRunTimeLibs(Platform platform,bool isDebug,unsigned short machineArch)1129 static QStringList compilerRunTimeLibs(Platform platform, bool isDebug, unsigned short machineArch)
1130 {
1131     QStringList result;
1132     switch (platform) {
1133     case WindowsDesktopMinGW: { // MinGW: Add runtime libraries
1134         static const char *minGwRuntimes[] = {"*gcc_", "*stdc++", "*winpthread"};
1135         const QString gcc = findInPath(QStringLiteral("g++.exe"));
1136         if (gcc.isEmpty()) {
1137             std::wcerr << "Warning: Cannot find GCC installation directory. g++.exe must be in the path.\n";
1138             break;
1139         }
1140         const QString binPath = QFileInfo(gcc).absolutePath();
1141         QStringList filters;
1142         const QString suffix = QLatin1Char('*') + sharedLibrarySuffix(platform);
1143         for (auto minGwRuntime : minGwRuntimes)
1144             filters.append(QLatin1String(minGwRuntime) + suffix);
1145         const QFileInfoList &dlls = QDir(binPath).entryInfoList(filters, QDir::Files);
1146         for (const QFileInfo &dllFi : dlls)
1147                 result.append(dllFi.absoluteFilePath());
1148     }
1149         break;
1150 #ifdef Q_OS_WIN
1151     case WindowsDesktopMsvc: { // MSVC/Desktop: Add redistributable packages.
1152         QString vcRedistDirName = vcRedistDir();
1153         if (vcRedistDirName.isEmpty())
1154              break;
1155         QStringList redistFiles;
1156         QDir vcRedistDir(vcRedistDirName);
1157         const QString machineArchString = getArchString(machineArch);
1158         if (isDebug) {
1159             // Append DLLs from Debug_NonRedist\x??\Microsoft.VC<version>.DebugCRT.
1160             if (vcRedistDir.cd(vcDebugRedistDir()) && vcRedistDir.cd(machineArchString)) {
1161                 const QStringList names = vcRedistDir.entryList(QStringList(QStringLiteral("Microsoft.VC*.DebugCRT")), QDir::Dirs);
1162                 if (!names.isEmpty() && vcRedistDir.cd(names.first())) {
1163                     const QFileInfoList &dlls = vcRedistDir.entryInfoList(QStringList(QLatin1String("*.dll")));
1164                     for (const QFileInfo &dll : dlls)
1165                         redistFiles.append(dll.absoluteFilePath());
1166                 }
1167             }
1168         } else { // release: Bundle vcredist<>.exe
1169             QString releaseRedistDir = vcRedistDirName;
1170             const QStringList countryCodes = vcRedistDir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Dirs);
1171             if (!countryCodes.isEmpty()) // Pre MSVC2017
1172                 releaseRedistDir += QLatin1Char('/') + countryCodes.constFirst();
1173             QFileInfo fi(releaseRedistDir + QLatin1Char('/') + QStringLiteral("vc_redist.")
1174                          + machineArchString + QStringLiteral(".exe"));
1175             if (!fi.isFile()) { // Pre MSVC2017/15.5
1176                 fi.setFile(releaseRedistDir + QLatin1Char('/') + QStringLiteral("vcredist_")
1177                            + machineArchString + QStringLiteral(".exe"));
1178             }
1179             if (fi.isFile())
1180                 redistFiles.append(fi.absoluteFilePath());
1181         }
1182         if (redistFiles.isEmpty()) {
1183             std::wcerr << "Warning: Cannot find Visual Studio " << (isDebug ? "debug" : "release")
1184                        << " redistributable files in " << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n";
1185             break;
1186         }
1187         result.append(redistFiles);
1188     }
1189         break;
1190 #endif // Q_OS_WIN
1191     default:
1192         break;
1193     }
1194     return result;
1195 }
1196 
qtVersion(const QMap<QString,QString> & qmakeVariables)1197 static inline int qtVersion(const QMap<QString, QString> &qmakeVariables)
1198 {
1199     const QString versionString = qmakeVariables.value(QStringLiteral("QT_VERSION"));
1200     const QChar dot = QLatin1Char('.');
1201     const int majorVersion = versionString.section(dot, 0, 0).toInt();
1202     const int minorVersion = versionString.section(dot, 1, 1).toInt();
1203     const int patchVersion = versionString.section(dot, 2, 2).toInt();
1204     return (majorVersion << 16) | (minorVersion << 8) | patchVersion;
1205 }
1206 
1207 // Determine the Qt lib infix from the library path of "Qt5Core<qtblibinfix>[d].dll".
qtlibInfixFromCoreLibName(const QString & path,bool isDebug,Platform platform)1208 static inline QString qtlibInfixFromCoreLibName(const QString &path, bool isDebug, Platform platform)
1209 {
1210     const int startPos = path.lastIndexOf(QLatin1Char('/')) + 8;
1211     int endPos = path.lastIndexOf(QLatin1Char('.'));
1212     if (isDebug && (platform & WindowsBased))
1213         endPos--;
1214     return endPos > startPos ? path.mid(startPos, endPos - startPos) : QString();
1215 }
1216 
1217 // Deploy a library along with its .pdb debug info file (MSVC) should it exist.
updateLibrary(const QString & sourceFileName,const QString & targetDirectory,const Options & options,QString * errorMessage)1218 static bool updateLibrary(const QString &sourceFileName, const QString &targetDirectory,
1219                           const Options &options, QString *errorMessage)
1220 {
1221     if (!updateFile(sourceFileName, targetDirectory, options.updateFileFlags, options.json, errorMessage)) {
1222         if (options.ignoreLibraryErrors) {
1223             std::wcerr << "Warning: Could not update " << sourceFileName << " :" << *errorMessage << '\n';
1224             errorMessage->clear();
1225             return true;
1226         }
1227         return false;
1228     }
1229 
1230     if (options.deployPdb) {
1231         const QFileInfo pdb(pdbFileName(sourceFileName));
1232         if (pdb.isFile())
1233             return updateFile(pdb.absoluteFilePath(), targetDirectory, options.updateFileFlags, nullptr, errorMessage);
1234     }
1235     return true;
1236 }
1237 
deploy(const Options & options,const QMap<QString,QString> & qmakeVariables,QString * errorMessage)1238 static DeployResult deploy(const Options &options,
1239                            const QMap<QString, QString> &qmakeVariables,
1240                            QString *errorMessage)
1241 {
1242     DeployResult result;
1243 
1244     const QChar slash = QLatin1Char('/');
1245 
1246     const QString qtBinDir = qmakeVariables.value(QStringLiteral("QT_INSTALL_BINS"));
1247     const QString libraryLocation = options.platform == Unix ? qmakeVariables.value(QStringLiteral("QT_INSTALL_LIBS")) : qtBinDir;
1248     const QString infix = qmakeVariables.value(QLatin1String(qmakeInfixKey));
1249     const int version = qtVersion(qmakeVariables);
1250     Q_UNUSED(version);
1251 
1252     if (optVerboseLevel > 1)
1253         std::wcout << "Qt binaries in " << QDir::toNativeSeparators(qtBinDir) << '\n';
1254 
1255     QStringList dependentQtLibs;
1256     bool detectedDebug;
1257     unsigned wordSize;
1258     unsigned short machineArch;
1259     int directDependencyCount = 0;
1260     if (!findDependentQtLibraries(libraryLocation, options.binaries.first(), options.platform, errorMessage, &dependentQtLibs, &wordSize,
1261                                   &detectedDebug, &machineArch, &directDependencyCount)) {
1262         return result;
1263     }
1264     for (int b = 1; b < options.binaries.size(); ++b) {
1265         if (!findDependentQtLibraries(libraryLocation, options.binaries.at(b), options.platform, errorMessage, &dependentQtLibs,
1266                                       nullptr, nullptr, nullptr)) {
1267             return result;
1268         }
1269     }
1270 
1271     DebugMatchMode debugMatchMode = MatchDebugOrRelease;
1272     result.isDebug = false;
1273     switch (options.debugDetection) {
1274     case Options::DebugDetectionAuto:
1275         // Debug detection is only relevant for Msvc/ClangMsvc which have distinct
1276         // runtimes and binaries. For anything else, use MatchDebugOrRelease
1277         // since also debug cannot be reliably detect for MinGW.
1278         if (options.platform.testFlag(Msvc) || options.platform.testFlag(ClangMsvc)) {
1279             result.isDebug = detectedDebug;
1280             debugMatchMode = result.isDebug ? MatchDebug : MatchRelease;
1281         }
1282         break;
1283     case Options::DebugDetectionForceDebug:
1284         result.isDebug = true;
1285         debugMatchMode = MatchDebug;
1286         break;
1287     case Options::DebugDetectionForceRelease:
1288         debugMatchMode = MatchRelease;
1289         break;
1290     }
1291 
1292     // Determine application type, check Quick2 is used by looking at the
1293     // direct dependencies (do not be fooled by QtWebKit depending on it).
1294     QString qtLibInfix;
1295     for (int m = 0; m < directDependencyCount; ++m) {
1296         const quint64 module = qtModule(dependentQtLibs.at(m), infix);
1297         result.directlyUsedQtLibraries |= module;
1298         if (module == QtCoreModule)
1299             qtLibInfix = qtlibInfixFromCoreLibName(dependentQtLibs.at(m), detectedDebug, options.platform);
1300     }
1301 
1302     const bool usesQml2 = !(options.disabledLibraries & QtQmlModule)
1303         && ((result.directlyUsedQtLibraries & (QtQmlModule | QtQuickModule | Qt3DQuickModule))
1304                                 || (options.additionalLibraries & QtQmlModule));
1305 
1306     if (optVerboseLevel) {
1307         std::wcout << QDir::toNativeSeparators(options.binaries.first()) << ' '
1308                    << wordSize << " bit, " << (result.isDebug ? "debug" : "release")
1309                    << " executable";
1310         if (usesQml2)
1311             std::wcout << " [QML]";
1312         std::wcout << '\n';
1313     }
1314 
1315     if (dependentQtLibs.isEmpty()) {
1316         *errorMessage = QDir::toNativeSeparators(options.binaries.first()) +  QStringLiteral(" does not seem to be a Qt executable.");
1317         return result;
1318     }
1319 
1320     // Some Windows-specific checks: Qt5Core depends on ICU when configured with "-icu". Other than
1321     // that, Qt5WebKit has a hard dependency on ICU.
1322     if (options.platform.testFlag(WindowsBased))  {
1323         const QStringList qtLibs = dependentQtLibs.filter(QStringLiteral("Qt5Core"), Qt::CaseInsensitive)
1324             + dependentQtLibs.filter(QStringLiteral("Qt5WebKit"), Qt::CaseInsensitive);
1325         for (const QString &qtLib : qtLibs) {
1326             QStringList icuLibs = findDependentLibraries(qtLib, options.platform, errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive);
1327             if (!icuLibs.isEmpty()) {
1328                 // Find out the ICU version to add the data library icudtXX.dll, which does not show
1329                 // as a dependency.
1330                 QRegExp numberExpression(QStringLiteral("\\d+"));
1331                 Q_ASSERT(numberExpression.isValid());
1332                 const int index = numberExpression.indexIn(icuLibs.front());
1333                 if (index >= 0)  {
1334                     const QString icuVersion = icuLibs.front().mid(index, numberExpression.matchedLength());
1335                     if (optVerboseLevel > 1)
1336                         std::wcout << "Adding ICU version " << icuVersion << '\n';
1337                     icuLibs.push_back(QStringLiteral("icudt") + icuVersion + QLatin1String(windowsSharedLibrarySuffix));
1338                 }
1339                 for (const QString &icuLib : qAsConst(icuLibs)) {
1340                     const QString icuPath = findInPath(icuLib);
1341                     if (icuPath.isEmpty()) {
1342                         *errorMessage = QStringLiteral("Unable to locate ICU library ") + icuLib;
1343                         return result;
1344                     }
1345                     dependentQtLibs.push_back(icuPath);
1346                 } // for each icuLib
1347                 break;
1348             } // !icuLibs.isEmpty()
1349         } // Qt5Core/Qt5WebKit
1350     } // Windows
1351 
1352     // Scan Quick2 imports
1353     QmlImportScanResult qmlScanResult;
1354     if (options.quickImports && usesQml2) {
1355         // Custom list of import paths provided by user
1356         QStringList qmlImportPaths = options.qmlImportPaths;
1357         // Qt's own QML modules
1358         qmlImportPaths << qmakeVariables.value(QStringLiteral("QT_INSTALL_QML"));
1359         QStringList qmlDirectories = options.qmlDirectories;
1360         if (qmlDirectories.isEmpty()) {
1361             const QString qmlDirectory = findQmlDirectory(options.platform, options.directory);
1362             if (!qmlDirectory.isEmpty())
1363                 qmlDirectories.append(qmlDirectory);
1364         }
1365         for (const QString &qmlDirectory : qAsConst(qmlDirectories)) {
1366             if (optVerboseLevel >= 1)
1367                 std::wcout << "Scanning " << QDir::toNativeSeparators(qmlDirectory) << ":\n";
1368             const QmlImportScanResult scanResult =
1369                 runQmlImportScanner(qmlDirectory, qmlImportPaths,
1370                                     result.directlyUsedQtLibraries & QtWidgetsModule,
1371                                     options.platform, debugMatchMode, errorMessage);
1372             if (!scanResult.ok)
1373                 return result;
1374             qmlScanResult.append(scanResult);
1375             // Additional dependencies of QML plugins.
1376             for (const QString &plugin : qAsConst(qmlScanResult.plugins)) {
1377                 if (!findDependentQtLibraries(libraryLocation, plugin, options.platform, errorMessage, &dependentQtLibs, &wordSize, &detectedDebug, &machineArch))
1378                     return result;
1379             }
1380             if (optVerboseLevel >= 1) {
1381                 std::wcout << "QML imports:\n";
1382                 for (const QmlImportScanResult::Module &mod : qAsConst(qmlScanResult.modules)) {
1383                     std::wcout << "  '" << mod.name << "' "
1384                                << QDir::toNativeSeparators(mod.sourcePath) << '\n';
1385                 }
1386                 if (optVerboseLevel >= 2) {
1387                     std::wcout << "QML plugins:\n";
1388                     for (const QString &p : qAsConst(qmlScanResult.plugins))
1389                         std::wcout << "  " << QDir::toNativeSeparators(p) << '\n';
1390                 }
1391             }
1392         }
1393     }
1394 
1395     // Find the plugins and check whether ANGLE, D3D are required on the platform plugin.
1396     QString platformPlugin;
1397     // Sort apart Qt 5 libraries in the ones that are represented by the
1398     // QtModule enumeration (and thus controlled by flags) and others.
1399     QStringList deployedQtLibraries;
1400     for (int i = 0 ; i < dependentQtLibs.size(); ++i)  {
1401         if (const quint64 qtm = qtModule(dependentQtLibs.at(i), infix))
1402             result.usedQtLibraries |= qtm;
1403         else
1404             deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag.
1405     }
1406     result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries;
1407 
1408     const QStringList plugins =
1409         findQtPlugins(&result.deployedQtLibraries,
1410                       // For non-QML applications, disable QML to prevent it from being pulled in by the qtaccessiblequick plugin.
1411                       options.disabledLibraries | (usesQml2 ? 0 : (QtQmlModule | QtQuickModule)),
1412                       options.disabledPlugins,
1413                       qmakeVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), libraryLocation, infix,
1414                       debugMatchMode, options.platform, &platformPlugin);
1415 
1416     // Apply options flags and re-add library names.
1417     QString qtGuiLibrary;
1418     for (const auto &qtModule : qtModuleEntries) {
1419         if (result.deployedQtLibraries & qtModule.module) {
1420             const QString library = libraryPath(libraryLocation, qtModule.libraryName, qtLibInfix, options.platform, result.isDebug);
1421             deployedQtLibraries.append(library);
1422             if (qtModule.module == QtGuiModule)
1423                 qtGuiLibrary = library;
1424         }
1425     }
1426 
1427     if (optVerboseLevel >= 1) {
1428         std::wcout << "Direct dependencies: " << formatQtModules(result.directlyUsedQtLibraries).constData()
1429                    << "\nAll dependencies   : " << formatQtModules(result.usedQtLibraries).constData()
1430                    << "\nTo be deployed     : " << formatQtModules(result.deployedQtLibraries).constData() << '\n';
1431     }
1432 
1433     if (optVerboseLevel > 1)
1434         std::wcout << "Plugins: " << plugins.join(QLatin1Char(',')) << '\n';
1435 
1436     if ((result.deployedQtLibraries & QtGuiModule) && platformPlugin.isEmpty()) {
1437         *errorMessage =QStringLiteral("Unable to find the platform plugin.");
1438         return result;
1439     }
1440 
1441     // Check for ANGLE on the Qt5Gui library.
1442     if (options.platform.testFlag(WindowsBased) && !qtGuiLibrary.isEmpty())  {
1443         QString libGlesName = QStringLiteral("libGLESv2");
1444         if (result.isDebug && platformHasDebugSuffix(options.platform))
1445             libGlesName += QLatin1Char('d');
1446         libGlesName += QLatin1String(windowsSharedLibrarySuffix);
1447         QString libCombinedQtAngleName = QStringLiteral("QtANGLE");
1448         if (result.isDebug && platformHasDebugSuffix(options.platform))
1449             libCombinedQtAngleName += QLatin1Char('d');
1450         libCombinedQtAngleName += QLatin1String(windowsSharedLibrarySuffix);
1451         const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, options.platform, errorMessage);
1452         const bool dependsOnAngle = !guiLibraries.filter(libGlesName, Qt::CaseInsensitive).isEmpty();
1453         const bool dependsOnCombinedAngle = !guiLibraries.filter(libCombinedQtAngleName, Qt::CaseInsensitive).isEmpty();
1454         const bool dependsOnOpenGl = !guiLibraries.filter(QStringLiteral("opengl32"), Qt::CaseInsensitive).isEmpty();
1455         if (options.angleDetection != Options::AngleDetectionForceOff
1456             && (dependsOnAngle || dependsOnCombinedAngle || !dependsOnOpenGl || options.angleDetection == Options::AngleDetectionForceOn)) {
1457             const QString combinedAngleFullPath = qtBinDir + slash + libCombinedQtAngleName;
1458             if (QFileInfo::exists(combinedAngleFullPath)) {
1459                 deployedQtLibraries.append(combinedAngleFullPath);
1460             } else {
1461                 const QString libGlesFullPath = qtBinDir + slash + libGlesName;
1462                 deployedQtLibraries.append(libGlesFullPath);
1463                 QString libEglFullPath = qtBinDir + slash + QStringLiteral("libEGL");
1464                 if (result.isDebug && platformHasDebugSuffix(options.platform))
1465                     libEglFullPath += QLatin1Char('d');
1466                 libEglFullPath += QLatin1String(windowsSharedLibrarySuffix);
1467                 deployedQtLibraries.append(libEglFullPath);
1468             }
1469             // Find the system D3d Compiler matching the D3D library.
1470             // Any arm64 OS will be new enough to be shipped with the D3DCompiler inbox.
1471             if (options.systemD3dCompiler && !options.isWinRt() && machineArch != IMAGE_FILE_MACHINE_ARM64) {
1472                 const QString d3dCompiler = findD3dCompiler(options.platform, qtBinDir, wordSize);
1473                 if (d3dCompiler.isEmpty()) {
1474                     std::wcerr << "Warning: Cannot find any version of the d3dcompiler DLL.\n";
1475                 } else {
1476                     deployedQtLibraries.push_back(d3dCompiler);
1477                 }
1478             }
1479         } // deployAngle
1480         if (options.softwareRasterizer && !dependsOnOpenGl) {
1481             const QFileInfo softwareRasterizer(qtBinDir + slash + QStringLiteral("opengl32sw") + QLatin1String(windowsSharedLibrarySuffix));
1482             if (softwareRasterizer.isFile())
1483                 deployedQtLibraries.append(softwareRasterizer.absoluteFilePath());
1484         }
1485     } // Windows
1486 
1487     // We need to copy ucrtbased.dll on WinRT as this library is not part of
1488     // the c runtime package. VS 2015 does the same when deploying to a device
1489     // or creating an appx.
1490     if (result.isDebug && options.platform == WinRtArmMsvc
1491              && qmakeVariables.value(QStringLiteral("QMAKE_XSPEC")).endsWith(QLatin1String("msvc2015"))) {
1492         const QString extensionPath = QString::fromLocal8Bit(qgetenv("ExtensionSdkDir"));
1493         const QString ucrtVersion = QString::fromLocal8Bit(qgetenv("UCRTVersion"));
1494         if (extensionPath.isEmpty() || ucrtVersion.isEmpty()) {
1495             std::wcerr << "Warning: Cannot find ucrtbased.dll as either "
1496                 << "ExtensionSdkDir or UCRTVersion is not set in "
1497                 << "your environment.\n";
1498         } else {
1499             const QString ucrtbasedLib = extensionPath
1500                     + QStringLiteral("/Microsoft.UniversalCRT.Debug/")
1501                     + ucrtVersion
1502                     + QStringLiteral("/Redist/Debug/arm/ucrtbased.dll");
1503             const QFileInfo ucrtPath(ucrtbasedLib);
1504             if (ucrtPath.exists() && ucrtPath.isFile()) {
1505                 deployedQtLibraries.append(ucrtPath.absoluteFilePath());
1506             } else {
1507                 std::wcerr << "Warning: Cannot find ucrtbased.dll at "
1508                            << QDir::toNativeSeparators(ucrtbasedLib)
1509                            << " or it is not a file.\n";
1510             }
1511         }
1512     }
1513 
1514     // Update libraries
1515     if (options.libraries) {
1516         const QString targetPath = options.libraryDirectory.isEmpty() ?
1517             options.directory : options.libraryDirectory;
1518         QStringList libraries = deployedQtLibraries;
1519         if (options.compilerRunTime)
1520             libraries.append(compilerRunTimeLibs(options.platform, result.isDebug, machineArch));
1521         for (const QString &qtLib : qAsConst(libraries)) {
1522             if (!updateLibrary(qtLib, targetPath, options, errorMessage))
1523                 return result;
1524         }
1525 
1526         if (options.patchQt  && !options.dryRun && !options.isWinRt()) {
1527             const QString qt5CoreName = QFileInfo(libraryPath(libraryLocation, "Qt5Core", qtLibInfix,
1528                                                               options.platform, result.isDebug)).fileName();
1529 #ifndef QT_RELOCATABLE
1530             if (!patchQtCore(targetPath + QLatin1Char('/') + qt5CoreName, errorMessage)) {
1531                 std::wcerr << "Warning: " << *errorMessage << '\n';
1532                 errorMessage->clear();
1533             }
1534 #endif
1535         }
1536     } // optLibraries
1537 
1538     // Update plugins
1539     if (options.plugins) {
1540         const QString targetPath = options.pluginDirectory.isEmpty() ?
1541             options.directory : options.pluginDirectory;
1542         QDir dir(targetPath);
1543         if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
1544             *errorMessage = QLatin1String("Cannot create ") +
1545                             QDir::toNativeSeparators(dir.absolutePath()) +  QLatin1Char('.');
1546             return result;
1547         }
1548         for (const QString &plugin : plugins) {
1549             const QString targetDirName = plugin.section(slash, -2, -2);
1550             const QString targetPath = dir.absoluteFilePath(targetDirName);
1551             if (!dir.exists(targetDirName)) {
1552                 if (optVerboseLevel)
1553                     std::wcout << "Creating directory " << targetPath << ".\n";
1554                 if (!(options.updateFileFlags & SkipUpdateFile) && !dir.mkdir(targetDirName)) {
1555                     *errorMessage = QStringLiteral("Cannot create ") + targetDirName +  QLatin1Char('.');
1556                     return result;
1557                 }
1558             }
1559             if (!updateLibrary(plugin, targetPath, options, errorMessage))
1560                 return result;
1561         }
1562     } // optPlugins
1563 
1564     // Update Quick imports
1565     const bool usesQuick1 = result.deployedQtLibraries & QtDeclarativeModule;
1566     // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports
1567     // for WebKit1-applications. Check direct dependency only.
1568     if (options.quickImports && (usesQuick1 || usesQml2)) {
1569         if (usesQml2) {
1570             for (const QmlImportScanResult::Module &module : qAsConst(qmlScanResult.modules)) {
1571                 const QString installPath = module.installPath(options.directory);
1572                 if (optVerboseLevel > 1)
1573                     std::wcout << "Installing: '" << module.name
1574                                << "' from " << module.sourcePath << " to "
1575                                << QDir::toNativeSeparators(installPath) << '\n';
1576                 if (installPath != options.directory && !createDirectory(installPath, errorMessage))
1577                     return result;
1578                 unsigned updateFileFlags = options.updateFileFlags | SkipQmlDesignerSpecificsDirectories;
1579                 unsigned qmlDirectoryFileFlags = 0;
1580                 if (options.deployPdb)
1581                     qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb;
1582                 if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(options.platform, debugMatchMode, qmlDirectoryFileFlags),
1583                                 installPath, updateFileFlags, options.json, errorMessage)) {
1584                     return result;
1585                 }
1586             }
1587         } // Quick 2
1588         if (usesQuick1) {
1589             const QString quick1ImportPath = qmakeVariables.value(QStringLiteral("QT_INSTALL_IMPORTS"));
1590             const QmlDirectoryFileEntryFunction qmlFileEntryFunction(options.platform, debugMatchMode, options.deployPdb ? QmlDirectoryFileEntryFunction::DeployPdb : 0);
1591             QStringList quick1Imports(QStringLiteral("Qt"));
1592             if (result.deployedQtLibraries & QtWebKitModule)
1593                 quick1Imports << QStringLiteral("QtWebKit");
1594             for (const QString &quick1Import : qAsConst(quick1Imports)) {
1595                 const QString sourceFile = quick1ImportPath + slash + quick1Import;
1596                 if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, options.json, errorMessage))
1597                     return result;
1598             }
1599         } // Quick 1
1600     } // optQuickImports
1601 
1602     if (options.translations) {
1603         if (!options.dryRun && !createDirectory(options.translationsDirectory, errorMessage))
1604             return result;
1605         if (!deployTranslations(qmakeVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")),
1606                                 result.deployedQtLibraries, options.translationsDirectory,
1607                                 options, errorMessage)) {
1608             return result;
1609         }
1610     }
1611 
1612     result.success = true;
1613     return result;
1614 }
1615 
deployWebProcess(const QMap<QString,QString> & qmakeVariables,const char * binaryName,const Options & sourceOptions,QString * errorMessage)1616 static bool deployWebProcess(const QMap<QString, QString> &qmakeVariables,
1617                              const char *binaryName,
1618                              const Options &sourceOptions, QString *errorMessage)
1619 {
1620     // Copy the web process and its dependencies
1621     const QString webProcess = webProcessBinary(binaryName, sourceOptions.platform);
1622     const QString webProcessSource = qmakeVariables.value(QStringLiteral("QT_INSTALL_LIBEXECS")) +
1623                                      QLatin1Char('/') + webProcess;
1624     if (!updateFile(webProcessSource, sourceOptions.directory, sourceOptions.updateFileFlags, sourceOptions.json, errorMessage))
1625         return false;
1626     Options options(sourceOptions);
1627     options.binaries.append(options.directory + QLatin1Char('/') + webProcess);
1628     options.quickImports = false;
1629     options.translations = false;
1630     return deploy(options, qmakeVariables, errorMessage);
1631 }
1632 
deployWebEngineCore(const QMap<QString,QString> & qmakeVariables,const Options & options,bool isDebug,QString * errorMessage)1633 static bool deployWebEngineCore(const QMap<QString, QString> &qmakeVariables,
1634                                 const Options &options, bool isDebug, QString *errorMessage)
1635 {
1636     static const char *installDataFiles[] = {"icudtl.dat",
1637                                              "qtwebengine_devtools_resources.pak",
1638                                              "qtwebengine_resources.pak",
1639                                              "qtwebengine_resources_100p.pak",
1640                                              "qtwebengine_resources_200p.pak"};
1641     QByteArray webEngineProcessName(webEngineProcessC);
1642     if (isDebug && platformHasDebugSuffix(options.platform))
1643         webEngineProcessName.append('d');
1644     if (optVerboseLevel)
1645         std::wcout << "Deploying: " << webEngineProcessName.constData() << "...\n";
1646     if (!deployWebProcess(qmakeVariables, webEngineProcessName, options, errorMessage))
1647         return false;
1648     const QString resourcesSubDir = QStringLiteral("/resources");
1649     const QString resourcesSourceDir
1650         = qmakeVariables.value(QStringLiteral("QT_INSTALL_DATA")) + resourcesSubDir
1651             + QLatin1Char('/');
1652     const QString resourcesTargetDir(options.directory + resourcesSubDir);
1653     if (!createDirectory(resourcesTargetDir, errorMessage))
1654         return false;
1655     for (auto installDataFile : installDataFiles) {
1656         if (!updateFile(resourcesSourceDir + QLatin1String(installDataFile),
1657                         resourcesTargetDir, options.updateFileFlags, options.json, errorMessage)) {
1658             return false;
1659         }
1660     }
1661     const QFileInfo translations(qmakeVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS"))
1662                                  + QStringLiteral("/qtwebengine_locales"));
1663     if (!translations.isDir()) {
1664         std::wcerr << "Warning: Cannot find the translation files of the QtWebEngine module at "
1665             << QDir::toNativeSeparators(translations.absoluteFilePath()) << ".\n";
1666         return true;
1667     }
1668     if (options.translations) {
1669         // Copy the whole translations directory.
1670         return createDirectory(options.translationsDirectory, errorMessage)
1671                 && updateFile(translations.absoluteFilePath(), options.translationsDirectory,
1672                               options.updateFileFlags, options.json, errorMessage);
1673     }
1674     // Translations have been turned off, but QtWebEngine needs at least one.
1675     const QFileInfo enUSpak(translations.filePath() + QStringLiteral("/en-US.pak"));
1676     if (!enUSpak.exists()) {
1677         std::wcerr << "Warning: Cannot find "
1678                    << QDir::toNativeSeparators(enUSpak.absoluteFilePath()) << ".\n";
1679         return true;
1680     }
1681     const QString webEngineTranslationsDir = options.translationsDirectory + QLatin1Char('/')
1682             + translations.fileName();
1683     if (!createDirectory(webEngineTranslationsDir, errorMessage))
1684         return false;
1685     return updateFile(enUSpak.absoluteFilePath(), webEngineTranslationsDir,
1686                       options.updateFileFlags, options.json, errorMessage);
1687 }
1688 
main(int argc,char ** argv)1689 int main(int argc, char **argv)
1690 {
1691     QCoreApplication a(argc, argv);
1692     QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
1693 
1694     const QByteArray qtBinPath = QFile::encodeName(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()));
1695     QByteArray path = qgetenv("PATH");
1696     if (!path.contains(qtBinPath)) { // QTBUG-39177, ensure Qt is in the path so that qt.conf is taken into account.
1697         path += ';';
1698         path += qtBinPath;
1699         qputenv("PATH", path);
1700     }
1701 
1702     Options options;
1703     QString errorMessage;
1704     const QMap<QString, QString> qmakeVariables = queryQMakeAll(&errorMessage);
1705     const QString xSpec = qmakeVariables.value(QStringLiteral("QMAKE_XSPEC"));
1706     options.platform = platformFromMkSpec(xSpec);
1707     if (options.platform == WindowsDesktopMinGW || options.platform == WindowsDesktopMsvc)
1708         options.compilerRunTime = true;
1709 
1710     {   // Command line
1711         QCommandLineParser parser;
1712         QString errorMessage;
1713         const int result = parseArguments(QCoreApplication::arguments(), &parser, &options, &errorMessage);
1714         if (result & CommandLineParseError)
1715             std::wcerr << errorMessage << "\n\n";
1716         if (result & CommandLineParseHelpRequested)
1717             std::fputs(qPrintable(helpText(parser)), stdout);
1718         if (result & CommandLineParseError)
1719             return 1;
1720         if (result & CommandLineParseHelpRequested)
1721             return 0;
1722     }
1723 
1724     if (qmakeVariables.isEmpty() || xSpec.isEmpty() || !qmakeVariables.contains(QStringLiteral("QT_INSTALL_BINS"))) {
1725         std::wcerr << "Unable to query qmake: " << errorMessage << '\n';
1726         return 1;
1727     }
1728 
1729     if (options.platform == UnknownPlatform) {
1730         std::wcerr << "Unsupported platform " << xSpec << '\n';
1731         return 1;
1732     }
1733 
1734     // Create directories
1735     if (!createDirectory(options.directory, &errorMessage)) {
1736         std::wcerr << errorMessage << '\n';
1737         return 1;
1738     }
1739     if (!options.libraryDirectory.isEmpty() && options.libraryDirectory != options.directory
1740         && !createDirectory(options.libraryDirectory, &errorMessage)) {
1741         std::wcerr << errorMessage << '\n';
1742         return 1;
1743     }
1744 
1745     if (optWebKit2 == OptionEnabled)
1746         options.additionalLibraries |= QtWebKitModule;
1747 
1748 
1749     const DeployResult result = deploy(options, qmakeVariables, &errorMessage);
1750     if (!result) {
1751         std::wcerr << errorMessage << '\n';
1752         return 1;
1753     }
1754 
1755     if ((optWebKit2 != OptionDisabled)
1756         && (optWebKit2 == OptionEnabled
1757             || ((result.deployedQtLibraries & QtWebKitModule)
1758                 && (result.directlyUsedQtLibraries & QtQuickModule)))) {
1759         if (optVerboseLevel)
1760             std::wcout << "Deploying: " << webKitProcessC << "...\n";
1761         if (!deployWebProcess(qmakeVariables, webKitProcessC, options, &errorMessage)) {
1762             std::wcerr << errorMessage << '\n';
1763             return 1;
1764         }
1765     }
1766 
1767     if (result.deployedQtLibraries & QtWebEngineCoreModule) {
1768         if (!deployWebEngineCore(qmakeVariables, options, result.isDebug, &errorMessage)) {
1769             std::wcerr << errorMessage << '\n';
1770             return 1;
1771         }
1772     }
1773 
1774     if (options.json) {
1775         if (options.list)
1776             std::fputs(options.json->toList(options.list, options.directory).constData(), stdout);
1777         else
1778             std::fputs(options.json->toJson().constData(), stdout);
1779         delete options.json;
1780         options.json = nullptr;
1781     }
1782 
1783     return 0;
1784 }
1785 
1786 QT_END_NAMESPACE
1787