Commit b4d4bd3a authored by Andoni Morales Alastruey's avatar Andoni Morales Alastruey Committed by Sebastian Dröge
Browse files

Make macOS binaries fully relocatable

Fixes issue #27
parent f95f74e4
Pipeline #14836 passed with stages
in 69 minutes and 2 seconds
......@@ -29,6 +29,7 @@ from cerbero.config import Platform
from cerbero.errors import FatalError
from cerbero.ide.vs.genlib import GenLib, GenGnuLib
from cerbero.tools.osxuniversalgenerator import OSXUniversalGenerator
from cerbero.tools.osxrelocator import OSXRelocator
from cerbero.utils import N_, _
from cerbero.utils import shell, add_system_libs
from cerbero.utils import messages as m
......@@ -79,6 +80,7 @@ class BuildSteps(object):
CHECK = (N_('Check'), 'check')
GEN_LIBFILES = (N_('Gen Library File'), 'gen_library_file')
MERGE = (N_('Merge universal binaries'), 'merge')
RELOCATE_OSX_LIBRARIES = (N_('Relocate OSX libraries'), 'relocate_osx_libraries')
def __new__(klass):
return [BuildSteps.FETCH, BuildSteps.EXTRACT,
......@@ -139,6 +141,8 @@ class Recipe(FilesProvider, metaclass=MetaRecipe):
self._steps = self._default_steps[:]
if self.config.target_platform == Platform.WINDOWS:
self._steps.append(BuildSteps.GEN_LIBFILES)
if self.config.target_platform == Platform.DARWIN:
self._steps.append(BuildSteps.RELOCATE_OSX_LIBRARIES)
FilesProvider.__init__(self, config)
try:
self.stype.__init__(self)
......@@ -322,6 +326,25 @@ class Recipe(FilesProvider, metaclass=MetaRecipe):
LibtoolLibrary(lib.name, lib.major, lib.minor, lib.micro, lib.libdir,
self.config.target_platform, deps=dep_libs).save()
def relocate_osx_libraries(self):
'''
Make OSX libraries relocatable
'''
relocator = OSXRelocator(self.config.prefix, self.config.prefix, True)
def get_real_path(fp):
return os.path.realpath(os.path.join(self.config.prefix, fp))
def file_is_relocatable(fp):
return fp.split('/')[0] in ['lib', 'bin', 'libexec'] and \
os.path.splitext(fp)[1] not in ['.a', '.pc', '.la']
# Only relocate files are that are potentially relocatable and
# remove duplicates by symbolic links so we relocate libs only
# once.
for f in set([get_real_path(x) for x in self.files_list() \
if file_is_relocatable(x)]):
relocator.relocate_file(f)
def post_install(self):
'''
Runs post installation steps
......
......@@ -31,7 +31,6 @@ from cerbero.packages.osx.distribution import DistributionXML
from cerbero.packages.osx.bundles import FrameworkBundlePackager,\
ApplicationBundlePackager
from cerbero.packages.osx.buildtools import PackageBuild, ProductBuild
from cerbero.tools.osxrelocator import OSXRelocator
from cerbero.utils import shell, _
from cerbero.tools import strip
from cerbero.utils import messages as m
......@@ -373,7 +372,6 @@ class ApplicationPackage(PackagerBase):
self._create_bundle()
self._create_app_bundle()
self._strip_binaries()
self._relocate_binaries()
if self.package.osx_create_pkg:
pkg = self._create_product()
self._add_applications_link()
......@@ -417,18 +415,6 @@ class ApplicationPackage(PackagerBase):
s = strip.Strip(self.config, self.package.strip_excludes)
s.strip_dir(s_dir)
def _relocate_binaries(self):
if not self.package.relocate_osx_binaries:
return
prefix = self.config.prefix
if prefix[-1] == '/':
prefix = prefix[:-1]
for path in ['bin', 'lib', 'libexec']:
relocator = OSXRelocator(
os.path.join(self.appdir, 'Contents', 'Home', path),
self.config.prefix, '@executable_path/../', True)
relocator.relocate()
def _add_applications_link(self):
# Create link to /Applications
applications_link = os.path.join(self.approot, 'Applications')
......
......@@ -52,8 +52,6 @@ class PackageBase(object):
@type sys_deps_devel: dict
@cvar ignore_package_prefix: don't use the package prefix set in the config
@type ignore_package_prefix: bool
@cvar relocate_osx_binaries: relocate osx binaries
@type relocate_osx_binaries: bool
@cvar resources_license: filename of the .txt license file
@type resources_license: str
@cvar resources_license_unwrapped: filename of the .txt license file
......@@ -86,7 +84,6 @@ class PackageBase(object):
ignore_package_prefix = False
sys_deps = {}
sys_deps_devel = {}
relocate_osx_binaries = True
resources_license = 'license.txt'
resources_license_unwrapped = 'license_unwrapped.txt'
resources_license_rtf = 'license.txt'
......
......@@ -36,34 +36,53 @@ class OSXRelocator(object):
ID if the file is a shared library.
'''
def __init__(self, root, lib_prefix, new_lib_prefix, recursive):
def __init__(self, root, lib_prefix, recursive):
self.root = root
self.lib_prefix = self._fix_path(lib_prefix)
self.new_lib_prefix = self._fix_path(new_lib_prefix)
self.recursive = recursive
self.use_relative_paths = True
def relocate(self):
self.parse_dir(self.root)
def relocate_file(self, object_file, id=None):
def relocate_dir(self, dirname):
self.parse_dir(os.path.join(self.root, dirname))
def relocate_file(self, object_file):
self.change_libs_path(object_file)
self.change_id(object_file, id)
def change_id(self, object_file, id=None):
id = id or object_file.replace(self.lib_prefix, self.new_lib_prefix)
id = id or object_file.replace(self.lib_prefix, '@rpath')
filename = os.path.basename(object_file)
if not (filename.endswith('so') or filename.endswith('dylib')):
return
cmd = '%s -id %s %s' % (INT_CMD, id, object_file)
shell.call(cmd)
cmd = '%s -id "%s" "%s"' % (INT_CMD, id, object_file)
shell.call(cmd, fail=False)
def change_libs_path(self, object_file):
depth = len(object_file.split('/')) - len(self.root.split('/')) - 1
p_depth = '/..' * depth
rpaths = ['@loader_path' + p_depth, '@executable_path' + p_depth]
rpaths += ['@loader_path' + '/../lib', '@executable_path' + '/../lib']
if depth > 1:
rpaths += ['@loader_path/..', '@executable_path/..']
for p in rpaths:
cmd = '%s -add_rpath %s "%s"' % (INT_CMD, p, object_file)
shell.call(cmd, fail=False)
for lib in self.list_shared_libraries(object_file):
if self.lib_prefix in lib:
new_lib = lib.replace(self.lib_prefix, self.new_lib_prefix)
cmd = '%s -change %s %s %s' % (INT_CMD, lib, new_lib,
new_lib = lib.replace(self.lib_prefix, '@rpath')
cmd = '%s -change "%s" "%s" "%s"' % (INT_CMD, lib, new_lib,
object_file)
shell.call(cmd, fail=False)
def change_lib_path(self, object_file, old_path, new_path):
for lib in self.list_shared_libraries(object_file):
if old_path in lib:
new_path = lib.replace(old_path, new_path)
cmd = '%s -change "%s" "%s" "%s"' % (INT_CMD, lib, new_path,
object_file)
shell.call(cmd)
shell.call(cmd, fail=True)
def parse_dir(self, dir_path, filters=None):
for dirpath, dirnames, filenames in os.walk(dir_path):
......@@ -71,17 +90,13 @@ class OSXRelocator(object):
if filters is not None and \
os.path.splitext(f)[1] not in filters:
continue
lib = os.path.join(dirpath, f)
id = self.library_id_name(lib).replace(
self.lib_prefix, self.new_lib_prefix)
self.change_libs_path(lib)
self.change_id(lib, id)
self.change_libs_path(os.path.join(dirpath, f))
if not self.recursive:
break
@staticmethod
def list_shared_libraries(object_file):
cmd = '%s -L %s' % (OTOOL_CMD, object_file)
cmd = '%s -L "%s"' % (OTOOL_CMD, object_file)
res = shell.check_call(cmd).split('\n')
# We don't use the first line
libs = res[1:]
......@@ -91,14 +106,6 @@ class OSXRelocator(object):
libs = [x.split(' ', 1)[0] for x in libs]
return libs
@staticmethod
def library_id_name(object_file):
cmd = '%s -D %s' % (OTOOL_CMD, object_file)
res = shell.check_call(cmd).split('\n')[0]
# the library name ends with ':'
lib_name = res[:-1]
return lib_name
def _fix_path(self, path):
if path.endswith('/'):
return path[:-1]
......@@ -112,7 +119,7 @@ class Main(object):
# might be run in OS X 10.6 or older, which do not provide the argparse
# module
import optparse
usage = "usage: %prog [options] directory old_prefix new_prefix"
usage = "usage: %prog [options] library_path old_prefix new_prefix"
description = 'Rellocates object files changing the dependant '\
' dynamic libraries location path with a new one'
parser = optparse.OptionParser(usage=usage, description=description)
......@@ -124,8 +131,8 @@ class Main(object):
if len(args) != 3:
parser.print_usage()
exit(1)
relocator = OSXRelocator(args[0], args[1], args[2], options.recursive)
relocator.relocate()
relocator = OSXRelocator(args[1], args[2], options.recursive)
relocator.relocate_file(args[0])
exit(0)
......
......@@ -22,7 +22,6 @@ import subprocess
import shutil
import tempfile
import sys
import os.path
from cerbero.utils import shell
......@@ -53,6 +52,8 @@ file_types = [
('Zip', 'copy'),
('empty', 'copy'),
('PEM certificate', 'copy'),
('data', 'copy'),
('GVariant Database', 'copy'),
]
class OSXUniversalGenerator(object):
......@@ -87,6 +88,8 @@ class OSXUniversalGenerator(object):
self.missing = []
def merge_files(self, filelist, dirs):
if len(filelist) == 0:
return
for f in filelist:
self.do_merge(f, dirs)
......@@ -104,13 +107,13 @@ class OSXUniversalGenerator(object):
tmp = tempfile.NamedTemporaryFile(suffix=os.path.basename(f))
tmp_inputs.append(tmp)
shutil.copy(f, tmp.name)
prefix_to_replace = os.path.abspath(next(get_parent_prefix(f, dirs)))
relocator = OSXRelocator (self.output_root, prefix_to_replace, self.output_root,
prefix_to_replace = [d for d in dirs if d in f][0]
relocator = OSXRelocator (self.output_root, prefix_to_replace,
False)
# since we are using a temporary file, we must force the library id
# name to real one and not based on the filename
relocator.relocate_file(tmp.name,
id=f.replace(prefix_to_replace, self.output_root))
relocator.relocate_file(tmp.name)
relocator.change_id(tmp.name, id=f.replace(prefix_to_replace, self.output_root))
cmd = '%s -create %s -output %s' % (self.LIPO_CMD,
' '.join([f.name for f in tmp_inputs]), output)
self._call(cmd)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment