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