def selectImports(pth, xtrapath=None): """ Return the dependencies of a binary that should be included. Return a list of pairs (name, fullpath) """ rv = [] if xtrapath is None: xtrapath = [os.path.dirname(pth)] else: assert isinstance(xtrapath, list) xtrapath = [os.path.dirname(pth)] + xtrapath # make a copy dlls = getImports(pth) for lib in dlls: if lib.upper() in seen: continue if not compat.is_win: # all other platforms npth = lib lib = os.path.basename(lib) else: # plain win case npth = getfullnameof(lib, xtrapath) # now npth is a candidate lib if found # check again for excludes but with regex FIXME: split the list if npth: candidatelib = npth else: candidatelib = lib if not dylib.include_library(candidatelib): if (candidatelib.find('libpython') < 0 and candidatelib.find('Python.framework') < 0): # skip libs not containing (libpython or Python.framework) if npth.upper() not in seen: logger.debug("Skipping %s dependency of %s", lib, os.path.basename(pth)) continue else: pass if npth: if npth.upper() not in seen: logger.debug("Adding %s dependency of %s from %s", lib, os.path.basename(pth), npth) rv.append((lib, npth)) elif dylib.warn_missing_lib(lib): logger.warning("lib not found: %s dependency of %s", lib, pth) return rv
def _getImports_macholib(pth): """ Find the binary dependencies of PTH. This implementation is for Mac OS X and uses library macholib. """ from macholib.MachO import MachO from macholib.mach_o import LC_RPATH from macholib.dyld import dyld_find from macholib.util import in_system_path 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].decode('utf-8') # Remove trailing '\x00' characters. # e.g. '../lib\x00\x00' rpath = rpath.rstrip('\x00') # Replace the @executable_path and @loader_path keywords # with the actual path to the binary. executable_path = os.path.dirname(pth) rpath = re.sub('^@(executable_path|loader_path|rpath)(/|$)', executable_path + r'\2', rpath) # Make rpath absolute. According to Apple doc LC_RPATH # is always relative to the binary location. rpath = os.path.normpath(os.path.join(executable_path, rpath)) run_paths.update([rpath]) else: # Frameworks that have this structure Name.framework/Versions/N/Name # need to to search at the same level as the framework dir. # This is specifically needed so that the QtWebEngine dependencies # can be found. if '.framework' in pth: run_paths.update(['../../../']) # for distributions like Anaconda, all of the dylibs are stored in the lib directory # of the Python distribution, not alongside of the .so's in each module's subdirectory. run_paths.add(os.path.join(compat.base_prefix, 'lib')) ## 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 warning if no existing file found. if not final_lib and dylib.warn_missing_lib(lib): logger.warning('Cannot 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: # Starting with Big Sur, system libraries are hidden. And # we do not collect system libraries on any macOS version # anyway, so suppress the corresponding error messages. if not in_system_path(lib) and dylib.warn_missing_lib(lib): logger.warning('Cannot find path %s (needed by %s)', lib, pth) return rslt
def _getImports_ldd(pth): """ Find the binary dependencies of PTH. This implementation is for ldd platforms (mostly unix). """ rslt = set() if compat.is_aix: # Match libs of the form # 'archivelib.a(objectmember.so/.o)' # or # 'sharedlib.so' # Will not match the fake lib '/unix' lddPattern = re.compile( r"^\s*(((?P<libarchive>(.*\.a))(?P<objectmember>\(.*\)))|((?P<libshared>(.*\.so))))$" ) elif compat.is_hpux: # Match libs of the form # 'sharedlib.so => full-path-to-lib # e.g. # 'libpython2.7.so => /usr/local/lib/hpux32/libpython2.7.so' lddPattern = re.compile(r"^\s+(.*)\s+=>\s+(.*)$") elif compat.is_solar: # Match libs of the form # 'sharedlib.so => full-path-to-lib # e.g. # 'libpython2.7.so.1.0 => /usr/local/lib/libpython2.7.so.1.0' # Will not match the platform specific libs starting with '/platform' lddPattern = re.compile(r"^\s+(.*)\s+=>\s+(.*)$") else: lddPattern = re.compile(r"\s*(.*?)\s+=>\s+(.*?)\s+\(.*\)") for line in compat.exec_command('ldd', pth).splitlines(): m = lddPattern.search(line) if m: if compat.is_aix: libarchive = m.group('libarchive') if libarchive: # We matched an archive lib with a request for a particular # embedded shared object. # 'archivelib.a(objectmember.so/.o)' lib = libarchive name = os.path.basename(lib) + m.group('objectmember') else: # We matched a stand-alone shared library. # 'sharedlib.so' lib = m.group('libshared') name = os.path.basename(lib) elif compat.is_hpux: name, lib = m.group(1), m.group(2) else: name, lib = m.group(1), m.group(2) if name[:10] in ('linux-gate', 'linux-vdso'): # linux-gate is a fake library which does not exist and # should be ignored. See also: # http://www.trilithium.com/johan/2005/08/linux-gate/ continue if compat.is_cygwin: # exclude Windows system library if lib.lower().startswith('/cygdrive/c/windows/system'): continue if os.path.exists(lib): # Add lib if it is not already found. if lib not in rslt: rslt.add(lib) elif dylib.warn_missing_lib(name): logger.warning('Cannot find %s in path %s (needed by %s)', name, lib, pth) elif line.endswith("not found"): # On glibc-based linux distributions, missing libraries # are marked with name.so => not found tokens = line.split('=>') if len(tokens) != 2: continue name = tokens[0].strip() if dylib.warn_missing_lib(name): logger.warning('Cannot find %s (needed by %s)', name, pth) return rslt
def _getImports_ldd(pth): """ Find the binary dependencies of PTH. This implementation is for ldd platforms (mostly unix). """ rslt = set() if compat.is_aix: # Match libs of the form # 'archivelib.a(objectmember.so/.o)' # or # 'sharedlib.so' # Will not match the fake lib '/unix' lddPattern = re.compile( r"^\s*(((?P<libarchive>(.*\.a))(?P<objectmember>\(.*\)))|((?P<libshared>(.*\.so))))$" ) elif compat.is_hpux: # Match libs of the form # 'sharedlib.so => full-path-to-lib # e.g. # 'libpython2.7.so => /usr/local/lib/hpux32/libpython2.7.so' lddPattern = re.compile(r"^\s+(.*)\s+=>\s+(.*)$") elif compat.is_solar: # Match libs of the form # 'sharedlib.so => full-path-to-lib # e.g. # 'libpython2.7.so.1.0 => /usr/local/lib/libpython2.7.so.1.0' # Will not match the platform specific libs starting with '/platform' lddPattern = re.compile(r"^\s+(.*)\s+=>\s+(.*)$") else: lddPattern = re.compile(r"\s*(.*?)\s+=>\s+(.*?)\s+\(.*\)") p = subprocess.run(['ldd', pth], stderr=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) for line in p.stderr.splitlines(): if not line: continue # Python extensions (including stdlib ones) are not linked against python.so but rely on Python's symbols having # already been loaded into symbol space at runtime. musl's ldd issues a series of harmless warnings to stderr # telling us that those symbols are unfindable. These should be suppressed. elif line.startswith("Error relocating ") and line.endswith( " symbol not found"): continue # Propagate any other warnings it might have. print(line, file=sys.stderr) for line in p.stdout.splitlines(): m = lddPattern.search(line) if m: if compat.is_aix: libarchive = m.group('libarchive') if libarchive: # We matched an archive lib with a request for a particular embedded shared object. # 'archivelib.a(objectmember.so/.o)' lib = libarchive name = os.path.basename(lib) + m.group('objectmember') else: # We matched a stand-alone shared library. # 'sharedlib.so' lib = m.group('libshared') name = os.path.basename(lib) elif compat.is_hpux: name, lib = m.group(1), m.group(2) else: name, lib = m.group(1), m.group(2) if name[:10] in ('linux-gate', 'linux-vdso'): # linux-gate is a fake library which does not exist and should be ignored. See also: # http://www.trilithium.com/johan/2005/08/linux-gate/ continue if compat.is_cygwin: # exclude Windows system library if lib.lower().startswith('/cygdrive/c/windows/system'): continue if os.path.exists(lib): # Add lib if it is not already found. if lib not in rslt: rslt.add(lib) elif dylib.warn_missing_lib(name): logger.warning('Cannot find %s in path %s (needed by %s)', name, lib, pth) elif line.endswith("not found"): # On glibc-based linux distributions, missing libraries are marked with name.so => not found tokens = line.split('=>') if len(tokens) != 2: continue name = tokens[0].strip() if dylib.warn_missing_lib(name): logger.warning('Cannot find %s (needed by %s)', name, pth) return rslt