tools/macdeployqt/shared/shared.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the tools applications of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 #include <QString>
       
    42 #include <QStringList>
       
    43 #include <QDebug>
       
    44 #include <iostream>
       
    45 #include <QProcess>
       
    46 #include <QDir>
       
    47 #include <QRegExp>
       
    48 #include <QSet>
       
    49 #include <QDirIterator>
       
    50 #include "shared.h"
       
    51 
       
    52 bool runStripEnabled = true;
       
    53 int logLevel = 1;
       
    54 
       
    55 using std::cout;
       
    56 using std::endl;
       
    57 
       
    58 bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
       
    59 {
       
    60     return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
       
    61 }
       
    62 
       
    63 QDebug operator<<(QDebug debug, const FrameworkInfo &info)
       
    64 {
       
    65     debug << "Framework name" << info.frameworkName << "\n";
       
    66     debug << "Framework directory" << info.frameworkDirectory << "\n";
       
    67     debug << "Framework path" << info.frameworkPath << "\n";
       
    68     debug << "Binary directory" << info.binaryDirectory << "\n";
       
    69     debug << "Binary name" << info.binaryName << "\n";
       
    70     debug << "Binary path" << info.binaryPath << "\n";
       
    71     debug << "Version" << info.version << "\n";
       
    72     debug << "Install name" << info.installName << "\n";
       
    73     debug << "Deployed install name" << info.deployedInstallName << "\n";
       
    74     debug << "Source file Path" << info.sourceFilePath << "\n";
       
    75     debug << "Deployed Directory (relative to bundle)" << info.destinationDirectory << "\n";
       
    76 
       
    77     return debug;
       
    78 }
       
    79 
       
    80 const QString bundleFrameworkDirectory = "Contents/Frameworks";
       
    81 const QString bundleBinaryDirectory = "Contents/MacOS";
       
    82 
       
    83 inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
       
    84 {
       
    85     debug << "Application bundle path" << info.path << "\n";
       
    86     debug << "Binary path" << info.binaryPath << "\n";
       
    87     return debug;
       
    88 }
       
    89 
       
    90 bool copyFilePrintStatus(const QString &from, const QString &to)
       
    91 {
       
    92     if (QFile::copy(from, to)) {
       
    93         LogNormal() << " copied:" << from;
       
    94         LogNormal() << " to" << to;
       
    95         return true;
       
    96     } else {
       
    97         LogError() << "file copy failed from" << from;
       
    98         LogError() << " to" << to;
       
    99         return false;
       
   100     }
       
   101 }
       
   102 
       
   103 FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs)
       
   104 {
       
   105     FrameworkInfo info;
       
   106     QString trimmed = line.trimmed();
       
   107 
       
   108     if (trimmed.isEmpty())
       
   109         return info;
       
   110 
       
   111     // Don't deploy system libraries.
       
   112     if (trimmed.startsWith("/System/Library/") ||
       
   113         (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene
       
   114         || trimmed.startsWith("@executable_path"))
       
   115         return info;
       
   116 
       
   117     enum State {QtPath, FrameworkName, DylibName, Version, End};
       
   118     State state = QtPath;
       
   119     int part = 0;
       
   120     QString name;
       
   121     QString qtPath;
       
   122     QString suffix = useDebugLibs ? "_debug" : "";
       
   123 
       
   124     // Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/
       
   125     QStringList parts = trimmed.split("/");
       
   126     while (part < parts.count()) {
       
   127         const QString currentPart = parts.at(part).simplified() ;
       
   128         ++part;
       
   129         if (currentPart == "")
       
   130             continue;
       
   131 
       
   132         if (state == QtPath) {
       
   133             // Check for library name part
       
   134             if (part < parts.count() && parts.at(part).contains(".dylib ")) {
       
   135                 state = DylibName;
       
   136                 info.installName += "/" + (qtPath + "lib/").simplified();
       
   137                 info.frameworkDirectory = info.installName;
       
   138                 state = DylibName;
       
   139                 continue;
       
   140             } else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
       
   141                 info.installName += "/" + (qtPath + "lib/").simplified();
       
   142                 info.frameworkDirectory = info.installName;
       
   143                 state = FrameworkName;
       
   144                 continue;
       
   145             } else if (trimmed.startsWith("/") == false) {      // If the line does not contain a full path, the app is using a binary Qt package.
       
   146                 if (currentPart.contains(".framework")) {
       
   147                     info.frameworkDirectory = "/Library/Frameworks/";
       
   148                     state = FrameworkName;
       
   149                 } else {
       
   150                     info.frameworkDirectory = "/usr/lib/";
       
   151                     state = DylibName;
       
   152                 }
       
   153 
       
   154                 --part;
       
   155                 continue;
       
   156             }
       
   157             qtPath += (currentPart + "/");
       
   158 
       
   159         } if (state == FrameworkName) {
       
   160             // remove ".framework"
       
   161             name = currentPart;
       
   162             name.chop(QString(".framework").length());
       
   163             info.frameworkName = currentPart;
       
   164             state = Version;
       
   165             ++part;
       
   166             continue;
       
   167         } if (state == DylibName) {
       
   168             name = currentPart.split(" (compatibility").at(0);
       
   169             info.frameworkName = name;
       
   170             info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
       
   171             info.installName += name;
       
   172             info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
       
   173             info.frameworkPath = info.frameworkDirectory + info.binaryName;
       
   174             info.sourceFilePath = info.frameworkPath;
       
   175             info.destinationDirectory = bundleFrameworkDirectory + "/";
       
   176             info.binaryDirectory = info.frameworkDirectory;
       
   177             info.binaryPath = info.frameworkPath;
       
   178             state = End;
       
   179             ++part;
       
   180             continue;
       
   181         } else if (state == Version) {
       
   182             info.version = currentPart;
       
   183             info.binaryDirectory = "Versions/" + info.version;
       
   184             info.binaryName = name + suffix;
       
   185             info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
       
   186             info.installName += info.frameworkName + "/" + info.binaryDirectory + "/" + name;
       
   187             info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
       
   188             info.frameworkPath = info.frameworkDirectory + info.frameworkName;
       
   189             info.sourceFilePath = info.frameworkPath + info.binaryPath;
       
   190             info.destinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName + "/" + info.binaryDirectory;
       
   191             state = End;
       
   192         } else if (state == End) {
       
   193             break;
       
   194         }
       
   195     }
       
   196 
       
   197     return info;
       
   198 }
       
   199 
       
   200 QString findAppBinary(const QString &appBundlePath)
       
   201 {
       
   202     QString appName = QFileInfo(appBundlePath).completeBaseName();
       
   203     QString binaryPath = appBundlePath  + "/Contents/MacOS/" + appName;
       
   204 
       
   205     if (QFile::exists(binaryPath))
       
   206         return binaryPath;
       
   207     LogError() << "Could not find bundle binary for" << appBundlePath;
       
   208     return QString();
       
   209 }
       
   210 
       
   211 QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs)
       
   212 {
       
   213     QList<FrameworkInfo> libraries;
       
   214     foreach(const QString line, otoolLines) {
       
   215         FrameworkInfo info = parseOtoolLibraryLine(line, useDebugLibs);
       
   216         if (info.frameworkName.isEmpty() == false) {
       
   217             LogDebug() << "Adding framework:";
       
   218             LogDebug() << info;
       
   219             libraries.append(info);
       
   220         }
       
   221     }
       
   222     return libraries;
       
   223 }
       
   224 
       
   225 QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs)
       
   226 {
       
   227     LogDebug() << "Using otool:";
       
   228     LogDebug() << " inspecting" << path;
       
   229     QProcess otool;
       
   230     otool.start("otool", QStringList() << "-L" << path);
       
   231     otool.waitForFinished();
       
   232 
       
   233     if (otool.exitCode() != 0) {
       
   234         LogError() << otool.readAllStandardError();
       
   235     }
       
   236 
       
   237     QString output = otool.readAllStandardOutput();
       
   238     QStringList outputLines = output.split("\n");
       
   239     outputLines.removeFirst(); // remove line containing the binary path
       
   240     if (path.contains(".framework") || path.contains(".dylib"))
       
   241         outputLines.removeFirst(); // frameworks and dylibs lists themselves as a dependency.
       
   242 
       
   243     return getQtFrameworks(outputLines, useDebugLibs);
       
   244 }
       
   245 
       
   246 // copies everything _inside_ sourcePath to destinationPath
       
   247 void recursiveCopy(const QString &sourcePath, const QString &destinationPath)
       
   248 {
       
   249     QDir().mkpath(destinationPath);
       
   250 
       
   251     QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
       
   252     foreach (QString file, files) {
       
   253         const QString fileSourcePath = sourcePath + "/" + file;
       
   254         const QString fileDestinationPath = destinationPath + "/" + file;
       
   255         copyFilePrintStatus(fileSourcePath, fileDestinationPath);
       
   256     }
       
   257 
       
   258     QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
       
   259     foreach (QString dir, subdirs) {
       
   260         recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
       
   261     }
       
   262 }
       
   263 
       
   264 QString copyFramework(const FrameworkInfo &framework, const QString path)
       
   265 {
       
   266     QString from = framework.sourceFilePath;
       
   267     QString toDir = path + "/" + framework.destinationDirectory;
       
   268     QString to = toDir + "/" + framework.binaryName;
       
   269 
       
   270     if (QFile::exists(from) == false) {
       
   271         LogError() << "no file at" << from;
       
   272         return QString();
       
   273     }
       
   274 
       
   275 
       
   276     QDir dir;
       
   277     if (dir.mkpath(toDir) == false) {
       
   278         LogError() << "could not create destination directory" << to;
       
   279         return QString();
       
   280     }
       
   281 
       
   282 
       
   283     if (QFile::exists(to)) {
       
   284         return QString();
       
   285     }
       
   286 
       
   287     copyFilePrintStatus(from, to);
       
   288 
       
   289     const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
       
   290     const QString resourcesDestianationPath = path + "/Contents/Frameworks/" + framework.frameworkName + "/Resources";
       
   291     recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
       
   292 
       
   293     return to;
       
   294 }
       
   295 
       
   296 void runInstallNameTool(QStringList options)
       
   297 {
       
   298     QProcess installNametool;
       
   299     installNametool.start("install_name_tool", options);
       
   300     installNametool.waitForFinished();
       
   301     if (installNametool.exitCode() != 0) {
       
   302         LogError() << installNametool.readAllStandardError();
       
   303         LogError() << installNametool.readAllStandardOutput();
       
   304     }
       
   305 }
       
   306 
       
   307 void changeIdentification(const QString &id, const QString &binaryPath)
       
   308 {
       
   309     LogDebug() << "Using install_name_tool:";
       
   310     LogDebug() << " change identification in" << binaryPath;
       
   311     LogDebug() << " to" << id;
       
   312     runInstallNameTool(QStringList() << "-id" << id << binaryPath);
       
   313 }
       
   314 
       
   315 void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
       
   316 {
       
   317     LogDebug() << "Using install_name_tool:";
       
   318     LogDebug() << " in" << binaryPath;
       
   319     LogDebug() << " change reference" << oldName;
       
   320     LogDebug() << " to" << newName;
       
   321     runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath);
       
   322 }
       
   323 
       
   324 void runStrip(const QString &binaryPath)
       
   325 {
       
   326     if (runStripEnabled == false)
       
   327         return;
       
   328 
       
   329     LogDebug() << "Using strip:";
       
   330     LogDebug() << " stripped" << binaryPath;
       
   331     QProcess strip;
       
   332     strip.start("strip", QStringList() << "-x" << binaryPath);
       
   333     strip.waitForFinished();
       
   334     if (strip.exitCode() != 0) {
       
   335         LogError() << strip.readAllStandardError();
       
   336         LogError() << strip.readAllStandardOutput();
       
   337     }
       
   338 }
       
   339 
       
   340 /*
       
   341     Deploys the the listed frameworks listed into an app bundle.
       
   342     The frameworks are searched for dependencies, which are also deployed.
       
   343     (deploying Qt3Support will also deploy QtNetwork and QtSql for example.)
       
   344     Returns a DeploymentInfo structure containing the Qt path used and a
       
   345     a list of actually deployed frameworks.
       
   346 */
       
   347 DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
       
   348         const QString &bundlePath, const QString &binaryPath, bool useDebugLibs)
       
   349 {
       
   350     LogNormal();
       
   351     LogNormal() << "Deploying Qt frameworks found inside:" << binaryPath;
       
   352     QStringList copiedFrameworks;
       
   353     DeploymentInfo deploymenInfo;
       
   354 
       
   355     while (frameworks.isEmpty() == false) {
       
   356         const FrameworkInfo framework = frameworks.takeFirst();
       
   357         copiedFrameworks.append(framework.frameworkName);
       
   358 
       
   359         // Get the qt path from one of the Qt frameworks;
       
   360         if (deploymenInfo.qtPath.isNull() && framework.frameworkName.contains("Qt") 
       
   361             && framework.frameworkDirectory.contains("/lib"))
       
   362         {
       
   363                 deploymenInfo.qtPath = framework.frameworkDirectory;
       
   364                 deploymenInfo.qtPath.chop(5); // remove "/lib/"
       
   365         }
       
   366 
       
   367         if (framework.installName.startsWith("/@executable_path/")) {
       
   368             LogError()  << framework.frameworkName << "already deployed, skipping.";
       
   369             continue;
       
   370         }
       
   371 
       
   372         // Install_name_tool the new id into the binary
       
   373         changeInstallName(framework.installName, framework.deployedInstallName, binaryPath);
       
   374 
       
   375         // Copy farmework to app bundle.
       
   376         const QString deployedBinaryPath = copyFramework(framework, bundlePath);
       
   377         // Skip the rest if already was deployed.
       
   378         if (deployedBinaryPath.isNull())
       
   379             continue;
       
   380 
       
   381         runStrip(deployedBinaryPath);
       
   382 
       
   383         // Install_name_tool it a new id.
       
   384         changeIdentification(framework.deployedInstallName, deployedBinaryPath);
       
   385         // Check for framework dependencies
       
   386         QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs);
       
   387 
       
   388         foreach (FrameworkInfo dependency, dependencies) {
       
   389             changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath);
       
   390 
       
   391             // Deploy framework if neccesary.
       
   392             if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) {
       
   393                 frameworks.append(dependency);
       
   394             }
       
   395         }
       
   396     }
       
   397     deploymenInfo.deployedFrameworks = copiedFrameworks;
       
   398     return deploymenInfo;
       
   399 }
       
   400 
       
   401 DeploymentInfo deployQtFrameworks(const QString &appBundlePath, bool useDebugLibs)
       
   402 {
       
   403    ApplicationBundleInfo applicationBundle;
       
   404    applicationBundle.path = appBundlePath;
       
   405    applicationBundle.binaryPath = findAppBinary(appBundlePath);
       
   406    QList<FrameworkInfo> frameworks = getQtFrameworks(applicationBundle.binaryPath, useDebugLibs);
       
   407    if (frameworks.isEmpty()) {
       
   408         LogWarning();
       
   409         LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
       
   410         LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?";
       
   411         LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
       
   412         return DeploymentInfo();
       
   413    } else {
       
   414        return deployQtFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, useDebugLibs);
       
   415    }
       
   416 }
       
   417 
       
   418 void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath,
       
   419         const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
       
   420 {
       
   421     LogNormal() << "Deploying plugins from" << pluginSourcePath;
       
   422     QStringList plugins = QDir(pluginSourcePath).entryList(QStringList() << "*.dylib");
       
   423 
       
   424     foreach (QString pluginName, plugins) {
       
   425         if (pluginSourcePath.contains(deploymentInfo.pluginPath)) {
       
   426             QStringList deployedFrameworks = deploymentInfo.deployedFrameworks;
       
   427 
       
   428             // Skip the debug versions of the plugins, unless specified otherwise.
       
   429             if (!useDebugLibs && pluginName.endsWith("_debug.dylib"))
       
   430                 continue;
       
   431 
       
   432             // Skip the release versions of the plugins, unless specified otherwise.
       
   433             if (useDebugLibs && !pluginName.endsWith("_debug.dylib"))
       
   434                 continue;
       
   435 
       
   436             // Skip the designer plugins
       
   437             if (pluginSourcePath.contains("plugins/designer"))
       
   438                 continue;
       
   439 
       
   440 #ifndef QT_GRAPHICSSYSTEM_OPENGL
       
   441             // SKip the opengl graphicssystem plugin when not in use.
       
   442             if (pluginName.contains("libqglgraphicssystem"))
       
   443                 continue;
       
   444 #endif
       
   445             // Deploy accessibility for Qt3Support only if the Qt3Support.framework is in use
       
   446             if (deployedFrameworks.indexOf("Qt3Support.framework") == -1 && pluginName.contains("accessiblecompatwidgets"))
       
   447                 continue;
       
   448 
       
   449             // Deploy the svg icon plugin if QtSvg.framework is in use.
       
   450             if (deployedFrameworks.indexOf("QtSvg.framework") == -1 && pluginName.contains("svg"))
       
   451                 continue;
       
   452 
       
   453             // Deploy the phonon plugins if phonon.framework is in use
       
   454             if (deployedFrameworks.indexOf("phonon.framework") == -1 && pluginName.contains("phonon"))
       
   455                 continue;
       
   456 
       
   457             // Deploy the sql plugins if QtSql.framework is in use
       
   458             if (deployedFrameworks.indexOf("QtSql.framework") == -1 && pluginName.contains("sql"))
       
   459                 continue;
       
   460 
       
   461             // Deploy the script plugins if QtScript.framework is in use
       
   462             if (deployedFrameworks.indexOf("QtScript.framework") == -1 && pluginName.contains("script"))
       
   463                 continue;
       
   464         }
       
   465 
       
   466         QDir dir;
       
   467         dir.mkpath(pluginDestinationPath);
       
   468 
       
   469         const QString sourcePath = pluginSourcePath + "/" + pluginName;
       
   470         const QString destinationPath = pluginDestinationPath + "/" + pluginName;
       
   471         if (copyFilePrintStatus(sourcePath, destinationPath)) {
       
   472 
       
   473             runStrip(destinationPath);
       
   474 
       
   475             // Special case for the phonon plugin: CoreVideo is not available as a separate framework
       
   476             // on panther, link against the QuartzCore framework instead. (QuartzCore contians CoreVideo.)
       
   477             if (pluginName.contains("libphonon_qt7")) {
       
   478                 changeInstallName("/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
       
   479                         "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
       
   480                         destinationPath);
       
   481             }
       
   482 
       
   483             QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, useDebugLibs);
       
   484             deployQtFrameworks(frameworks, appBundleInfo.path, destinationPath, useDebugLibs);
       
   485         }
       
   486     } // foreach plugins
       
   487 
       
   488     QStringList subdirs = QDir(pluginSourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
       
   489     foreach (const QString &subdir, subdirs)
       
   490         deployPlugins(appBundleInfo, pluginSourcePath + "/" + subdir, pluginDestinationPath + "/" + subdir, deploymentInfo, useDebugLibs);
       
   491 }
       
   492 
       
   493 void createQtConf(const QString &appBundlePath)
       
   494 {
       
   495     QByteArray contents = "[Paths]\nPlugins = PlugIns\n";
       
   496     QString filePath = appBundlePath + "/Contents/Resources/";
       
   497     QString fileName = filePath + "qt.conf";
       
   498 
       
   499     QDir().mkpath(filePath);
       
   500 
       
   501     QFile qtconf(fileName);
       
   502     if (qtconf.exists()) {
       
   503         LogWarning();
       
   504         LogWarning() << fileName << "already exists, will not overwrite.";
       
   505         LogWarning() << "To make sure the plugins are loaded from the correct location,";
       
   506         LogWarning() << "please make sure qt.conf contains the following lines:";
       
   507         LogWarning() << "[Paths]";
       
   508         LogWarning() << "  Plugins = PlugIns";
       
   509         return;
       
   510     }
       
   511 
       
   512     qtconf.open(QIODevice::WriteOnly);
       
   513     if (qtconf.write(contents) != -1) {
       
   514         LogNormal() << "Created configuration file:" << fileName;
       
   515         LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns";
       
   516     }
       
   517 }
       
   518 
       
   519 void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
       
   520 {
       
   521     ApplicationBundleInfo applicationBundle;
       
   522     applicationBundle.path = appBundlePath;
       
   523     applicationBundle.binaryPath = findAppBinary(appBundlePath);
       
   524 
       
   525     const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns";
       
   526     deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
       
   527 }
       
   528 
       
   529 
       
   530 void changeQtFrameworks(const QList<FrameworkInfo> frameworks, const QString &appBinaryPath, const QString &absoluteQtPath)
       
   531 {
       
   532     LogNormal() << "Changing" << appBinaryPath << "to link against";
       
   533     LogNormal() << "Qt in" << absoluteQtPath;
       
   534     QString finalQtPath = absoluteQtPath;
       
   535 
       
   536     if (!absoluteQtPath.startsWith("/Library/Frameworks"))
       
   537         finalQtPath += "/lib/";
       
   538 
       
   539     foreach (FrameworkInfo framework, frameworks) {
       
   540         const QString oldBinaryId = framework.installName;
       
   541         const QString newBinaryId = finalQtPath + framework.frameworkName +  framework.binaryPath;
       
   542         changeInstallName(oldBinaryId, newBinaryId, appBinaryPath);
       
   543     }
       
   544 }
       
   545 
       
   546 void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDebugLibs)
       
   547 {
       
   548     const QString appBinaryPath = findAppBinary(appPath);
       
   549     const QList<FrameworkInfo> frameworks = getQtFrameworks(appBinaryPath, useDebugLibs);
       
   550     if (frameworks.isEmpty()) {
       
   551         LogWarning();
       
   552         LogWarning() << "Could not find any _external_ Qt frameworks to change in" << appPath;
       
   553         return;
       
   554     } else {
       
   555         const QString absoluteQtPath = QDir(qtPath).absolutePath();
       
   556         changeQtFrameworks(frameworks, appBinaryPath, absoluteQtPath);
       
   557     }
       
   558 }
       
   559 
       
   560 
       
   561 void createDiskImage(const QString &appBundlePath)
       
   562 {
       
   563     QString appBaseName = appBundlePath;
       
   564     appBaseName.chop(4); // remove ".app" from end
       
   565 
       
   566     QString dmgName = appBaseName + ".dmg";
       
   567 
       
   568     QFile dmg(dmgName);
       
   569 
       
   570     if (dmg.exists()) {
       
   571         LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName();
       
   572     } else {
       
   573         LogNormal() << "Creating disk image (.dmg) for" << appBundlePath;
       
   574     }
       
   575 
       
   576     // More dmg options can be found in the hdiutil man page.
       
   577     QString options = QString("create %1.dmg -srcfolder %1.app -format UDZO -volname %1").arg(appBaseName);
       
   578 
       
   579     QProcess hdutil;
       
   580     hdutil.start("hdiutil", options.split(' '));
       
   581     hdutil.waitForFinished(-1);
       
   582 }