Move macdeployqt and windeployqt from qttools to qtbase

Having all *deployqt tools in qtbase will allow us to couple deployment
support more tightly with the build system.

Change-Id: I299efdacfa6b66a303bb3996ff3ff84e723210a5
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
This commit is contained in:
Joerg Bornemann 2021-11-18 14:52:19 +01:00
parent 1e9f9a4b7d
commit 3f56950862
29 changed files with 6791 additions and 0 deletions

View File

@ -20,3 +20,11 @@ if(QT_FEATURE_androiddeployqt)
add_subdirectory(androidtestrunner) add_subdirectory(androidtestrunner)
endif() endif()
endif() endif()
if(QT_FEATURE_macdeployqt)
add_subdirectory(macdeployqt)
endif()
if(QT_FEATURE_windeployqt)
add_subdirectory(windeployqt)
endif()

View File

@ -4,6 +4,18 @@ qt_feature("androiddeployqt" PRIVATE
PURPOSE "The Android deployment tool automates the process of creating Android packages." PURPOSE "The Android deployment tool automates the process of creating Android packages."
CONDITION NOT CMAKE_CROSSCOMPILING AND QT_FEATURE_regularexpression) CONDITION NOT CMAKE_CROSSCOMPILING AND QT_FEATURE_regularexpression)
qt_feature("macdeployqt" PRIVATE
SECTION "Deployment"
LABEL "macOS deployment tool"
PURPOSE "The Mac deployment tool automates the process of creating a deployable application bundle that contains the Qt libraries as private frameworks."
CONDITION MACOS)
qt_feature("windeployqt" PRIVATE
SECTION "Deployment"
LABEL "Windows deployment tool"
PURPOSE "The Windows deployment tool is designed to automate the process of creating a deployable folder containing the Qt-related dependencies (libraries, QML imports, plugins, and translations) required to run the application from that folder. It creates a sandbox for Universal Windows Platform (UWP) or an installation tree for Windows desktop applications, which can be easily bundled into an installation package."
CONDITION WIN32)
qt_feature("qmake" PRIVATE qt_feature("qmake" PRIVATE
PURPOSE "The qmake tool helps simplify the build process for development projects across different platforms." PURPOSE "The qmake tool helps simplify the build process for development projects across different platforms."
CONDITION QT_FEATURE_settings AND QT_FEATURE_alloca AND CONDITION QT_FEATURE_settings AND QT_FEATURE_alloca AND
@ -12,5 +24,7 @@ qt_feature("qmake" PRIVATE
qt_configure_add_summary_section(NAME "Core tools") qt_configure_add_summary_section(NAME "Core tools")
qt_configure_add_summary_entry(ARGS "androiddeployqt") qt_configure_add_summary_entry(ARGS "androiddeployqt")
qt_configure_add_summary_entry(ARGS "macdeployqt")
qt_configure_add_summary_entry(ARGS "windeployqt")
qt_configure_add_summary_entry(ARGS "qmake") qt_configure_add_summary_entry(ARGS "qmake")
qt_configure_end_summary_section() qt_configure_end_summary_section()

View File

@ -0,0 +1,6 @@
# Generated from macdeployqt.pro.
if(NOT QT_FEATURE_macdeployqt)
return()
endif()
add_subdirectory(macdeployqt)

View File

@ -0,0 +1,16 @@
#####################################################################
## macdeployqt Tool:
#####################################################################
qt_get_tool_target_name(target_name macdeployqt)
qt_internal_add_tool(${target_name}
TOOLS_TARGET Core
USER_FACING
TARGET_DESCRIPTION "Qt macOS Deployment Tool"
SOURCES
../shared/shared.cpp
main.cpp
LIBRARIES
${FWCoreFoundation}
)
qt_internal_return_unless_building_tools()

View File

@ -0,0 +1,278 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QCoreApplication>
#include <QDir>
#include <QLibraryInfo>
#include "../shared/shared.h"
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QString appBundlePath;
if (argc > 1)
appBundlePath = QString::fromLocal8Bit(argv[1]);
if (argc < 2 || appBundlePath.startsWith("-")) {
qDebug() << "Usage: macdeployqt app-bundle [options]";
qDebug() << "";
qDebug() << "Options:";
qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
qDebug() << " -no-plugins : Skip plugin deployment";
qDebug() << " -dmg : Create a .dmg disk image";
qDebug() << " -no-strip : Don't run 'strip' on the binaries";
qDebug() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)";
qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
qDebug() << " -qmldir=<path> : Scan for QML imports in the given path";
qDebug() << " -qmlimport=<path> : Add the given path to the QML module search locations";
qDebug() << " -always-overwrite : Copy files even if the target file exists";
qDebug() << " -codesign=<ident> : Run codesign with the given identity on all executables";
qDebug() << " -hardened-runtime : Enable Hardened Runtime when code signing";
qDebug() << " -timestamp : Include a secure timestamp when code signing (requires internet connection)";
qDebug() << " -sign-for-notarization=<ident>: Activate the necessary options for notarization (requires internet connection)";
qDebug() << " -appstore-compliant : Skip deployment of components that use private API";
qDebug() << " -libpath=<path> : Add the given path to the library search path";
qDebug() << " -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
qDebug() << "";
qDebug() << "macdeployqt takes an application bundle as input and makes it";
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
qDebug() << "the application uses.";
qDebug() << "";
qDebug() << "Plugins related to a framework are copied in with the";
qDebug() << "framework. The accessibility, image formats, and text codec";
qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
qDebug() << "";
qDebug() << "Qt plugins may use private API and will cause the app to be";
qDebug() << "rejected from the Mac App store. MacDeployQt will print a warning";
qDebug() << "when known incompatible plugins are deployed. Use -appstore-compliant ";
qDebug() << "to skip these plugins. Currently two SQL plugins are known to";
qDebug() << "be incompatible: qsqlodbc and qsqlpsql.";
qDebug() << "";
qDebug() << "See the \"Deploying Applications on OS X\" topic in the";
qDebug() << "documentation for more information about deployment on OS X.";
return 1;
}
appBundlePath = QDir::cleanPath(appBundlePath);
if (!QDir(appBundlePath).exists()) {
qDebug() << "Error: Could not find app bundle" << appBundlePath;
return 1;
}
bool plugins = true;
bool dmg = false;
QByteArray filesystem("HFS+");
bool useDebugLibs = false;
extern bool runStripEnabled;
extern bool alwaysOwerwriteEnabled;
extern QStringList librarySearchPath;
QStringList additionalExecutables;
bool qmldirArgumentUsed = false;
QStringList qmlDirs;
QStringList qmlImportPaths;
extern bool runCodesign;
extern QString codesignIdentiy;
extern bool hardenedRuntime;
extern bool appstoreCompliant;
extern bool deployFramework;
extern bool secureTimestamp;
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
if (argument == QByteArray("-no-plugins")) {
LogDebug() << "Argument found:" << argument;
plugins = false;
} else if (argument == QByteArray("-dmg")) {
LogDebug() << "Argument found:" << argument;
dmg = true;
} else if (argument == QByteArray("-no-strip")) {
LogDebug() << "Argument found:" << argument;
runStripEnabled = false;
} else if (argument == QByteArray("-use-debug-libs")) {
LogDebug() << "Argument found:" << argument;
useDebugLibs = true;
runStripEnabled = false;
} else if (argument.startsWith(QByteArray("-verbose"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf("=");
bool ok = false;
int number = argument.mid(index+1).toInt(&ok);
if (!ok)
LogError() << "Could not parse verbose level";
else
logLevel = number;
} else if (argument.startsWith(QByteArray("-executable"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1)
LogError() << "Missing executable path";
else
additionalExecutables << argument.mid(index+1);
} else if (argument.startsWith(QByteArray("-qmldir"))) {
LogDebug() << "Argument found:" << argument;
qmldirArgumentUsed = true;
int index = argument.indexOf('=');
if (index == -1)
LogError() << "Missing qml directory path";
else
qmlDirs << argument.mid(index+1);
} else if (argument.startsWith(QByteArray("-qmlimport"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1)
LogError() << "Missing qml import path";
else
qmlImportPaths << argument.mid(index+1);
} else if (argument.startsWith(QByteArray("-libpath"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1)
LogError() << "Missing library search path";
else
librarySearchPath << argument.mid(index+1);
} else if (argument == QByteArray("-always-overwrite")) {
LogDebug() << "Argument found:" << argument;
alwaysOwerwriteEnabled = true;
} else if (argument.startsWith(QByteArray("-codesign"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf("=");
if (index < 0 || index >= argument.size()) {
LogError() << "Missing code signing identity";
} else {
runCodesign = true;
codesignIdentiy = argument.mid(index+1);
}
} else if (argument.startsWith(QByteArray("-sign-for-notarization"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf("=");
if (index < 0 || index >= argument.size()) {
LogError() << "Missing code signing identity";
} else {
runCodesign = true;
hardenedRuntime = true;
secureTimestamp = true;
codesignIdentiy = argument.mid(index+1);
}
} else if (argument.startsWith(QByteArray("-hardened-runtime"))) {
LogDebug() << "Argument found:" << argument;
hardenedRuntime = true;
} else if (argument.startsWith(QByteArray("-timestamp"))) {
LogDebug() << "Argument found:" << argument;
secureTimestamp = true;
} else if (argument == QByteArray("-appstore-compliant")) {
LogDebug() << "Argument found:" << argument;
appstoreCompliant = true;
// Undocumented option, may not work as intended
} else if (argument == QByteArray("-deploy-framework")) {
LogDebug() << "Argument found:" << argument;
deployFramework = true;
} else if (argument.startsWith(QByteArray("-fs"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1)
LogError() << "Missing filesystem type";
else
filesystem = argument.mid(index+1);
} else if (argument.startsWith("-")) {
LogError() << "Unknown argument" << argument << "\n";
return 1;
}
}
DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
if (deploymentInfo.isDebug)
useDebugLibs = true;
if (deployFramework && deploymentInfo.isFramework)
fixupFramework(appBundlePath);
// Convenience: Look for .qml files in the current directory if no -qmldir specified.
if (qmlDirs.isEmpty()) {
QDir dir;
if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
qmlDirs += QStringLiteral(".");
}
}
if (!qmlDirs.isEmpty()) {
bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths);
if (!ok && qmldirArgumentUsed)
return 1; // exit if the user explicitly asked for qml import deployment
// Update deploymentInfo.deployedFrameworks - the QML imports
// may have brought in extra frameworks as dependencies.
deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath);
deploymentInfo.deployedFrameworks =
QSet<QString>(deploymentInfo.deployedFrameworks.begin(),
deploymentInfo.deployedFrameworks.end()).values();
}
// Handle plugins
if (plugins) {
// Set the plugins search directory
deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath);
// Sanity checks
if (deploymentInfo.pluginPath.isEmpty()) {
LogError() << "Missing Qt plugins path\n";
return 1;
}
if (!QDir(deploymentInfo.pluginPath).exists()) {
LogError() << "Plugins path does not exist" << deploymentInfo.pluginPath << "\n";
return 1;
}
// Deploy plugins
Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
if (!deploymentInfo.pluginPath.isEmpty()) {
LogNormal();
deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
createQtConf(appBundlePath);
}
}
if (runStripEnabled)
stripAppBinary(appBundlePath);
if (runCodesign)
codesign(codesignIdentiy, appBundlePath);
if (dmg) {
LogNormal();
createDiskImage(appBundlePath, filesystem);
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,141 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MAC_DEPLOMYMENT_SHARED_H
#define MAC_DEPLOMYMENT_SHARED_H
#include <QString>
#include <QStringList>
#include <QDebug>
#include <QSet>
#include <QVersionNumber>
extern int logLevel;
#define LogError() if (logLevel < 0) {} else qDebug() << "ERROR:"
#define LogWarning() if (logLevel < 1) {} else qDebug() << "WARNING:"
#define LogNormal() if (logLevel < 2) {} else qDebug() << "Log:"
#define LogDebug() if (logLevel < 3) {} else qDebug() << "Log:"
extern bool runStripEnabled;
class FrameworkInfo
{
public:
bool isDylib;
QString frameworkDirectory;
QString frameworkName;
QString frameworkPath;
QString binaryDirectory;
QString binaryName;
QString binaryPath;
QString rpathUsed;
QString version;
QString installName;
QString deployedInstallName;
QString sourceFilePath;
QString frameworkDestinationDirectory;
QString binaryDestinationDirectory;
bool isDebugLibrary() const
{
return binaryName.endsWith(QStringLiteral("_debug"));
}
};
class DylibInfo
{
public:
QString binaryPath;
QVersionNumber currentVersion;
QVersionNumber compatibilityVersion;
};
class OtoolInfo
{
public:
QString installName;
QString binaryPath;
QVersionNumber currentVersion;
QVersionNumber compatibilityVersion;
QList<DylibInfo> dependencies;
};
bool operator==(const FrameworkInfo &a, const FrameworkInfo &b);
QDebug operator<<(QDebug debug, const FrameworkInfo &info);
class ApplicationBundleInfo
{
public:
QString path;
QString binaryPath;
QStringList libraryPaths;
};
class DeploymentInfo
{
public:
QString qtPath;
QString pluginPath;
QStringList deployedFrameworks;
QList<QString> rpathsUsed;
bool useLoaderPath;
bool isFramework;
bool isDebug;
bool containsModule(const QString &module, const QString &libInFix) const;
};
inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info);
OtoolInfo findDependencyInfo(const QString &binaryPath);
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
QString findAppBinary(const QString &appBundlePath);
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
QString copyFramework(const FrameworkInfo &framework, const QString path);
DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs);
DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath);
void createQtConf(const QString &appBundlePath);
void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs);
bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths);
void changeIdentification(const QString &id, const QString &binaryPath);
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath);
void runStrip(const QString &binaryPath);
void stripAppBinary(const QString &bundlePath);
QString findAppBinary(const QString &appBundlePath);
QStringList findAppFrameworkNames(const QString &appBundlePath);
QStringList findAppFrameworkPaths(const QString &appBundlePath);
void codesignFile(const QString &identity, const QString &filePath);
QSet<QString> codesignBundle(const QString &identity,
const QString &appBundlePath,
QList<QString> additionalBinariesContainingRpaths);
void codesign(const QString &identity, const QString &appBundlePath);
void createDiskImage(const QString &appBundlePath, const QString &filesystemType);
void fixupFramework(const QString &appBundlePath);
#endif

View File

@ -0,0 +1,32 @@
#####################################################################
## windeployqt Tool:
#####################################################################
qt_get_tool_target_name(target_name windeployqt)
qt_internal_add_tool(${target_name}
TOOLS_TARGET Core
USER_FACING
TARGET_DESCRIPTION "Qt Windows Deployment Tool"
SOURCES
elfreader.cpp elfreader.h
qmlutils.cpp qmlutils.h
utils.cpp utils.h
main.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_FOREACH
LIBRARIES
Qt::CorePrivate
)
qt_internal_return_unless_building_tools()
qt_internal_extend_target(${target_name} CONDITION WIN32
PUBLIC_LIBRARIES
shlwapi
)
qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_relocatable
DEFINES
QT_RELOCATABLE
)

View File

@ -0,0 +1,440 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "elfreader.h"
#include <QDir>
QT_BEGIN_NAMESPACE
/* This is a copy of the ELF reader contained in Qt Creator (src/libs/utils),
* extended by the dependencies() function to read out the dependencies of a dynamic executable. */
quint16 getHalfWord(const unsigned char *&s, const ElfData &context)
{
quint16 res;
if (context.endian == Elf_ELFDATA2MSB)
res = qFromBigEndian<quint16>(s);
else
res = qFromLittleEndian<quint16>(s);
s += 2;
return res;
}
quint32 getWord(const unsigned char *&s, const ElfData &context)
{
quint32 res;
if (context.endian == Elf_ELFDATA2MSB)
res = qFromBigEndian<quint32>(s);
else
res = qFromLittleEndian<quint32>(s);
s += 4;
return res;
}
quint64 getAddress(const unsigned char *&s, const ElfData &context)
{
quint64 res;
if (context.elfclass == Elf_ELFCLASS32) {
if (context.endian == Elf_ELFDATA2MSB)
res = qFromBigEndian<quint32>(s);
else
res = qFromLittleEndian<quint32>(s);
s += 4;
} else {
if (context.endian == Elf_ELFDATA2MSB)
res = qFromBigEndian<quint64>(s);
else
res = qFromLittleEndian<quint64>(s);
s += 8;
}
return res;
}
quint64 getOffset(const unsigned char *&s, const ElfData &context)
{
return getAddress(s, context);
}
static void parseSectionHeader(const uchar *s, ElfSectionHeader *sh, const ElfData &context)
{
sh->index = getWord(s, context);
sh->type = getWord(s, context);
sh->flags = quint32(getOffset(s, context));
sh->addr = getAddress(s, context);
sh->offset = getOffset(s, context);
sh->size = getOffset(s, context);
}
static void parseProgramHeader(const uchar *s, ElfProgramHeader *sh, const ElfData &context)
{
sh->type = getWord(s, context);
sh->offset = getOffset(s, context);
/* p_vaddr = */ getAddress(s, context);
/* p_paddr = */ getAddress(s, context);
sh->filesz = getWord(s, context);
sh->memsz = getWord(s, context);
}
class ElfMapper
{
public:
ElfMapper(const ElfReader *reader) : file(reader->m_binary) {}
bool map()
{
if (!file.open(QIODevice::ReadOnly))
return false;
fdlen = quint64(file.size());
ustart = file.map(0, qint64(fdlen));
if (ustart == 0) {
// Try reading the data into memory instead.
raw = file.readAll();
start = raw.constData();
fdlen = quint64(raw.size());
}
return true;
}
public:
QFile file;
QByteArray raw;
union { const char *start; const uchar *ustart; };
quint64 fdlen;
};
ElfReader::ElfReader(const QString &binary)
: m_binary(binary)
{
}
ElfData ElfReader::readHeaders()
{
readIt();
return m_elfData;
}
static inline QString msgInvalidElfObject(const QString &binary, const QString &why)
{
return QStringLiteral("'%1' is an invalid ELF object (%2)")
.arg(QDir::toNativeSeparators(binary), why);
}
ElfReader::Result ElfReader::readIt()
{
if (!m_elfData.sectionHeaders.isEmpty())
return Ok;
if (!m_elfData.programHeaders.isEmpty())
return Ok;
ElfMapper mapper(this);
if (!mapper.map())
return Corrupt;
const quint64 fdlen = mapper.fdlen;
if (fdlen < 64) {
m_errorString = QStringLiteral("'%1' is not an ELF object (file too small)").arg(QDir::toNativeSeparators(m_binary));
return NotElf;
}
if (strncmp(mapper.start, "\177ELF", 4) != 0) {
m_errorString = QStringLiteral("'%1' is not an ELF object").arg(QDir::toNativeSeparators(m_binary));
return NotElf;
}
// 32 or 64 bit
m_elfData.elfclass = ElfClass(mapper.start[4]);
const bool is64Bit = m_elfData.elfclass == Elf_ELFCLASS64;
if (m_elfData.elfclass != Elf_ELFCLASS32 && m_elfData.elfclass != Elf_ELFCLASS64) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd cpu architecture"));
return Corrupt;
}
// int bits = (data[4] << 5);
// If you remove this check to read ELF objects of a different arch,
// please make sure you modify the typedefs
// to match the _plugin_ architecture.
// if ((sizeof(void*) == 4 && bits != 32)
// || (sizeof(void*) == 8 && bits != 64)) {
// if (errorString)
// *errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)")
// .arg(m_binary).arg(QLatin1String("wrong cpu architecture"));
// return Corrupt;
// }
// Read Endianhness.
m_elfData.endian = ElfEndian(mapper.ustart[5]);
if (m_elfData.endian != Elf_ELFDATA2LSB && m_elfData.endian != Elf_ELFDATA2MSB) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd endianness"));
return Corrupt;
}
const uchar *data = mapper.ustart + 16; // e_ident
m_elfData.elftype = ElfType(getHalfWord(data, m_elfData));
m_elfData.elfmachine = ElfMachine(getHalfWord(data, m_elfData));
/* e_version = */ getWord(data, m_elfData);
m_elfData.entryPoint = getAddress(data, m_elfData);
quint64 e_phoff = getOffset(data, m_elfData);
quint64 e_shoff = getOffset(data, m_elfData);
/* e_flags = */ getWord(data, m_elfData);
quint32 e_shsize = getHalfWord(data, m_elfData);
if (e_shsize > fdlen) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shsize"));
return Corrupt;
}
quint32 e_phentsize = getHalfWord(data, m_elfData);
if (e_phentsize != (is64Bit ? 56 : 32)) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("invalid structure"));
return ElfReader::Corrupt;
}
quint32 e_phnum = getHalfWord(data, m_elfData);
quint32 e_shentsize = getHalfWord(data, m_elfData);
if (e_shentsize % 4) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shentsize"));
return Corrupt;
}
quint32 e_shnum = getHalfWord(data, m_elfData);
quint32 e_shtrndx = getHalfWord(data, m_elfData);
if (data != mapper.ustart + (is64Bit ? 64 : 52)) {
m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_phentsize"));
return ElfReader::Corrupt;
}
if (quint64(e_shnum) * e_shentsize > fdlen) {
const QString reason = QStringLiteral("announced %1 sections, each %2 bytes, exceed file size").arg(e_shnum).arg(e_shentsize);
m_errorString = msgInvalidElfObject(m_binary, reason);
return Corrupt;
}
quint64 soff = e_shoff + e_shentsize * e_shtrndx;
// if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) {
// m_errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)")
// .arg(m_binary)
// .arg(QLatin1String("shstrtab section header seems to be at %1"))
// .arg(QString::number(soff, 16));
// return Corrupt;
// }
if (e_shoff) {
ElfSectionHeader strtab;
parseSectionHeader(mapper.ustart + soff, &strtab, m_elfData);
const quint64 stringTableFileOffset = strtab.offset;
if (quint32(stringTableFileOffset + e_shentsize) >= fdlen
|| stringTableFileOffset == 0) {
const QString reason = QStringLiteral("string table seems to be at 0x%1").arg(soff, 0, 16);
m_errorString = msgInvalidElfObject(m_binary, reason);
return Corrupt;
}
for (quint32 i = 0; i < e_shnum; ++i) {
const uchar *s = mapper.ustart + e_shoff + i * e_shentsize;
ElfSectionHeader sh;
parseSectionHeader(s, &sh, m_elfData);
if (stringTableFileOffset + sh.index > fdlen) {
const QString reason = QStringLiteral("section name %1 of %2 behind end of file")
.arg(i).arg(e_shnum);
m_errorString = msgInvalidElfObject(m_binary, reason);
return Corrupt;
}
sh.name = mapper.start + stringTableFileOffset + sh.index;
if (sh.name == ".gdb_index") {
m_elfData.symbolsType = FastSymbols;
} else if (sh.name == ".debug_info") {
m_elfData.symbolsType = PlainSymbols;
} else if (sh.name == ".gnu_debuglink") {
m_elfData.debugLink = QByteArray(mapper.start + sh.offset);
m_elfData.symbolsType = LinkedSymbols;
} else if (sh.name == ".note.gnu.build-id") {
m_elfData.symbolsType = BuildIdSymbols;
if (sh.size > 16)
m_elfData.buildId = QByteArray(mapper.start + sh.offset + 16,
int(sh.size) - 16).toHex();
}
m_elfData.sectionHeaders.append(sh);
}
}
if (e_phoff) {
for (quint32 i = 0; i < e_phnum; ++i) {
const uchar *s = mapper.ustart + e_phoff + i * e_phentsize;
ElfProgramHeader ph;
parseProgramHeader(s, &ph, m_elfData);
m_elfData.programHeaders.append(ph);
}
}
return Ok;
}
QByteArray ElfReader::readSection(const QByteArray &name)
{
readIt();
int i = m_elfData.indexOf(name);
if (i == -1)
return QByteArray();
ElfMapper mapper(this);
if (!mapper.map())
return QByteArray();
const ElfSectionHeader &section = m_elfData.sectionHeaders.at(i);
return QByteArray(mapper.start + section.offset, int(section.size));
}
static QByteArray cutout(const char *s)
{
QByteArray res(s, 80);
const int pos = res.indexOf('\0');
if (pos != -1)
res.resize(pos - 1);
return res;
}
QByteArray ElfReader::readCoreName(bool *isCore)
{
*isCore = false;
readIt();
ElfMapper mapper(this);
if (!mapper.map())
return QByteArray();
if (m_elfData.elftype != Elf_ET_CORE)
return QByteArray();
*isCore = true;
for (int i = 0, n = m_elfData.sectionHeaders.size(); i != n; ++i)
if (m_elfData.sectionHeaders.at(i).type == Elf_SHT_NOTE) {
const ElfSectionHeader &header = m_elfData.sectionHeaders.at(i);
return cutout(mapper.start + header.offset + 0x40);
}
for (int i = 0, n = m_elfData.programHeaders.size(); i != n; ++i)
if (m_elfData.programHeaders.at(i).type == Elf_PT_NOTE) {
const ElfProgramHeader &header = m_elfData.programHeaders.at(i);
return cutout(mapper.start + header.offset + 0xec);
}
return QByteArray();
}
int ElfData::indexOf(const QByteArray &name) const
{
for (int i = 0, n = sectionHeaders.size(); i != n; ++i)
if (sectionHeaders.at(i).name == name)
return i;
return -1;
}
/* Helpers for reading out the .dynamic section containing the dependencies.
* The ".dynamic" section is an array of
* typedef struct {
* Elf32_Sword d_tag;
* union {
* Elf32_Word d_val;
* dElf32_Addr d_ptr;
* } d_un;
* } Elf32_Dyn
* with entries where a tag DT_NEEDED indicates that m_val is an offset into
* the string table ".dynstr". The documentation states that entries with the
* tag DT_STRTAB contain an offset for the string table to be used, but that
* has been found not to contain valid entries. */
enum DynamicSectionTags {
DT_NULL = 0,
DT_NEEDED = 1,
DT_STRTAB = 5,
DT_SONAME = 14,
DT_RPATH = 15
};
QList<QByteArray> ElfReader::dependencies()
{
QList<QByteArray> result;
ElfMapper mapper(this);
if (!mapper.map()) {
m_errorString = QStringLiteral("Mapper failure");
return result;
}
quint64 dynStrOffset = 0;
quint64 dynamicOffset = 0;
quint64 dynamicSize = 0;
const QList<ElfSectionHeader> &headers = readHeaders().sectionHeaders;
for (const ElfSectionHeader &eh : headers) {
if (eh.name == QByteArrayLiteral(".dynstr")) {
dynStrOffset = eh.offset;
} else if (eh.name == QByteArrayLiteral(".dynamic")) {
dynamicOffset = eh.offset;
dynamicSize = eh.size;
}
if (dynStrOffset && dynamicOffset)
break;
}
if (!dynStrOffset || !dynamicOffset) {
m_errorString = QStringLiteral("Not a dynamically linked executable.");
return result;
}
const unsigned char *dynamicData = mapper.ustart + dynamicOffset;
const unsigned char *dynamicDataEnd = dynamicData + dynamicSize;
while (dynamicData < dynamicDataEnd) {
const quint32 tag = getWord(dynamicData, m_elfData);
if (tag == DT_NULL)
break;
if (m_elfData.elfclass == Elf_ELFCLASS64)
dynamicData += sizeof(quint32); // padding to d_val/d_ptr.
if (tag == DT_NEEDED) {
const quint32 offset = getWord(dynamicData, m_elfData);
if (m_elfData.elfclass == Elf_ELFCLASS64)
dynamicData += sizeof(quint32); // past d_ptr.
const char *name = mapper.start + dynStrOffset + offset;
result.push_back(name);
} else {
dynamicData += m_elfData.elfclass == Elf_ELFCLASS64 ? 8 : 4;
}
}
return result;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,176 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef ELFREADER_H
#define ELFREADER_H
#include <QtCore/QList>
#include <QtCore/QString>
#include <QtCore/QtEndian>
QT_BEGIN_NAMESPACE
enum ElfProgramHeaderType
{
Elf_PT_NULL = 0,
Elf_PT_LOAD = 1,
Elf_PT_DYNAMIC = 2,
Elf_PT_INTERP = 3,
Elf_PT_NOTE = 4,
Elf_PT_SHLIB = 5,
Elf_PT_PHDR = 6,
Elf_PT_TLS = 7,
Elf_PT_NUM = 8
};
enum ElfSectionHeaderType
{
Elf_SHT_NULL = 0,
Elf_SHT_PROGBITS = 1,
Elf_SHT_SYMTAB = 2,
Elf_SHT_STRTAB = 3,
Elf_SHT_RELA = 4,
Elf_SHT_HASH = 5,
Elf_SHT_DYNAMIC = 6,
Elf_SHT_NOTE = 7,
Elf_SHT_NOBITS = 8,
Elf_SHT_REL = 9,
Elf_SHT_SHLIB = 10,
Elf_SHT_DYNSYM = 11,
Elf_SHT_INIT_ARRAY = 14,
Elf_SHT_FINI_ARRAY = 15,
Elf_SHT_PREINIT_ARRAY = 16,
Elf_SHT_GROUP = 17,
Elf_SHT_SYMTAB_SHNDX = 18
};
enum ElfEndian
{
Elf_ELFDATANONE = 0,
Elf_ELFDATA2LSB = 1,
Elf_ELFDATA2MSB = 2,
Elf_ELFDATANUM = 3
};
enum ElfClass
{
Elf_ELFCLASS32 = 1,
Elf_ELFCLASS64 = 2
};
enum ElfType
{
Elf_ET_NONE = 0,
Elf_ET_REL = 1,
Elf_ET_EXEC = 2,
Elf_ET_DYN = 3,
Elf_ET_CORE = 4
};
enum ElfMachine
{
Elf_EM_386 = 3,
Elf_EM_ARM = 40,
Elf_EM_X86_64 = 62
};
enum DebugSymbolsType
{
UnknownSymbols = 0, // Unknown.
NoSymbols = 1, // No usable symbols.
LinkedSymbols = 2, // Link to symols available.
BuildIdSymbols = 4, // BuildId available.
PlainSymbols = 8, // Ordinary symbols available.
FastSymbols = 16 // Dwarf index available.
};
class ElfSectionHeader
{
public:
QByteArray name;
quint32 index;
quint32 type;
quint32 flags;
quint64 offset;
quint64 size;
quint64 addr;
};
class ElfProgramHeader
{
public:
quint32 name;
quint32 type;
quint64 offset;
quint64 filesz;
quint64 memsz;
};
class ElfData
{
public:
ElfData() : symbolsType(UnknownSymbols) {}
int indexOf(const QByteArray &name) const;
public:
ElfEndian endian;
ElfType elftype;
ElfMachine elfmachine;
ElfClass elfclass;
quint64 entryPoint;
QByteArray debugLink;
QByteArray buildId;
DebugSymbolsType symbolsType;
QList<ElfSectionHeader> sectionHeaders;
QList<ElfProgramHeader> programHeaders;
};
class ElfReader
{
public:
explicit ElfReader(const QString &binary);
enum Result { Ok, NotElf, Corrupt };
ElfData readHeaders();
QByteArray readSection(const QByteArray &sectionName);
QString errorString() const { return m_errorString; }
QByteArray readCoreName(bool *isCore);
QList<QByteArray> dependencies();
private:
friend class ElfMapper;
Result readIt();
QString m_binary;
QString m_errorString;
ElfData m_elfData;
};
QT_END_NAMESPACE
#endif // ELFREADER_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmlutils.h"
#include "utils.h"
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QCoreApplication>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonParseError>
QT_BEGIN_NAMESPACE
bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2)
{
return m1.className.isEmpty() ? m1.name == m2.name : m1.className == m2.className;
}
// Return install path (cp -r semantics)
QString QmlImportScanResult::Module::installPath(const QString &root) const
{
QString result = root;
const int lastSlashPos = relativePath.lastIndexOf(QLatin1Char('/'));
if (lastSlashPos != -1) {
result += QLatin1Char('/');
result += QStringView{relativePath}.left(lastSlashPos);
}
return result;
}
static QString qmlDirectoryRecursion(Platform platform, const QString &path)
{
QDir dir(path);
if (!dir.entryList(QStringList(QStringLiteral("*.qml")), QDir::Files, QDir::NoSort).isEmpty())
return dir.path();
const QFileInfoList &subDirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
for (const QFileInfo &subDirFi : subDirs) {
if (!isBuildDirectory(platform, subDirFi.fileName())) {
const QString subPath = qmlDirectoryRecursion(platform, subDirFi.absoluteFilePath());
if (!subPath.isEmpty())
return subPath;
}
}
return QString();
}
// Find a directory containing QML files in the project
QString findQmlDirectory(Platform platform, const QString &startDirectoryName)
{
QDir startDirectory(startDirectoryName);
if (isBuildDirectory(platform, startDirectory.dirName()))
startDirectory.cdUp();
return qmlDirectoryRecursion(platform, startDirectory.path());
}
static void findFileRecursion(const QDir &directory, Platform platform,
DebugMatchMode debugMatchMode, QStringList *matches)
{
const QStringList &dlls = findSharedLibraries(directory, platform, debugMatchMode);
for (const QString &dll : dlls)
matches->append(directory.filePath(dll));
const QFileInfoList &subDirs = directory.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for (const QFileInfo &subDirFi : subDirs) {
QDir subDirectory(subDirFi.absoluteFilePath());
if (subDirectory.isReadable())
findFileRecursion(subDirectory, platform, debugMatchMode, matches);
}
}
QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths,
bool usesWidgets, int platform, DebugMatchMode debugMatchMode,
QString *errorMessage)
{
Q_UNUSED(usesWidgets);
QmlImportScanResult result;
QStringList arguments;
for (const QString &importPath : qmlImportPaths)
arguments << QStringLiteral("-importPath") << importPath;
arguments << QStringLiteral("-rootPath") << directory;
unsigned long exitCode;
QByteArray stdOut;
QByteArray stdErr;
const QString binary = QStringLiteral("qmlimportscanner");
if (!runProcess(binary, arguments, QDir::currentPath(), &exitCode, &stdOut, &stdErr, errorMessage))
return result;
if (exitCode) {
*errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode)
+ QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
return result;
}
QJsonParseError jsonParseError{};
const QJsonDocument data = QJsonDocument::fromJson(stdOut, &jsonParseError);
if (data.isNull() ) {
*errorMessage = binary + QStringLiteral(" returned invalid JSON output: ")
+ jsonParseError.errorString() + QStringLiteral(" :\"")
+ QString::fromLocal8Bit(stdOut) + QLatin1Char('"');
return result;
}
const QJsonArray array = data.array();
const int childCount = array.count();
for (int c = 0; c < childCount; ++c) {
const QJsonObject object = array.at(c).toObject();
if (object.value(QStringLiteral("type")).toString() == QLatin1String("module")) {
const QString path = object.value(QStringLiteral("path")).toString();
if (!path.isEmpty()) {
QmlImportScanResult::Module module;
module.name = object.value(QStringLiteral("name")).toString();
module.className = object.value(QStringLiteral("classname")).toString();
module.sourcePath = path;
module.relativePath = object.value(QStringLiteral("relativePath")).toString();
result.modules.append(module);
findFileRecursion(QDir(path), Platform(platform), debugMatchMode, &result.plugins);
}
}
}
result.ok = true;
return result;
}
void QmlImportScanResult::append(const QmlImportScanResult &other)
{
for (const QmlImportScanResult::Module &module : other.modules) {
if (std::find(modules.cbegin(), modules.cend(), module) == modules.cend())
modules.append(module);
}
for (const QString &plugin : other.plugins) {
if (!plugins.contains(plugin))
plugins.append(plugin);
}
}
QT_END_NAMESPACE

