testdiff2/tools/osxrelocator.py

161 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python
# cerbero - a multi-platform build system for Open Source software
# Copyright (C) 2012 Andoni Morales Alastruey <ylatuya@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import subprocess
INT_CMD = 'install_name_tool'
OTOOL_CMD = 'otool'
def shell_call(cmd, cmd_dir='.', fail=True):
#print("call", cmd)
try:
ret = subprocess.check_call(
cmd, cwd=cmd_dir,
env=os.environ.copy())
except subprocess.CalledProcessError:
if fail:
raise SystemError("Error running command: {}".format(cmd))
else:
ret = 0
return ret
def shell_check_call(cmd):
#print("ccall", cmd)
try:
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE)
output, _ = process.communicate()
except Exception:
raise SystemError("Error running command: {}".format(cmd))
return output
class OSXRelocator(object):
'''
Wrapper for OS X's install_name_tool and otool commands to help
relocating shared libraries.
It parses lib/ /libexec and bin/ directories, changes the prefix path of
the shared libraries that an object file uses and changes it's library
ID if the file is a shared library.
'''
def __init__(self, root, lib_prefix, new_lib_prefix, recursive):
self.root = root
self.lib_prefix = self._fix_path(lib_prefix).encode('utf-8')
self.new_lib_prefix = self._fix_path(new_lib_prefix).encode('utf-8')
self.recursive = recursive
def relocate(self):
self.parse_dir(self.root, filters=['', '.dylib', '.so'])
def relocate_file(self, object_file, id=None):
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.decode('utf-8'), self.new_lib_prefix.decode('utf-8'))
filename = os.path.basename(object_file)
if not (filename.endswith('so') or filename.endswith('dylib')):
return
cmd = [INT_CMD, "-id", id, object_file]
shell_call(cmd, fail=False)
def change_libs_path(self, object_file):
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 = [INT_CMD, "-change", lib, new_lib, object_file]
shell_call(cmd)
def parse_dir(self, dir_path, filters=None):
for dirpath, dirnames, filenames in os.walk(dir_path):
for f in filenames:
if filters is not None and \
os.path.splitext(f)[1] not in filters:
continue
fn = os.path.join(dirpath, f)
if os.path.islink(fn):
continue
if not os.path.isfile(fn):
continue
self.relocate_file(fn)
if not self.recursive:
break
@staticmethod
def list_shared_libraries(object_file):
cmd = [OTOOL_CMD, "-L", object_file]
res = shell_check_call(cmd).split(b'\n')
# We don't use the first line
libs = res[1:]
# Remove the first character tabulation
libs = [x[1:] for x in libs]
# Remove the version info
libs = [x.split(b' ', 1)[0] for x in libs]
return libs
@staticmethod
def library_id_name(object_file):
cmd = [OTOOL_CMD, "-D", 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]
return path
class Main(object):
def run(self):
# We use OptionParser instead of ArgumentsParse because this script
# 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"
description = 'Rellocates object files changing the dependent '\
' dynamic libraries location path with a new one'
parser = optparse.OptionParser(usage=usage, description=description)
parser.add_option('-r', '--recursive', action='store_true',
default=False, dest='recursive',
help='Scan directories recursively')
options, args = parser.parse_args()
if len(args) != 3:
parser.print_usage()
exit(1)
relocator = OSXRelocator(args[0], args[1], args[2], options.recursive)
relocator.relocate()
exit(0)
def main():
main = Main()
main.run()
if __name__ == "__main__":
main()