miniupnpc Porfile removed; new and improved macdeployqtplus
[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, 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     if not framework.isDylib(): # Copy resources for real frameworks
261         fromResourcesDir = framework.sourceResourcesDirectory
262         if os.path.exists(fromResourcesDir):
263             toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
264             shutil.copytree(fromResourcesDir, toResourcesDir)
265             if verbose >= 3:
266                 print "Copied resources:", fromResourcesDir
267                 print " to:", toResourcesDir
268     elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
269         qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
270         qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
271         if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
272             shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath)
273             if verbose >= 3:
274                 print "Copied for libQtGui:", qtMenuNibSourcePath
275                 print " to:", qtMenuNibDestinationPath
276     
277     return toPath
278
279 def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
280     if deploymentInfo is None:
281         deploymentInfo = DeploymentInfo()
282     
283     while len(frameworks) > 0:
284         framework = frameworks.pop(0)
285         deploymentInfo.deployedFrameworks.append(framework.frameworkName)
286         
287         if verbose >= 2:
288             print "Processing", framework.frameworkName, "..."
289         
290         # Get the Qt path from one of the Qt frameworks
291         if deploymentInfo.qtPath is None and framework.isQtFramework():
292             deploymentInfo.detectQtPath(framework.frameworkDirectory)
293         
294         if framework.installName.startswith("@executable_path"):
295             if verbose >= 2:
296                 print framework.frameworkName, "already deployed, skipping."
297             continue
298         
299         # install_name_tool the new id into the binary
300         changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
301         
302         # Copy farmework to app bundle.
303         deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
304         # Skip the rest if already was deployed.
305         if deployedBinaryPath is None:
306             continue
307         
308         if strip:
309             runStrip(deployedBinaryPath, verbose)
310         
311         # install_name_tool it a new id.
312         changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
313         # Check for framework dependencies
314         dependencies = getFrameworks(deployedBinaryPath, verbose)
315         
316         for dependency in dependencies:
317             changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
318             
319             # Deploy framework if necessary.
320             if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
321                 frameworks.append(dependency)
322     
323     return deploymentInfo
324
325 def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
326     frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
327     if len(frameworks) == 0 and verbose >= 1:
328         print "Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path)
329         return DeploymentInfo()
330     else:
331         return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
332
333 def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
334     # Lookup available plugins, exclude unneeded
335     plugins = []
336     for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
337         pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
338         if pluginDirectory == "designer":
339             # Skip designer plugins
340             continue
341         elif pluginDirectory == "phonon":
342             # Deploy the phonon plugins only if phonon is in use
343             if not deploymentInfo.usesFramework("phonon"):
344                 continue
345         elif pluginDirectory == "sqldrivers":
346             # Deploy the sql plugins only if QtSql is in use
347             if not deploymentInfo.usesFramework("QtSql"):
348                 continue
349         elif pluginDirectory == "script":
350             # Deploy the script plugins only if QtScript is in use
351             if not deploymentInfo.usesFramework("QtScript"):
352                 continue
353         elif pluginDirectory == "qmltooling":
354             # Deploy the qml plugins only if QtDeclarative is in use
355             if not deploymentInfo.usesFramework("QtDeclarative"):
356                 continue
357         elif pluginDirectory == "bearer":
358             # Deploy the bearer plugins only if QtNetwork is in use
359             if not deploymentInfo.usesFramework("QtNetwork"):
360                 continue
361         
362         for pluginName in filenames:
363             pluginPath = os.path.join(pluginDirectory, pluginName)
364             if pluginName.endswith("_debug.dylib"):
365                 # Skip debug plugins
366                 continue
367             elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
368                 # Deploy the svg plugins only if QtSvg is in use
369                 if not deploymentInfo.usesFramework("QtSvg"):
370                     continue
371             elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
372                 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
373                 if not deploymentInfo.usesFramework("Qt3Support"):
374                     continue
375             elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
376                 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
377                 if not deploymentInfo.usesFramework("QtOpenGL"):
378                     continue
379             
380             plugins.append((pluginDirectory, pluginName))
381     
382     for pluginDirectory, pluginName in plugins:
383         if verbose >= 2:
384             print "Processing plugin", os.path.join(pluginDirectory, pluginName), "..."
385         
386         sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
387         destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
388         if not os.path.exists(destinationDirectory):
389             os.makedirs(destinationDirectory)
390         
391         destinationPath = os.path.join(destinationDirectory, pluginName)
392         shutil.copy2(sourcePath, destinationPath)
393         if verbose >= 3:
394             print "Copied:", sourcePath
395             print " to:", destinationPath
396         
397         if strip:
398             runStrip(destinationPath, verbose)
399         
400         dependencies = getFrameworks(destinationPath, verbose)
401         
402         for dependency in dependencies:
403             changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
404             
405             # Deploy framework if necessary.
406             if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
407                 deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
408
409 qt_conf="""[Paths]
410 translations=Resources
411 plugins=PlugIns
412 """
413
414 ap = ArgumentParser(description="""Improved version of macdeployqt.
415
416 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
417 Note, that the "dist" folder will be deleted before deploying on each run.
418
419 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.""")
420
421 ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
422 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")
423 ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
424 ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
425 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")
426 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")
427 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")
428 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")
429
430 config = ap.parse_args()
431
432 verbose = config.verbose[0]
433
434 # ------------------------------------------------
435
436 app_bundle = config.app_bundle[0]
437
438 if not os.path.exists(app_bundle):
439     if verbose >= 1:
440         sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
441     sys.exit(1)
442
443 app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
444
445 # ------------------------------------------------
446
447 for p in config.add_resources:
448     if verbose >= 3:
449         print "Checking for \"%s\"..." % p
450     if not os.path.exists(p):
451         if verbose >= 1:
452             sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
453         sys.exit(1)
454
455 # ------------------------------------------------
456
457 if len(config.fancy) == 1:
458     if verbose >= 3:
459         print "Fancy: Importing plistlib..."
460     try:
461         import plistlib
462     except ImportError:
463         if verbose >= 1:
464             sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
465         sys.exit(1)
466     
467     if verbose >= 3:
468         print "Fancy: Importing appscript..."
469     try:
470         import appscript
471     except ImportError:
472         if verbose >= 1:
473             sys.stderr.write("Error: Could not import appscript which is required for fancy disk images.\n")
474             sys.stderr.write("Please install it e.g. with \"sudo easy_install appscript\".\n")
475         sys.exit(1)
476     
477     p = config.fancy[0]
478     if verbose >= 3:
479         print "Fancy: Loading \"%s\"..." % p
480     if not os.path.exists(p):
481         if verbose >= 1:
482             sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
483         sys.exit(1)
484     
485     try:
486         fancy = plistlib.readPlist(p)
487     except:
488         if verbose >= 1:
489             sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
490         sys.exit(1)
491     
492     try:
493         assert not fancy.has_key("window_bounds") or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
494         assert not fancy.has_key("background_picture") or isinstance(fancy["background_picture"], str)
495         assert not fancy.has_key("icon_size") or isinstance(fancy["icon_size"], int)
496         assert not fancy.has_key("applications_symlink") or isinstance(fancy["applications_symlink"], bool)
497         if fancy.has_key("items_position"):
498             assert isinstance(fancy["items_position"], dict)
499             for key, value in fancy["items_position"].iteritems():
500                 assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
501     except:
502         if verbose >= 1:
503             sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
504         sys.exit(1)
505     
506     if fancy.has_key("background_picture"):
507         bp = fancy["background_picture"]
508         if verbose >= 3:
509             print "Fancy: Resolving background picture \"%s\"..." % bp
510         if not os.path.exists(bp):
511             bp = os.path.join(os.path.dirname(p), bp)
512             if not os.path.exists(bp):
513                 if verbose >= 1:
514                     sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
515                 sys.exit(1)
516             else:
517                 fancy["background_picture"] = bp
518 else:
519     fancy = None
520
521 # ------------------------------------------------
522
523 if os.path.exists("dist"):
524     if verbose >= 2:
525         print "+ Removing old dist folder +"
526     
527     shutil.rmtree("dist")
528
529 # ------------------------------------------------
530
531 target = os.path.join("dist", app_bundle)
532
533 if verbose >= 2:
534     print "+ Copying source bundle +"
535 if verbose >= 3:
536     print app_bundle, "->", target
537
538 os.mkdir("dist")
539 shutil.copytree(app_bundle, target)
540
541 applicationBundle = ApplicationBundleInfo(target)
542
543 # ------------------------------------------------
544
545 if verbose >= 2:
546     print "+ Deploying frameworks +"
547
548 try:
549     deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
550     if deploymentInfo.qtPath is None:
551         deploymentInfo.qtPath = os.getenv("QTDIR", None)
552         if deploymentInfo.qtPath is None:
553             if verbose >= 1:
554                 sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
555             config.plugins = False
556 except RuntimeError as e:
557     if verbose >= 1:
558         sys.stderr.write("Error: %s\n" % str(e))
559     sys.exit(ret)
560
561 # ------------------------------------------------
562
563 if config.plugins:
564     if verbose >= 2:
565         print "+ Deploying plugins +"
566     
567     try:
568         deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
569     except RuntimeError as e:
570         if verbose >= 1:
571             sys.stderr.write("Error: %s\n" % str(e))
572         sys.exit(ret)
573
574 # ------------------------------------------------
575
576 if len(config.add_qt_tr) == 0:
577     add_qt_tr = []
578 else:
579     qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
580     add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
581     for lng_file in add_qt_tr:
582         p = os.path.join(qt_tr_dir, lng_file)
583         if verbose >= 3:
584             print "Checking for \"%s\"..." % p
585         if not os.path.exists(p):
586             if verbose >= 1:
587                 sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
588                 sys.exit(1)
589
590 # ------------------------------------------------
591
592 if verbose >= 2:
593     print "+ Installing qt.conf +"
594
595 f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")
596 f.write(qt_conf)
597 f.close()
598
599 # ------------------------------------------------
600
601 if len(add_qt_tr) > 0 and verbose >= 2:
602     print "+ Adding Qt translations +"
603
604 for lng_file in add_qt_tr:
605     if verbose >= 3:
606         print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file)
607     shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
608
609 # ------------------------------------------------
610
611 if len(config.add_resources) > 0 and verbose >= 2:
612     print "+ Adding additional resources +"
613
614 for p in config.add_resources:
615     t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
616     if verbose >= 3:
617         print p, "->", t
618     if os.path.isdir(p):
619         shutil.copytree(p, t)
620     else:
621         shutil.copy2(p, t)
622
623 # ------------------------------------------------
624
625 if config.dmg is not None:
626     def runHDIUtil(verb, image_basename, **kwargs):
627         hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
628         if kwargs.has_key("capture_stdout"):
629             del kwargs["capture_stdout"]
630             run = subprocess.check_output
631         else:
632             if verbose < 2:
633                 hdiutil_args.append("-quiet")
634             elif verbose >= 3:
635                 hdiutil_args.append("-verbose")
636             run = subprocess.check_call
637         
638         for key, value in kwargs.iteritems():
639             hdiutil_args.append("-" + key)
640             if not value is True:
641                 hdiutil_args.append(str(value))
642         
643         return run(hdiutil_args)
644     
645     if verbose >= 2:
646         if fancy is None:
647             print "+ Creating .dmg disk image +"
648         else:
649             print "+ Preparing .dmg disk image +"
650     
651     if config.dmg != "":
652         dmg_name = config.dmg
653     else:
654         spl = app_bundle_name.split(" ")
655         dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
656     
657     if fancy is None:
658         try:
659             runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True)
660         except subprocess.CalledProcessError as e:
661             sys.exit(e.returncode)
662     else:
663         if verbose >= 3:
664             print "Determining size of \"dist\"..."
665         size = 0
666         for path, dirs, files in os.walk("dist"):
667             for file in files:
668                 size += os.path.getsize(os.path.join(path, file))
669         size += int(size * 0.1)
670         
671         if verbose >= 3:
672             print "Creating temp image for modification..."
673         try:
674             runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True)
675         except subprocess.CalledProcessError as e:
676             sys.exit(e.returncode)
677         
678         if verbose >= 3:
679             print "Attaching temp image..."
680         try:
681             output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
682         except subprocess.CalledProcessError as e:
683             sys.exit(e.returncode)
684         
685         m = re.search("/Volumes/(.+$)", output)
686         disk_root = m.group(0)
687         disk_name = m.group(1)
688         
689         if verbose >= 2:
690             print "+ Applying fancy settings +"
691         
692         if fancy.has_key("background_picture"):
693             bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
694             if verbose >= 3:
695                 print fancy["background_picture"], "->", bg_path
696             shutil.copy2(fancy["background_picture"], bg_path)
697         else:
698             bg_path = None
699         
700         if fancy.get("applications_symlink", False):
701             os.symlink("/Applications", os.path.join(disk_root, "Applications"))
702         
703         finder = appscript.app("Finder")
704         disk = finder.disks[disk_name]
705         disk.open()
706         window = disk.container_window
707         window.current_view.set(appscript.k.icon_view)
708         window.toolbar_visible.set(False)
709         window.statusbar_visible.set(False)
710         if fancy.has_key("window_bounds"):
711             window.bounds.set(fancy["window_bounds"])
712         view_options = window.icon_view_options
713         view_options.arrangement.set(appscript.k.not_arranged)
714         if fancy.has_key("icon_size"):
715             view_options.icon_size.set(fancy["icon_size"])
716         if bg_path is not None:
717             view_options.background_picture.set(disk.files[os.path.basename(bg_path)])
718         if fancy.has_key("items_position"):
719             for name, position in fancy["items_position"].iteritems():
720                 window.items[name].position.set(position)
721         disk.close()
722         if bg_path is not None:
723             subprocess.call(["SetFile", "-a", "V", bg_path])
724         disk.update(registering_applications=False)
725         sleep(2)
726         disk.eject()
727         
728         if verbose >= 2:
729             print "+ Finalizing .dmg disk image +"
730         
731         try:
732             runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
733         except subprocess.CalledProcessError as e:
734             sys.exit(e.returncode)
735         
736         os.unlink(dmg_name + ".temp.dmg")
737
738 # ------------------------------------------------
739
740 if verbose >= 2:
741     print "+ Done +"
742
743 sys.exit(0)