source: trunk/tools/macdeployqt/shared/shared.cpp@ 235

Last change on this file since 235 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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