View File

@ -0,0 +1,65 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMLUTILS_H
#define QMLUTILS_H
#include "utils.h"
#include <QStringList>
QT_BEGIN_NAMESPACE
QString findQmlDirectory(Platform platform, const QString &startDirectoryName);
struct QmlImportScanResult {
struct Module {
QString installPath(const QString &root) const;
QString name;
QString className;
QString sourcePath;
QString relativePath;
};
void append(const QmlImportScanResult &other);
bool ok = false;
QList<Module> modules;
QStringList plugins;
};
bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2);
QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths,
bool usesWidgets, int platform, DebugMatchMode debugMatchMode,
QString *errorMessage);
QT_END_NAMESPACE
#endif // QMLUTILS_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,404 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef UTILS_H
#define UTILS_H
#include <QStringList>
#include <QMap>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QDateTime>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <iostream>
QT_BEGIN_NAMESPACE
enum PlatformFlag {
// OS
WindowsBased = 0x00001,
UnixBased = 0x00002,
// CPU
IntelBased = 0x00010,
ArmBased = 0x00020,
// Compiler
Msvc = 0x00100,
MinGW = 0x00200,
ClangMsvc = 0x00400,
ClangMinGW = 0x00800,
// Platforms
WindowsDesktopMsvc = WindowsBased + IntelBased + Msvc,
WindowsDesktopMinGW = WindowsBased + IntelBased + MinGW,
WindowsDesktopClangMsvc = WindowsBased + IntelBased + ClangMsvc,
WindowsDesktopClangMinGW = WindowsBased + IntelBased + ClangMinGW,
Unix = UnixBased,
UnknownPlatform
};
Q_DECLARE_FLAGS(Platform, PlatformFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(Platform)
inline bool platformHasDebugSuffix(Platform p) // Uses 'd' debug suffix
{
return p.testFlag(Msvc) || p.testFlag(ClangMsvc);
}
enum ListOption {
ListNone = 0,
ListSource,
ListTarget,
ListRelative,
ListMapping
};
inline std::wostream &operator<<(std::wostream &str, const QString &s)
{
#ifdef Q_OS_WIN
str << reinterpret_cast<const wchar_t *>(s.utf16());
#else
str << s.toStdWString();
#endif
return str;
}
// Container class for JSON output
class JsonOutput
{
using SourceTargetMapping = QPair<QString, QString>;
using SourceTargetMappings = QList<SourceTargetMapping>;
public:
void addFile(const QString &source, const QString &target)
{
m_files.append(SourceTargetMapping(source, target));
}
void removeTargetDirectory(const QString &targetDirectory)
{
for (int i = m_files.size() - 1; i >= 0; --i) {
if (m_files.at(i).second == targetDirectory)
m_files.removeAt(i);
}
}
QByteArray toJson() const
{
QJsonObject document;
QJsonArray files;
for (const SourceTargetMapping &mapping : m_files) {
QJsonObject object;
object.insert(QStringLiteral("source"), QDir::toNativeSeparators(mapping.first));
object.insert(QStringLiteral("target"), QDir::toNativeSeparators(mapping.second));
files.append(object);
}
document.insert(QStringLiteral("files"), files);
return QJsonDocument(document).toJson();
}
QByteArray toList(ListOption option, const QDir &base) const
{
QByteArray list;
for (const SourceTargetMapping &mapping : m_files) {
const QString source = QDir::toNativeSeparators(mapping.first);
const QString fileName = QFileInfo(mapping.first).fileName();
const QString target = QDir::toNativeSeparators(mapping.second) + QDir::separator() + fileName;
switch (option) {
case ListNone:
break;
case ListSource:
list += source.toUtf8() + '\n';
break;
case ListTarget:
list += target.toUtf8() + '\n';
break;
case ListRelative:
list += QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + '\n';
break;
case ListMapping:
list += '"' + source.toUtf8() + "\" \"" + QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + "\"\n";
break;
}
}
return list;
}
private:
SourceTargetMappings m_files;
};
#ifdef Q_OS_WIN
QString normalizeFileName(const QString &name);
QString winErrorMessage(unsigned long error);
QString findSdkTool(const QString &tool);
#else // !Q_OS_WIN
inline QString normalizeFileName(const QString &name) { return name; }
#endif // !Q_OS_WIN
static const char windowsSharedLibrarySuffix[] = ".dll";
static const char unixSharedLibrarySuffix[] = ".so";
inline QString sharedLibrarySuffix(Platform platform) { return QLatin1String((platform & WindowsBased) ? windowsSharedLibrarySuffix : unixSharedLibrarySuffix); }
bool isBuildDirectory(Platform platform, const QString &dirName);
bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage);
bool createDirectory(const QString &directory, QString *errorMessage);
QString findInPath(const QString &file);
extern const char *qmakeInfixKey; // Fake key containing the libinfix
QMap<QString, QString> queryQtPaths(const QString &qmakeBinary, QString *errorMessage);
enum DebugMatchMode {
MatchDebug,
MatchRelease,
MatchDebugOrRelease
};
QStringList findSharedLibraries(const QDir &directory, Platform platform,
DebugMatchMode debugMatchMode,
const QString &prefix = QString());
bool updateFile(const QString &sourceFileName, const QStringList &nameFilters,
const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage);
bool runProcess(const QString &binary, const QStringList &args,
const QString &workingDirectory = QString(),
unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0,
QString *errorMessage = 0);
bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage,
QStringList *dependentLibraries = 0, unsigned *wordSize = 0,
bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr);
bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage,
QStringList *dependentLibraries = 0, unsigned *wordSize = 0,
bool *isDebug = 0);
inline bool readExecutable(const QString &executableFileName, Platform platform,
QString *errorMessage, QStringList *dependentLibraries = 0,
unsigned *wordSize = 0, bool *isDebug = 0, unsigned short *machineArch = nullptr)
{
return platform == Unix ?
readElfExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug) :
readPeExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug,
(platform == WindowsDesktopMinGW), machineArch);
}
#ifdef Q_OS_WIN
# if !defined(IMAGE_FILE_MACHINE_ARM64)
# define IMAGE_FILE_MACHINE_ARM64 0xAA64
# endif
QString getArchString (unsigned short machineArch);
#endif // Q_OS_WIN
// Return dependent modules of executable files.
inline QStringList findDependentLibraries(const QString &executableFileName, Platform platform, QString *errorMessage)
{
QStringList result;
readExecutable(executableFileName, platform, errorMessage, &result);
return result;
}
QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize);
bool patchQtCore(const QString &path, QString *errorMessage);
extern int optVerboseLevel;
// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir
// to obtain the files.
enum UpdateFileFlag {
ForceUpdateFile = 0x1,
SkipUpdateFile = 0x2,
RemoveEmptyQmlDirectories = 0x4,
SkipQmlDesignerSpecificsDirectories = 0x8
};
template <class DirectoryFileEntryFunction>
bool updateFile(const QString &sourceFileName,
DirectoryFileEntryFunction directoryFileEntryFunction,
const QString &targetDirectory,
unsigned flags,
JsonOutput *json,
QString *errorMessage)
{
const QFileInfo sourceFileInfo(sourceFileName);
const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName();
if (optVerboseLevel > 1)
std::wcout << "Checking " << sourceFileName << ", " << targetFileName << '\n';
if (!sourceFileInfo.exists()) {
*errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName));
return false;
}
const QFileInfo targetFileInfo(targetFileName);
if (sourceFileInfo.isSymLink()) {
const QString sourcePath = sourceFileInfo.symLinkTarget();
const QString relativeSource = QDir(sourceFileInfo.absolutePath()).relativeFilePath(sourcePath);
if (relativeSource.contains(QLatin1Char('/'))) {
*errorMessage = QString::fromLatin1("Symbolic links across directories are not supported (%1).")
.arg(QDir::toNativeSeparators(sourceFileName));
return false;
}
// Update the linked-to file
if (!updateFile(sourcePath, directoryFileEntryFunction, targetDirectory, flags, json, errorMessage))
return false;
if (targetFileInfo.exists()) {
if (!targetFileInfo.isSymLink()) {
*errorMessage = QString::fromLatin1("%1 already exists and is not a symbolic link.")
.arg(QDir::toNativeSeparators(targetFileName));
return false;
} // Not a symlink
const QString relativeTarget = QDir(targetFileInfo.absolutePath()).relativeFilePath(targetFileInfo.symLinkTarget());
if (relativeSource == relativeTarget) // Exists and points to same entry: happy.
return true;
QFile existingTargetFile(targetFileName);
if (!(flags & SkipUpdateFile) && !existingTargetFile.remove()) {
*errorMessage = QString::fromLatin1("Cannot remove existing symbolic link %1: %2")
.arg(QDir::toNativeSeparators(targetFileName), existingTargetFile.errorString());
return false;
}
} // target symbolic link exists
return createSymbolicLink(QFileInfo(targetDirectory + QLatin1Char('/') + relativeSource), sourceFileInfo.fileName(), errorMessage);
} // Source is symbolic link
if (sourceFileInfo.isDir()) {
if ((flags & SkipQmlDesignerSpecificsDirectories) && sourceFileInfo.fileName() == QLatin1String("designer")) {
if (optVerboseLevel)
std::wcout << "Skipping " << QDir::toNativeSeparators(sourceFileName) << ".\n";
return true;
}
bool created = false;
if (targetFileInfo.exists()) {
if (!targetFileInfo.isDir()) {
*errorMessage = QString::fromLatin1("%1 already exists and is not a directory.")
.arg(QDir::toNativeSeparators(targetFileName));
return false;
} // Not a directory.
} else { // exists.
QDir d(targetDirectory);
if (optVerboseLevel)
std::wcout << "Creating " << targetFileName << ".\n";
if (!(flags & SkipUpdateFile)) {
created = d.mkdir(sourceFileInfo.fileName());
if (!created) {
*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);
for (const QString &entry : allEntries)
if (!updateFile(sourceFileName + QLatin1Char('/') + entry, directoryFileEntryFunction, targetFileName, flags, json, errorMessage))
return false;
// Remove empty directories, for example QML import folders for which the filter did not match.
if (created && (flags & RemoveEmptyQmlDirectories)) {
QDir d(targetFileName);
const QStringList entries = d.entryList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
if (entries.isEmpty() || (entries.size() == 1 && entries.first() == QLatin1String("qmldir"))) {
if (!d.removeRecursively()) {
*errorMessage = QString::fromLatin1("Cannot remove empty directory %1.")
.arg(QDir::toNativeSeparators(targetFileName));
return false;
}
if (json)
json->removeTargetDirectory(targetFileName);
}
}
return true;
} // Source is directory.
if (targetFileInfo.exists()) {
if (!(flags & ForceUpdateFile)
&& targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) {
if (optVerboseLevel)
std::wcout << sourceFileInfo.fileName() << " is up to date.\n";
if (json)
json->addFile(sourceFileName, targetDirectory);
return true;
}
QFile targetFile(targetFileName);
if (!(flags & SkipUpdateFile) && !targetFile.remove()) {
*errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2")
.arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString());
return false;
}
} // target exists
QFile file(sourceFileName);
if (optVerboseLevel)
std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n";
if (!(flags & SkipUpdateFile)) {
if (!file.copy(targetFileName)) {
*errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3")
.arg(QDir::toNativeSeparators(sourceFileName),
QDir::toNativeSeparators(targetFileName),
file.errorString());
return false;
}
if (!(file.permissions() & QFile::WriteUser)) { // QTBUG-40152, clear inherited read-only attribute
QFile targetFile(targetFileName);
if (!targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser)) {
*errorMessage = QString::fromLatin1("Cannot set write permission on %1: %2")
.arg(QDir::toNativeSeparators(targetFileName), file.errorString());
return false;
}
} // Check permissions
} // !SkipUpdateFile
if (json)
json->addFile(sourceFileName, targetDirectory);
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, unsigned flags, JsonOutput *json, QString *errorMessage)
{
return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, flags, json, errorMessage);
}
QT_END_NAMESPACE
#endif // UTILS_H

