7981eca402e416f866ba1436d7ae9a5414618a29
[novacoin.git] / contrib / macdeploy / macdeployqtplus
1 #!/usr/bin/env python
2
3 #
4 # Copyright (C) 2011  Patrick "p2k" Schneider <me@p2k-network.org>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import subprocess, sys, re, os, shutil, stat, os.path
21 from string import Template
22 from time import sleep
23 from argparse import ArgumentParser
24
25 # This is ported from the original macdeployqt with modifications
26
27 class FrameworkInfo(object):
28     def __init__(self):
29         self.frameworkDirectory = ""
30         self.frameworkName = ""
31         self.frameworkPath = ""
32         self.binaryDirectory = ""
33         self.binaryName = ""
34         self.binaryPath = ""
35         self.version = ""
36         self.installName = ""
37         self.deployedInstallName = ""
38         self.sourceFilePath = ""
39         self.destinationDirectory = ""
40         self.sourceResourcesDirectory = ""
41         self.destinationResourcesDirectory = ""
42     
43     def __eq__(self, other):
44         if self.__class__ == other.__class__:
45             return self.__dict__ == other.__dict__
46         else:
47             return False
48     
49     def __str__(self):
50         return """ Framework name: %s
51  Framework directory: %s
52  Framework path: %s
53  Binary name: %s
54  Binary directory: %s
55  Binary path: %s
56  Version: %s
57  Install name: %s
58  Deployed install name: %s
59  Source file Path: %s
60  Deployed Directory (relative to bundle): %s
61 """ % (self.frameworkName,
62        self.frameworkDirectory,
63        self.frameworkPath,
64        self.binaryName,
65        self.binaryDirectory,
66        self.binaryPath,
67        self.version,
68        self.installName,
69        self.deployedInstallName,
70        self.sourceFilePath,
71        self.destinationDirectory)
72     
73     def isDylib(self):
74         return self.frameworkName.endswith(".dylib")
75     
76     def isQtFramework(self):
77         if self.isDylib():
78             return self.frameworkName.startswith("libQt")
79         else:
80             return self.frameworkName.startswith("Qt")
81     
82     reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
83     bundleFrameworkDirectory = "Contents/Frameworks"
84     bundleBinaryDirectory = "Contents/MacOS"
85     
86     @classmethod
87     def fromOtoolLibraryLine(cls, line):
88         # Note: line must be trimmed
89         if line == "":
90             return None
91         
92         # Don't deploy system libraries (exception for libQtuitools and libQtlucene).
93         if line.startswith("/System/Library/") or line.startswith("@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line):
94             return None
95         
96         m = cls.reOLine.match(line)
97         if m is None:
98             raise RuntimeError("otool line could not be parsed: " + line)
99         
100         path = m.group(1)
101         
102         info = cls()
103         info.sourceFilePath = path
104         info.installName = path
105         
106         if path.endswith(".dylib"):
107             dirname, filename = os.path.split(path)
108             info.frameworkName = filename
109             info.frameworkDirectory = dirname
110             info.frameworkPath = path
111             
112             info.binaryDirectory = dirname
113             info.binaryName = filename
114             info.binaryPath = path
115             info.version = "-"
116             
117             info.installName = path
118             info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName
119             info.sourceFilePath = path
120             info.destinationDirectory = cls.bundleFrameworkDirectory
121         else:
122             parts = path.split("/")
123             i = 0
124             # Search for the .framework directory
125             for part in parts:
126                 if part.endswith(".framework"):
127                     break
128                 i += 1
129             if i == len(parts):
130                 raise RuntimeError("Could not find .framework or .dylib in otool line: " + line)
131             
132             info.frameworkName = parts[i]
133             info.frameworkDirectory = "/".join(parts[:i])
134             info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
135             
136             info.binaryName = parts[i+3]
137             info.binaryDirectory = "/".join(parts[i+1:i+3])
138             info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
139             info.version = parts[i+2]
140             
141             info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath)
142             info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
143             
144             info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
145             info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
146         
147         return info
148
149 class ApplicationBundleInfo(object):
150     def __init__(self, path):
151         self.path = path
152         appName = os.path.splitext(os.path.basename(path))[0]
153         self.binaryPath = os.path.join(path, "Contents", "MacOS", appName)
154         if not os.path.exists(self.binaryPath):
155             raise RuntimeError("Could not find bundle binary for " + path)
156         self.resourcesPath = os.path.join(path, "Contents", "Resources")
157         self.pluginPath = os.path.join(path, "Contents", "PlugIns")
158
159 class DeploymentInfo(object):
160     def __init__(self):
161         self.qtPath = None
162         self.pluginPath = None
163         self.deployedFrameworks = []
164     
165     def detectQtPath(self, frameworkDirectory):
166         parentDir = os.path.dirname(frameworkDirectory)
167         if os.path.exists(os.path.join(parentDir, "translations")):
168             # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
169             self.qtPath = parentDir
170         elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")):
171             # MacPorts layout, e.g. "/opt/local/share/qt4"
172             self.qtPath = os.path.join(parentDir, "share", "qt4")
173         elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")):
174             # Newer Macports layout
175             self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4")
176         else:
177             self.qtPath = os.getenv("QTDIR", None)
178
179         if self.qtPath is not None:
180             pluginPath = os.path.join(self.qtPath, "plugins")
181             if os.path.exists(pluginPath):
182                 self.pluginPath = pluginPath
183     
184     def usesFramework(self, name):
185         nameDot = "%s." % name
186         libNameDot = "lib%s." % name
187         for framework in self.deployedFrameworks:
188             if framework.endswith(".framework"):
189                 if framework.startswith(nameDot):
190                     return True
191             elif framework.endswith(".dylib"):
192                 if framework.startswith(libNameDot):
193                     return True
194         return False
195
196 def getFrameworks(binaryPath, verbose):
197     if verbose >= 3:
198         print "Inspecting with otool: " + binaryPath
199     otool = subprocess.Popen(["otool", "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
200     o_stdout, o_stderr = otool.communicate()
201     if otool.returncode != 0:
202         if verbose >= 1:
203             sys.stderr.write(o_stderr)
204             sys.stderr.flush()
205             raise RuntimeError("otool failed with return code %d" % otool.returncode)
206     
207     otoolLines = o_stdout.split("\n")
208     otoolLines.pop(0) # First line is the inspected binary
209     if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
210         otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
211     
212     libraries = []
213     for line in otoolLines:
214         info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
215         if info is not None:
216             if verbose >= 3:
217                 print "Found framework:"
218                 print info
219             libraries.append(info)
220     
221     return libraries
222
223 def runInstallNameTool(action, *args):
224     subprocess.check_call(["install_name_tool", "-"+action] + list(args))
225
226 def changeInstallName(oldName, newName, binaryPath, verbose):
227     if verbose >= 3:
228         print "Using install_name_tool:"
229         print " in", binaryPath
230         print " change reference", oldName
231         print " to", newName
232     runInstallNameTool("change", oldName, newName, binaryPath)
233
234 def changeIdentification(id, binaryPath, verbose):
235     if verbose >= 3:
236         print "Using install_name_tool:"
237         print " change identification in", binaryPath
238         print " to", id
239     runInstallNameTool("id", id, binaryPath)
240
241 def runStrip(binaryPath, verbose):
242     if verbose >= 3:
243         print "Using strip:"
244         print " stripped", binaryPath
245     subprocess.check_call(["strip", "-x", binaryPath])
246
247 def copyFramework(framework, path, verbose):
248     if framework.sourceFilePath.startswith("Qt"):
249         #standard place for Nokia Qt installer's frameworks
250         fromPath = "/Library/Frameworks/" + framework.sourceFilePath
251     else:
252         fromPath = framework.sourceFilePath
253
254     toDir = os.path.join(path, framework.destinationDirectory)
255     toPath = os.path.join(toDir, framework.binaryName)
256     
257     if not os.path.exists(fromPath):
258         raise RuntimeError("No file at " + fromPath)
259     
260     if os.path.exists(toPath):
261         return None # Already there
262     
263     if not os.path.exists(toDir):
264         os.makedirs(toDir)
265     
266     shutil.copy2(fromPath, toPath)
267     if verbose >= 3:
268         print "Copied:", fromPath
269         print " to:", toPath
270
271     permissions = os.stat(toPath)
272     if not permissions.st_mode & stat.S_IWRITE:
273       os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
274
275     if not framework.isDylib(): # Copy resources for real frameworks
276         fromResourcesDir = framework.sourceResourcesDirectory
277         if os.path.exists(fromResourcesDir):
278             toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
279             shutil.copytree(fromResourcesDir, toResourcesDir)
280             if verbose >= 3:
281                 print "Copied resources:", fromResourcesDir
282                 print " to:", toResourcesDir
283     elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
284         qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
285         qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
286         if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
287             shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath)
288             if verbose >= 3:
289                 print "Copied for libQtGui:", qtMenuNibSourcePath
290                 print " to:", qtMenuNibDestinationPath
291     
292     return toPath
293
294 def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
295     if deploymentInfo is None:
296         deploymentInfo = DeploymentInfo()
297     
298     while len(frameworks) > 0:
299         framework = frameworks.pop(0)
300         deploymentInfo.deployedFrameworks.append(framework.frameworkName)
301         
302         if verbose >= 2:
303             print "Processing", framework.frameworkName, "..."
304         
305         # Get the Qt path from one of the Qt frameworks
306         if deploymentInfo.qtPath is None and framework.isQtFramework():
307             deploymentInfo.detectQtPath(framework.frameworkDirectory)
308         
309         if framework.installName.startswith("@executable_path"):
310             if verbose >= 2:
311                 print framework.frameworkName, "already deployed, skipping."
312             continue
313         
314         # install_name_tool the new id into the binary
315         changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
316         
317         # Copy farmework to app bundle.
318         deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
319         # Skip the rest if already was deployed.
320         if deployedBinaryPath is None:
321             continue
322         
323         if strip:
324             runStrip(deployedBinaryPath, verbose)
325         
326         # install_name_tool it a new id.
327         changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
328         # Check for framework dependencies
329         dependencies = getFrameworks(deployedBinaryPath, verbose)
330         
331         for dependency in dependencies:
332             changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
333             
334             # Deploy framework if necessary.
335             if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
336                 frameworks.append(dependency)
337     
338     return deploymentInfo
339
340 def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
341     frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
342     if len(frameworks) == 0 and verbose >= 1:
343         print "Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path)
344         return DeploymentInfo()
345     else:
346         return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
347
348 def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
349     # Lookup available plugins, exclude unneeded
350     plugins = []
351     for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
352         pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
353         if pluginDirectory == "designer":
354             # Skip designer plugins
355             continue
356         elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
357             # Deploy the phonon plugins only if phonon is in use
358             if not deploymentInfo.usesFramework("phonon"):
359                 continue
360         elif pluginDirectory == "sqldrivers":
361             # Deploy the sql plugins only if QtSql is in use
362             if not deploymentInfo.usesFramework("QtSql"):
363                 continue
364         elif pluginDirectory == "script":
365             # Deploy the script plugins only if QtScript is in use
366             if not deploymentInfo.usesFramework("QtScript"):
367                 continue
368         elif pluginDirectory == "qmltooling":
369             # Deploy the qml plugins only if QtDeclarative is in use
370             if not deploymentInfo.usesFramework("QtDeclarative"):
371                 continue
372         elif pluginDirectory == "bearer":
373             # Deploy the bearer plugins only if QtNetwork is in use
374             if not deploymentInfo.usesFramework("QtNetwork"):
375                 continue
376         
377         for pluginName in filenames:
378             pluginPath = os.path.join(pluginDirectory, pluginName)
379             if pluginName.endswith("_debug.dylib"):
380                 # Skip debug plugins
381                 continue
382             elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
383                 # Deploy the svg plugins only if QtSvg is in use
384                 if not deploymentInfo.usesFramework("QtSvg"):
385                     continue
386             elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
387                 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
388                 if not deploymentInfo.usesFramework("Qt3Support"):
389                     continue
390             elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
391                 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
392                 if not deploymentInfo.usesFramework("QtOpenGL"):
393                     continue
394             
395             plugins.append((pluginDirectory, pluginName))
396     
397     for pluginDirectory, pluginName in plugins:
398         if verbose >= 2:
399             print "Processing plugin", os.path.join(pluginDirectory, pluginName), "..."
400         
401         sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
402         destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
403         if not os.path.exists(destinationDirectory):
404             os.makedirs(destinationDirectory)
405         
406         destinationPath = os.path.join(destinationDirectory, pluginName)
407         shutil.copy2(sourcePath, destinationPath)
408         if verbose >= 3:
409             print "Copied:", sourcePath
410             print " to:", destinationPath
411         
412         if strip:
413             runStrip(destinationPath, verbose)
414         
415         dependencies = getFrameworks(destinationPath, verbose)
416         
417         for dependency in dependencies:
418             changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
419             
420             # Deploy framework if necessary.
421             if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
422                 deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
423
424 qt_conf="""[Paths]
425 translations=Resources
426 plugins=PlugIns
427 """
428
429 ap = ArgumentParser(description="""Improved version of macdeployqt.
430
431 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
432 Note, that the "dist" folder will be deleted before deploying on each run.
433
434 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.""")
435
436 ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
437 ap.add_argument("-verbose", type=int, nargs=1, default=[1], metavar="<0-3>", help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug")
438 ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
439 ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
440 ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used")
441 ap.add_argument("-fancy", nargs=1, metavar="plist", default=[], help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work")
442 ap.add_argument("-add-qt-tr", nargs=1, metavar="languages", default=[], help="add Qt translation files to the bundle's ressources; the language list must be separated with commas, not with whitespace")
443 ap.add_argument("-add-resources", nargs="+", metavar="path", default=[], help="list of additional files or folders to be copied into the bundle's resources; must be the last argument")
444
445 config = ap.parse_args()
446
447 verbose = config.verbose[0]
448
449 # ------------------------------------------------
450
451 app_bundle = config.app_bundle[0]
452
453 if not os.path.exists(app_bundle):
454     if verbose >= 1:
455         sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
456     sys.exit(1)
457
458 app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
459
460 # ------------------------------------------------
461
462 for p in config.add_resources:
463     if verbose >= 3:
464         print "Checking for \"%s\"..." % p
465     if not os.path.exists(p):
466         if verbose >= 1:
467             sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
468         sys.exit(1)
469
470 # ------------------------------------------------
471
472 if len(config.fancy) == 1:
473     if verbose >= 3:
474         print "Fancy: Importing plistlib..."
475     try:
476         import plistlib
477     except ImportError:
478         if verbose >= 1:
479             sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
480         sys.exit(1)
481     
482     if verbose >= 3:
483         print "Fancy: Importing appscript..."
484     try:
485         import appscript
486     except ImportError:
487         if verbose >= 1:
488             sys.stderr.write("Error: Could not import appscript which is required for fancy disk images.\n")
489             sys.stderr.write("Please install it e.g. with \"sudo easy_install appscript\".\n")
490         sys.exit(1)
491     
492     p = config.fancy[0]
493     if verbose >= 3:
494         print "Fancy: Loading \"%s\"..." % p
495     if not os.path.exists(p):
496         if verbose >= 1:
497             sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
498         sys.exit(1)
499     
500     try:
501         fancy = plistlib.readPlist(p)
502     except:
503         if verbose >= 1:
504             sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
505         sys.exit(1)
506     
507     try:
508         assert not fancy.has_key("window_bounds") or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
509         assert not fancy.has_key("background_picture") or isinstance(fancy["background_picture"], str)
510         assert not fancy.has_key("icon_size") or isinstance(fancy["icon_size"], int)
511         assert not fancy.has_key("applications_symlink") or isinstance(fancy["applications_symlink"], bool)
512         if fancy.has_key("items_position"):
513             assert isinstance(fancy["items_position"], dict)
514             for key, value in fancy["items_position"].iteritems():
515                 assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
516     except:
517         if verbose >= 1:
518             sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
519         sys.exit(1)
520     
521     if fancy.has_key("background_picture"):
522         bp = fancy["background_picture"]
523         if verbose >= 3:
524             print "Fancy: Resolving background picture \"%s\"..." % bp
525         if not os.path.exists(bp):
526             bp = os.path.join(os.path.dirname(p), bp)
527             if not os.path.exists(bp):
528                 if verbose >= 1:
529                     sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
530                 sys.exit(1)
531             else:
532                 fancy["background_picture"] = bp
533 else:
534     fancy = None
535
536 # ------------------------------------------------
537
538 if os.path.exists("dist"):
539     if verbose >= 2:
540         print "+ Removing old dist folder +"
541     
542     shutil.rmtree("dist")
543
544 # ------------------------------------------------
545
546 target = os.path.join("dist", app_bundle)
547
548 if verbose >= 2:
549     print "+ Copying source bundle +"
550 if verbose >= 3:
551     print app_bundle, "->", target
552
553 os.mkdir("dist")
554 shutil.copytree(app_bundle, target)
555
556 applicationBundle = ApplicationBundleInfo(target)
557
558 # ------------------------------------------------
559
560 if verbose >= 2:
561     print "+ Deploying frameworks +"
562
563 try:
564     deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
565     if deploymentInfo.qtPath is None:
566         deploymentInfo.qtPath = os.getenv("QTDIR", None)
567         if deploymentInfo.qtPath is None:
568             if verbose >= 1:
569                 sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
570             config.plugins = False
571 except RuntimeError as e:
572     if verbose >= 1:
573         sys.stderr.write("Error: %s\n" % str(e))
574     sys.exit(ret)
575
576 # ------------------------------------------------
577
578 if config.plugins:
579     if verbose >= 2:
580         print "+ Deploying plugins +"
581     
582     try:
583         deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
584     except RuntimeError as e:
585         if verbose >= 1:
586             sys.stderr.write("Error: %s\n" % str(e))
587         sys.exit(ret)
588
589 # ------------------------------------------------
590
591 if len(config.add_qt_tr) == 0:
592     add_qt_tr = []
593 else:
594     qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
595     add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
596     for lng_file in add_qt_tr:
597         p = os.path.join(qt_tr_dir, lng_file)
598         if verbose >= 3:
599             print "Checking for \"%s\"..." % p
600         if not os.path.exists(p):
601             if verbose >= 1:
602                 sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
603                 sys.exit(1)
604
605 # ------------------------------------------------
606
607 if verbose >= 2:
608     print "+ Installing qt.conf +"
609
610 f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")
611 f.write(qt_conf)
612 f.close()
613
614 # ------------------------------------------------
615
616 if len(add_qt_tr) > 0 and verbose >= 2:
617     print "+ Adding Qt translations +"
618
619 for lng_file in add_qt_tr:
620     if verbose >= 3:
621         print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file)
622     shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
623
624 # ------------------------------------------------
625
626 if len(config.add_resources) > 0 and verbose >= 2:
627     print "+ Adding additional resources +"
628
629 for p in config.add_resources:
630     t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
631     if verbose >= 3:
632         print p, "->", t
633     if os.path.isdir(p):
634         shutil.copytree(p, t)
635     else:
636         shutil.copy2(p, t)
637
638 # ------------------------------------------------
639
640 if config.dmg is not None:
641     def runHDIUtil(verb, image_basename, **kwargs):
642         hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
643         if kwargs.has_key("capture_stdout"):
644             del kwargs["capture_stdout"]
645             run = subprocess.check_output
646         else:
647             if verbose < 2:
648                 hdiutil_args.append("-quiet")
649             elif verbose >= 3:
650                 hdiutil_args.append("-verbose")
651             run = subprocess.check_call
652         
653         for key, value in kwargs.iteritems():
654             hdiutil_args.append("-" + key)
655             if not value is True:
656                 hdiutil_args.append(str(value))
657         
658         return run(hdiutil_args)
659     
660     if verbose >= 2:
661         if fancy is None:
662             print "+ Creating .dmg disk image +"
663         else:
664             print "+ Preparing .dmg disk image +"
665     
666     if config.dmg != "":
667         dmg_name = config.dmg
668     else:
669         spl = app_bundle_name.split(" ")
670         dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
671     
672     if fancy is None:
673         try:
674             runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True)
675         except subprocess.CalledProcessError as e:
676             sys.exit(e.returncode)
677     else:
678         if verbose >= 3:
679             print "Determining size of \"dist\"..."
680         size = 0
681         for path, dirs, files in os.walk("dist"):
682             for file in files:
683                 size += os.path.getsize(os.path.join(path, file))
684         size += int(size * 0.1)
685         
686         if verbose >= 3:
687             print "Creating temp image for modification..."
688         try:
689             runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True)
690         except subprocess.CalledProcessError as e:
691             sys.exit(e.returncode)
692         
693         if verbose >= 3:
694             print "Attaching temp image..."
695         try:
696             output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
697         except subprocess.CalledProcessError as e:
698             sys.exit(e.returncode)
699         
700         m = re.search("/Volumes/(.+$)", output)
701         disk_root = m.group(0)
702         disk_name = m.group(1)
703         
704         if verbose >= 2:
705             print "+ Applying fancy settings +"
706         
707         if fancy.has_key("background_picture"):
708             bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
709             if verbose >= 3:
710                 print fancy["background_picture"], "->", bg_path
711             shutil.copy2(fancy["background_picture"], bg_path)
712         else:
713             bg_path = None
714         
715         if fancy.get("applications_symlink", False):
716             os.symlink("/Applications", os.path.join(disk_root, "Applications"))
717         
718         finder = appscript.app("Finder")
719         disk = finder.disks[disk_name]
720         disk.open()
721         window = disk.container_window
722         window.current_view.set(appscript.k.icon_view)
723         window.toolbar_visible.set(False)
724         window.statusbar_visible.set(False)
725         if fancy.has_key("window_bounds"):
726             window.bounds.set(fancy["window_bounds"])
727         view_options = window.icon_view_options
728         view_options.arrangement.set(appscript.k.not_arranged)
729         if fancy.has_key("icon_size"):
730             view_options.icon_size.set(fancy["icon_size"])
731         if bg_path is not None:
732             view_options.background_picture.set(disk.files[os.path.basename(bg_path)])
733         if fancy.has_key("items_position"):
734             for name, position in fancy["items_position"].iteritems():
735                 window.items[name].position.set(position)
736         disk.close()
737         if bg_path is not None:
738             subprocess.call(["SetFile", "-a", "V", bg_path])
739         disk.update(registering_applications=False)
740         sleep(2)
741         disk.eject()
742         
743         if verbose >= 2:
744             print "+ Finalizing .dmg disk image +"
745         
746         try:
747             runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
748         except subprocess.CalledProcessError as e:
749             sys.exit(e.returncode)
750         
751         os.unlink(dmg_name + ".temp.dmg")
752
753 # ------------------------------------------------
754
755 if verbose >= 2:
756     print "+ Done +"
757
758 sys.exit(0)