mirror of
https://gitlab.gnome.org/GNOME/gitg
synced 2024-11-05 16:43:26 +00:00
326 lines
10 KiB
Python
Executable file
326 lines
10 KiB
Python
Executable file
#!/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
|
|
|
|
m = re.match('^pkg-root:([^:]+)$', x.group(1))
|
|
|
|
if m:
|
|
paths = []
|
|
|
|
if 'PKG_CONFIG_PATH' in os.environ:
|
|
paths.extend(os.environ['PKG_CONFIG_PATH'].split(':'))
|
|
|
|
paths.extend(subprocess.Popen(['pkg-config', '--variable', 'pc_path', 'pkg-config'], stdout=subprocess.PIPE).communicate()[0].strip().split(':'))
|
|
|
|
for path in paths:
|
|
if os.path.isfile(os.path.join(path, m.group(1) + '.pc')):
|
|
return os.path.dirname(os.path.dirname(path))
|
|
|
|
sys.stderr.write('Warning: failed to find package ' + m.group(1) + '\n')
|
|
return s
|
|
|
|
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 isinstance(target, dict) and 'files' in target and 'target' in target:
|
|
newfiles = []
|
|
|
|
for f in files:
|
|
newfiles.extend([os.path.join(f, x) for x in target['files']])
|
|
|
|
files = newfiles
|
|
target = target['target']
|
|
|
|
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)
|
|
|
|
def glib_compile_schemas(self):
|
|
subprocess.call(['glib-compile-schemas', os.path.join(self.variables['resources'], 'share', 'glib-2.0', 'schemas')])
|
|
|
|
def copy_schemas(self, schemas):
|
|
if not schemas:
|
|
return
|
|
|
|
self.copy_glob(schemas, application.copy_data)
|
|
self.glib_compile_schemas()
|
|
|
|
def copy_icon_themes(self, themes):
|
|
if not themes:
|
|
return
|
|
|
|
self.copy_glob(themes, application.copy_data)
|
|
|
|
for theme_path in themes.values():
|
|
subprocess.call(['gtk3-update-icon-cache', '-f', '-t', '--quiet', self.repl(theme_path)])
|
|
|
|
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)
|
|
|
|
# Compile glib schemas
|
|
application.copy_schemas(bundle['schemas'])
|
|
|
|
# Compile icon themes
|
|
application.copy_icon_themes(bundle['icon-themes'])
|
|
|
|
print('Application created in {0}.app'.format(bundle['name']))
|
|
|
|
# vi:ts=4:et
|