View File

@ -15,3 +15,12 @@ if(TARGET Qt::DBus)
add_subdirectory(qdbuscpp2xml) add_subdirectory(qdbuscpp2xml)
add_subdirectory(qdbusxml2cpp) add_subdirectory(qdbusxml2cpp)
endif() endif()
if(QT_FEATURE_process AND NOT CMAKE_CROSSCOMPILING)
if(QT_FEATURE_macdeployqt)
add_subdirectory(macdeployqt)
endif()
if(QT_FEATURE_windeployqt AND BUILD_SHARED_LIBS)
# windeployqt does not work with static Qt builds. See QTBUG-69427.
add_subdirectory(windeployqt)
endif()
endif()

View File

@ -0,0 +1,10 @@
# Generated from macdeployqt.pro.
#####################################################################
## tst_macdeployqt Test:
#####################################################################
qt_internal_add_test(tst_macdeployqt
SOURCES
tst_macdeployqt.cpp
)

View File

@ -0,0 +1 @@
SOURCES = main.cpp

View File

@ -0,0 +1,44 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QGuiApplication>
#include <QRasterWindow>
#include <QScreen>
#include <QTimer>
// Simple test application just to verify that it comes up properly
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
QRasterWindow w;
w.setTitle("macdeployqt test application");
w.show();
QTimer::singleShot(200, &w, &QCoreApplication::quit);
return app.exec();
}

