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

Last change on this file since 1147 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 22.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 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** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
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** 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 have questions regarding the use of this file, please contact
37** Nokia at qt-info@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;
53int logLevel = 1;
54
55using std::cout;
56using std::endl;
57
58bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
59{
60 return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
61}
62
63QDebug 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
80const QString bundleFrameworkDirectory = "Contents/Frameworks";
81const QString bundleBinaryDirectory = "Contents/MacOS";
82
83inline 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
90bool 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
103FrameworkInfo 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
200QString 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
211QList<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
225QList<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
247void 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
264QString 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
296void 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
307void 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
315void 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
324void 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*/
347DeploymentInfo 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 necessary.
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
401DeploymentInfo 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
418void 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
493void 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
519void 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
530void 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
546void 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
561void 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}
Note: See TracBrowser for help on using the repository browser.