def mac_set_relative_dylib_deps(libname): """ On Mac OS X set relative paths to dynamic library dependencies of `libname`. Relative paths allow to avoid using environment variable DYLD_LIBRARY_PATH. There are known some issues with DYLD_LIBRARY_PATH. Relative paths is more flexible mechanism. Current location of dependend libraries is derived from the location of the executable (paths start with '@executable_path'). @executable_path or @loader_path fail in some situations (@loader_path - qt4 plugins, @executable_path - Python built-in hashlib module). """ from PyInstaller.lib.macholib import util from PyInstaller.lib.macholib.MachO import MachO # Ignore bootloader otherwise PyInstaller fails with exception like # 'ValueError: total_size > low_offset (288 > 0)' if os.path.basename(libname) in _BOOTLOADER_FNAMES: return def match_func(pth): """For system libraries is still used absolute path. It is unchanged.""" # Match non system dynamic libraries. if not util.in_system_path(pth): # Use relative path to dependend dynamic libraries bases on # location of the executable. return os.path.join('@executable_path', os.path.basename(pth)) # Rewrite mach headers with @executable_path. dll = MachO(libname) dll.rewriteLoadCommands(match_func) # Write changes into file. # Write code is based on macholib example. try: f = open(dll.filename, 'rb+') for header in dll.headers: f.seek(0) dll.write(f) f.seek(0, 2) f.flush() f.close() except Exception: pass
def _getImports_macholib(pth): """ Find the binary dependencies of PTH. This implementation is for Mac OS X and uses library macholib. """ from PyInstaller.lib.macholib.MachO import MachO from PyInstaller.lib.macholib.dyld import dyld_find rslt = [] seen = set() # Libraries read from binary headers. # Walk through mach binary headers. m = MachO(pth) for header in m.headers: for idx, name, lib in header.walkRelocatables(): # Sometimes some libraries are present multiple times. if lib not in seen: seen.add(lib) # Try to find files in file system. exec_path = os.path.abspath('.') for lib in seen: if lib.startswith('@loader_path'): # macholib can't handle @loader_path. It has to be # handled the same way as @executable_path. # It is also replaced by 'exec_path'. lib = lib.replace('@loader_path', '@executable_path') try: lib = dyld_find(lib, executable_path=exec_path) rslt.append(lib) except ValueError: logger.error('Can not find path %s (needed by %s)', lib, pth) return rslt
def print_file(fp, path): print(path, file=fp) m = MachO(path) for header in m.headers: seen = set() if header.MH_MAGIC == MH_MAGIC_64: sz = '64-bit' else: sz = '32-bit' arch = CPU_TYPE_NAMES.get(header.header.cputype, header.header.cputype) print(' [%s endian=%r size=%r arch=%r]' % (header.__class__.__name__, header.endian, sz, arch), file=fp) for idx, name, other in header.walkRelocatables(): if other not in seen: seen.add(other) print('\t' + other, file=fp) print('', file=fp)
def _getImports_macholib(pth): """ Find the binary dependencies of PTH. This implementation is for Mac OS X and uses library macholib. """ from PyInstaller.lib.macholib.MachO import MachO from PyInstaller.lib.macholib.mach_o import LC_RPATH from PyInstaller.lib.macholib.dyld import dyld_find rslt = set() seen = set() # Libraries read from binary headers. ## Walk through mach binary headers. m = MachO(pth) for header in m.headers: for idx, name, lib in header.walkRelocatables(): # Sometimes some libraries are present multiple times. if lib not in seen: seen.add(lib) # Walk through mach binary headers and look for LC_RPATH. # macholib can't handle @rpath. LC_RPATH has to be read # from the MachO header. # TODO Do we need to remove LC_RPATH from MachO load commands? # Will it cause any harm to leave them untouched? # Removing LC_RPATH should be implemented when getting # files from the bincache if it is necessary. run_paths = set() for header in m.headers: for command in header.commands: # A command is a tupple like: # (<macholib.mach_o.load_command object at 0x>, # <macholib.mach_o.rpath_command object at 0x>, # '../lib\x00\x00') cmd_type = command[0].cmd if cmd_type == LC_RPATH: rpath = command[2] # Remove trailing '\x00' characters. # e.g. '../lib\x00\x00' rpath = rpath.rstrip('\x00') # Make rpath absolute. According to Apple doc LC_RPATH # is always relative to the binary location. rpath = os.path.normpath(os.path.join(os.path.dirname(pth), rpath)) run_paths.update([rpath]) ## Try to find files in file system. # In cases with @loader_path or @executable_path # try to look in the same directory as the checked binary is. # This seems to work in most cases. exec_path = os.path.abspath(os.path.dirname(pth)) for lib in seen: # Suppose that @rpath is not used for system libraries and # using macholib can be avoided. # macholib can't handle @rpath. if lib.startswith('@rpath'): lib = lib.replace('@rpath', '.') # Make path relative. final_lib = None # Absolute path to existing lib on disk. # Try multiple locations. for run_path in run_paths: # @rpath may contain relative value. Use exec_path as # base path. if not os.path.isabs(run_path): run_path = os.path.join(exec_path, run_path) # Stop looking for lib when found in first location. if os.path.exists(os.path.join(run_path, lib)): final_lib = os.path.abspath(os.path.join(run_path, lib)) rslt.add(final_lib) break # Log error if no existing file found. if not final_lib: logger.error('Can not find path %s (needed by %s)', lib, pth) # Macholib has to be used to get absolute path to libraries. else: # macholib can't handle @loader_path. It has to be # handled the same way as @executable_path. # It is also replaced by 'exec_path'. if lib.startswith('@loader_path'): lib = lib.replace('@loader_path', '@executable_path') try: lib = dyld_find(lib, executable_path=exec_path) rslt.add(lib) except ValueError: logger.error('Can not find path %s (needed by %s)', lib, pth) return rslt
def mac_set_relative_dylib_deps(libname, distname): """ On Mac OS X set relative paths to dynamic library dependencies of `libname`. Relative paths allow to avoid using environment variable DYLD_LIBRARY_PATH. There are known some issues with DYLD_LIBRARY_PATH. Relative paths is more flexible mechanism. Current location of dependend libraries is derived from the location of the library path (paths start with '@loader_path'). 'distname' path of the library relative to dist directory of frozen executable. We need this to determine the level of directory level for @loader_path of binaries not found in dist directory. E.g. qt4 plugins are not in the same directory as Qt*.dylib files. Without using '@loader_path/../..' for qt plugins Mac OS X would not be able to resolve shared library dependencies and qt plugins will not be loaded. """ from PyInstaller.lib.macholib import util from PyInstaller.lib.macholib.MachO import MachO # Ignore bootloader otherwise PyInstaller fails with exception like # 'ValueError: total_size > low_offset (288 > 0)' if os.path.basename(libname) in _BOOTLOADER_FNAMES: return # Determine how many directories up is the directory with shared # dynamic libraries. '../' # E.g. ./qt4_plugins/images/ -> ./../../ parent_dir = '' # Check if distname is not only base filename. if os.path.dirname(distname): parent_level = len(os.path.dirname(distname).split(os.sep)) parent_dir = parent_level * (os.pardir + os.sep) def match_func(pth): """ For system libraries is still used absolute path. It is unchanged. """ # Match non system dynamic libraries. if not util.in_system_path(pth): # Use relative path to dependend dynamic libraries bases on # location of the executable. return os.path.join('@loader_path', parent_dir, os.path.basename(pth)) # Rewrite mach headers with @loader_path. dll = MachO(libname) dll.rewriteLoadCommands(match_func) # Write changes into file. # Write code is based on macholib example. try: f = open(dll.filename, 'rb+') for header in dll.headers: f.seek(0) dll.write(f) f.seek(0, 2) f.flush() f.close() except Exception: pass