View File

@ -0,0 +1,36 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtSql>
int main(int argc, char ** argv)
{
QCoreApplication app(argc, argv);
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
return db.isValid() ? 0 : 1;
}

View File

@ -0,0 +1,2 @@
SOURCES = main.cpp
QT += sql

View File

@ -0,0 +1,35 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtNetwork>
int main(int argc, char ** argv)
{
QCoreApplication app(argc, argv);
return QSslSocket::supportsSsl() ? 0 : 1;
}

View File

@ -0,0 +1,2 @@
SOURCES = main.cpp
QT += network

View File

@ -0,0 +1,316 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore>
#include <QtTest>
bool g_testDirectoryBuild = false; // toggle to keep build output for debugging.
QTemporaryDir *g_temporaryDirectory;
QString g_macdeployqtBinary;
QString g_qmakeBinary;
QString g_makeBinary;
QString g_installNameToolBinary;
#if QT_CONFIG(process)
static const QString msgProcessError(const QProcess &process, const QString &what)
{
QString result;
QTextStream(&result) << what << ": \"" << process.program() << ' '
<< process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString();
return result;
}
static bool runProcess(const QString &binary,
const QStringList &arguments,
QString *errorMessage,
const QString &workingDir = QString(),
const QProcessEnvironment &env = QProcessEnvironment(),
int timeOut = 10000,
QByteArray *stdOut = nullptr, QByteArray *stdErr = nullptr)
{
QProcess process;
if (!env.isEmpty())
process.setProcessEnvironment(env);
if (!workingDir.isEmpty())
process.setWorkingDirectory(workingDir);
process.start(binary, arguments, QIODevice::ReadOnly);
if (!process.waitForStarted()) {
*errorMessage = msgProcessError(process, "Failed to start");
return false;
}
if (!process.waitForFinished(timeOut)) {
*errorMessage = msgProcessError(process, "Timed out");
process.terminate();
if (!process.waitForFinished(300))
process.kill();
return false;
}
if (stdOut)
*stdOut = process.readAllStandardOutput();
if (stdErr)
*stdErr= process.readAllStandardError();
if (process.exitStatus() != QProcess::NormalExit) {
*errorMessage = msgProcessError(process, "Crashed");
return false;
}
if (process.exitCode() != QProcess::NormalExit) {
*errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode()));
return false;
}
return true;
}
#else
static bool runProcess(const QString &binary,
const QStringList &arguments,
QString *arguments,
const QString &workingDir = QString(),
const QProcessEnvironment &env = QProcessEnvironment(),
int timeOut = 5000,
QByteArray *stdOut = Q_NULLPTR, QByteArray *stdErr = Q_NULLPTR)
{
Q_UNUSED(binary);
Q_UNUSED(arguments);
Q_UNUSED(arguments);
Q_UNUSED(workingDir);
Q_UNUSED(env);
Q_UNUSED(timeOut);
Q_UNUSED(stdOut);
Q_UNUSED(stdErr);
return false;
}
#endif
QString sourcePath(const QString &name)
{
return "source_" + name;
}
QString buildPath(const QString &name)
{
if (g_testDirectoryBuild)
return "build_" + name;
return g_temporaryDirectory->path() + "/build_" + name;
}
bool qmake(const QString &source, const QString &destination, QString *errorMessage)
{
QStringList args = QStringList() << source;
return runProcess(g_qmakeBinary, args, errorMessage, destination);
}
bool make(const QString &destination, QString *errorMessage)
{
QStringList args;
return runProcess(g_makeBinary, args, errorMessage, destination,
{}, 60000);
}
void build(const QString &name)
{
// Build the app or framework according to the convention used
// by this test:
// source_name (source code)
// build_name (build artifacts)
QString source = sourcePath(name);
QString build = buildPath(name);
QString profile = name + ".pro";
QString sourcePath = QFINDTESTDATA(source);
QVERIFY(!sourcePath.isEmpty());
// Clear/set up build dir
QString buildPath = build;
QVERIFY(QDir(buildPath).removeRecursively());
QVERIFY(QDir().mkdir(buildPath));
QVERIFY(QDir(buildPath).exists());
// Build application
QString sourceProFile = QDir(sourcePath).canonicalPath() + '/' + profile;
QString errorMessage;
QVERIFY2(qmake(sourceProFile, buildPath, &errorMessage), qPrintable(errorMessage));
QVERIFY2(make(buildPath, &errorMessage), qPrintable(errorMessage));
}
bool changeInstallName(const QString &path, const QString &binary, const QString &from, const QString &to)
{
QStringList args = QStringList() << binary << "-change" << from << to;
QString errorMessage;
return runProcess(g_installNameToolBinary, args, &errorMessage, path);
}
bool deploy(const QString &name, const QStringList &options, QString *errorMessage)
{
QString bundle = name + ".app";
QString path = buildPath(name);
QStringList args = QStringList() << bundle << options;
return runProcess(g_macdeployqtBinary, args, errorMessage, path);
}
bool debugDeploy(const QString &name, const QStringList &options, QString *errorMessage)
{
QString bundle = name + ".app";
QString path = buildPath(name);
QStringList args = QStringList() << bundle << options << "-verbose=3";
QByteArray stdOut;
QByteArray stdErr;
bool exitOK = runProcess(g_macdeployqtBinary, args, errorMessage, path, QProcessEnvironment(),
10000, &stdOut, &stdErr);
qDebug() << "macdeployqt exit OK" << exitOK;
qDebug() << qPrintable(stdOut);
qDebug() << qPrintable(stdErr);
return exitOK;
}
bool run(const QString &name, QString *errorMessage)
{
QString path = buildPath(name);
QStringList args;
QString binary = name + ".app/Contents/MacOS/" + name;
return runProcess(binary, args, errorMessage, path);
}
bool runPrintLibraries(const QString &name, QString *errorMessage, QByteArray *stdErr)
{
QString binary = name + ".app/Contents/MacOS/" + name;
QString path = buildPath(name);
QStringList args;
QProcessEnvironment env = QProcessEnvironment();
env.insert("DYLD_PRINT_LIBRARIES", "true");
QByteArray stdOut;
return runProcess(binary, args, errorMessage, path, env, 5000, &stdOut, stdErr);
}
void runVerifyDeployment(const QString &name)
{
QString errorMessage;
// Verify that the application runs after deployment and that it loads binaries from
// the application bundle only.
QByteArray libraries;
QVERIFY2(runPrintLibraries(name, &errorMessage, &libraries), qPrintable(errorMessage));
const QList<QString> parts = QString::fromLocal8Bit(libraries).split("dyld: loaded:");
const QString qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath);
// Let assume Qt is not installed in system
foreach (QString part, parts) {
part = part.trimmed();
if (part.isEmpty())
continue;
QVERIFY(!parts.startsWith(qtPath));
}
}
class tst_macdeployqt : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void cleanupTestCase();
void basicapp();
void plugins_data();
void plugins();
};
void tst_macdeployqt::initTestCase()
{
#ifdef QT_NO_PROCESS
QSKIP("This test requires QProcess support");
#endif
// Set up test-global unique temporary directory
g_temporaryDirectory = new QTemporaryDir();
QVERIFY(g_temporaryDirectory->isValid());
// Locate build and deployment tools
g_macdeployqtBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/macdeployqt";
QVERIFY(!g_macdeployqtBinary.isEmpty());
g_qmakeBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/qmake";
QVERIFY(!g_qmakeBinary.isEmpty());
g_makeBinary = QStandardPaths::findExecutable("make");
QVERIFY(!g_makeBinary.isEmpty());
g_installNameToolBinary = QStandardPaths::findExecutable("install_name_tool");
QVERIFY(!g_installNameToolBinary.isEmpty());
}
void tst_macdeployqt::cleanupTestCase()
{
delete g_temporaryDirectory;
}
// Verify that deployment of a basic Qt Gui application works
void tst_macdeployqt::basicapp()
{
#ifdef QT_NO_PROCESS
QSKIP("This test requires QProcess support");
#endif
QString errorMessage;
QString name = "basicapp";
// Build and verify that the application runs before deployment
build(name);
QVERIFY2(run(name, &errorMessage), qPrintable(errorMessage));
// Deploy application
QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage));
// Verify deployment
runVerifyDeployment(name);
}
void tst_macdeployqt::plugins_data()
{
QTest::addColumn<QString>("name");
QTest::newRow("sqlite") << "plugin_sqlite";
QTest::newRow("tls") << "plugin_tls";
}
void tst_macdeployqt::plugins()
{
QFETCH(QString, name);
build(name);
// Verify that the test app runs before deployment.
QString errorMessage;
if (!run(name, &errorMessage)) {
qDebug() << qPrintable(errorMessage);
QSKIP("Could not run test application before deployment");
}
QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage));
runVerifyDeployment(name);
}
QTEST_MAIN(tst_macdeployqt)
#include "tst_macdeployqt.moc"

