diff --git a/src/macdeployqt/shared/shared.cpp b/src/macdeployqt/shared/shared.cpp index 50b5878bf5926a739a890a3e150cad60c91b2e38..903d5b2c5b782fc3b7110922dd8124ed656949ef 100644 --- a/src/macdeployqt/shared/shared.cpp +++ b/src/macdeployqt/shared/shared.cpp @@ -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; diff --git a/src/macdeployqt/shared/shared.h b/src/macdeployqt/shared/shared.h index 8c6ea0bfe2425111b70afa006e3cf612b5114c7c..0fe7400fb64860c99236a12bc743907c66747b64 100644 --- a/src/macdeployqt/shared/shared.h +++ b/src/macdeployqt/shared/shared.h @@ -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);