Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tools: Use non-CMake tool for fixing up OS X bundles #2536

Merged
merged 3 commits into from
Jun 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Core/DolphinQt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ if(APPLE)
message(\"(Note: This is only necessary to produce a redistributable binary.\")
message(\"To skip, pass -DSKIP_POSTPROCESS_BUNDLE=1 to cmake.)\")
set(BU_CHMOD_BUNDLE_ITEMS ON)
fixup_bundle(\"${BUNDLE_PATH}\" \"\" \"\")
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/Tools/deploy-mac.py -p platforms/libqcocoa.dylib \"${BUNDLE_PATH}\")
file(INSTALL ${CMAKE_SOURCE_DIR}/Data/Sys
DESTINATION ${BUNDLE_PATH}/Contents/Resources
)
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/DolphinWX/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ elseif(wxWidgets_FOUND)
message(\"(Note: This is only necessary to produce a redistributable binary.\")
message(\"To skip, pass -DSKIP_POSTPROCESS_BUNDLE=1 to cmake.)\")
set(BU_CHMOD_BUNDLE_ITEMS ON)
fixup_bundle(\"${BUNDLE_PATH}\" \"\" \"\")
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/Tools/deploy-mac.py \"${BUNDLE_PATH}\")
file(INSTALL \"${CMAKE_SOURCE_DIR}/Data/Sys\"
DESTINATION \"${BUNDLE_PATH}/Contents/Resources\"
)
Expand Down
168 changes: 168 additions & 0 deletions Tools/deploy-mac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env python
from __future__ import print_function
import argparse
import errno
import os
import re
import shutil
import subprocess

qtPath = None
verbose = False

def splitPath(path):
folders = []
while True:
path, folder = os.path.split(path)
if folder != '':
folders.append(folder)
else:
if path != '':
folders.append(path)
break
folders.reverse()
return folders

def joinPath(path):
return reduce(os.path.join, path, '')

def findFramework(path):
child = []
while path and not path[-1].endswith('.framework'):
child.append(path.pop())
child.reverse()
return path, child

def findQtPath(path):
parent, child = findFramework(splitPath(path))
return joinPath(parent[:-2])

def makedirs(path):
split = splitPath(path)
accum = []
split.reverse()
while split:
accum.append(split.pop())
newPath = joinPath(accum)
if newPath == '/':
continue
try:
os.mkdir(newPath)
except OSError as e:
if e.errno != errno.EEXIST:
raise


def parseOtoolLine(line, execPath, root):
if not line.startswith('\t'):
return None, None, None, None
line = line[1:]
match = re.match('([@/].*) \(compatibility version.*\)', line)
path = match.group(1)
split = splitPath(path)
newExecPath = ['@executable_path', '..', 'Frameworks']
newPath = execPath[:-1]
newPath.append('Frameworks')
if split[:3] == ['/', 'usr', 'lib'] or split[:2] == ['/', 'System']:
return None, None, None, None
if split[0] == '@executable_path':
split[:1] = execPath
if split[0] == '/' and not os.access(joinPath(split), os.F_OK):
split[:1] = root

This comment was marked as off-topic.

This comment was marked as off-topic.

oldPath = os.path.realpath(joinPath(split))
split = splitPath(oldPath)
isFramework = False
if not split[-1].endswith('.dylib'):
isFramework = True
split, framework = findFramework(split)
newPath.append(split[-1])
newExecPath.append(split[-1])
if isFramework:
newPath.extend(framework)
newExecPath.extend(framework)
split.extend(framework)
newPath = joinPath(newPath)
newExecPath = joinPath(newExecPath)
return joinPath(split), newPath, path, newExecPath

def updateMachO(bin, execPath, root):
global qtPath
otoolOutput = subprocess.check_output([otool, '-L', bin])
toUpdate = []
for line in otoolOutput.split('\n'):
oldPath, newPath, oldExecPath, newExecPath = parseOtoolLine(line, execPath, root)
if not newPath:
continue
if os.access(newPath, os.F_OK):
if verbose:
print('Skipping copying {}, already done.'.format(oldPath))
elif os.path.abspath(oldPath) != os.path.abspath(newPath):
if verbose:
print('Copying {} to {}...'.format(oldPath, newPath))
parent, child = os.path.split(newPath)
makedirs(parent)
shutil.copy2(oldPath, newPath)
os.chmod(newPath, 0o644)
toUpdate.append((newPath, oldExecPath, newExecPath))
if not qtPath and 'Qt' in oldPath:
qtPath = findQtPath(oldPath)
if verbose:
print('Found Qt path at {}.'.format(qtPath))
args = [installNameTool]
for path, oldExecPath, newExecPath in toUpdate:
if path != bin:
updateMachO(path, execPath, root)
if verbose:
print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath))
args.extend(['-change', oldExecPath, newExecPath])
else:
if verbose:
print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath))
args.extend(['-id', newExecPath])
args.append(bin)
subprocess.check_call(args)

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-R', '--root', metavar='ROOT', default='/', help='root directory to search')
parser.add_argument('-I', '--install-name-tool', metavar='INSTALL_NAME_TOOL', default='install_name_tool', help='path to install_name_tool')
parser.add_argument('-O', '--otool', metavar='OTOOL', default='otool', help='path to otool')
parser.add_argument('-p', '--qt-plugins', metavar='PLUGINS', default='', help='Qt plugins to include (comma-separated)')
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='output more information')
parser.add_argument('bundle', help='application bundle to deploy')
args = parser.parse_args()

otool = args.otool
installNameTool = args.install_name_tool
verbose = args.verbose

try:
shutil.rmtree(os.path.join(args.bundle, 'Contents/Frameworks/'))
except OSError as e:
if e.errno != errno.ENOENT:
raise

for executable in os.listdir(os.path.join(args.bundle, 'Contents/MacOS')):
if executable.endswith('.dSYM'):
continue
fullPath = os.path.join(args.bundle, 'Contents/MacOS/', executable)
updateMachO(fullPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))
if args.qt_plugins:
try:
shutil.rmtree(os.path.join(args.bundle, 'Contents/PlugIns/'))
except OSError as e:
if e.errno != errno.ENOENT:
raise
makedirs(os.path.join(args.bundle, 'Contents/PlugIns'))
makedirs(os.path.join(args.bundle, 'Contents/Resources'))
with open(os.path.join(args.bundle, 'Contents/Resources/qt.conf'), 'w') as conf:
conf.write('[Paths]\nPlugins = PlugIns\n')
plugins = args.qt_plugins.split(',')
for plugin in plugins:
plugin = plugin.strip()
kind, plug = os.path.split(plugin)
newDir = os.path.join(args.bundle, 'Contents/PlugIns/', kind)
makedirs(newDir)
newPath = os.path.join(newDir, plug)
shutil.copy2(os.path.join(qtPath, 'plugins', plugin), newPath)
updateMachO(newPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))