[gitg] Add initial OS X app bundle generator



commit 393e7c04d9943520769fcc4bfaa037da236a6cfe
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Thu Dec 24 12:30:18 2015 +0100

    Add initial OS X app bundle generator

 configure.ac            |    1 +
 osx/.gitignore          |    1 +
 osx/bundle.json.in      |   22 ++++
 osx/data/Info.plist     |   36 ++++++
 osx/scripts/launcher    |   65 +++++++++++
 osx/scripts/make-bundle |  290 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 415 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index dc8c29c..016d988 100644
--- a/configure.ac
+++ b/configure.ac
@@ -357,6 +357,7 @@ libgitg/libgitg-1.0.pc
 libgitg-ext/libgitg-ext-1.0.pc
 data/gitg.desktop.in
 data/org.gnome.gitg.gschema.xml.in
+osx/bundle.json
 po/Makefile.in
 ])
 
diff --git a/osx/.gitignore b/osx/.gitignore
new file mode 100644
index 0000000..89316c0
--- /dev/null
+++ b/osx/.gitignore
@@ -0,0 +1 @@
+/Gitg.app
diff --git a/osx/bundle.json.in b/osx/bundle.json.in
new file mode 100644
index 0000000..08c6d3b
--- /dev/null
+++ b/osx/bundle.json.in
@@ -0,0 +1,22 @@
+{
+  "name": "Gitg",
+
+  "variables": {
+    "version": "@VERSION@",
+    "prefix": "@prefix@"
+  },
+
+  "main": "${resources}/bin/gitg",
+
+  "binaries": {
+    "${prefix}/bin/gitg": "${resources}/bin/gitg"
+  },
+
+  "data": {
+    "${prefix}/lib/girepository-1.0/*.typelib": "${resources}/lib/girepository-1.0/"
+  },
+
+  "data_interpolated": {
+    "${rootdir}/data/Info.plist": "${contents}/Info.plist"
+  }
+}
diff --git a/osx/data/Info.plist b/osx/data/Info.plist
new file mode 100644
index 0000000..ce9dd0c
--- /dev/null
+++ b/osx/data/Info.plist
@@ -0,0 +1,36 @@
+<?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>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>Gitg</string>
+       <key>CFBundleGetInfoString</key>
+       <string>${version} Copyright 2015, ${name}</string>
+       <key>CFBundleIconFile</key>
+       <string>Gitg.icns</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.gnome.Gitg</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleShortVersionString</key>
+       <string>${version}</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>${version}</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>Copyright 2015 ${name}, GNU General Public License.</string>
+       <key>LSMinimumSystemVersion</key>
+       <string>10.7</string>
+       <key>CFBundleName</key>
+       <string>${name}</string>
+       <key>CFBundleDisplayName</key>
+       <string>${name}</string>
+       <key>NSHighResolutionCapable</key>
+       <string>True</string>
+</dict>
+</plist>
diff --git a/osx/scripts/launcher b/osx/scripts/launcher
new file mode 100644
index 0000000..1e7ec5d
--- /dev/null
+++ b/osx/scripts/launcher
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+if test "x$GTK_DEBUG_LAUNCHER" != x; then
+       set -x
+fi
+
+if test "x$GTK_DEBUG_GDB" != x; then
+       EXEC="lldb --"
+elif test "x$GTK_DEBUG_DTRUSS" != x; then
+       EXEC="sudo dtruss sudo -u $USER"
+else
+       EXEC=exec
+fi
+
+name=$(basename "$0")
+dirn=$(dirname "$0")
+
+pushd "$dirn/../../" > /dev/null
+bundle=$(pwd -P)
+popd > /dev/null
+
+bundle_contents="$bundle"/Contents
+bundle_res="$bundle_contents"/Resources
+bundle_lib="$bundle_res"/lib
+bundle_bin="$bundle_res"/bin
+bundle_data="$bundle_res"/share
+bundle_etc="$bundle_res"/etc
+
+export PATH="$bundle_bin:$PATH"
+export DYLD_LIBRARY_PATH="$bundle_lib:$DYLD_LIBRARY_PATH"
+export XDG_CONFIG_DIRS="$bundle_etc:$XDG_CONFIG_DIRS"
+export XDG_DATA_DIRS="$bundle_data:$XDG_DATA_DIRS"
+export GTK_DATA_PREFIX="$bundle_res"
+export GTK_EXE_PREFIX="$bundle_res"
+export GTK_PATH="$bundle_res"
+export GDK_PIXBUF_MODULE_FILE="$bundle_lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
+#export GIO_EXTRA_MODULES="$bundle_lib/gio/modules"
+export GI_TYPELIB_PATH="$bundle_lib/girepository-1.0"
+export PANGO_LIBDIR="$bundle_lib"
+export PANGO_SYSCONFDIR="$bundle_etc"
+export PEAS_PLUGIN_LOADERS_DIR="$bundle_lib/libpeas-1.0/loaders"
+export ENCHANT_MODULES_DIR="$bundle_lib/enchant"
+export ENCHANT_DATA_DIR="$bundle_data/enchant"
+
+if test -f "$bundle_lib/charset.alias"; then
+       export CHARSETALIASDIR="$bundle_lib"
+fi
+
+# Extra arguments can be added in environment.sh.
+EXTRA_ARGS=
+
+if test -f "$bundle_res/environment.sh"; then
+       source "$bundle_res/environment.sh"
+fi
+
+# Strip out the argument added by the OS.
+if [ x`echo "x$1" | sed -e "s/^x-psn_.*//"` == x ]; then
+       shift 1
+fi
+
+if [ "x$GTK_DEBUG_SHELL" != "x" ]; then
+       exec bash
+else
+       $EXEC "$bundle_contents/MacOS/${name}-bin" "$@" $EXTRA_ARGS
+fi
diff --git a/osx/scripts/make-bundle b/osx/scripts/make-bundle
new file mode 100755
index 0000000..2936e9a
--- /dev/null
+++ b/osx/scripts/make-bundle
@@ -0,0 +1,290 @@
+#!/usr/bin/python
+
+import inspect, os, shutil, subprocess, glob, sys, re, argparse, json
+
+scriptdir = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
+rootdir = os.path.dirname(scriptdir)
+
+import argparse
+
+parser = argparse.ArgumentParser(description='gitg osx bundler')
+
+parser.add_argument('-d', '--debug', type=bool, help='enable debugging')
+parser.add_argument('bundle', type=str, metavar='FILE', help='bundle json config')
+
+args = parser.parse_args()
+
+class Application:
+    def __init__(self, name, variables):
+        self.name = name
+        self.path = os.path.join(rootdir, name + '.app')
+
+        self.install_path = os.path.join('/Applications', self.path)
+
+        self.variables = dict(variables)
+
+        self.variables['name'] = name
+        self.variables['path'] = self.path
+        self.variables['rootdir'] = rootdir
+        self.variables['contents'] = os.path.join(self.path, 'Contents')
+        self.variables['resources'] = os.path.join(self.variables['contents'], 'Resources')
+        self.variables['lib'] = os.path.join(self.variables['resources'], 'lib')
+        self.variables['macos'] = os.path.join(self.variables['contents'], 'MacOS')
+
+        shutil.rmtree(self.path, ignore_errors=True)
+
+        for p in (self.variables['contents'], self.variables['resources'], self.variables['macos']):
+            try:
+                os.makedirs(p)
+            except:
+                pass
+
+        self._resolved_libs = {}
+        self._pkg_cache = {}
+
+    def repl(self, s):
+        def replace(x):
+            m = re.match('^pkg:([^:]+):([^:]+)$', x.group(1))
+
+            if m:
+                cachename = m.group(1) + ':' + m.group(2)
+
+                if cachename in self._pkg_cache:
+                    return self._pkg_cache[cachename]
+
+                out = subprocess.Popen(['pkg-config', '--variable', m.group(2), m.group(1)], 
stdout=subprocess.PIPE).communicate()[0].strip()
+                self._pkg_cache[cachename] = out
+
+                return out
+            else:
+                return self.variables[x.group(1)]
+
+        return re.sub("\\${([^}]+)}", replace, s)
+
+    def needs_copy(self, p):
+        prefixes = [self.variables['prefix'], '/usr/local'];
+
+        for prefix in prefixes:
+            if p.startswith(prefix):
+                return True
+
+        return False
+
+    def future_path(self, p):
+        if not os.path.isabs(p):
+            return os.path.join(self.install_path, p)
+
+        prefixes = [self.variables['prefix'], '/usr/local'];
+
+        for prefix in prefixes:
+            if p.startswith(prefix):
+                return os.path.join(self.install_path, p[len(prefix) + 1:])
+        
+        return p
+
+    def copy_binary(self, binary, target):
+        binary = self.repl(binary)
+        target = self.repl(target)
+
+        target = self._copy(binary, target)
+
+        future = self.future_path(target)
+        self._resolved_libs[os.path.realpath(binary)] = future
+
+        os.chmod(target, 0755)
+
+        # Set the new id of the library
+        if binary.endswith('.so') or binary.endswith('.dylib'):
+            if not args.debug:
+                subprocess.call(['strip', '-x', target])
+
+            # Set the new id
+            subprocess.call(['install_name_tool', '-id', future, target])
+        else:
+            if not args.debug:
+                subprocess.call(['strip', '-u', '-r', target])
+
+        # Resolve and copy external dependencies
+        self.resolve_deps(target)
+
+    def otool_deps(self, path):
+        out = subprocess.Popen(['otool', '-L', path], stdout=subprocess.PIPE).communicate()[0]
+        return [x.strip().split(' ')[0] for x in out.splitlines()[1:]]
+
+    def resolve_deps(self, libname):
+        # Run otool to get the deps
+        deps = self.otool_deps(libname)
+
+        for dep in deps:
+            rdep = os.path.realpath(dep)
+
+            if not self.needs_copy(rdep) or rdep == libname:
+                continue
+
+            if not rdep in self._resolved_libs and rdep != libname:
+                # Copy the dependency
+                name = os.path.basename(rdep)
+                target = os.path.join(self.variables['lib'], name)
+
+                # Go deep
+                self.copy_binary(rdep, target)
+
+            newname = self._resolved_libs[rdep].replace(self.variables['contents'], '@executable_path/..')
+            subprocess.call(['install_name_tool', '-change', dep, newname, libname])
+
+    def _copy_file_name(self, source, target):
+        if target.endswith('/'):
+            return target + os.path.basename(source)
+        else:
+            return target
+
+    def _copy(self, source, target):
+        target = self._copy_file_name(source, target)
+
+        try:
+            os.makedirs(os.path.dirname(target))
+        except:
+            pass
+
+        if os.path.isdir(source):
+            shutil.copytree(source, target)
+        else:
+            shutil.copyfile(source, target)
+
+        return target
+
+    def copy_data(self, data, target):
+        self._copy(data, target)
+
+    def _interpolate_file(self, filename):
+        data = open(filename).read()
+        newdata = self.repl(data)
+
+        if newdata != data:
+            f = open(filename, 'w')
+            f.write(newdata)
+            f.flush()
+            f.close()
+
+    def copy_data_interpolated(self, data, target):
+        target = self._copy(data, target)
+
+        if os.path.isdir(target):
+            for root, dirnames, filenames in os.walk(target):
+                for filename in filenames:
+                    fullname = os.path.join(root, filename)
+                    self._interpolate_file(fullname)
+        else:
+            self._interpolate_file(target)
+
+    def copy_script(self, script, target):
+        target = self._copy(script, target)
+        os.chmod(target, 0755)
+
+    def copy_glob(self, items, fn):
+        for k in items:
+            g = self.repl(k)
+            files = glob.glob(g)
+
+            if len(files) == 0:
+                print('Warning: The glob `{0}\' did not result in any files'.format(g))
+                continue
+
+            target = items[k]
+
+            if not isinstance(target, list):
+                target = [target]
+
+            for t in target:
+                t = self.repl(t)
+
+                for f in files:
+                    fn(f, t)
+
+    def link_main(self, main):
+        launcher = open(os.path.join(scriptdir, 'launcher'), 'r').read()
+
+        launcher = self.repl(launcher)
+        main = self.repl(main)
+
+        lpath = os.path.join(self.variables['macos'], self.name)
+
+        with open(lpath, 'w') as f:
+            f.write(launcher)
+
+        os.chmod(lpath, 0755)
+
+        relpath = os.path.relpath(main, os.path.join(self.variables['macos']))
+        os.symlink(relpath, os.path.join(self.variables['macos'], self.name + '-bin'))
+
+    def link_binaries(self, binaries):
+        p = os.path.join(self._root, 'bin')
+        shutil.rmtree(p, ignore_errors=True)
+
+        try:
+            os.makedirs(p)
+        except:
+            pass
+
+        for b in binaries:
+            files = glob.glob(self.repl(b))
+
+            for f in files:
+                os.symlink(self.future_path(f), os.path.join(p, os.path.basename(f)))
+
+    def copy_pixbuf_loaders(self):
+        moduledir = self.repl('${pkg:gdk-pixbuf-2.0:gdk_pixbuf_moduledir}')
+        loaders = glob.glob(os.path.join(moduledir, '*.so'))
+
+        target_moduledir = self.repl('gdk-pixbuf-2.0/${pkg:gdk-pixbuf-2.0:gdk_pixbuf_binary_version}')
+
+        for loader in loaders:
+            self.copy_binary(loader, os.path.join(self.variables['lib'], target_moduledir, 'loaders', 
os.path.basename(loader)))
+
+        args = ['gdk-pixbuf-query-loaders']
+        args.extend(loaders)
+
+        cache = subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
+        cache = cache.replace(moduledir, os.path.join('@executable_path/../Resources/lib', target_moduledir, 
'loaders'))
+
+        with open(os.path.join(self.variables['lib'], target_moduledir, 'loaders.cache'), 'w') as f:
+            f.write(cache)
+
+bundle = json.load(open(args.bundle, 'r'))
+
+# Create the framework
+application = Application(bundle['name'], bundle['variables'])
+
+# Copy binaries
+application.copy_glob(bundle['binaries'], application.copy_binary)
+
+# Link main
+application.link_main(bundle['main'])
+
+# Copy pixbuf loaders
+application.copy_pixbuf_loaders()
+
+# Copy data
+application.copy_glob(bundle['data'], application.copy_data)
+
+# Copy data interpolated
+application.copy_glob(bundle['data_interpolated'], application.copy_data_interpolated)
+
+# # Copy scripts
+# framework.copy_glob(config['scripts'], framework.copy_script)
+
+# # Copy headers
+# framework.copy_glob(config['headers'], framework.copy_header)
+
+# # Rewrite header includes
+# framework.rewrite_headers(config['header_rewrites'])
+
+# # Create applications
+# framework.create_applications(config['applications'])
+
+# # Link bin
+# framework.link_binaries(config['linked-binaries'])
+
+print('Application created in {0}.app'.format(bundle['name']))
+
+# vi:ts=4:et


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]