Permalink
Browse files

Mac Deployment Script

See notes.txt in contrib/macdeploy.

Also added a dash to the application name in src/qt/bitcoin.cpp
  • Loading branch information...
1 parent fbea7ec commit 6eaa1b36fcf5a3b28c2440a3cf8ab4780f1afef9 @p2k p2k committed Nov 2, 2011
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>window_bounds</key>
+ <array>
+ <integer>300</integer>
+ <integer>300</integer>
+ <integer>800</integer>
+ <integer>620</integer>
+ </array>
+ <key>background_picture</key>
+ <string>background.png</string>
+ <key>icon_size</key>
+ <integer>96</integer>
+ <key>applications_symlink</key>
+ <true/>
+ <key>items_position</key>
+ <dict>
+ <key>Applications</key>
+ <array>
+ <integer>370</integer>
+ <integer>156</integer>
+ </array>
+ <key>Bitcoin-Qt.app</key>
+ <array>
+ <integer>128</integer>
+ <integer>156</integer>
+ </array>
+ </dict>
+</dict>
+</plist>
@@ -0,0 +1,341 @@
+#!/usr/bin/env python
+
+#
+# Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import subprocess, sys, re, os, shutil, os.path
+from time import sleep
+from argparse import ArgumentParser
+
+qt_conf="""[Paths]
+translations=Resources
+plugins=PlugIns
+"""
+
+ap = ArgumentParser(description="""Front-end to macdeployqt with some additional functions.
+
+Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
+Note, that the "dist" folder will be deleted before deploying on each run.
+
+Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.""")
+
+ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
+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")
+ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
+ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
+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")
+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")
+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")
+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")
+
+config = ap.parse_args()
+
+verbose = config.verbose[0]
+
+# ------------------------------------------------
+
+app_bundle = config.app_bundle[0]
+
+if not os.path.exists(app_bundle):
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
+ sys.exit(1)
+
+app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
+
+# ------------------------------------------------
+
+for p in config.add_resources:
+ if verbose >= 3:
+ print "Checking for \"%s\"..." % p
+ if not os.path.exists(p):
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
+ sys.exit(1)
+
+# ------------------------------------------------
+
+if len(config.add_qt_tr) == 0:
+ add_qt_tr = []
+else:
+ qt_tr_dir = os.path.join(os.getenv("QTDIR", ""), "translations")
+ add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
+ for lng_file in add_qt_tr:
+ p = os.path.join(qt_tr_dir, lng_file)
+ if verbose >= 3:
+ print "Checking for \"%s\"..." % p
+ if not os.path.exists(p):
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
+ sys.exit(1)
+
+# ------------------------------------------------
+
+if len(config.fancy) == 1:
+ if verbose >= 3:
+ print "Fancy: Importing plistlib..."
+ try:
+ import plistlib
+ except ImportError:
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
+ sys.exit(1)
+
+ if verbose >= 3:
+ print "Fancy: Importing appscript..."
+ try:
+ import appscript
+ except ImportError:
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not import appscript which is required for fancy disk images.\n")
+ sys.stderr.write("Please install it e.g. with \"sudo easy_install appscript\".\n")
+ sys.exit(1)
+
+ p = config.fancy[0]
+ if verbose >= 3:
+ print "Fancy: Loading \"%s\"..." % p
+ if not os.path.exists(p):
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
+ sys.exit(1)
+
+ try:
+ fancy = plistlib.readPlist(p)
+ except:
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
+ sys.exit(1)
+
+ try:
+ assert not fancy.has_key("window_bounds") or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
+ assert not fancy.has_key("background_picture") or isinstance(fancy["background_picture"], str)
+ assert not fancy.has_key("icon_size") or isinstance(fancy["icon_size"], int)
+ assert not fancy.has_key("applications_symlink") or isinstance(fancy["applications_symlink"], bool)
+ if fancy.has_key("items_position"):
+ assert isinstance(fancy["items_position"], dict)
+ for key, value in fancy["items_position"].iteritems():
+ assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
+ except:
+ if verbose >= 1:
+ sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
+ sys.exit(1)
+
+ if fancy.has_key("background_picture"):
+ bp = fancy["background_picture"]
+ if verbose >= 3:
+ print "Fancy: Resolving background picture \"%s\"..." % bp
+ if not os.path.exists(bp):
+ bp = os.path.join(os.path.dirname(p), bp)
+ if not os.path.exists(bp):
+ if verbose >= 1:
+ sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
+ sys.exit(1)
+ else:
+ fancy["background_picture"] = bp
+else:
+ fancy = None
+
+# ------------------------------------------------
+
+if os.path.exists("dist"):
+ if verbose >= 2:
+ print "+ Removing old dist folder +"
+
+ shutil.rmtree("dist")
+
+# ------------------------------------------------
+
+target = os.path.join("dist", app_bundle)
+target_res = os.path.join(target, "Contents", "Resources")
+
+if verbose >= 2:
+ print "+ Copying source bundle +"
+if verbose >= 3:
+ print app_bundle, "->", target
+
+os.mkdir("dist")
+shutil.copytree(app_bundle, target)
+
+# ------------------------------------------------
+
+macdeployqt_args = ["macdeployqt", target, "-verbose=%d" % verbose]
+if not config.plugins:
+ macdeployqt_args.append("-no-plugins")
+if not config.strip:
+ macdeployqt_args.append("-no-strip")
+
+if verbose >= 2:
+ print "+ Running macdeployqt +"
+
+ret = subprocess.call(macdeployqt_args)
+if ret != 0:
+ sys.exit(ret)
+
+# ------------------------------------------------
+
+if verbose >= 2:
+ print "+ Installing qt.conf +"
+
+f = open(os.path.join(target_res, "qt.conf"), "wb")
+f.write(qt_conf)
+f.close()
+
+# ------------------------------------------------
+
+if len(add_qt_tr) > 0 and verbose >= 2:
+ print "+ Adding Qt translations +"
+
+for lng_file in add_qt_tr:
+ if verbose >= 3:
+ print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(target_res, lng_file)
+ shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(target_res, lng_file))
+
+# ------------------------------------------------
+
+if len(config.add_resources) > 0 and verbose >= 2:
+ print "+ Adding additional resources +"
+
+for p in config.add_resources:
+ t = os.path.join(target_res, os.path.basename(p))
+ if verbose >= 3:
+ print p, "->", t
+ if os.path.isdir(p):
+ shutil.copytree(p, t)
+ else:
+ shutil.copy2(p, t)
+
+# ------------------------------------------------
+
+if config.dmg is not None:
+ def runHDIUtil(verb, image_basename, **kwargs):
+ hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
+ if kwargs.has_key("capture_stdout"):
+ del kwargs["capture_stdout"]
+ run = subprocess.check_output
+ else:
+ if verbose < 2:
+ hdiutil_args.append("-quiet")
+ elif verbose >= 3:
+ hdiutil_args.append("-verbose")
+ run = subprocess.check_call
+
+ for key, value in kwargs.iteritems():
+ hdiutil_args.append("-" + key)
+ if not value is True:
+ hdiutil_args.append(str(value))
+
+ return run(hdiutil_args)
+
+ if verbose >= 2:
+ if fancy is None:
+ print "+ Creating .dmg disk image +"
+ else:
+ print "+ Preparing .dmg disk image +"
+
+ if config.dmg != "":
+ dmg_name = config.dmg
+ else:
+ spl = app_bundle_name.split(" ")
+ dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
+
+ if fancy is None:
+ try:
+ runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True)
+ except subprocess.CalledProcessError as e:
+ sys.exit(e.returncode)
+ else:
+ if verbose >= 3:
+ print "Determining size of \"dist\"..."
+ size = 0
+ for path, dirs, files in os.walk("dist"):
+ for file in files:
+ size += os.path.getsize(os.path.join(path, file))
+ size += int(size * 0.1)
+
+ if verbose >= 3:
+ print "Creating temp image for modification..."
+ try:
+ runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True)
+ except subprocess.CalledProcessError as e:
+ sys.exit(e.returncode)
+
+ if verbose >= 3:
+ print "Attaching temp image..."
+ try:
+ output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
+ except subprocess.CalledProcessError as e:
+ sys.exit(e.returncode)
+
+ m = re.search("/Volumes/(.+$)", output)
+ disk_root = m.group(0)
+ disk_name = m.group(1)
+
+ if verbose >= 2:
+ print "+ Applying fancy settings +"
+
+ if fancy.has_key("background_picture"):
+ bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
+ if verbose >= 3:
+ print fancy["background_picture"], "->", bg_path
+ shutil.copy2(fancy["background_picture"], bg_path)
+ else:
+ bg_path = None
+
+ if fancy.get("applications_symlink", False):
+ os.symlink("/Applications", os.path.join(disk_root, "Applications"))
+
+ finder = appscript.app("Finder")
+ disk = finder.disks[disk_name]
+ disk.open()
+ window = disk.container_window
+ window.current_view.set(appscript.k.icon_view)
+ window.toolbar_visible.set(False)
+ window.statusbar_visible.set(False)
+ if fancy.has_key("window_bounds"):
+ window.bounds.set(fancy["window_bounds"])
+ view_options = window.icon_view_options
+ view_options.arrangement.set(appscript.k.not_arranged)
+ if fancy.has_key("icon_size"):
+ view_options.icon_size.set(fancy["icon_size"])
+ if bg_path is not None:
+ view_options.background_picture.set(disk.files[os.path.basename(bg_path)])
+ if fancy.has_key("items_position"):
+ for name, position in fancy["items_position"].iteritems():
+ window.items[name].position.set(position)
+ disk.close()
+ if bg_path is not None:
+ subprocess.call(["SetFile", "-a", "V", bg_path])
+ disk.update(registering_applications=False)
+ sleep(2)
+ disk.eject()
+
+ if verbose >= 2:
+ print "+ Finalizing .dmg disk image +"
+
+ try:
+ runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
+ except subprocess.CalledProcessError as e:
+ sys.exit(e.returncode)
+
+ os.unlink(dmg_name + ".temp.dmg")
+
+# ------------------------------------------------
+
+if verbose >= 2:
+ print "+ Done +"
+
+sys.exit(0)
Oops, something went wrong.

0 comments on commit 6eaa1b3

Please sign in to comment.