Fix typos
[bitcoinplatinum.git] / contrib / macdeploy / macdeployqtplus
blob5995f9f438983b6050c361459af41c1eb5056fee
1 #!/usr/bin/env python
2 from __future__ import division, print_function, unicode_literals
4 # Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
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.
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.
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/>.
20 import subprocess, sys, re, os, shutil, stat, os.path, time
21 from string import Template
22 from argparse import ArgumentParser
24 # This is ported from the original macdeployqt with modifications
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.sourceVersionContentsDirectory = ""
41 self.sourceContentsDirectory = ""
42 self.destinationResourcesDirectory = ""
43 self.destinationVersionContentsDirectory = ""
45 def __eq__(self, other):
46 if self.__class__ == other.__class__:
47 return self.__dict__ == other.__dict__
48 else:
49 return False
51 def __str__(self):
52 return """ Framework name: %s
53 Framework directory: %s
54 Framework path: %s
55 Binary name: %s
56 Binary directory: %s
57 Binary path: %s
58 Version: %s
59 Install name: %s
60 Deployed install name: %s
61 Source file Path: %s
62 Deployed Directory (relative to bundle): %s
63 """ % (self.frameworkName,
64 self.frameworkDirectory,
65 self.frameworkPath,
66 self.binaryName,
67 self.binaryDirectory,
68 self.binaryPath,
69 self.version,
70 self.installName,
71 self.deployedInstallName,
72 self.sourceFilePath,
73 self.destinationDirectory)
75 def isDylib(self):
76 return self.frameworkName.endswith(".dylib")
78 def isQtFramework(self):
79 if self.isDylib():
80 return self.frameworkName.startswith("libQt")
81 else:
82 return self.frameworkName.startswith("Qt")
84 reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
85 bundleFrameworkDirectory = "Contents/Frameworks"
86 bundleBinaryDirectory = "Contents/MacOS"
88 @classmethod
89 def fromOtoolLibraryLine(cls, line):
90 # Note: line must be trimmed
91 if line == "":
92 return None
94 # Don't deploy system libraries (exception for libQtuitools and libQtlucene).
95 if line.startswith("/System/Library/") or line.startswith("@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line):
96 return None
98 m = cls.reOLine.match(line)
99 if m is None:
100 raise RuntimeError("otool line could not be parsed: " + line)
102 path = m.group(1)
104 info = cls()
105 info.sourceFilePath = path
106 info.installName = path
108 if path.endswith(".dylib"):
109 dirname, filename = os.path.split(path)
110 info.frameworkName = filename
111 info.frameworkDirectory = dirname
112 info.frameworkPath = path
114 info.binaryDirectory = dirname
115 info.binaryName = filename
116 info.binaryPath = path
117 info.version = "-"
119 info.installName = path
120 info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName
121 info.sourceFilePath = path
122 info.destinationDirectory = cls.bundleFrameworkDirectory
123 else:
124 parts = path.split("/")
125 i = 0
126 # Search for the .framework directory
127 for part in parts:
128 if part.endswith(".framework"):
129 break
130 i += 1
131 if i == len(parts):
132 raise RuntimeError("Could not find .framework or .dylib in otool line: " + line)
134 info.frameworkName = parts[i]
135 info.frameworkDirectory = "/".join(parts[:i])
136 info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
138 info.binaryName = parts[i+3]
139 info.binaryDirectory = "/".join(parts[i+1:i+3])
140 info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
141 info.version = parts[i+2]
143 info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath)
144 info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
146 info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
147 info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents")
148 info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents")
149 info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
150 info.destinationContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Contents")
151 info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents")
153 return info
155 class ApplicationBundleInfo(object):
156 def __init__(self, path):
157 self.path = path
158 appName = "Bitcoin-Qt"
159 self.binaryPath = os.path.join(path, "Contents", "MacOS", appName)
160 if not os.path.exists(self.binaryPath):
161 raise RuntimeError("Could not find bundle binary for " + path)
162 self.resourcesPath = os.path.join(path, "Contents", "Resources")
163 self.pluginPath = os.path.join(path, "Contents", "PlugIns")
165 class DeploymentInfo(object):
166 def __init__(self):
167 self.qtPath = None
168 self.pluginPath = None
169 self.deployedFrameworks = []
171 def detectQtPath(self, frameworkDirectory):
172 parentDir = os.path.dirname(frameworkDirectory)
173 if os.path.exists(os.path.join(parentDir, "translations")):
174 # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
175 self.qtPath = parentDir
176 elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")):
177 # MacPorts layout, e.g. "/opt/local/share/qt4"
178 self.qtPath = os.path.join(parentDir, "share", "qt4")
179 elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")):
180 # Newer Macports layout
181 self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4")
182 else:
183 self.qtPath = os.getenv("QTDIR", None)
185 if self.qtPath is not None:
186 pluginPath = os.path.join(self.qtPath, "plugins")
187 if os.path.exists(pluginPath):
188 self.pluginPath = pluginPath
190 def usesFramework(self, name):
191 nameDot = "%s." % name
192 libNameDot = "lib%s." % name
193 for framework in self.deployedFrameworks:
194 if framework.endswith(".framework"):
195 if framework.startswith(nameDot):
196 return True
197 elif framework.endswith(".dylib"):
198 if framework.startswith(libNameDot):
199 return True
200 return False
202 def getFrameworks(binaryPath, verbose):
203 if verbose >= 3:
204 print("Inspecting with otool: " + binaryPath)
205 otoolbin=os.getenv("OTOOL", "otool")
206 otool = subprocess.Popen([otoolbin, "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
207 o_stdout, o_stderr = otool.communicate()
208 if otool.returncode != 0:
209 if verbose >= 1:
210 sys.stderr.write(o_stderr)
211 sys.stderr.flush()
212 raise RuntimeError("otool failed with return code %d" % otool.returncode)
214 otoolLines = o_stdout.decode().split("\n")
215 otoolLines.pop(0) # First line is the inspected binary
216 if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
217 otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
219 libraries = []
220 for line in otoolLines:
221 line = line.replace("@loader_path", os.path.dirname(binaryPath))
222 info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
223 if info is not None:
224 if verbose >= 3:
225 print("Found framework:")
226 print(info)
227 libraries.append(info)
229 return libraries
231 def runInstallNameTool(action, *args):
232 installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool")
233 subprocess.check_call([installnametoolbin, "-"+action] + list(args))
235 def changeInstallName(oldName, newName, binaryPath, verbose):
236 if verbose >= 3:
237 print("Using install_name_tool:")
238 print(" in", binaryPath)
239 print(" change reference", oldName)
240 print(" to", newName)
241 runInstallNameTool("change", oldName, newName, binaryPath)
243 def changeIdentification(id, binaryPath, verbose):
244 if verbose >= 3:
245 print("Using install_name_tool:")
246 print(" change identification in", binaryPath)
247 print(" to", id)
248 runInstallNameTool("id", id, binaryPath)
250 def runStrip(binaryPath, verbose):
251 stripbin=os.getenv("STRIP", "strip")
252 if verbose >= 3:
253 print("Using strip:")
254 print(" stripped", binaryPath)
255 subprocess.check_call([stripbin, "-x", binaryPath])
257 def copyFramework(framework, path, verbose):
258 if framework.sourceFilePath.startswith("Qt"):
259 #standard place for Nokia Qt installer's frameworks
260 fromPath = "/Library/Frameworks/" + framework.sourceFilePath
261 else:
262 fromPath = framework.sourceFilePath
263 toDir = os.path.join(path, framework.destinationDirectory)
264 toPath = os.path.join(toDir, framework.binaryName)
266 if not os.path.exists(fromPath):
267 raise RuntimeError("No file at " + fromPath)
269 if os.path.exists(toPath):
270 return None # Already there
272 if not os.path.exists(toDir):
273 os.makedirs(toDir)
275 shutil.copy2(fromPath, toPath)
276 if verbose >= 3:
277 print("Copied:", fromPath)
278 print(" to:", toPath)
280 permissions = os.stat(toPath)
281 if not permissions.st_mode & stat.S_IWRITE:
282 os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
284 if not framework.isDylib(): # Copy resources for real frameworks
286 linkfrom = os.path.join(path, "Contents","Frameworks", framework.frameworkName, "Versions", "Current")
287 linkto = framework.version
288 if not os.path.exists(linkfrom):
289 os.symlink(linkto, linkfrom)
290 if verbose >= 2:
291 print("Linked:", linkfrom, "->", linkto)
292 fromResourcesDir = framework.sourceResourcesDirectory
293 if os.path.exists(fromResourcesDir):
294 toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
295 shutil.copytree(fromResourcesDir, toResourcesDir, symlinks=True)
296 if verbose >= 3:
297 print("Copied resources:", fromResourcesDir)
298 print(" to:", toResourcesDir)
299 fromContentsDir = framework.sourceVersionContentsDirectory
300 if not os.path.exists(fromContentsDir):
301 fromContentsDir = framework.sourceContentsDirectory
302 if os.path.exists(fromContentsDir):
303 toContentsDir = os.path.join(path, framework.destinationVersionContentsDirectory)
304 shutil.copytree(fromContentsDir, toContentsDir, symlinks=True)
305 contentslinkfrom = os.path.join(path, framework.destinationContentsDirectory)
306 if verbose >= 3:
307 print("Copied Contents:", fromContentsDir)
308 print(" to:", toContentsDir)
309 elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
310 qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
311 qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
312 if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
313 shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True)
314 if verbose >= 3:
315 print("Copied for libQtGui:", qtMenuNibSourcePath)
316 print(" to:", qtMenuNibDestinationPath)
318 return toPath
320 def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
321 if deploymentInfo is None:
322 deploymentInfo = DeploymentInfo()
324 while len(frameworks) > 0:
325 framework = frameworks.pop(0)
326 deploymentInfo.deployedFrameworks.append(framework.frameworkName)
328 if verbose >= 2:
329 print("Processing", framework.frameworkName, "...")
331 # Get the Qt path from one of the Qt frameworks
332 if deploymentInfo.qtPath is None and framework.isQtFramework():
333 deploymentInfo.detectQtPath(framework.frameworkDirectory)
335 if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
336 if verbose >= 2:
337 print(framework.frameworkName, "already deployed, skipping.")
338 continue
340 # install_name_tool the new id into the binary
341 changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
343 # Copy framework to app bundle.
344 deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
345 # Skip the rest if already was deployed.
346 if deployedBinaryPath is None:
347 continue
349 if strip:
350 runStrip(deployedBinaryPath, verbose)
352 # install_name_tool it a new id.
353 changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
354 # Check for framework dependencies
355 dependencies = getFrameworks(deployedBinaryPath, verbose)
357 for dependency in dependencies:
358 changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
360 # Deploy framework if necessary.
361 if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
362 frameworks.append(dependency)
364 return deploymentInfo
366 def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
367 frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
368 if len(frameworks) == 0 and verbose >= 1:
369 print("Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path))
370 return DeploymentInfo()
371 else:
372 return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
374 def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
375 # Lookup available plugins, exclude unneeded
376 plugins = []
377 if deploymentInfo.pluginPath is None:
378 return
379 for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
380 pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
381 if pluginDirectory == "designer":
382 # Skip designer plugins
383 continue
384 elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
385 # Deploy the phonon plugins only if phonon is in use
386 if not deploymentInfo.usesFramework("phonon"):
387 continue
388 elif pluginDirectory == "sqldrivers":
389 # Deploy the sql plugins only if QtSql is in use
390 if not deploymentInfo.usesFramework("QtSql"):
391 continue
392 elif pluginDirectory == "script":
393 # Deploy the script plugins only if QtScript is in use
394 if not deploymentInfo.usesFramework("QtScript"):
395 continue
396 elif pluginDirectory == "qmltooling" or pluginDirectory == "qml1tooling":
397 # Deploy the qml plugins only if QtDeclarative is in use
398 if not deploymentInfo.usesFramework("QtDeclarative"):
399 continue
400 elif pluginDirectory == "bearer":
401 # Deploy the bearer plugins only if QtNetwork is in use
402 if not deploymentInfo.usesFramework("QtNetwork"):
403 continue
404 elif pluginDirectory == "position":
405 # Deploy the position plugins only if QtPositioning is in use
406 if not deploymentInfo.usesFramework("QtPositioning"):
407 continue
408 elif pluginDirectory == "sensors" or pluginDirectory == "sensorgestures":
409 # Deploy the sensor plugins only if QtSensors is in use
410 if not deploymentInfo.usesFramework("QtSensors"):
411 continue
412 elif pluginDirectory == "audio" or pluginDirectory == "playlistformats":
413 # Deploy the audio plugins only if QtMultimedia is in use
414 if not deploymentInfo.usesFramework("QtMultimedia"):
415 continue
416 elif pluginDirectory == "mediaservice":
417 # Deploy the mediaservice plugins only if QtMultimediaWidgets is in use
418 if not deploymentInfo.usesFramework("QtMultimediaWidgets"):
419 continue
421 for pluginName in filenames:
422 pluginPath = os.path.join(pluginDirectory, pluginName)
423 if pluginName.endswith("_debug.dylib"):
424 # Skip debug plugins
425 continue
426 elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
427 # Deploy the svg plugins only if QtSvg is in use
428 if not deploymentInfo.usesFramework("QtSvg"):
429 continue
430 elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
431 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
432 if not deploymentInfo.usesFramework("Qt3Support"):
433 continue
434 elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
435 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
436 if not deploymentInfo.usesFramework("QtOpenGL"):
437 continue
438 elif pluginPath == "accessible/libqtaccessiblequick.dylib":
439 # Deploy the accessible qtquick plugin only if QtQuick is in use
440 if not deploymentInfo.usesFramework("QtQuick"):
441 continue
443 plugins.append((pluginDirectory, pluginName))
445 for pluginDirectory, pluginName in plugins:
446 if verbose >= 2:
447 print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
449 sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
450 destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
451 if not os.path.exists(destinationDirectory):
452 os.makedirs(destinationDirectory)
454 destinationPath = os.path.join(destinationDirectory, pluginName)
455 shutil.copy2(sourcePath, destinationPath)
456 if verbose >= 3:
457 print("Copied:", sourcePath)
458 print(" to:", destinationPath)
460 if strip:
461 runStrip(destinationPath, verbose)
463 dependencies = getFrameworks(destinationPath, verbose)
465 for dependency in dependencies:
466 changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
468 # Deploy framework if necessary.
469 if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
470 deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
472 qt_conf="""[Paths]
473 Translations=Resources
474 Plugins=PlugIns
477 ap = ArgumentParser(description="""Improved version of macdeployqt.
479 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
480 Note, that the "dist" folder will be deleted before deploying on each run.
482 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.
484 Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments
485 to the codesign tool.
486 E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""")
488 ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
489 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")
490 ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
491 ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
492 ap.add_argument("-sign", dest="sign", action="store_true", default=False, help="sign .app bundle with codesign tool")
493 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")
494 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")
495 ap.add_argument("-add-qt-tr", nargs=1, metavar="languages", default=[], help="add Qt translation files to the bundle's resources; the language list must be separated with commas, not with whitespace")
496 ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translation files")
497 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")
498 ap.add_argument("-volname", nargs=1, metavar="volname", default=[], help="custom volume name for dmg")
500 config = ap.parse_args()
502 verbose = config.verbose[0]
504 # ------------------------------------------------
506 app_bundle = config.app_bundle[0]
508 if not os.path.exists(app_bundle):
509 if verbose >= 1:
510 sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
511 sys.exit(1)
513 app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
515 # ------------------------------------------------
516 translations_dir = None
517 if config.translations_dir and config.translations_dir[0]:
518 if os.path.exists(config.translations_dir[0]):
519 translations_dir = config.translations_dir[0]
520 else:
521 if verbose >= 1:
522 sys.stderr.write("Error: Could not find translation dir \"%s\"\n" % (translations_dir))
523 sys.exit(1)
524 # ------------------------------------------------
526 for p in config.add_resources:
527 if verbose >= 3:
528 print("Checking for \"%s\"..." % p)
529 if not os.path.exists(p):
530 if verbose >= 1:
531 sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
532 sys.exit(1)
534 # ------------------------------------------------
536 if len(config.fancy) == 1:
537 if verbose >= 3:
538 print("Fancy: Importing plistlib...")
539 try:
540 import plistlib
541 except ImportError:
542 if verbose >= 1:
543 sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
544 sys.exit(1)
546 p = config.fancy[0]
547 if verbose >= 3:
548 print("Fancy: Loading \"%s\"..." % p)
549 if not os.path.exists(p):
550 if verbose >= 1:
551 sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
552 sys.exit(1)
554 try:
555 fancy = plistlib.readPlist(p)
556 except:
557 if verbose >= 1:
558 sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
559 sys.exit(1)
561 try:
562 assert "window_bounds" not in fancy or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
563 assert "background_picture" not in fancy or isinstance(fancy["background_picture"], str)
564 assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int)
565 assert "applications_symlink" not in fancy or isinstance(fancy["applications_symlink"], bool)
566 if "items_position" in fancy:
567 assert isinstance(fancy["items_position"], dict)
568 for key, value in fancy["items_position"].items():
569 assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
570 except:
571 if verbose >= 1:
572 sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
573 sys.exit(1)
575 if "background_picture" in fancy:
576 bp = fancy["background_picture"]
577 if verbose >= 3:
578 print("Fancy: Resolving background picture \"%s\"..." % bp)
579 if not os.path.exists(bp):
580 bp = os.path.join(os.path.dirname(p), bp)
581 if not os.path.exists(bp):
582 if verbose >= 1:
583 sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
584 sys.exit(1)
585 else:
586 fancy["background_picture"] = bp
587 else:
588 fancy = None
590 # ------------------------------------------------
592 if os.path.exists("dist"):
593 if verbose >= 2:
594 print("+ Removing old dist folder +")
596 shutil.rmtree("dist")
598 # ------------------------------------------------
600 if len(config.volname) == 1:
601 volname = config.volname[0]
602 else:
603 volname = app_bundle_name
605 # ------------------------------------------------
607 target = os.path.join("dist", "Bitcoin-Qt.app")
609 if verbose >= 2:
610 print("+ Copying source bundle +")
611 if verbose >= 3:
612 print(app_bundle, "->", target)
614 os.mkdir("dist")
615 shutil.copytree(app_bundle, target, symlinks=True)
617 applicationBundle = ApplicationBundleInfo(target)
619 # ------------------------------------------------
621 if verbose >= 2:
622 print("+ Deploying frameworks +")
624 try:
625 deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
626 if deploymentInfo.qtPath is None:
627 deploymentInfo.qtPath = os.getenv("QTDIR", None)
628 if deploymentInfo.qtPath is None:
629 if verbose >= 1:
630 sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
631 config.plugins = False
632 except RuntimeError as e:
633 if verbose >= 1:
634 sys.stderr.write("Error: %s\n" % str(e))
635 sys.exit(1)
637 # ------------------------------------------------
639 if config.plugins:
640 if verbose >= 2:
641 print("+ Deploying plugins +")
643 try:
644 deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
645 except RuntimeError as e:
646 if verbose >= 1:
647 sys.stderr.write("Error: %s\n" % str(e))
648 sys.exit(1)
650 # ------------------------------------------------
652 if len(config.add_qt_tr) == 0:
653 add_qt_tr = []
654 else:
655 if translations_dir is not None:
656 qt_tr_dir = translations_dir
657 else:
658 if deploymentInfo.qtPath is not None:
659 qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
660 else:
661 sys.stderr.write("Error: Could not find Qt translation path\n")
662 sys.exit(1)
663 add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
664 for lng_file in add_qt_tr:
665 p = os.path.join(qt_tr_dir, lng_file)
666 if verbose >= 3:
667 print("Checking for \"%s\"..." % p)
668 if not os.path.exists(p):
669 if verbose >= 1:
670 sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
671 sys.exit(1)
673 # ------------------------------------------------
675 if verbose >= 2:
676 print("+ Installing qt.conf +")
678 f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")
679 f.write(qt_conf.encode())
680 f.close()
682 # ------------------------------------------------
684 if len(add_qt_tr) > 0 and verbose >= 2:
685 print("+ Adding Qt translations +")
687 for lng_file in add_qt_tr:
688 if verbose >= 3:
689 print(os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file))
690 shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
692 # ------------------------------------------------
694 if len(config.add_resources) > 0 and verbose >= 2:
695 print("+ Adding additional resources +")
697 for p in config.add_resources:
698 t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
699 if verbose >= 3:
700 print(p, "->", t)
701 if os.path.isdir(p):
702 shutil.copytree(p, t, symlinks=True)
703 else:
704 shutil.copy2(p, t)
706 # ------------------------------------------------
708 if config.sign and 'CODESIGNARGS' not in os.environ:
709 print("You must set the CODESIGNARGS environment variable. Skipping signing.")
710 elif config.sign:
711 if verbose >= 1:
712 print("Code-signing app bundle %s"%(target,))
713 subprocess.check_call("codesign --force %s %s"%(os.environ['CODESIGNARGS'], target), shell=True)
715 # ------------------------------------------------
717 if config.dmg is not None:
719 #Patch in check_output for Python 2.6
720 if "check_output" not in dir( subprocess ):
721 def f(*popenargs, **kwargs):
722 if 'stdout' in kwargs:
723 raise ValueError('stdout argument not allowed, it will be overridden.')
724 process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
725 output, unused_err = process.communicate()
726 retcode = process.poll()
727 if retcode:
728 cmd = kwargs.get("args")
729 if cmd is None:
730 cmd = popenargs[0]
731 raise CalledProcessError(retcode, cmd)
732 return output
733 subprocess.check_output = f
735 def runHDIUtil(verb, image_basename, **kwargs):
736 hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
737 if "capture_stdout" in kwargs:
738 del kwargs["capture_stdout"]
739 run = subprocess.check_output
740 else:
741 if verbose < 2:
742 hdiutil_args.append("-quiet")
743 elif verbose >= 3:
744 hdiutil_args.append("-verbose")
745 run = subprocess.check_call
747 for key, value in kwargs.items():
748 hdiutil_args.append("-" + key)
749 if not value is True:
750 hdiutil_args.append(str(value))
752 return run(hdiutil_args)
754 if verbose >= 2:
755 if fancy is None:
756 print("+ Creating .dmg disk image +")
757 else:
758 print("+ Preparing .dmg disk image +")
760 if config.dmg != "":
761 dmg_name = config.dmg
762 else:
763 spl = app_bundle_name.split(" ")
764 dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
766 if fancy is None:
767 try:
768 runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=volname, ov=True)
769 except subprocess.CalledProcessError as e:
770 sys.exit(e.returncode)
771 else:
772 if verbose >= 3:
773 print("Determining size of \"dist\"...")
774 size = 0
775 for path, dirs, files in os.walk("dist"):
776 for file in files:
777 size += os.path.getsize(os.path.join(path, file))
778 size += int(size * 0.15)
780 if verbose >= 3:
781 print("Creating temp image for modification...")
782 try:
783 runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=volname, ov=True)
784 except subprocess.CalledProcessError as e:
785 sys.exit(e.returncode)
787 if verbose >= 3:
788 print("Attaching temp image...")
789 try:
790 output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
791 except subprocess.CalledProcessError as e:
792 sys.exit(e.returncode)
794 m = re.search("/Volumes/(.+$)", output.decode())
795 disk_root = m.group(0)
796 disk_name = m.group(1)
798 if verbose >= 2:
799 print("+ Applying fancy settings +")
801 if "background_picture" in fancy:
802 bg_path = os.path.join(disk_root, ".background", os.path.basename(fancy["background_picture"]))
803 os.mkdir(os.path.dirname(bg_path))
804 if verbose >= 3:
805 print(fancy["background_picture"], "->", bg_path)
806 shutil.copy2(fancy["background_picture"], bg_path)
807 else:
808 bg_path = None
810 if fancy.get("applications_symlink", False):
811 os.symlink("/Applications", os.path.join(disk_root, "Applications"))
813 # The Python appscript package broke with OSX 10.8 and isn't being fixed.
814 # So we now build up an AppleScript string and use the osascript command
815 # to make the .dmg file pretty:
816 appscript = Template( """
817 on run argv
818 tell application "Finder"
819 tell disk "$disk"
820 open
821 set current view of container window to icon view
822 set toolbar visible of container window to false
823 set statusbar visible of container window to false
824 set the bounds of container window to {$window_bounds}
825 set theViewOptions to the icon view options of container window
826 set arrangement of theViewOptions to not arranged
827 set icon size of theViewOptions to $icon_size
828 $background_commands
829 $items_positions
830 close -- close/reopen works around a bug...
831 open
832 update without registering applications
833 delay 5
834 eject
835 end tell
836 end tell
837 end run
838 """)
840 itemscript = Template('set position of item "${item}" of container window to {${position}}')
841 items_positions = []
842 if "items_position" in fancy:
843 for name, position in fancy["items_position"].items():
844 params = { "item" : name, "position" : ",".join([str(p) for p in position]) }
845 items_positions.append(itemscript.substitute(params))
847 params = {
848 "disk" : volname,
849 "window_bounds" : "300,300,800,620",
850 "icon_size" : "96",
851 "background_commands" : "",
852 "items_positions" : "\n ".join(items_positions)
854 if "window_bounds" in fancy:
855 params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]])
856 if "icon_size" in fancy:
857 params["icon_size"] = str(fancy["icon_size"])
858 if bg_path is not None:
859 # Set background file, then call SetFile to make it invisible.
860 # (note: making it invisible first makes set background picture fail)
861 bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic"
862 do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """)
863 params["background_commands"] = bgscript.substitute({"bgpic" : os.path.basename(bg_path), "disk" : params["disk"]})
865 s = appscript.substitute(params)
866 if verbose >= 2:
867 print("Running AppleScript:")
868 print(s)
870 p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE)
871 p.communicate(input=s.encode('utf-8'))
872 if p.returncode:
873 print("Error running osascript.")
875 if verbose >= 2:
876 print("+ Finalizing .dmg disk image +")
877 time.sleep(5)
879 try:
880 runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
881 except subprocess.CalledProcessError as e:
882 sys.exit(e.returncode)
884 os.unlink(dmg_name + ".temp.dmg")
886 # ------------------------------------------------
888 if verbose >= 2:
889 print("+ Done +")
891 sys.exit(0)