Exemplo n.º 1
0
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'))
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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