Mac Deployment Script
authorp2k <patrick.p2k.schneider@gmail.com>
Wed, 2 Nov 2011 13:58:50 +0000 (14:58 +0100)
committerp2k <patrick.p2k.schneider@gmail.com>
Wed, 2 Nov 2011 13:58:50 +0000 (14:58 +0100)
See notes.txt in contrib/macdeploy.

Also added a dash to the application name in src/qt/bitcoin.cpp

contrib/macdeploy/background.png [new file with mode: 0644]
contrib/macdeploy/background.psd [new file with mode: 0644]
contrib/macdeploy/fancy.plist [new file with mode: 0644]
contrib/macdeploy/macdeployqtplus [new file with mode: 0755]
contrib/macdeploy/notes.txt [new file with mode: 0644]
src/qt/bitcoin.cpp

diff --git a/contrib/macdeploy/background.png b/contrib/macdeploy/background.png
new file mode 100644 (file)
index 0000000..fce12e3
Binary files /dev/null and b/contrib/macdeploy/background.png differ
diff --git a/contrib/macdeploy/background.psd b/contrib/macdeploy/background.psd
new file mode 100644 (file)
index 0000000..5889676
Binary files /dev/null and b/contrib/macdeploy/background.psd differ
diff --git a/contrib/macdeploy/fancy.plist b/contrib/macdeploy/fancy.plist
new file mode 100644 (file)
index 0000000..e73b9b6
--- /dev/null
@@ -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>
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
new file mode 100755 (executable)
index 0000000..a43e710
--- /dev/null
@@ -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)
diff --git a/contrib/macdeploy/notes.txt b/contrib/macdeploy/notes.txt
new file mode 100644 (file)
index 0000000..ac794d0
--- /dev/null
@@ -0,0 +1,26 @@
+
+macdeployqtplus works best on OS X Lion, for Snow Leopard you'd need to install
+Python 2.7 and make it your default Python installation.
+
+You will need the appscript package for the fancy disk image creation to work.
+Install it by invoking "sudo easy_install appscript".
+
+Ths script should be invoked in the target directory like this:
+$source_dir/contrib/macdeploy/macdeployqtplus Bitcoin-Qt.app -add-qt-tr de,ru -dmg -fancy $source_dir/contrib/macdeploy/fancy.plist
+
+During the process, the disk image window will pop up briefly where the fancy
+settings are applied. This is normal, please do not interfere.
+
+You can also set up Qt Creator for invoking the script. For this, go to the
+"Projects" tab on the left side, switch to "Run Settings" above and add a
+deploy configuration. Next add a deploy step choosing "Custom Process Step".
+Fill in the following.
+
+Enable custom process step: [x]
+Command: %{sourceDir}/contrib/macdeploy/macdeployqtplus
+Working directory: %{buildDir}
+Command arguments: Bitcoin-Qt.app -add-qt-tr de,ru -dmg -fancy %{sourceDir}/contrib/macdeploy/fancy.plist 
+
+After that you can start the deployment process through the menu with
+Build -> Deploy Project "bitcoin-qt"
+
index c8e3324..7f946a4 100644 (file)
@@ -129,7 +129,7 @@ int main(int argc, char *argv[])
     if (!translator.isEmpty())
         app.installTranslator(&translator);
 
-    app.setApplicationName(QApplication::translate("main", "Bitcoin Qt"));
+    app.setApplicationName(QApplication::translate("main", "Bitcoin-Qt"));
 
     QSplashScreen splash(QPixmap(":/images/splash"), 0);
     splash.show();