View File

@ -0,0 +1,4 @@
# Generated from windeployqt.pro.
add_subdirectory(testapp)
add_subdirectory(test)

View File

@ -0,0 +1,11 @@
# Generated from test.pro.
#####################################################################
## tst_windeployqt Test:
#####################################################################
qt_internal_add_test(tst_windeployqt
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../" # special case
SOURCES
../tst_windeployqt.cpp
)

View File

@ -0,0 +1,21 @@
# Generated from testapp.pro.
#####################################################################
## testapp Binary:
#####################################################################
qt_internal_add_executable(windeploy_testapp # special case
GUI
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
SOURCES
main.cpp
PUBLIC_LIBRARIES
Qt::Gui
)
# special case begin
set_target_properties(windeploy_testapp
PROPERTIES
OUTPUT_NAME testapp
)
# special case end

View File

@ -0,0 +1,46 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QGuiApplication>
#include <QRasterWindow>
#include <QScreen>
#include <QTimer>
// Simple test application just to verify that it comes up properly
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
QRasterWindow w;
w.setTitle("windeployqt test application");
const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
w.resize(availableGeometry.size() / 4);
w.show();
QTimer::singleShot(200, &w, &QCoreApplication::quit);
return app.exec();
}

View File

@ -0,0 +1,181 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QLibraryInfo>
#include <QtCore/QProcess>
#include <QtCore/QProcessEnvironment>
#include <QtCore/QStandardPaths>
#include <QtCore/QTextStream>
#include <QtTest/QtTest>
static const QString msgProcessError(const QProcess &process, const QString &what,
const QByteArray &stdOut = QByteArray(),
const QByteArray &stdErr = QByteArray())
{
QString result;
QTextStream str(&result);
str << what << ": \"" << process.program() << ' '
<< process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString();
if (!stdOut.isEmpty())
str << "\nStandard output:\n" << stdOut;
if (!stdErr.isEmpty())
str << "\nStandard error:\n" << stdErr;
return result;
}
static bool runProcess(const QString &binary,
const QStringList &arguments,
QString *errorMessage,
const QString &workingDir = QString(),
const QProcessEnvironment &env = QProcessEnvironment(),
int timeOut = 5000,
QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr)
{
QProcess process;
if (!env.isEmpty())
process.setProcessEnvironment(env);
if (!workingDir.isEmpty())
process.setWorkingDirectory(workingDir);
qDebug().noquote().nospace() << "Running: " << QDir::toNativeSeparators(binary)
<< ' ' << arguments.join(QLatin1Char(' '));
process.start(binary, arguments, QIODevice::ReadOnly);
if (!process.waitForStarted()) {
*errorMessage = msgProcessError(process, "Failed to start");
return false;
}
if (!process.waitForFinished(timeOut)) {
*errorMessage = msgProcessError(process, "Timed out");
process.terminate();
if (!process.waitForFinished(300))
process.kill();
return false;
}
const QByteArray stdOut = process.readAllStandardOutput();
const QByteArray stdErr = process.readAllStandardError();
if (stdOutIn)
*stdOutIn = stdOut;
if (stdErrIn)
*stdErrIn = stdErr;
if (process.exitStatus() != QProcess::NormalExit) {
*errorMessage = msgProcessError(process, "Crashed", stdOut, stdErr);
return false;
}
if (process.exitCode() != QProcess::NormalExit) {
*errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode()),
stdOut, stdErr);
return false;
}
return true;
}
class tst_windeployqt : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void help();
void deploy();
private:
QString m_windeployqtBinary;
QString m_testApp;
QString m_testAppBinary;
};
void tst_windeployqt::initTestCase()
{
m_windeployqtBinary = QStandardPaths::findExecutable("windeployqt");
QVERIFY(!m_windeployqtBinary.isEmpty());
m_testApp = QFINDTESTDATA("testapp");
QVERIFY(!m_testApp.isEmpty());
const QFileInfo testAppBinary(m_testApp + QLatin1String("/testapp.exe"));
QVERIFY2(testAppBinary.isFile(), qPrintable(testAppBinary.absoluteFilePath()));
m_testAppBinary = testAppBinary.absoluteFilePath();
}
void tst_windeployqt::help()
{
QString errorMessage;
QByteArray stdOut;
QByteArray stdErr;
QVERIFY2(runProcess(m_windeployqtBinary, QStringList("--help"), &errorMessage,
QString(), QProcessEnvironment(), 5000, &stdOut, &stdErr),
qPrintable(errorMessage));
QVERIFY2(!stdOut.isEmpty(), stdErr);
}
// deploy(): Deploys the test application and launches it with Qt removed from the environment
// to verify it runs stand-alone.
void tst_windeployqt::deploy()
{
QString errorMessage;
// Deploy application
QStringList deployArguments;
deployArguments << QLatin1String("--no-translations") << QDir::toNativeSeparators(m_testAppBinary);
QVERIFY2(runProcess(m_windeployqtBinary, deployArguments, &errorMessage, QString(), QProcessEnvironment(), 20000),
qPrintable(errorMessage));
// Create environment with Qt and all "lib" paths removed.
const QString qtBinDir = QDir::toNativeSeparators(QLibraryInfo::path(QLibraryInfo::BinariesPath));
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
const QString pathKey = QLatin1String("PATH");
const QChar pathSeparator(QLatin1Char(';')); // ### fixme: Qt 5.6: QDir::listSeparator()
const QString origPath = env.value(pathKey);
QString newPath;
const QStringList pathElements = origPath.split(pathSeparator, Qt::SkipEmptyParts);
for (const QString &pathElement : pathElements) {
if (pathElement.compare(qtBinDir, Qt::CaseInsensitive)
&& !pathElement.contains(QLatin1String("\\lib"), Qt::CaseInsensitive)) {
if (!newPath.isEmpty())
newPath.append(pathSeparator);
newPath.append(pathElement);
}
}
if (newPath == origPath)
qWarning() << "Unable to remove Qt from PATH";
env.insert(pathKey, newPath);
// Create qt.conf to enforce usage of local plugins
QFile qtConf(QFileInfo(m_testAppBinary).absolutePath() + QLatin1String("/qt.conf"));
QVERIFY2(qtConf.open(QIODevice::WriteOnly | QIODevice::Text),
qPrintable(qtConf.fileName() + QLatin1String(": ") + qtConf.errorString()));
QVERIFY(qtConf.write("[Paths]\nPrefix = .\n"));
qtConf.close();
// Verify that application still runs
QVERIFY2(runProcess(m_testAppBinary, QStringList(), &errorMessage, QString(), env, 10000),
qPrintable(errorMessage));
}
QTEST_MAIN(tst_windeployqt)
#include "tst_windeployqt.moc"