Skip to content
Snippets Groups Projects
osxrelocator.py 5.41 KiB
Newer Older
  • Learn to ignore specific revisions
  • #!/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):
    
        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):
    
        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"
    
    Ricardo de Almeida Gonzaga's avatar
    Ricardo de Almeida Gonzaga committed
            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()