all repos — mgba @ ce71cc5af9dee9385262e990c02f1a3679ef1eaf

mGBA Game Boy Advance Emulator

tools/deploy-mac.py (view raw)

  1#!/usr/bin/env python
  2from __future__ import print_function
  3import argparse
  4import errno
  5import os
  6import re
  7import shutil
  8import subprocess
  9
 10qtPath = None
 11
 12def splitPath(path):
 13	folders = []
 14	while True:
 15		path, folder = os.path.split(path)
 16		if folder != '':
 17			folders.append(folder)
 18		else:
 19			if path != '':
 20				folders.append(path)
 21			break
 22	folders.reverse()
 23	return folders
 24
 25def joinPath(path):
 26	return reduce(os.path.join, path, '')
 27
 28def findFramework(path):
 29	child = []
 30	while path and not path[-1].endswith('.framework'):
 31		child.append(path.pop())
 32	child.reverse()
 33	return path, child
 34
 35def findQtPath(path):
 36	parent, child = findFramework(splitPath(path))
 37	return joinPath(parent[:-2])
 38
 39def makedirs(path):
 40	split = splitPath(path)
 41	accum = []
 42	split.reverse()
 43	while split:
 44		accum.append(split.pop())
 45		newPath = joinPath(accum)
 46		try:
 47			os.mkdir(newPath)
 48		except OSError as e:
 49			if e.errno != errno.EEXIST:
 50				raise
 51
 52
 53def parseOtoolLine(line, execPath, root):
 54	if not line.startswith('\t'):
 55		return None, None, None, None
 56	line = line[1:]
 57	match = re.match('([@/].*) \(compatibility version.*\)', line)
 58	path = match.group(1)
 59	split = splitPath(path)
 60	newExecPath = ['@executable_path', '..', 'Frameworks']
 61	newPath = execPath[:-1]
 62	newPath.append('Frameworks')
 63	if split[:3] == ['/', 'usr', 'lib'] or split[:2] == ['/', 'System']:
 64		return None, None, None, None
 65	if split[0] == '@executable_path':
 66		split[:1] = execPath
 67	if split[0] == '/' and not os.access(joinPath(split), os.F_OK):
 68		split[:1] = root
 69	isFramework = False
 70	if not split[-1].endswith('.dylib'):
 71		isFramework = True
 72		split, framework = findFramework(split)
 73	newPath.append(split[-1])
 74	newExecPath.append(split[-1])
 75	if isFramework:
 76		newPath.extend(framework)
 77		newExecPath.extend(framework)
 78		split.extend(framework)
 79	newPath = joinPath(newPath)
 80	newExecPath = joinPath(newExecPath)
 81	return joinPath(split), newPath, path, newExecPath
 82
 83def updateMachO(bin, execPath, root):
 84	otoolOutput = subprocess.check_output([otool, '-L', bin])
 85	toUpdate = []
 86	for line in otoolOutput.split('\n'):
 87		oldPath, newPath, oldExecPath, newExecPath = parseOtoolLine(line, execPath, root)
 88		if not newPath:
 89			continue
 90		if os.access(newPath, os.F_OK):
 91			print('Skipping copying {}, already done.'.format(oldPath))
 92		elif os.path.abspath(oldPath) != os.path.abspath(newPath):
 93			print('Copying {} to {}...'.format(oldPath, newPath))
 94			parent, child = os.path.split(newPath)
 95			makedirs(parent)
 96			shutil.copy2(oldPath, newPath)
 97			os.chmod(newPath, 0o644)
 98		toUpdate.append((newPath, oldExecPath, newExecPath))
 99		if not qtPath and 'Qt' in oldPath:
100			global qtPath
101			qtPath = findQtPath(oldPath)
102			print('Found Qt path at {}.'.format(qtPath))
103	for path, oldExecPath, newExecPath in toUpdate:
104		if path != bin:
105			updateMachO(path, execPath, root)
106			print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath))
107			subprocess.check_call([installNameTool, '-change', oldExecPath, newExecPath, bin])
108		else:
109			print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath))
110			subprocess.check_call([installNameTool, '-id', newExecPath, bin])
111
112if __name__ == '__main__':
113	parser = argparse.ArgumentParser()
114	parser.add_argument('-R', '--root', metavar='ROOT', default='/', help='root directory to search')
115	parser.add_argument('-I', '--install-name-tool', metavar='INSTALL_NAME_TOOL', default='install_name_tool', help='path to install_name_tool')
116	parser.add_argument('-O', '--otool', metavar='OTOOL', default='otool', help='path to otool')
117	parser.add_argument('-p', '--qt-plugins', metavar='PLUGINS', default='', help='Qt plugins to include (comma-separated)')
118	parser.add_argument('bundle', help='application bundle to deploy')
119	args = parser.parse_args()
120
121	otool = args.otool
122	installNameTool = args.install_name_tool
123
124	try:
125		shutil.rmtree(os.path.join(args.bundle, 'Contents/Frameworks/'))
126	except OSError as e:
127		if e.errno != errno.ENOENT:
128			raise
129
130	for executable in os.listdir(os.path.join(args.bundle, 'Contents/MacOS')):
131		if executable.endswith('.dSYM'):
132			continue
133		fullPath = os.path.join(args.bundle, 'Contents/MacOS/', executable)
134		updateMachO(fullPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))
135	if args.qt_plugins:
136		try:
137			shutil.rmtree(os.path.join(args.bundle, 'Contents/PlugIns/'))
138		except OSError as e:
139			if e.errno != errno.ENOENT:
140				raise
141		makedirs(os.path.join(args.bundle, 'Contents/PlugIns'))
142		makedirs(os.path.join(args.bundle, 'Contents/Resources'))
143		with open(os.path.join(args.bundle, 'Contents/Resources/qt.conf'), 'w') as conf:
144			conf.write('[Paths]\nPlugins = PlugIns\n')
145		plugins = args.qt_plugins.split(',')
146		for plugin in plugins:
147			plugin = plugin.strip()
148			kind, plug = os.path.split(plugin)
149			newDir = os.path.join(args.bundle, 'Contents/PlugIns/', kind)
150			makedirs(newDir)
151			newPath = os.path.join(newDir, plug)
152			shutil.copy2(os.path.join(qtPath, 'plugins', plugin), newPath)
153			updateMachO(newPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))