def append_rpath_within_wheel(lib_name: str, rpath: str, wheel_base_dir: str, patcher: ElfPatcher) -> None: """Add a new rpath entry to a file while preserving as many existing rpath entries as possible. In order to preserve an rpath entry it must: 1) Point to a location within wheel_base_dir. 2) Not be a duplicate of an already-existing rpath entry. """ if not isabs(lib_name): lib_name = abspath(lib_name) lib_dir = dirname(lib_name) if not isabs(wheel_base_dir): wheel_base_dir = abspath(wheel_base_dir) def is_valid_rpath(rpath: str) -> bool: return _is_valid_rpath(rpath, lib_dir, wheel_base_dir) old_rpaths = patcher.get_rpath(lib_name) rpaths = filter(is_valid_rpath, old_rpaths.split(':')) # Remove duplicates while preserving ordering # Fake an OrderedSet using OrderedDict rpath_set = OrderedDict([(old_rpath, '') for old_rpath in rpaths]) rpath_set[rpath] = '' patcher.set_rpath(lib_name, ':'.join(rpath_set))
def repair_wheel(wheel_path: str, abi: str, lib_sdir: str, out_dir: str, update_tags: bool, patcher: ElfPatcher) -> Optional[str]: external_refs_by_fn = get_wheel_elfdata(wheel_path)[1] # Do not repair a pure wheel, i.e. has no external refs if not external_refs_by_fn: return soname_map = {} # type: Dict[str, str] if not isabs(out_dir): out_dir = abspath(out_dir) wheel_fname = basename(wheel_path) with InWheelCtx(wheel_path) as ctx: ctx.out_wheel = pjoin(out_dir, wheel_fname) dest_dir = WHEEL_INFO_RE(wheel_fname).group('name') + lib_sdir if not exists(dest_dir): os.mkdir(dest_dir) # here, fn is a path to a python extension library in # the wheel, and v['libs'] contains its required libs for fn, v in external_refs_by_fn.items(): ext_libs = v[abi]['libs'] # type: Dict[str, str] for soname, src_path in ext_libs.items(): if src_path is None: raise ValueError(('Cannot repair wheel, because required ' 'library "%s" could not be located') % soname) new_soname, new_path = copylib(src_path, dest_dir, patcher) soname_map[soname] = (new_soname, new_path) patcher.replace_needed(fn, soname, new_soname) if len(ext_libs) > 0: new_rpath = os.path.relpath(dest_dir, os.path.dirname(fn)) new_rpath = os.path.join('$ORIGIN', new_rpath) patcher.set_rpath(fn, new_rpath) # we grafted in a bunch of libraries and modified their sonames, but # they may have internal dependencies (DT_NEEDED) on one another, so # we need to update those records so each now knows about the new # name of the other. for old_soname, (new_soname, path) in soname_map.items(): needed = elf_read_dt_needed(path) for n in needed: if n in soname_map: patcher.replace_needed(path, n, soname_map[n][0]) if update_tags: ctx.out_wheel = add_platforms(ctx, [abi], get_replace_platforms(abi)) return ctx.out_wheel
def copylib(src_path: str, dest_dir: str, patcher: ElfPatcher) -> Tuple[str, str]: """Graft a shared library from the system into the wheel and update the relevant links. 1) Copy the file from src_path to dest_dir/ 2) Rename the shared object from soname to soname.<unique> 3) If the library has a RUNPATH/RPATH, clear it and set RPATH to point to its new location. """ # Copy the a shared library from the system (src_path) into the wheel # if the library has a RUNPATH/RPATH we clear it and set RPATH to point to # its new location. with open(src_path, 'rb') as f: shorthash = hashfile(f)[:8] src_name = os.path.basename(src_path) base, ext = src_name.split('.', 1) if not base.endswith('-%s' % shorthash): new_soname = f'{base}-{shorthash}.{ext}' else: new_soname = src_name dest_path = os.path.join(dest_dir, new_soname) if os.path.exists(dest_path): return new_soname, dest_path logger.debug('Grafting: %s -> %s', src_path, dest_path) rpaths = elf_read_rpaths(src_path) shutil.copy2(src_path, dest_path) statinfo = os.stat(dest_path) if not statinfo.st_mode & stat.S_IWRITE: os.chmod(dest_path, statinfo.st_mode | stat.S_IWRITE) patcher.set_soname(dest_path, new_soname) if any(itertools.chain(rpaths['rpaths'], rpaths['runpaths'])): patcher.set_rpath(dest_path, dest_dir) return new_soname, dest_path
def copylib(src_path: str, dest_dir: str, patcher: ElfPatcher) -> Tuple[str, str]: # Do NOT hash filename to make it unique in the particular case of boost # python modules, since otherwise it will be impossible to share a common # registery, which is necessary for cross module interoperability. if "libboost_python" in src_path: src_name = os.path.basename(src_path) dest_path = os.path.join(dest_dir, src_name) if os.path.exists(dest_path): return src_name, dest_path logger.debug('Grafting: %s -> %s', src_path, dest_path) shutil.copy2(src_path, dest_path) rpaths = elf_read_rpaths(src_path) statinfo = os.stat(dest_path) if not statinfo.st_mode & stat.S_IWRITE: os.chmod(dest_path, statinfo.st_mode | stat.S_IWRITE) patcher.set_soname(dest_path, src_name) if any(itertools.chain(rpaths['rpaths'], rpaths['runpaths'])): patcher.set_rpath(dest_path, dest_dir) return src_name, dest_path return copylib_orig(src_path, dest_dir, patcher)