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> : Deploy imports used by .qml files in the given path";
qDebug() << "";
qDebug() << "macdeployqt takes an application bundle as input and makes it";
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
bool useDebugLibs = false;
extern bool runStripEnabled;
QStringList additionalExecutables;
+ QStringList qmlDirs;
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
logLevel = number;
} else if (argument.startsWith(QByteArray("-executable"))) {
LogDebug() << "Argument found:" << argument;
- int index = argument.indexOf("=");
- if (index < 0 || index >= argument.size())
+ 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;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing qml directory path";
+ else
+ qmlDirs << argument.mid(index+1);
} else if (argument.startsWith("-")) {
LogError() << "Unknown argument" << argument << "\n";
return 0;
createQtConf(appBundlePath);
}
+ // Convenience: Look for .qml files in the current directoty if no -qmldir specified.
+ if (qmlDirs.isEmpty()) {
+ QDir dir;
+ if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
+ qmlDirs += QStringLiteral(".");
+ }
+ }
+
+ if (!qmlDirs.isEmpty())
+ deployQmlImports(appBundlePath, qmlDirs);
+
if (dmg) {
LogNormal();
createDiskImage(appBundlePath);
#include <QRegExp>
#include <QSet>
#include <QDirIterator>
+#include <QLibraryInfo>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
#include "shared.h"
bool runStripEnabled = true;
bool copyFilePrintStatus(const QString &from, const QString &to)
{
- if (QFile::copy(from, to)) {
+ if (QFile(to).exists()) {
+ LogNormal() << "File exists, skip copy:" << to;
+ return false;
+ } else if (QFile::copy(from, to)) {
LogNormal() << " copied:" << from;
LogNormal() << " to" << to;
return true;
{
QDir().mkpath(destinationPath);
+ LogNormal() << "copy:" << sourcePath << destinationPath;
+
QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
foreach (QString file, files) {
const QString fileSourcePath = sourcePath + "/" + file;
}
}
+void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourcePath, const QString &destinationPath)
+{
+ QDir().mkpath(destinationPath);
+
+ LogNormal() << "copy:" << sourcePath << destinationPath;
+
+ QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot);
+ foreach (QString file, files) {
+ const QString fileSourcePath = sourcePath + QLatin1Char('/') + file;
+ const QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
+
+ if (file.endsWith("_debug.dylib")) {
+ continue; // Skip debug versions
+ } else if (file.endsWith(QStringLiteral(".dylib"))) {
+ if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
+ runStrip(fileDestinationPath);
+ bool useDebugLibs = false;
+ bool useLoaderPath = false;
+ QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, useDebugLibs);
+ deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
+ }
+ } else {
+ copyFilePrintStatus(fileSourcePath, fileDestinationPath);
+ }
+ }
+
+ QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
+ foreach (QString dir, subdirs) {
+ recursiveCopyAndDeploy(appBundlePath, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir);
+ }
+}
+
+
QString copyFramework(const FrameworkInfo &framework, const QString path)
{
QString from = framework.sourceFilePath;
QFileInfo fromDirInfo(framework.frameworkPath + QLatin1Char('/')
+ framework.binaryDirectory);
bool fromDirIsSymLink = fromDirInfo.isSymLink();
- QString unresolvedToDir = path + "/" + framework.destinationDirectory;
+ QString unresolvedToDir = path + QLatin1Char('/') + framework.destinationDirectory;
QString resolvedToDir;
QString relativeLinkTarget; // will contain the link from Current to e.g. 4 in the Versions directory
if (fromDirIsSymLink) {
void createQtConf(const QString &appBundlePath)
{
- QByteArray contents = "[Paths]\nPlugins = PlugIns\n";
+ // Set Plugins and imports paths. These are relative to App.app/Contents.
+ QByteArray contents = "[Paths]\n"
+ "Plugins = PlugIns\n"
+ "Imports = Resources/qml\n"
+ "Qml2Imports = Resources/qml\n";
+
QString filePath = appBundlePath + "/Contents/Resources/";
QString fileName = filePath + "qt.conf";
deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
}
+void deployQmlImport(const QString &appBundlePath, const QString &importSourcePath, const QString &importName)
+{
+ QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
+ recursiveCopyAndDeploy(appBundlePath, importSourcePath, importDestinationPath);
+}
+
+// Scan qml files in qmldirs for import statements, deploy used imports from Qml2ImportsPath to Contents/Resources/qml.
+void deployQmlImports(const QString &appBundlePath, QStringList &qmlDirs)
+{
+ // verify that qmlimportscanner is in BinariesPath
+ QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
+ if (!QFile(qmlImportScannerPath).exists()) {
+ LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
+ LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
+ return;
+ }
+
+ // run qmlimportscanner
+ QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
+ QProcess qmlImportScanner;
+ qmlImportScanner.setProcessChannelMode(QProcess::MergedChannels);
+ qmlImportScanner.start(qmlImportScannerPath, QStringList() << qmlDirs << "-importPath" << qmlImportsPath);
+ if (!qmlImportScanner.waitForStarted()) {
+ LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
+ return;
+ }
+
+ qmlImportScanner.waitForFinished();
+ QByteArray json = qmlImportScanner.readAll();
+
+ // parse qmlimportscanner json
+ QJsonDocument doc = QJsonDocument::fromJson(json);
+ if (!doc.isArray()) {
+ LogError() << "qmlimportscanner output error. Expected json array, got:";
+ LogError() << json;
+ return;
+ }
+
+ // deploy each import
+ foreach (const QJsonValue &importValue, doc.array()) {
+ if (!importValue.isObject())
+ continue;
+
+ QJsonObject import = importValue.toObject();
+ QString name = import["name"].toString();
+ QString path = import["path"].toString();
+
+ // Create the destination path from the name
+ // and version (grabbed from the source path)
+ // ### let qmlimportscanner provide this.
+ name.replace(QLatin1Char('.'), QLatin1Char('/'));
+ int secondTolast = path.length() - 2;
+ QString version = path.mid(secondTolast);
+ if (version.startsWith(QLatin1Char('.')))
+ name.append(version);
+
+ deployQmlImport(appBundlePath, path, name);
+ }
+}
void changeQtFrameworks(const QList<FrameworkInfo> frameworks, const QStringList &binaryPaths, const QString &absoluteQtPath)
{
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, 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 QString &binaryPath);
+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);
+void deployQmlImports(const QString &appBundlePath, QStringList &qmlDirs);
void changeIdentification(const QString &id, const QString &binaryPath);
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath);
+void runStrip(const QString &binaryPath);
QString findAppBinary(const QString &appBundlePath);
void createDiskImage(const QString &appBundlePath);
+
#endif