all repos — mgba @ 66a1af39be2d17eb01020d5f82981e4191372e6d

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[0] == '/':
 64		split[:1] = root
 65	if split[0] == '@executable_path':
 66		split[:1] = execPath
 67	if split[:3] == ['/', 'usr', 'lib'] or split[:2] == ['/', 'System']:
 68		newPath = None
 69		newExecPath = None
 70	else:
 71		isFramework = False
 72		if not split[-1].endswith('.dylib'):
 73			isFramework = True
 74			split, framework = findFramework(split)
 75		newPath.append(split[-1])
 76		newExecPath.append(split[-1])
 77		if isFramework:
 78			newPath.extend(framework)
 79			newExecPath.extend(framework)
 80			split.extend(framework)
 81		newPath = joinPath(newPath)
 82		newExecPath = joinPath(newExecPath)
 83	return joinPath(split), newPath, path, newExecPath
 84
 85def updateMachO(bin, execPath, root):
 86	otoolOutput = subprocess.check_output([otool, '-L', bin])
 87	toUpdate = []
 88	for line in otoolOutput.split('\n'):
 89		oldPath, newPath, oldExecPath, newExecPath = parseOtoolLine(line, execPath, root)
 90		if not newPath:
 91			continue
 92		if os.access(newPath, os.F_OK):
 93			print('Skipping copying {}, already done.'.format(oldPath))
 94		elif os.path.abspath(oldPath) != os.path.abspath(newPath):
 95			print('Copying {} to {}...'.format(oldPath, newPath))
 96			parent, child = os.path.split(newPath)
 97			makedirs(parent)
 98			shutil.copy2(oldPath, newPath)
 99			os.chmod(newPath, 0o644)
100		toUpdate.append((newPath, oldExecPath, newExecPath))
101		if not qtPath and 'Qt' in oldPath:
102			global qtPath
103			qtPath = findQtPath(oldPath)
104			print('Found Qt path at {}.'.format(qtPath))
105	for path, oldExecPath, newExecPath in toUpdate:
106		if path != bin:
107			updateMachO(path, execPath, root)
108			print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath))
109			subprocess.check_call([installNameTool, '-change', oldExecPath, newExecPath, bin])
110		else:
111			print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath))
112			subprocess.check_call([installNameTool, '-id', newExecPath, bin])
113
114if __name__ == '__main__':
115	parser = argparse.ArgumentParser()
116	parser.add_argument('-R', '--root', metavar='ROOT', default='/', help='root directory to search')
117	parser.add_argument('-I', '--install-name-tool', metavar='INSTALL_NAME_TOOL', default='install_name_tool', help='path to install_name_tool')
118	parser.add_argument('-O', '--otool', metavar='OTOOL', default='otool', help='path to otool')
119	parser.add_argument('-p', '--qt-plugins', metavar='PLUGINS', default='', help='Qt plugins to include (comma-separated)')
120	parser.add_argument('bundle', help='application bundle to deploy')
121	args = parser.parse_args()
122
123	otool = args.otool
124	installNameTool = args.install_name_tool
125
126	try:
127		shutil.rmtree(os.path.join(args.bundle, 'Contents/Frameworks/'))
128	except OSError as e:
129		if e.errno != errno.ENOENT:
130			raise
131
132	for executable in os.listdir(os.path.join(args.bundle, 'Contents/MacOS')):
133		if executable.endswith('.dSYM'):
134			continue
135		fullPath = os.path.join(args.bundle, 'Contents/MacOS/', executable)
136		updateMachO(fullPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), args.root)
137	if args.qt_plugins:
138		try:
139			shutil.rmtree(os.path.join(args.bundle, 'Contents/PlugIns/'))
140		except OSError as e:
141			if e.errno != errno.ENOENT:
142				raise
143		makedirs(os.path.join(args.bundle, 'Contents/PlugIns'))
144		makedirs(os.path.join(args.bundle, 'Contents/Resources'))
145		with open(os.path.join(args.bundle, 'Contents/Resources/qt.conf'), 'w') as conf:
146			conf.write('[Paths]\nPlugins = PlugIns\n')
147		plugins = args.qt_plugins.split(',')
148		for plugin in plugins:
149			plugin = plugin.strip()
150			kind, plug = os.path.split(plugin)
151			newDir = os.path.join(args.bundle, 'Contents/PlugIns/', kind)
152			makedirs(newDir)
153			newPath = os.path.join(newDir, plug)
154			shutil.copy2(os.path.join(qtPath, 'plugins', plugin), newPath)
155			updateMachO(newPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), args.root)