Add initial OS X app bundle generator

This commit is contained in:
Jesse van den Kieboom 2015-12-24 12:30:18 +01:00
parent c2282eb13e
commit 393e7c04d9
6 changed files with 415 additions and 0 deletions

View file

@ -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
])

1
osx/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/Gitg.app

22
osx/bundle.json.in Normal file
View file

@ -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"
}
}

36
osx/data/Info.plist Normal file
View file

@ -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>

65
osx/scripts/launcher Normal file
View file

@ -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

290
osx/scripts/make-bundle Executable file
View file

@ -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