Commit a5850598 authored by Morten Johan Sørvig's avatar Morten Johan Sørvig
Browse files

Fix framework bundle deployment

Rework copyFramework(). Split out dylib deployment
into copyDylib(). Remove the special symlink handling.
Always create symlinks from "Current" to the actual
version.

This ensures we're compatible with Apple's framework
bundle spec [1]. It also makes the 'codesign' utility
happy, easing the step to sandbox Qt OS X applications.

[1]: https://developer.apple.com/library/mac/documentation/


macosx/conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html

Task-number: QTBUG-32896
Change-Id: Ide23437c9bb6515d5013d37ae6ff4133a69085ad
Reviewed-by: default avatarMorten Johan Sørvig <morten.sorvig@digia.com>
Showing with 94 additions and 44 deletions
......@@ -78,7 +78,8 @@ QDebug operator<<(QDebug debug, const FrameworkInfo &info)
debug << "Install name" << info.installName << "\n";
debug << "Deployed install name" << info.deployedInstallName << "\n";
debug << "Source file Path" << info.sourceFilePath << "\n";
debug << "Destination Directory (relative to bundle)" << info.destinationDirectory << "\n";
debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n";
debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n";
return debug;
}
......@@ -131,6 +132,25 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
}
}
bool linkFilePrintStatus(const QString &file, const QString &link)
{
if (QFile(link).exists()) {
if (QFile(link).symLinkTarget().isEmpty())
LogError() << link << "exists but it's a file.";
else
LogNormal() << "Symlink exists, skipping:" << link;
return false;
} else if (QFile::link(file, link)) {
LogNormal() << " symlink" << link;
LogNormal() << " points to" << file;
return true;
} else {
LogError() << "failed to symlink" << link;
LogError() << " to" << file;
return false;
}
}
FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs)
{
FrameworkInfo info;
......@@ -190,19 +210,22 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs)
// remove ".framework"
name = currentPart;
name.chop(QString(".framework").length());
info.isDylib = false;
info.frameworkName = currentPart;
state = Version;
++part;
continue;
} if (state == DylibName) {
name = currentPart.split(" (compatibility").at(0);
info.isDylib = true;
info.frameworkName = name;
info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
info.installName += name;
info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
info.frameworkPath = info.frameworkDirectory + info.binaryName;
info.sourceFilePath = info.frameworkPath;
info.destinationDirectory = bundleFrameworkDirectory + "/";
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/";
info.binaryDestinationDirectory = info.frameworkDestinationDirectory;
info.binaryDirectory = info.frameworkDirectory;
info.binaryPath = info.frameworkPath;
state = End;
......@@ -217,7 +240,8 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs)
info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
info.frameworkPath = info.frameworkDirectory + info.frameworkName;
info.sourceFilePath = info.frameworkPath + info.binaryPath;
info.destinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName + "/" + info.binaryDirectory;
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName;
info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory;
state = End;
} else if (state == End) {
break;
......@@ -355,56 +379,79 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP
}
}
QString copyFramework(const FrameworkInfo &framework, const QString path)
QString copyDylib(const FrameworkInfo &framework, const QString path)
{
QString from = framework.sourceFilePath;
if (!QFile::exists(from)) {
LogError() << "no file at" << from;
if (!QFile::exists(framework.sourceFilePath)) {
LogError() << "no file at" << framework.sourceFilePath;
return QString();
}
QFileInfo fromDirInfo(framework.frameworkPath + QLatin1Char('/')
+ framework.binaryDirectory);
bool fromDirIsSymLink = fromDirInfo.isSymLink();
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) {
// handle the case where framework is referenced with Versions/Current
// which is a symbolic link, so copy to target and recreate as symbolic link
relativeLinkTarget = QDir(fromDirInfo.canonicalPath())
.relativeFilePath(QFileInfo(fromDirInfo.symLinkTarget()).canonicalFilePath());
resolvedToDir = QFileInfo(unresolvedToDir).path() + QLatin1Char('/') + relativeLinkTarget;
} else {
resolvedToDir = unresolvedToDir;
// Construct destination paths. The full path typically looks like
// MyApp.app/Contents/Frameworks/libfoo.dylib
QString dylibDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory;
QString dylibDestinationBinaryPath = dylibDestinationDirectory + QLatin1Char('/') + framework.binaryName;
// Create destination directory
if (!QDir().mkpath(dylibDestinationDirectory)) {
LogError() << "could not create destination directory" << dylibDestinationDirectory;
return QString();
}
QString to = resolvedToDir + "/" + framework.binaryName;
// Retrun if the dylib has aleardy been deployed
if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled)
return dylibDestinationBinaryPath;
// Copy dylib binary
copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
return dylibDestinationBinaryPath;
}
// create the (non-symlink) dir
QDir dir;
if (!dir.mkpath(resolvedToDir)) {
LogError() << "could not create destination directory" << to;
QString copyFramework(const FrameworkInfo &framework, const QString path)
{
if (!QFile::exists(framework.sourceFilePath)) {
LogError() << "no file at" << framework.sourceFilePath;
return QString();
}
if (!QFile::exists(to) || alwaysOwerwriteEnabled) { // copy the binary and resources if that wasn't done before
copyFilePrintStatus(from, to);
// Construct destination paths. The full path typically looks like
// MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo
QString frameworkDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory;
QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + QLatin1Char('/') + framework.binaryDirectory;
QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + QLatin1Char('/') + framework.binaryName;
const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
const QString resourcesDestianationPath = path + "/Contents/Frameworks/" + framework.frameworkName + "/Resources";
recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
}
// Return if the framework has aleardy been deployed
if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled)
return QString();
// create the Versions/Current symlink dir if necessary
if (fromDirIsSymLink) {
QFile::link(relativeLinkTarget, unresolvedToDir);
LogNormal() << " linked:" << unresolvedToDir;
LogNormal() << " to" << resolvedToDir << "(" << relativeLinkTarget << ")";
// Create destination directory
if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory;
return QString();
}
return to;
// Now copy the framework. Some parts should be left out (headers/, .prl files).
// Some parts should be included (Resources/, symlink structure). We want this
// function to make as few assumtions about the framework as possible while at
// the same time producing a codesign-compatible framework.
// Copy framework binary
copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
// Copy Resouces/
const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
// Create symlink structure. Links at the framework root point to Versions/Current/
// which again points to the actual version:
// QtFoo.framework/QtFoo -> Versions/Current/QtFoo
// QtFoo.framework/Resources -> Versions/Current/Resources
// QtFoo.framework/Versions/Current -> 5
linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName);
linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources");
linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current");
return frameworkDestinationBinaryPath;
}
void runInstallNameTool(QStringList options)
......@@ -433,7 +480,7 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework
QString deployedInstallName;
if (useLoaderPath) {
deployedInstallName = QLatin1String("@loader_path/")
+ QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.destinationDirectory + QLatin1Char('/') + framework.binaryName);
+ QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.binaryDestinationDirectory + QLatin1Char('/') + framework.binaryName);
} else {
deployedInstallName = framework.deployedInstallName;
}
......@@ -503,8 +550,9 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
// Install_name_tool the new id into the binaries
changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
// Copy farmework to app bundle.
const QString deployedBinaryPath = copyFramework(framework, bundlePath);
// Copy the framework/dylib to the app bundle.
const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
: copyFramework(framework, bundlePath);
// Skip the rest if already was deployed.
if (deployedBinaryPath.isNull())
continue;
......
......@@ -56,6 +56,7 @@ extern bool runStripEnabled;
class FrameworkInfo
{
public:
bool isDylib;
QString frameworkDirectory;
QString frameworkName;
QString frameworkPath;
......@@ -66,7 +67,8 @@ public:
QString installName;
QString deployedInstallName;
QString sourceFilePath;
QString destinationDirectory;
QString frameworkDestinationDirectory;
QString binaryDestinationDirectory;
};
bool operator==(const FrameworkInfo &a, const FrameworkInfo &b);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment