def main(pyi_config, specfile, noconfirm, ascii=False, **kw): from PyInstaller.config import CONF CONF['noconfirm'] = noconfirm # Some modules are included if they are detected at build-time or # if a command-line argument is specified. (e.g. --ascii) if CONF.get('hiddenimports') is None: CONF['hiddenimports'] = [] # Test unicode support. if not ascii: CONF['hiddenimports'].extend(get_unicode_modules()) # FIXME: this should be a global import, but can't due to recursive imports # If configuration dict is supplied - skip configuration step. if pyi_config is None: import PyInstaller.configure as configure CONF.update(configure.get_config(kw.get('upx_dir'))) else: CONF.update(pyi_config) if CONF['hasUPX']: setupUPXFlags() CONF['ui_admin'] = kw.get('ui_admin', False) CONF['ui_access'] = kw.get('ui_uiaccess', False) build(specfile, kw.get('distpath'), kw.get('workpath'), kw.get('clean_build'))
def checkCache(fnm, strip=False, upx=False, upx_exclude=None, dist_nm=None): """ Cache prevents preprocessing binary files again and again. 'dist_nm' Filename relative to dist directory. We need it on Mac to determine level of paths for @loader_path like '@loader_path/../../' for qt4 plugins. """ from ..config import CONF # On darwin a cache is required anyway to keep the libaries # with relative install names. Caching on darwin does not work # since we need to modify binary headers to use relative paths # to dll depencies and starting with '@loader_path'. if not strip and not upx and not is_darwin and not is_win: return fnm if dist_nm is not None and ":" in dist_nm: # A file embedded in another pyinstaller build via multipackage # No actual file exists to process return fnm if strip: strip = True else: strip = False upx_exclude = upx_exclude or [] upx = (upx and (is_win or is_cygwin) and os.path.normcase(os.path.basename(fnm)) not in upx_exclude) # Load cache index # Make cachedir per Python major/minor version. # This allows parallel building of executables with different # Python versions as one user. pyver = ('py%d%s') % (sys.version_info[0], sys.version_info[1]) arch = platform.architecture()[0] cachedir = os.path.join(CONF['cachedir'], 'bincache%d%d_%s_%s' % (strip, upx, pyver, arch)) if not os.path.exists(cachedir): os.makedirs(cachedir) cacheindexfn = os.path.join(cachedir, "index.dat") if os.path.exists(cacheindexfn): try: cache_index = load_py_data_struct(cacheindexfn) except Exception as e: # tell the user they may want to fix their cache # .. however, don't delete it for them; if it keeps getting # corrupted, we'll never find out logger.warn("pyinstaller bincache may be corrupted; " "use pyinstaller --clean to fix") raise else: cache_index = {} # Verify if the file we're looking for is present in the cache. # Use the dist_mn if given to avoid different extension modules # sharing the same basename get corrupted. if dist_nm: basenm = os.path.normcase(dist_nm) else: basenm = os.path.normcase(os.path.basename(fnm)) # Binding redirects should be taken into account to see if the file # needs to be reprocessed. The redirects may change if the versions of dependent # manifests change due to system updates. redirects = CONF.get('binding_redirects', []) digest = cacheDigest(fnm, redirects) cachedfile = os.path.join(cachedir, basenm) cmd = None if basenm in cache_index: if digest != cache_index[basenm]: os.remove(cachedfile) else: # On Mac OS X we need relative paths to dll dependencies # starting with @executable_path if is_darwin: dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) return cachedfile # Optionally change manifest and its deps to private assemblies if fnm.lower().endswith(".manifest"): manifest = winmanifest.Manifest() manifest.filename = fnm with open(fnm, "rb") as f: manifest.parse_string(f.read()) if CONF.get('win_private_assemblies', False): if manifest.publicKeyToken: logger.info("Changing %s into private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None applyRedirects(manifest, redirects) manifest.writeprettyxml(cachedfile) return cachedfile if upx: if strip: fnm = checkCache(fnm, strip=True, upx=False) bestopt = "--best" # FIXME: Linux builds of UPX do not seem to contain LZMA (they assert out) # A better configure-time check is due. if CONF["hasUPX"] >= (3, ) and os.name == "nt": bestopt = "--lzma" upx_executable = "upx" if CONF.get('upx_dir'): upx_executable = os.path.join(CONF['upx_dir'], upx_executable) cmd = [upx_executable, bestopt, "-q", cachedfile] else: if strip: strip_options = [] if is_darwin: # The default strip behaviour breaks some shared libraries # under Mac OSX. # -S = strip only debug symbols. strip_options = ["-S"] cmd = ["strip"] + strip_options + [cachedfile] if not os.path.exists(os.path.dirname(cachedfile)): os.makedirs(os.path.dirname(cachedfile)) # There are known some issues with 'shutil.copy2' on Mac OS X 10.11 # with copying st_flags. Issue #1650. # 'shutil.copy' copies also permission bits and it should be sufficient for # PyInstalle purposes. shutil.copy(fnm, cachedfile) # TODO find out if this is still necessary when no longer using shutil.copy2() if hasattr(os, 'chflags'): # Some libraries on FreeBSD have immunable flag (libthr.so.3, for example) # If flags still remains, os.chmod will failed with: # OSError: [Errno 1] Operation not permitted. try: os.chflags(cachedfile, 0) except OSError: pass os.chmod(cachedfile, 0o755) if os.path.splitext(fnm.lower())[1] in (".pyd", ".dll"): # When shared assemblies are bundled into the app, they may optionally be # changed into private assemblies. try: res = winmanifest.GetManifestResources(os.path.abspath(cachedfile)) except winresource.pywintypes.error as e: if e.args[0] == winresource.ERROR_BAD_EXE_FORMAT: # Not a win32 PE file pass else: logger.error(os.path.abspath(cachedfile)) raise else: if winmanifest.RT_MANIFEST in res and len( res[winmanifest.RT_MANIFEST]): for name in res[winmanifest.RT_MANIFEST]: for language in res[winmanifest.RT_MANIFEST][name]: try: manifest = winmanifest.Manifest() manifest.filename = ":".join([ cachedfile, str(winmanifest.RT_MANIFEST), str(name), str(language) ]) manifest.parse_string( res[winmanifest.RT_MANIFEST][name][language], False) except Exception as exc: logger.error( "Cannot parse manifest resource %s, " "%s", name, language) logger.error("From file %s", cachedfile, exc_info=1) else: # optionally change manifest to private assembly private = CONF.get('win_private_assemblies', False) if private: if manifest.publicKeyToken: logger.info( "Changing %s into a private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None # Change dep to private assembly for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None redirecting = applyRedirects(manifest, redirects) if redirecting or private: try: manifest.update_resources( os.path.abspath(cachedfile), [name], [language]) except Exception as e: logger.error(os.path.abspath(cachedfile)) raise if cmd: logger.info("Executing - " + ' '.join(cmd)) # terminates if execution fails compat.exec_command(*cmd) # update cache index cache_index[basenm] = digest save_py_data_struct(cacheindexfn, cache_index) # On Mac OS X we need relative paths to dll dependencies # starting with @executable_path if is_darwin: dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) return cachedfile
def checkCache(fnm, strip=False, upx=False, upx_exclude=None, dist_nm=None, target_arch=None, codesign_identity=None, entitlements_file=None): """ Cache prevents preprocessing binary files again and again. 'dist_nm' Filename relative to dist directory. We need it on Mac to determine level of paths for @loader_path like '@loader_path/../../' for qt4 plugins. """ from PyInstaller.config import CONF # On Mac OS, a cache is required anyway to keep the libaries with relative install names. # Caching on Mac OS does not work since we need to modify binary headers to use relative paths to dll depencies and # starting with '@loader_path'. if not strip and not upx and not is_darwin and not is_win: return fnm if dist_nm is not None and ":" in dist_nm: # A file embedded in another PyInstaller build via multipackage. # No actual file exists to process. return fnm if strip: strip = True else: strip = False upx_exclude = upx_exclude or [] upx = (upx and (is_win or is_cygwin) and os.path.normcase(os.path.basename(fnm)) not in upx_exclude) # Load cache index. # Make cachedir per Python major/minor version. # This allows parallel building of executables with different Python versions as one user. pyver = 'py%d%s' % (sys.version_info[0], sys.version_info[1]) arch = platform.architecture()[0] cachedir = os.path.join(CONF['cachedir'], 'bincache%d%d_%s_%s' % (strip, upx, pyver, arch)) if target_arch: cachedir = os.path.join(cachedir, target_arch) if is_darwin: # Separate by codesign identity if codesign_identity: # Compute hex digest of codesign identity string to prevent issues with invalid characters. csi_hash = hashlib.sha256(codesign_identity.encode('utf-8')) cachedir = os.path.join(cachedir, csi_hash.hexdigest()) else: cachedir = os.path.join(cachedir, 'adhoc') # ad-hoc signing # Separate by entitlements if entitlements_file: # Compute hex digest of entitlements file contents with open(entitlements_file, 'rb') as fp: ef_hash = hashlib.sha256(fp.read()) cachedir = os.path.join(cachedir, ef_hash.hexdigest()) else: cachedir = os.path.join(cachedir, 'no-entitlements') if not os.path.exists(cachedir): os.makedirs(cachedir) cacheindexfn = os.path.join(cachedir, "index.dat") if os.path.exists(cacheindexfn): try: cache_index = misc.load_py_data_struct(cacheindexfn) except Exception: # Tell the user they may want to fix their cache... However, do not delete it for them; if it keeps getting # corrupted, we will never find out. logger.warning( "PyInstaller bincache may be corrupted; use pyinstaller --clean to fix it." ) raise else: cache_index = {} # Verify that the file we are looking for is present in the cache. Use the dist_mn if given to avoid different # extension modules sharing the same basename get corrupted. if dist_nm: basenm = os.path.normcase(dist_nm) else: basenm = os.path.normcase(os.path.basename(fnm)) # Binding redirects should be taken into account to see if the file needs to be reprocessed. The redirects may # change if the versions of dependent manifests change due to system updates. redirects = CONF.get('binding_redirects', []) digest = cacheDigest(fnm, redirects) cachedfile = os.path.join(cachedir, basenm) cmd = None if basenm in cache_index: if digest != cache_index[basenm]: os.remove(cachedfile) else: return cachedfile # Optionally change manifest and its dependencies to private assemblies. if fnm.lower().endswith(".manifest"): manifest = winmanifest.Manifest() manifest.filename = fnm with open(fnm, "rb") as f: manifest.parse_string(f.read()) if CONF.get('win_private_assemblies', False): if manifest.publicKeyToken: logger.info("Changing %s into private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None applyRedirects(manifest, redirects) manifest.writeprettyxml(cachedfile) return cachedfile if upx: if strip: fnm = checkCache(fnm, strip=True, upx=False, dist_nm=dist_nm, target_arch=target_arch, codesign_identity=codesign_identity, entitlements_file=entitlements_file) # We need to avoid using UPX with Windows DLLs that have Control Flow Guard enabled, as it breaks them. if is_win and versioninfo.pefile_check_control_flow_guard(fnm): logger.info('Disabling UPX for %s due to CFG!', fnm) elif misc.is_file_qt_plugin(fnm): logger.info('Disabling UPX for %s due to it being a Qt plugin!', fnm) else: bestopt = "--best" # FIXME: Linux builds of UPX do not seem to contain LZMA (they assert out). # A better configure-time check is due. if CONF["hasUPX"] >= (3, ) and os.name == "nt": bestopt = "--lzma" upx_executable = "upx" if CONF.get('upx_dir'): upx_executable = os.path.join(CONF['upx_dir'], upx_executable) cmd = [upx_executable, bestopt, "-q", cachedfile] else: if strip: strip_options = [] if is_darwin: # The default strip behavior breaks some shared libraries under Mac OS. strip_options = ["-S"] # -S = strip only debug symbols. cmd = ["strip"] + strip_options + [cachedfile] if not os.path.exists(os.path.dirname(cachedfile)): os.makedirs(os.path.dirname(cachedfile)) # There are known some issues with 'shutil.copy2' on Mac OS 10.11 with copying st_flags. Issue #1650. # 'shutil.copy' copies also permission bits and it should be sufficient for PyInstaller's purposes. shutil.copy(fnm, cachedfile) # TODO: find out if this is still necessary when no longer using shutil.copy2() if hasattr(os, 'chflags'): # Some libraries on FreeBSD have immunable flag (libthr.so.3, for example). If this flag is preserved, # os.chmod() fails with: OSError: [Errno 1] Operation not permitted. try: os.chflags(cachedfile, 0) except OSError: pass os.chmod(cachedfile, 0o755) if os.path.splitext(fnm.lower())[1] in (".pyd", ".dll"): # When shared assemblies are bundled into the app, they may optionally be changed into private assemblies. try: res = winmanifest.GetManifestResources(os.path.abspath(cachedfile)) except winresource.pywintypes.error as e: if e.args[0] == winresource.ERROR_BAD_EXE_FORMAT: # Not a win32 PE file pass else: logger.error(os.path.abspath(cachedfile)) raise else: if winmanifest.RT_MANIFEST in res and len( res[winmanifest.RT_MANIFEST]): for name in res[winmanifest.RT_MANIFEST]: for language in res[winmanifest.RT_MANIFEST][name]: try: manifest = winmanifest.Manifest() manifest.filename = ":".join([ cachedfile, str(winmanifest.RT_MANIFEST), str(name), str(language) ]) manifest.parse_string( res[winmanifest.RT_MANIFEST][name][language], False) except Exception: logger.error( "Cannot parse manifest resource %s, =%s", name, language) logger.error("From file %s", cachedfile, exc_info=1) else: # optionally change manifest to private assembly private = CONF.get('win_private_assemblies', False) if private: if manifest.publicKeyToken: logger.info( "Changing %s into a private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None # Change dep to private assembly for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None redirecting = applyRedirects(manifest, redirects) if redirecting or private: try: manifest.update_resources( os.path.abspath(cachedfile), [name], [language]) except Exception: logger.error(os.path.abspath(cachedfile)) raise if cmd: logger.info("Executing - " + ' '.join(cmd)) # terminates if execution fails compat.exec_command(*cmd) # update cache index cache_index[basenm] = digest misc.save_py_data_struct(cacheindexfn, cache_index) # On Mac OS we need relative paths to dll dependencies starting with @executable_path. While modifying # the headers invalidates existing signatures, we avoid removing them in order to speed things up (and # to avoid potential bugs in the codesign utility, like the one reported on Mac OS 10.13 in #6167). # The forced re-signing at the end should take care of the invalidated signatures. if is_darwin: osxutils.binary_to_target_arch(cachedfile, target_arch, display_name=fnm) #osxutils.remove_signature_from_binary(cachedfile) # Disabled as per comment above. dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) osxutils.sign_binary(cachedfile, codesign_identity, entitlements_file) return cachedfile
def getAssemblyFiles(pth, manifest=None, redirects=None): """ Find all assemblies that are dependencies of the given binary and return the files that make up the assemblies as (name, fullpath) tuples. If a WinManifest object is passed as `manifest`, also updates that manifest to reference the returned assemblies. This is done only to update the built app's .exe with the dependencies of python.exe If a list is passed as `redirects`, and binding redirects in policy files are applied when searching for assemblies, BindingRedirect objects are appended to this list. Return a list of pairs (name, fullpath) """ rv = [] if manifest: _depNames = set(dep.name for dep in manifest.dependentAssemblies) for assembly in getAssemblies(pth): if assembly.getid().upper() in seen: continue if manifest and assembly.name not in _depNames: # Add assembly as dependency to our final output exe's manifest logger.info( "Adding %s to dependent assemblies " "of final executable\n required by %s", assembly.name, pth) manifest.dependentAssemblies.append(assembly) _depNames.add(assembly.name) if not dylib.include_library(assembly.name): logger.debug("Skipping assembly %s", assembly.getid()) continue if assembly.optional: logger.debug("Skipping optional assembly %s", assembly.getid()) continue from PyInstaller.config import CONF if CONF.get("win_no_prefer_redirects"): files = assembly.find_files() else: files = [] if not len(files): # If no files were found, it may be the case that the required version # of the assembly is not installed, and the policy file is redirecting it # to a newer version. So, we collect the newer version instead. files = assembly.find_files(ignore_policies=False) if len(files) and redirects is not None: # New version was found, old version was not. Add a redirect in the # app configuration old_version = assembly.version new_version = assembly.get_policy_redirect() logger.info("Adding redirect %s version %s -> %s", assembly.name, old_version, new_version) redirects.append( BindingRedirect( name=assembly.name, language=assembly.language, arch=assembly.processorArchitecture, publicKeyToken=assembly.publicKeyToken, oldVersion=old_version, newVersion=new_version, )) if files: seen.add(assembly.getid().upper()) for fn in files: fname, fext = os.path.splitext(fn) if fext.lower() == ".manifest": nm = assembly.name + fext else: nm = os.path.basename(fn) ftocnm = nm if assembly.language not in (None, "", "*", "neutral"): ftocnm = os.path.join(assembly.getlanguage(), ftocnm) nm, ftocnm, fn = [ item.encode(sys.getfilesystemencoding()) for item in (nm, ftocnm, fn) ] if fn.upper() not in seen: logger.debug("Adding %s", ftocnm) seen.add(nm.upper()) seen.add(fn.upper()) rv.append((ftocnm, fn)) else: #logger.info("skipping %s part of assembly %s dependency of %s", # ftocnm, assembly.name, pth) pass else: logger.error("Assembly %s not found", assembly.getid()) # Convert items in list from 'bytes' type to 'str' type. # NOTE: With Python 3 we somehow get type 'bytes' and it # then causes other issues and failures with PyInstaller. new_rv = [] for item in rv: a = item[0].decode('ascii') b = item[1].decode('ascii') new_rv.append((a, b)) rv = new_rv return rv
def checkCache(fnm, strip=False, upx=False, dist_nm=None): """ Cache prevents preprocessing binary files again and again. 'dist_nm' Filename relative to dist directory. We need it on Mac to determine level of paths for @loader_path like '@loader_path/../../' for qt4 plugins. """ from ..config import CONF # On darwin a cache is required anyway to keep the libaries # with relative install names. Caching on darwin does not work # since we need to modify binary headers to use relative paths # to dll depencies and starting with '@loader_path'. if not strip and not upx and not is_darwin and not is_win: return fnm if dist_nm is not None and ":" in dist_nm: # A file embedded in another pyinstaller build via multipackage # No actual file exists to process return fnm if strip: strip = True else: strip = False if upx: upx = True else: upx = False # Load cache index # Make cachedir per Python major/minor version. # This allows parallel building of executables with different # Python versions as one user. pyver = ('py%d%s') % (sys.version_info[0], sys.version_info[1]) arch = platform.architecture()[0] cachedir = os.path.join(CONF['cachedir'], 'bincache%d%d_%s_%s' % (strip, upx, pyver, arch)) if not os.path.exists(cachedir): os.makedirs(cachedir) cacheindexfn = os.path.join(cachedir, "index.dat") if os.path.exists(cacheindexfn): cache_index = load_py_data_struct(cacheindexfn) else: cache_index = {} # Verify if the file we're looking for is present in the cache. # Use the dist_mn if given to avoid different extension modules # sharing the same basename get corrupted. if dist_nm: basenm = os.path.normcase(dist_nm) else: basenm = os.path.normcase(os.path.basename(fnm)) # Binding redirects should be taken into account to see if the file # needs to be reprocessed. The redirects may change if the versions of dependent # manifests change due to system updates. redirects = CONF.get('binding_redirects', []) digest = cacheDigest(fnm, redirects) cachedfile = os.path.join(cachedir, basenm) cmd = None if basenm in cache_index: if digest != cache_index[basenm]: os.remove(cachedfile) else: # On Mac OS X we need relative paths to dll dependencies # starting with @executable_path if is_darwin: dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) return cachedfile # Optionally change manifest and its deps to private assemblies if fnm.lower().endswith(".manifest"): manifest = winmanifest.Manifest() manifest.filename = fnm with open(fnm, "rb") as f: manifest.parse_string(f.read()) if CONF.get('win_private_assemblies', False): if manifest.publicKeyToken: logger.info("Changing %s into private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None applyRedirects(manifest, redirects) manifest.writeprettyxml(cachedfile) return cachedfile if upx: if strip: fnm = checkCache(fnm, strip=True, upx=False) bestopt = "--best" # FIXME: Linux builds of UPX do not seem to contain LZMA (they assert out) # A better configure-time check is due. if CONF["hasUPX"] >= (3,) and os.name == "nt": bestopt = "--lzma" upx_executable = "upx" if CONF.get('upx_dir'): upx_executable = os.path.join(CONF['upx_dir'], upx_executable) cmd = [upx_executable, bestopt, "-q", cachedfile] else: if strip: strip_options = [] if is_darwin: # The default strip behaviour breaks some shared libraries # under Mac OSX. # -S = strip only debug symbols. strip_options = ["-S"] cmd = ["strip"] + strip_options + [cachedfile] if not os.path.exists(os.path.dirname(cachedfile)): os.makedirs(os.path.dirname(cachedfile)) # There are known some issues with 'shutil.copy2' on Mac OS X 10.11 # with copying st_flags. Issue #1650. # 'shutil.copy' copies also permission bits and it should be sufficient for # PyInstalle purposes. shutil.copy(fnm, cachedfile) # TODO find out if this is still necessary when no longer using shutil.copy2() if hasattr(os, 'chflags'): # Some libraries on FreeBSD have immunable flag (libthr.so.3, for example) # If flags still remains, os.chmod will failed with: # OSError: [Errno 1] Operation not permitted. try: os.chflags(cachedfile, 0) except OSError: pass os.chmod(cachedfile, 0o755) if os.path.splitext(fnm.lower())[1] in (".pyd", ".dll"): # When shared assemblies are bundled into the app, they may optionally be # changed into private assemblies. try: res = winmanifest.GetManifestResources(os.path.abspath(cachedfile)) except winresource.pywintypes.error as e: if e.args[0] == winresource.ERROR_BAD_EXE_FORMAT: # Not a win32 PE file pass else: logger.error(os.path.abspath(cachedfile)) raise else: if winmanifest.RT_MANIFEST in res and len(res[winmanifest.RT_MANIFEST]): for name in res[winmanifest.RT_MANIFEST]: for language in res[winmanifest.RT_MANIFEST][name]: try: manifest = winmanifest.Manifest() manifest.filename = ":".join([cachedfile, str(winmanifest.RT_MANIFEST), str(name), str(language)]) manifest.parse_string(res[winmanifest.RT_MANIFEST][name][language], False) except Exception as exc: logger.error("Cannot parse manifest resource %s, " "%s", name, language) logger.error("From file %s", cachedfile, exc_info=1) else: # optionally change manifest to private assembly if CONF.get('win_private_assemblies', False): if manifest.publicKeyToken: logger.info("Changing %s into a private assembly", os.path.basename(fnm)) manifest.publicKeyToken = None # Change dep to private assembly for dep in manifest.dependentAssemblies: # Exclude common-controls which is not bundled if dep.name != "Microsoft.Windows.Common-Controls": dep.publicKeyToken = None applyRedirects(manifest, redirects) try: manifest.update_resources(os.path.abspath(cachedfile), [name], [language]) except Exception as e: logger.error(os.path.abspath(cachedfile)) raise if cmd: try: logger.info("Executing - " + ' '.join(cmd)) compat.exec_command(*cmd) except OSError as e: raise SystemExit("Execution failed: %s" % e) # update cache index cache_index[basenm] = digest save_py_data_struct(cacheindexfn, cache_index) # On Mac OS X we need relative paths to dll dependencies # starting with @executable_path if is_darwin: dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) return cachedfile