CMake: use BundleUtilities to fix up Dolphin.app
This commit is contained in:
parent
529dc6aa53
commit
38816bf51d
|
@ -255,9 +255,6 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||||
# This doesn't play well with the packaging script that doesn't understand @rpath
|
|
||||||
set(CMAKE_MACOSX_RPATH OFF)
|
|
||||||
|
|
||||||
if(NOT OSX_USE_DEFAULT_SEARCH_PATH)
|
if(NOT OSX_USE_DEFAULT_SEARCH_PATH)
|
||||||
# Hack up the path to prioritize the path to built-in OS libraries to
|
# Hack up the path to prioritize the path to built-in OS libraries to
|
||||||
# increase the chance of not depending on a bunch of copies of them
|
# increase the chance of not depending on a bunch of copies of them
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# This module can be used in two different ways.
|
||||||
|
#
|
||||||
|
# When invoked as `cmake -P DolphinPostprocessBundle.cmake`, it fixes up an
|
||||||
|
# application folder to be standalone. It bundles all required libraries from
|
||||||
|
# the system and fixes up library IDs. Any additional shared libraries, like
|
||||||
|
# plugins, that are found under Contents/MacOS/ will be made standalone as well.
|
||||||
|
#
|
||||||
|
# When called with `include(DolphinPostprocessBundle)`, it defines a helper
|
||||||
|
# function `dolphin_postprocess_bundle` that sets up the command form of the
|
||||||
|
# module as a post-build step.
|
||||||
|
|
||||||
|
if(CMAKE_GENERATOR)
|
||||||
|
# Being called as include(DolphinPostprocessBundle), so define a helper function.
|
||||||
|
set(_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION "${CMAKE_CURRENT_LIST_FILE}")
|
||||||
|
function(dolphin_postprocess_bundle target)
|
||||||
|
add_custom_command(TARGET ${target} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DDOLPHIN_BUNDLE_PATH="$<TARGET_FILE_DIR:${target}>/../.."
|
||||||
|
-P "${_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION}"
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(DOLPHIN_BUNDLE_PATH "${DOLPHIN_BUNDLE_PATH}" REALPATH)
|
||||||
|
message(STATUS "Fixing up application bundle: ${DOLPHIN_BUNDLE_PATH}")
|
||||||
|
|
||||||
|
# Make sure to fix up any additional shared libraries (like plugins) that are
|
||||||
|
# needed.
|
||||||
|
file(GLOB_RECURSE extra_libs "${DOLPHIN_BUNDLE_PATH}/Contents/MacOS/*.dylib")
|
||||||
|
|
||||||
|
# BundleUtilities doesn't support DYLD_FALLBACK_LIBRARY_PATH behavior, which
|
||||||
|
# makes it sometimes break on libraries that do weird things with @rpath. Specify
|
||||||
|
# equivalent search directories until https://gitlab.kitware.com/cmake/cmake/issues/16625
|
||||||
|
# is fixed and in our minimum CMake version.
|
||||||
|
set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib")
|
||||||
|
|
||||||
|
# BundleUtilities is overly verbose, so disable most of its messages
|
||||||
|
function(message)
|
||||||
|
if(NOT ARGV MATCHES "^STATUS;")
|
||||||
|
_message(${ARGV})
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
include(BundleUtilities)
|
||||||
|
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||||
|
fixup_bundle("${DOLPHIN_BUNDLE_PATH}" "${extra_libs}" "${extra_dirs}")
|
|
@ -49,13 +49,16 @@ if(APPLE)
|
||||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update library references to make the bundle portable
|
# Copy qt.conf into the bundle
|
||||||
add_custom_command(TARGET ${DOLPHINQT2_BINARY} POST_BUILD
|
target_sources(${DOLPHINQT2_BINARY} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf")
|
||||||
COMMAND echo "Fixing up application bundle: ${BUNDLE_PATH}"
|
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/qt.conf" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
COMMAND echo ${CMAKE_SOURCE_DIR}/Tools/deploy-mac.py $<TARGET_FILE_DIR:${DOLPHINQT2_BINARY}>/../..
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy resources in the bundle
|
# Copy Qt plugins into the bundle
|
||||||
|
get_target_property(qtcocoa_location Qt5::QCocoaIntegrationPlugin LOCATION)
|
||||||
|
target_sources(${DOLPHINQT2_BINARY} PRIVATE "${qtcocoa_location}")
|
||||||
|
set_source_files_properties("${qtcocoa_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/platforms)
|
||||||
|
|
||||||
|
# Copy resources into the bundle
|
||||||
file(GLOB_RECURSE resources RELATIVE "${CMAKE_SOURCE_DIR}/Data" "${CMAKE_SOURCE_DIR}/Data/Sys/*")
|
file(GLOB_RECURSE resources RELATIVE "${CMAKE_SOURCE_DIR}/Data" "${CMAKE_SOURCE_DIR}/Data/Sys/*")
|
||||||
foreach(res ${resources})
|
foreach(res ${resources})
|
||||||
target_sources(${DOLPHINQT2_BINARY} PRIVATE "${CMAKE_SOURCE_DIR}/Data/${res}")
|
target_sources(${DOLPHINQT2_BINARY} PRIVATE "${CMAKE_SOURCE_DIR}/Data/${res}")
|
||||||
|
@ -64,6 +67,10 @@ if(APPLE)
|
||||||
MACOSX_PACKAGE_LOCATION "Resources/${resdir}")
|
MACOSX_PACKAGE_LOCATION "Resources/${resdir}")
|
||||||
source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/Data/${res}")
|
source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/Data/${res}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# Update library references to make the bundle portable
|
||||||
|
include(DolphinPostprocessBundle)
|
||||||
|
dolphin_postprocess_bundle(${DOLPHINQT2_BINARY})
|
||||||
else()
|
else()
|
||||||
install(TARGETS ${DOLPHINQT2_BINARY} RUNTIME DESTINATION ${bindir})
|
install(TARGETS ${DOLPHINQT2_BINARY} RUNTIME DESTINATION ${bindir})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -176,11 +176,8 @@ if(wxWidgets_FOUND)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Update library references to make the bundle portable
|
# Update library references to make the bundle portable
|
||||||
add_custom_command(TARGET ${DOLPHIN_EXE} POST_BUILD
|
include(DolphinPostprocessBundle)
|
||||||
COMMAND echo "Fixing up application bundle: ${BUNDLE_PATH}"
|
dolphin_postprocess_bundle(${DOLPHIN_EXE})
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/Tools/deploy-mac.py $<TARGET_FILE_DIR:${DOLPHIN_EXE}>/../..
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Install bundle into systemwide /Applications directory.
|
# Install bundle into systemwide /Applications directory.
|
||||||
install(TARGETS ${DOLPHIN_EXE} DESTINATION /Applications)
|
install(TARGETS ${DOLPHIN_EXE} DESTINATION /Applications)
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
#!/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
|
|
||||||
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))
|
|
||||||
newPath = None
|
|
||||||
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:
|
|
||||||
if path:
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Check we actually have to update some paths
|
|
||||||
if len(args) > 2:
|
|
||||||
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
|
|
||||||
|
|
||||||
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:
|
|
||||||
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))
|
|
Loading…
Reference in New Issue