diff options
author | Friedemann Kleint <Friedemann.Kleint@digia.com> | 2013-05-24 14:57:03 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@digia.com> | 2013-05-27 16:07:35 +0200 |
commit | b208f7bd0eab5c166057e58c5f7e6ee264975432 (patch) | |
tree | 11f3b4a78ebf6b84f3356301ba886794bb880594 | |
parent | 106767ca7db2172f7db0df4dc4a76eaff2b93aee (diff) | |
download | qttools-b208f7bd0eab5c166057e58c5f7e6ee264975432.tar.gz |
windeployqt: Exactly match debug/release DLLs based on PE information.
Change updateFile() to be a template to be able to match files
with a predicate function and implement it for discriminating
debug/release DLLs. Reshuffle code, move findQtPlugins() to
main.cpp.
Change-Id: I75fe90ed2329a482d294abf6faeac700e1234167
Reviewed-by: Oliver Wolff <oliver.wolff@digia.com>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
-rw-r--r-- | src/windeployqt/main.cpp | 131 | ||||
-rw-r--r-- | src/windeployqt/utils.cpp | 56 | ||||
-rw-r--r-- | src/windeployqt/utils.h | 101 |
3 files changed, 214 insertions, 74 deletions
diff --git a/src/windeployqt/main.cpp b/src/windeployqt/main.cpp index a886faa97..59dc5243d 100644 --- a/src/windeployqt/main.cpp +++ b/src/windeployqt/main.cpp @@ -119,10 +119,11 @@ static inline QString findD3dCompiler() // Helper for recursively finding all dependent Qt libraries. static bool findDependentQtLibraries(const QString &qtBinDir, const QString &binary, - QStringList *result, QString *errorMessage) + QString *errorMessage, QStringList *result, + unsigned *wordSize = 0, bool *isDebug = 0) { - const QStringList dependentLibs = findDependentLibraries(binary, errorMessage); - if (dependentLibs.isEmpty()) { + QStringList dependentLibs; + if (!readPeExecutable(binary, errorMessage, &dependentLibs, wordSize, isDebug)) { errorMessage->prepend(QLatin1String("Unable to find dependent libraries of ") + QDir::toNativeSeparators(binary) + QLatin1String(" :")); return false; @@ -134,13 +135,113 @@ static bool findDependentQtLibraries(const QString &qtBinDir, const QString &bin const QString path = normalizeFileName(qtBinDir + QLatin1Char('/') + QFileInfo(qtLib).fileName()); if (!result->contains(path)) { result->append(path); - if (!findDependentQtLibraries(qtBinDir, path, result, errorMessage)) + if (!findDependentQtLibraries(qtBinDir, path, errorMessage, result)) return false; } } return true; } +// Base class to filter debug/release DLLs for functions to be passed to updateFile(). +// Tries to pre-filter by namefilter and does check via PE. +class DllDirectoryFileEntryFunction { +public: + explicit DllDirectoryFileEntryFunction(bool debug, const QString &prefix = QStringLiteral("*")) : + m_nameFilter(QStringList(prefix + (debug ? QStringLiteral("d.dll") : QStringLiteral(".dll")))), + m_dllDebug(debug) {} + + QStringList operator()(const QDir &dir) const + { + QStringList result; + QString errorMessage; + foreach (const QString &dll, m_nameFilter(dir)) { + const QString dllPath = dir.absoluteFilePath(dll); + bool debugDll; + if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll)) { + if (debugDll == m_dllDebug) { + result.push_back(dll); + } + } else { + std::fprintf(stderr, "Warning: Unable to read %s: %s", + qPrintable(QDir::toNativeSeparators(dllPath)), qPrintable(errorMessage)); + } + } + return result; + } + +private: + const NameFilterFileEntryFunction m_nameFilter; + const bool m_dllDebug; +}; + +// File entry filter function for updateFile() that returns a list of files for +// QML import trees: DLLs (matching debgug) and .qml/,js, etc. +class QmlDirectoryFileEntryFunction { +public: + explicit QmlDirectoryFileEntryFunction(bool debug) + : m_qmlNameFilter(QStringList() << QStringLiteral("*.js") << QStringLiteral("qmldir") << QStringLiteral("*.qmltypes")) + , m_dllFilter(debug) + {} + + QStringList operator()(const QDir &dir) const { return m_dllFilter(dir) + m_qmlNameFilter(dir); } + +private: + NameFilterFileEntryFunction m_qmlNameFilter; + DllDirectoryFileEntryFunction m_dllFilter; +}; + +static inline unsigned qtModuleForPlugin(const QString &subDirName) +{ + if (subDirName == QLatin1String("accessible") || subDirName == QLatin1String("iconengines") + || subDirName == QLatin1String("imageformats") || subDirName == QLatin1String("platforms")) { + return GuiModule; + } + if (subDirName == QLatin1String("bearer")) + return NetworkModule; + if (subDirName == QLatin1String("sqldrivers")) + return SqlModule; + if (subDirName == QLatin1String("mediaservice") || subDirName == QLatin1String("playlistformats")) + return MultimediaModule; + if (subDirName == QLatin1String("printsupport")) + return PrintSupportModule; + if (subDirName == QLatin1String("qmltooling")) + return Quick1Module | Quick2Module; + return 0; // "designer" +} + +QStringList findQtPlugins(unsigned usedQtModules, + const QString qtPluginsDirName, + bool debug, Platform platform, + QString *platformPlugin) +{ + if (qtPluginsDirName.isEmpty()) + return QStringList(); + QDir pluginsDir(qtPluginsDirName); + QStringList result; + foreach (const QString &subDirName, pluginsDir.entryList(QStringList(QLatin1String("*")), QDir::Dirs | QDir::NoDotAndDotDot)) { + const unsigned module = qtModuleForPlugin(subDirName); + if (module & usedQtModules) { + const QString subDirPath = qtPluginsDirName + QLatin1Char('/') + subDirName; + QDir subDir(subDirPath); + // Filter for platform or any. + QString filter; + const bool isPlatformPlugin = subDirName == QLatin1String("platforms"); + if (isPlatformPlugin) { + filter = platform == WinRt ? QStringLiteral("qwinrt") : QStringLiteral("qwindows"); + } else { + filter = QLatin1String("*"); + } + foreach (const QString &plugin, DllDirectoryFileEntryFunction(debug, filter)(subDir)) { + const QString pluginPath = subDir.absoluteFilePath(plugin); + if (isPlatformPlugin) + *platformPlugin = pluginPath; + result.push_back(pluginPath); + } // for filter + } // type matches + } // for plugin folder + return result; +} + static unsigned qtModules(const QStringList &qtLibraries) { unsigned result = 0; @@ -200,22 +301,25 @@ int main(int argc, char **argv) std::fprintf(stderr, "Qt binaries in %s\n", qPrintable(QDir::toNativeSeparators(qtBinDir))); QStringList dependentQtLibs; - if (!findDependentQtLibraries(qtBinDir, binary, &dependentQtLibs, &errorMessage)) { + bool isDebug; + unsigned wordSize; + if (!findDependentQtLibraries(qtBinDir, binary, &errorMessage, &dependentQtLibs, &wordSize, &isDebug)) { std::fputs(qPrintable(errorMessage), stderr); return 1; } + std::printf("%s: %ubit, %s executable.\n", qPrintable(QDir::toNativeSeparators(binary)), + wordSize, isDebug ? "debug" : "release"); + if (dependentQtLibs.isEmpty()) { std::fprintf(stderr, "%s does not seem to be a Qt executable\n", qPrintable(QDir::toNativeSeparators(binary))); return 1; } - // Some checks in QtCore: Debug, ICU - bool isDebug = false; + // Some checks in QtCore: ICU const QStringList qt5Core = dependentQtLibs.filter(QStringLiteral("Qt5Core"), Qt::CaseInsensitive); if (!qt5Core.isEmpty()) { - isDebug = qt5Core.front().contains(QStringLiteral("Qt5Cored.dll"), Qt::CaseInsensitive); QStringList icuLibs = findDependentLibraries(qt5Core.front(), &errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive); if (!icuLibs.isEmpty()) { // Find out the ICU version to add the data library icudtXX.dll, which does not show @@ -247,7 +351,8 @@ int main(int argc, char **argv) // Find the plugins and check whether ANGLE, D3D are required on the platform plugin. QString platformPlugin; const unsigned usedQtModules = qtModules(dependentQtLibs); - const QStringList plugins = findQtPlugins(usedQtModules, isDebug, platform, &platformPlugin, &errorMessage); + const QStringList plugins = findQtPlugins(usedQtModules, qmakeVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), + isDebug, platform, &platformPlugin); if (optVerboseLevel > 1) std::fprintf(stderr, "Plugins: %s\n", qPrintable(plugins.join(QLatin1Char(',')))); @@ -312,9 +417,7 @@ int main(int argc, char **argv) // Update Quick imports if (optQuickImports && (usedQtModules & (Quick1Module | Quick2Module))) { - const QStringList importNameFilters = QStringList() << QStringLiteral("*.qml") - << QStringLiteral("*.js") << QStringLiteral("*.dll") - << QStringLiteral("qmldir") << QStringLiteral("*.qmltypes"); + const QmlDirectoryFileEntryFunction qmlFileEntryFunction(isDebug); if (usedQtModules & Quick2Module) { const QString quick2ImportPath = qmakeVariables.value(QStringLiteral("QT_INSTALL_QML")); QStringList quick2Imports; @@ -326,7 +429,7 @@ int main(int argc, char **argv) if (usedQtModules & WebKitModule) quick2Imports << QStringLiteral("QtWebKit"); foreach (const QString &quick2Import, quick2Imports) { - if (!updateFile(quick2ImportPath + slash + quick2Import, importNameFilters, optDirectory, &errorMessage)) { + if (!updateFile(quick2ImportPath + slash + quick2Import, qmlFileEntryFunction, optDirectory, &errorMessage)) { std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); return 1; } @@ -338,7 +441,7 @@ int main(int argc, char **argv) if (usedQtModules & WebKitModule) quick1Imports << QStringLiteral("QtWebKit"); foreach (const QString &quick1Import, quick1Imports) { - if (!updateFile(quick1ImportPath + slash + quick1Import, importNameFilters, optDirectory, &errorMessage)) { + if (!updateFile(quick1ImportPath + slash + quick1Import, qmlFileEntryFunction, optDirectory, &errorMessage)) { std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); return 1; } diff --git a/src/windeployqt/utils.cpp b/src/windeployqt/utils.cpp index c8e8dcb0d..086d5b0b8 100644 --- a/src/windeployqt/utils.cpp +++ b/src/windeployqt/utils.cpp @@ -45,10 +45,7 @@ #include <QtCore/QFileInfo> #include <QtCore/qt_windows.h> #include <QtCore/QTemporaryFile> -#include <QtCore/QFile> -#include <QtCore/QDir> #include <QtCore/QScopedPointer> -#include <QtCore/QDateTime> #include <QtCore/QStandardPaths> #include <cstdio> @@ -268,59 +265,6 @@ QString queryQMake(const QString &variable, QString *errorMessage) return QString::fromLocal8Bit(stdOut).trimmed(); } -static inline unsigned qtModuleForPlugin(const QString &subDirName) -{ - if (subDirName == QLatin1String("accessible") || subDirName == QLatin1String("iconengines") - || subDirName == QLatin1String("imageformats") || subDirName == QLatin1String("platforms")) { - return GuiModule; - } - if (subDirName == QLatin1String("bearer")) - return NetworkModule; - if (subDirName == QLatin1String("sqldrivers")) - return SqlModule; - if (subDirName == QLatin1String("mediaservice") || subDirName == QLatin1String("playlistformats")) - return MultimediaModule; - if (subDirName == QLatin1String("printsupport")) - return PrintSupportModule; - if (subDirName == QLatin1String("qmltooling")) - return Quick1Module | Quick2Module; - return 0; // "designer" -} - -QStringList findQtPlugins(unsigned usedQtModules, bool debug, Platform platform, QString *platformPlugin, QString *errorMessage) -{ - const QString qtPluginsDirName = queryQMake(QStringLiteral("QT_INSTALL_PLUGINS"), errorMessage); - if (qtPluginsDirName.isEmpty()) - return QStringList(); - QDir pluginsDir(qtPluginsDirName); - QStringList result; - foreach (const QString &subDirName, pluginsDir.entryList(QStringList(QLatin1String("*")), QDir::Dirs | QDir::NoDotAndDotDot)) { - const unsigned module = qtModuleForPlugin(subDirName); - if (module & usedQtModules) { - const QString subDirPath = qtPluginsDirName + QLatin1Char('/') + subDirName; - QDir subDir(subDirPath); - // Filter for platform or any. - QString filter; - const bool isPlatformPlugin = subDirName == QLatin1String("platforms"); - if (isPlatformPlugin) { - filter = platform == WinRt ? QStringLiteral("qwinrt") : QStringLiteral("qwindows"); - if (debug) - filter.append(QLatin1Char('d')); - } else { - filter = QLatin1String(debug ? "*d" : "*[^d]"); - } - filter += QStringLiteral(".dll"); - foreach (const QString &dll, subDir.entryList(QStringList(filter), QDir::Files)) { - const QString plugin = subDirPath + QLatin1Char('/') + dll; - result.push_back(plugin); - if (isPlatformPlugin && platformPlugin) - *platformPlugin = plugin; - } // for filter - } // type matches - } // for plugin folder - return result; -} - // Update a file or directory. bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, const QString &targetDirectory, QString *errorMessage) diff --git a/src/windeployqt/utils.h b/src/windeployqt/utils.h index 477a80e90..c58681cb1 100644 --- a/src/windeployqt/utils.h +++ b/src/windeployqt/utils.h @@ -44,6 +44,9 @@ #include <QStringList> #include <QMap> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDateTime> enum Platform { Windows, WinRt }; @@ -66,12 +69,8 @@ enum QtModule { WebKitModule = 0x400 }; -QStringList findQtPlugins(unsigned usedQtModules, bool debug, Platform platform, - QString *platformPlugin, QString *errorMessage); bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, const QString &targetDirectory, QString *errorMessage); -inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, QString *errorMessage) -{ return updateFile(sourceFileName, QStringList(), targetDirectory, errorMessage); } bool runProcess(const QString &commandLine, const QString &workingDirectory = QString(), unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0, QString *errorMessage = 0); @@ -91,4 +90,98 @@ inline QStringList findDependentLibraries(const QString &peExecutableFileName, Q extern int optVerboseLevel; +// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir +// to obtain the files. +template <class DirectoryFileEntryFunction> +bool updateFile(const QString &sourceFileName, + DirectoryFileEntryFunction directoryFileEntryFunction, + const QString &targetDirectory, + QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::fprintf(stderr, "Checking %s, %s\n", qPrintable(sourceFileName), qPrintable(targetFileName)); + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + if (sourceFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isDir()) { + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + std::printf("Creating %s.\n", qPrintable(targetFileName)); + if (!d.mkdir(sourceFileInfo.fileName())) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + // Recurse into directory + QDir dir(sourceFileName); + + const QStringList allEntries = directoryFileEntryFunction(dir) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &entry, allEntries) + if (!updateFile(sourceFileName + QLatin1Char('/') + entry, directoryFileEntryFunction, targetFileName, errorMessage)) + return false; + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::printf("%s is up to date.\n", qPrintable(sourceFileInfo.fileName())); + return true; + } + QFile targetFile(targetFileName); + if (!targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::printf("Updating %s.\n", qPrintable(sourceFileInfo.fileName())); + if (!file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + return true; +} + +// Base class to filter files by name filters functions to be passed to updateFile(). +class NameFilterFileEntryFunction { +public: + explicit NameFilterFileEntryFunction(const QStringList &nameFilters) : m_nameFilters(nameFilters) {} + QStringList operator()(const QDir &dir) const { return dir.entryList(m_nameFilters, QDir::Files); } + +private: + const QStringList m_nameFilters; +}; + +// Convenience for all files. +inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, QString *errorMessage) +{ + return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, errorMessage); +} + #endif // UTILS_H |