Merge #11702: [build] Add a script for installing db4
[bitcoinplatinum.git] / contrib / macdeploy / macdeployqtplus
blob23a568ad13aeb91d79e51de4283b0959c4baf027
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 if verbose >= 3:
306 print("Copied Contents:", fromContentsDir)
307 print(" to:", toContentsDir)
308 elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
309 qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
310 qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
311 if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
312 shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True)
313 if verbose >= 3:
314 print("Copied for libQtGui:", qtMenuNibSourcePath)
315 print(" to:", qtMenuNibDestinationPath)
317 return toPath
319 def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
320 if deploymentInfo is None:
321 deploymentInfo = DeploymentInfo()
323 while len(frameworks) > 0:
324 framework = frameworks.pop(0)
325 deploymentInfo.deployedFrameworks.append(framework.frameworkName)
327 if verbose >= 2:
328 print("Processing", framework.frameworkName, "...")
330 # Get the Qt path from one of the Qt frameworks
331 if deploymentInfo.qtPath is None and framework.isQtFramework():
332 deploymentInfo.detectQtPath(framework.frameworkDirectory)
334 if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
335 if verbose >= 2:
336 print(framework.frameworkName, "already deployed, skipping.")
337 continue
339 # install_name_tool the new id into the binary
340 changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
342 # Copy framework to app bundle.
343 deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
344 # Skip the rest if already was deployed.
345 if deployedBinaryPath is None:
346 continue
348 if strip:
349 runStrip(deployedBinaryPath, verbose)
351 # install_name_tool it a new id.
352 changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
353 # Check for framework dependencies
354 dependencies = getFrameworks(deployedBinaryPath, verbose)
356 for dependency in dependencies:
357 changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
359 # Deploy framework if necessary.
360 if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
361 frameworks.append(dependency)
363 return deploymentInfo
365 def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
366 frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
367 if len(frameworks) == 0 and verbose >= 1:
368 print("Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path))
369 return DeploymentInfo()
370 else:
371 return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
373 def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
374 # Lookup available plugins, exclude unneeded
375 plugins = []
376 if deploymentInfo.pluginPath is None:
377 return
378 for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
379 pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
380 if pluginDirectory == "designer":
381 # Skip designer plugins
382 continue
383 elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
384 # Deploy the phonon plugins only if phonon is in use
385 if not deploymentInfo.usesFramework("phonon"):
386 continue
387 elif pluginDirectory == "sqldrivers":
388 # Deploy the sql plugins only if QtSql is in use
389 if not deploymentInfo.usesFramework("QtSql"):
390 continue
391 elif pluginDirectory == "script":
392 # Deploy the script plugins only if QtScript is in use
393 if not deploymentInfo.usesFramework("QtScript"):
394 continue
395 elif pluginDirectory == "qmltooling" or pluginDirectory == "qml1tooling":
396 # Deploy the qml plugins only if QtDeclarative is in use
397 if not deploymentInfo.usesFramework("QtDeclarative"):
398 continue
399 elif pluginDirectory == "bearer":
400 # Deploy the bearer plugins only if QtNetwork is in use
401 if not deploymentInfo.usesFramework("QtNetwork"):
402 continue
403 elif pluginDirectory == "position":
404 # Deploy the position plugins only if QtPositioning is in use
405 if not deploymentInfo.usesFramework("QtPositioning"):
406 continue
407 elif pluginDirectory == "sensors" or pluginDirectory == "sensorgestures":
408 # Deploy the sensor plugins only if QtSensors is in use
409 if not deploymentInfo.usesFramework("QtSensors"):
410 continue
411 elif pluginDirectory == "audio" or pluginDirectory == "playlistformats":
412 # Deploy the audio plugins only if QtMultimedia is in use
413 if not deploymentInfo.usesFramework("QtMultimedia"):
414 continue
415 elif pluginDirectory == "mediaservice":
416 # Deploy the mediaservice plugins only if QtMultimediaWidgets is in use
417 if not deploymentInfo.usesFramework("QtMultimediaWidgets"):
418 continue
420 for pluginName in filenames:
421 pluginPath = os.path.join(pluginDirectory, pluginName)
422 if pluginName.endswith("_debug.dylib"):
423 # Skip debug plugins
424 continue
425 elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
426 # Deploy the svg plugins only if QtSvg is in use
427 if not deploymentInfo.usesFramework("QtSvg"):
428 continue
429 elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
430 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
431 if not deploymentInfo.usesFramework("Qt3Support"):
432 continue
433 elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
434 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
435 if not deploymentInfo.usesFramework("QtOpenGL"):
436 continue
437 elif pluginPath == "accessible/libqtaccessiblequick.dylib":
438 # Deploy the accessible qtquick plugin only if QtQuick is in use
439 if not deploymentInfo.usesFramework("QtQuick"):
440 continue
442 plugins.append((pluginDirectory, pluginName))
444 for pluginDirectory, pluginName in plugins:
445 if verbose >= 2:
446 print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
448 sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
449 destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
450 if not os.path.exists(destinationDirectory):
451 os.makedirs(destinationDirectory)
453 destinationPath = os.path.join(destinationDirectory, pluginName)
454 shutil.copy2(sourcePath, destinationPath)
455 if verbose >= 3:
456 print("Copied:", sourcePath)
457 print(" to:", destinationPath)
459 if strip:
460 runStrip(destinationPath, verbose)
462 dependencies = getFrameworks(destinationPath, verbose)
464 for dependency in dependencies:
465 changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
467 # Deploy framework if necessary.
468 if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
469 deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
471 qt_conf="""[Paths]
472 Translations=Resources
473 Plugins=PlugIns
476 ap = ArgumentParser(description="""Improved version of macdeployqt.
478 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
479 Note, that the "dist" folder will be deleted before deploying on each run.
481 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.
483 Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments
484 to the codesign tool.
485 E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""")
487 ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
488 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")
489 ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
490 ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
491 ap.add_argument("-sign", dest="sign", action="store_true", default=False, help="sign .app bundle with codesign tool")
492 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")
493 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")
494 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")
495 ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translation files")
496 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")
497 ap.add_argument("-volname", nargs=1, metavar="volname", default=[], help="custom volume name for dmg")
499 config = ap.parse_args()
501 verbose = config.verbose[0]
503 # ------------------------------------------------
505 app_bundle = config.app_bundle[0]
507 if not os.path.exists(app_bundle):
508 if verbose >= 1:
509 sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
510 sys.exit(1)
512 app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
514 # ------------------------------------------------
515 translations_dir = None
516 if config.translations_dir and config.translations_dir[0]:
517 if os.path.exists(config.translations_dir[0]):
518 translations_dir = config.translations_dir[0]
519 else:
520 if verbose >= 1:
521 sys.stderr.write("Error: Could not find translation dir \"%s\"\n" % (translations_dir))
522 sys.exit(1)
523 # ------------------------------------------------
525 for p in config.add_resources:
526 if verbose >= 3:
527 print("Checking for \"%s\"..." % p)
528 if not os.path.exists(p):
529 if verbose >= 1:
530 sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
531 sys.exit(1)
533 # ------------------------------------------------
535 if len(config.fancy) == 1:
536 if verbose >= 3:
537 print("Fancy: Importing plistlib...")
538 try:
539 import plistlib
540 except ImportError:
541 if verbose >= 1:
542 sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
543 sys.exit(1)
545 p = config.fancy[0]
546 if verbose >= 3:
547 print("Fancy: Loading \"%s\"..." % p)
548 if not os.path.exists(p):
549 if verbose >= 1:
550 sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
551 sys.exit(1)
553 try:
554 fancy = plistlib.readPlist(p)
555 except:
556 if verbose >= 1:
557 sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
558 sys.exit(1)
560 try:
561 assert "window_bounds" not in fancy or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
562 assert "background_picture" not in fancy or isinstance(fancy["background_picture"], str)
563 assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int)
564 assert "applications_symlink" not in fancy or isinstance(fancy["applications_symlink"], bool)
565 if "items_position" in fancy:
566 assert isinstance(fancy["items_position"], dict)
567 for key, value in fancy["items_position"].items():
568 assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
569 except:
570 if verbose >= 1:
571 sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
572 sys.exit(1)
574 if "background_picture" in fancy:
575 bp = fancy["background_picture"]
576 if verbose >= 3:
577 print("Fancy: Resolving background picture \"%s\"..." % bp)
578 if not os.path.exists(bp):
579 bp = os.path.join(os.path.dirname(p), bp)
580 if not os.path.exists(bp):
581 if verbose >= 1:
582 sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
583 sys.exit(1)
584 else:
585 fancy["background_picture"] = bp
586 else:
587 fancy = None
589 # ------------------------------------------------
591 if os.path.exists("dist"):
592 if verbose >= 2:
593 print("+ Removing old dist folder +")
595 shutil.rmtree("dist")
597 # ------------------------------------------------
599 if len(config.volname) == 1:
600 volname = config.volname[0]
601 else:
602 volname = app_bundle_name
604 # ------------------------------------------------
606 target = os.path.join("dist", "Bitcoin-Qt.app")
608 if verbose >= 2:
609 print("+ Copying source bundle +")
610 if verbose >= 3:
611 print(app_bundle, "->", target)
613 os.mkdir("dist")
614 shutil.copytree(app_bundle, target, symlinks=True)
616 applicationBundle = ApplicationBundleInfo(target)
618 # ------------------------------------------------
620 if verbose >= 2:
621 print("+ Deploying frameworks +")
623 try:
624 deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
625 if deploymentInfo.qtPath is None:
626 deploymentInfo.qtPath = os.getenv("QTDIR", None)
627 if deploymentInfo.qtPath is None:
628 if verbose >= 1:
629 sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
630 config.plugins = False
631 except RuntimeError as e:
632 if verbose >= 1:
633 sys.stderr.write("Error: %s\n" % str(e))
634 sys.exit(1)
636 # ------------------------------------------------
638 if config.plugins:
639 if verbose >= 2:
640 print("+ Deploying plugins +")
642 try:
643 deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
644 except RuntimeError as e:
645 if verbose >= 1:
646 sys.stderr.write("Error: %s\n" % str(e))
647 sys.exit(1)
649 # ------------------------------------------------
651 if len(config.add_qt_tr) == 0:
652 add_qt_tr = []
653 else:
654 if translations_dir is not None:
655 qt_tr_dir = translations_dir
656 else:
657 if deploymentInfo.qtPath is not None:
658 qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
659 else:
660 sys.stderr.write("Error: Could not find Qt translation path\n")
661 sys.exit(1)
662 add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
663 for lng_file in add_qt_tr:
664 p = os.path.join(qt_tr_dir, lng_file)
665 if verbose >= 3:
666 print("Checking for \"%s\"..." % p)
667 if not os.path.exists(p):
668 if verbose >= 1:
669 sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
670 sys.exit(1)
672 # ------------------------------------------------
674 if verbose >= 2:
675 print("+ Installing qt.conf +")
677 with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f:
678 f.write(qt_conf.encode())
680 # ------------------------------------------------
682 if len(add_qt_tr) > 0 and verbose >= 2:
683 print("+ Adding Qt translations +")
685 for lng_file in add_qt_tr:
686 if verbose >= 3:
687 print(os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file))
688 shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
690 # ------------------------------------------------
692 if len(config.add_resources) > 0 and verbose >= 2:
693 print("+ Adding additional resources +")
695 for p in config.add_resources:
696 t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
697 if verbose >= 3:
698 print(p, "->", t)
699 if os.path.isdir(p):
700 shutil.copytree(p, t, symlinks=True)
701 else:
702 shutil.copy2(p, t)
704 # ------------------------------------------------
706 if config.sign and 'CODESIGNARGS' not in os.environ:
707 print("You must set the CODESIGNARGS environment variable. Skipping signing.")
708 elif config.sign:
709 if verbose >= 1:
710 print("Code-signing app bundle %s"%(target,))
711 subprocess.check_call("codesign --force %s %s"%(os.environ['CODESIGNARGS'], target), shell=True)
713 # ------------------------------------------------
715 if config.dmg is not None:
717 #Patch in check_output for Python 2.6
718 if "check_output" not in dir( subprocess ):
719 def f(*popenargs, **kwargs):
720 if 'stdout' in kwargs:
721 raise ValueError('stdout argument not allowed, it will be overridden.')
722 process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
723 output, unused_err = process.communicate()
724 retcode = process.poll()
725 if retcode:
726 cmd = kwargs.get("args")
727 if cmd is None:
728 cmd = popenargs[0]
729 raise CalledProcessError(retcode, cmd)
730 return output
731 subprocess.check_output = f
733 def runHDIUtil(verb, image_basename, **kwargs):
734 hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
735 if "capture_stdout" in kwargs:
736 del kwargs["capture_stdout"]
737 run = subprocess.check_output
738 else:
739 if verbose < 2:
740 hdiutil_args.append("-quiet")
741 elif verbose >= 3:
742 hdiutil_args.append("-verbose")
743 run = subprocess.check_call
745 for key, value in kwargs.items():
746 hdiutil_args.append("-" + key)
747 if not value is True:
748 hdiutil_args.append(str(value))
750 return run(hdiutil_args)
752 if verbose >= 2:
753 if fancy is None:
754 print("+ Creating .dmg disk image +")
755 else:
756 print("+ Preparing .dmg disk image +")
758 if config.dmg != "":
759 dmg_name = config.dmg
760 else:
761 spl = app_bundle_name.split(" ")
762 dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
764 if fancy is None:
765 try:
766 runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=volname, ov=True)
767 except subprocess.CalledProcessError as e:
768 sys.exit(e.returncode)
769 else:
770 if verbose >= 3:
771 print("Determining size of \"dist\"...")
772 size = 0
773 for path, dirs, files in os.walk("dist"):
774 for file in files:
775 size += os.path.getsize(os.path.join(path, file))
776 size += int(size * 0.15)
778 if verbose >= 3:
779 print("Creating temp image for modification...")
780 try:
781 runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=volname, ov=True)
782 except subprocess.CalledProcessError as e:
783 sys.exit(e.returncode)
785 if verbose >= 3:
786 print("Attaching temp image...")
787 try:
788 output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
789 except subprocess.CalledProcessError as e:
790 sys.exit(e.returncode)
792 m = re.search("/Volumes/(.+$)", output.decode())
793 disk_root = m.group(0)
794 disk_name = m.group(1)
796 if verbose >= 2:
797 print("+ Applying fancy settings +")
799 if "background_picture" in fancy:
800 bg_path = os.path.join(disk_root, ".background", os.path.basename(fancy["background_picture"]))
801 os.mkdir(os.path.dirname(bg_path))
802 if verbose >= 3:
803 print(fancy["background_picture"], "->", bg_path)
804 shutil.copy2(fancy["background_picture"], bg_path)
805 else:
806 bg_path = None
808 if fancy.get("applications_symlink", False):
809 os.symlink("/Applications", os.path.join(disk_root, "Applications"))
811 # The Python appscript package broke with OSX 10.8 and isn't being fixed.
812 # So we now build up an AppleScript string and use the osascript command
813 # to make the .dmg file pretty:
814 appscript = Template( """
815 on run argv
816 tell application "Finder"
817 tell disk "$disk"
818 open
819 set current view of container window to icon view
820 set toolbar visible of container window to false
821 set statusbar visible of container window to false
822 set the bounds of container window to {$window_bounds}
823 set theViewOptions to the icon view options of container window
824 set arrangement of theViewOptions to not arranged
825 set icon size of theViewOptions to $icon_size
826 $background_commands
827 $items_positions
828 close -- close/reopen works around a bug...
829 open
830 update without registering applications
831 delay 5
832 eject
833 end tell
834 end tell
835 end run
836 """)
838 itemscript = Template('set position of item "${item}" of container window to {${position}}')
839 items_positions = []
840 if "items_position" in fancy:
841 for name, position in fancy["items_position"].items():
842 params = { "item" : name, "position" : ",".join([str(p) for p in position]) }
843 items_positions.append(itemscript.substitute(params))
845 params = {
846 "disk" : volname,
847 "window_bounds" : "300,300,800,620",
848 "icon_size" : "96",
849 "background_commands" : "",
850 "items_positions" : "\n ".join(items_positions)
852 if "window_bounds" in fancy:
853 params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]])
854 if "icon_size" in fancy:
855 params["icon_size"] = str(fancy["icon_size"])
856 if bg_path is not None:
857 # Set background file, then call SetFile to make it invisible.
858 # (note: making it invisible first makes set background picture fail)
859 bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic"
860 do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """)
861 params["background_commands"] = bgscript.substitute({"bgpic" : os.path.basename(bg_path), "disk" : params["disk"]})
863 s = appscript.substitute(params)
864 if verbose >= 2:
865 print("Running AppleScript:")
866 print(s)
868 p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE)
869 p.communicate(input=s.encode('utf-8'))
870 if p.returncode:
871 print("Error running osascript.")
873 if verbose >= 2:
874 print("+ Finalizing .dmg disk image +")
875 time.sleep(5)
877 try:
878 runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
879 except subprocess.CalledProcessError as e:
880 sys.exit(e.returncode)
882 os.unlink(dmg_name + ".temp.dmg")
884 # ------------------------------------------------
886 if verbose >= 2:
887 print("+ Done +")
889 sys.exit(0)