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