def hook(hook_api):
    # Use a hook-function to get the module's attr:`__file__` easily.
    """
    Freeze all external Tcl/Tk data files if this is a supported platform *or* log a non-fatal error otherwise.
    """
    if compat.is_win or compat.is_darwin or compat.is_unix:
        # collect_tcl_tk_files() returns a Tree, so we need to store it into `hook_api.datas` in order to prevent
        # `building.imphook.format_binaries_and_datas` from crashing with "too many values to unpack".
        hook_api.add_datas(collect_tcl_tk_files(hook_api.__file__))
    else:
        logger.error("... skipping Tcl/Tk handling on unsupported platform %s",
                     sys.platform)
示例#2
0
def hook(hook_api):
    # Use a hook-function to get the module's attr:`__file__` easily.
    """
    Freeze all external Tcl/Tk data files if this is a supported platform *or*
    log a non-fatal error otherwise.
    """
    if is_win or is_darwin or is_unix:
        # _collect_tcl_tk_files(hook_api) returns a Tree (which is okay),
        # so we need to store it into `hook_api.datas` to prevent
        # `building.imphook.format_binaries_and_datas` from crashing
        # with "too many values to unpack".
        hook_api.add_datas(_collect_tcl_tk_files(hook_api))
    else:
        logger.error("... skipping Tcl/Tk handling on unsupported platform %s", sys.platform)
示例#3
0
def _collect_tcl_tk_files(hook_api):
    """
    Get a list of TOC-style 3-tuples describing all external Tcl/Tk data files.

    Returns
    -------
    Tree
        Such list.
    """

    tcl_root, tk_root = _find_tcl_tk(hook_api)

    # TODO Shouldn't these be fatal exceptions?
    if not tcl_root:
        logger.error("Tcl/Tk improperly installed on this system.")
        return []
    if not os.path.isdir(tcl_root):
        logger.error('Tcl data directory "%s" not found.', tcl_root)
        return []
    if not os.path.isdir(tk_root):
        logger.error('Tk data directory "%s" not found.', tk_root)
        return []

    tcltree = Tree(tcl_root, prefix="tcl", excludes=["demos", "*.lib", "tclConfig.sh"])
    tktree = Tree(tk_root, prefix="tk", excludes=["demos", "*.lib", "tkConfig.sh"])

    # If the current Tcl installation is a Teapot-distributed version of
    # ActiveTcl and the current platform is OS X, warn that this is bad.
    if is_darwin:
        _warn_if_activetcl_or_teapot_installed(tcl_root, tcltree)

    return tcltree + tktree
def _collect_tcl_tk_files(hook_api):
    """
    Get a list of TOC-style 3-tuples describing all external Tcl/Tk data files.

    Returns
    -------
    Tree
        Such list.
    """
    tcl_root, tk_root = _find_tcl_tk(hook_api)

    # On macOS, we do not collect system libraries. Therefore, if system
    # Tcl/Tk framework is used, it makes no sense to collect its data,
    # either. In this case, _find_tcl_tk() will return None, None - either
    # deliberately (we found the data paths, but ignore them) or not
    # (starting with macOS 11, the data path cannot be found until shared
    # library discovery is fixed).
    if is_darwin and not tcl_root and not tk_root:
        logger.info('Not collecting Tcl/Tk data - either python is using '
                    'macOS\' system Tcl/Tk framework, or Tcl/Tk data '
                    'directories could not be found.')
        return []

    # TODO Shouldn't these be fatal exceptions?
    if not tcl_root:
        logger.error('Tcl/Tk improperly installed on this system.')
        return []
    if not os.path.isdir(tcl_root):
        logger.error('Tcl data directory "%s" not found.', tcl_root)
        return []
    if not os.path.isdir(tk_root):
        logger.error('Tk data directory "%s" not found.', tk_root)
        return []

    tcltree = Tree(tcl_root,
                   prefix='tcl',
                   excludes=['demos', '*.lib', 'tclConfig.sh'])
    tktree = Tree(tk_root,
                  prefix='tk',
                  excludes=['demos', '*.lib', 'tkConfig.sh'])

    # If the current Tcl installation is a Teapot-distributed version of
    # ActiveTcl and the current platform is OS X, warn that this is bad.
    if is_darwin:
        _warn_if_activetcl_or_teapot_installed(tcl_root, tcltree)

    # Collect Tcl modules
    tclmodulestree = _collect_tcl_modules(tcl_root)

    return (tcltree + tktree + tclmodulestree)
示例#5
0
def _collect_tcl_tk_files(hook_api):
    """
    Get a list of TOC-style 3-tuples describing all external Tcl/Tk data files.

    Returns
    -------
    Tree
        Such list.
    """
    # Workaround for broken Tcl/Tk detection in virtualenv on Windows.
    _handle_broken_tcl_tk()

    tcl_root, tk_root = _find_tcl_tk(hook_api)

    # TODO Shouldn't these be fatal exceptions?
    if not tcl_root:
        logger.error('Tcl/Tk improperly installed on this system.')
        return []
    if not os.path.isdir(tcl_root):
        logger.error('Tcl data directory "%s" not found.', tcl_root)
        return []
    if not os.path.isdir(tk_root):
        logger.error('Tk data directory "%s" not found.', tk_root)
        return []

    # The following lines are edited by ON 050218
    # according to https://github.com/viotti/pyinstaller/commit/597af5cb01c064620b53ea1ee537e30f56fa481d
    tcltree = Tree(
        #tcl_root, prefix='tcl', excludes=['demos', '*.lib', 'tclConfig.sh'])
        tcl_root,
        prefix='tclResources',
        excludes=['demos', '*.lib', 'tclConfig.sh'])
    tktree = Tree(
        #tk_root, prefix='tk', excludes=['demos', '*.lib', 'tkConfig.sh'])
        tk_root,
        prefix='tkResources',
        excludes=['demos', '*.lib', 'tkConfig.sh'])

    # If the current Tcl installation is a Teapot-distributed version of
    # ActiveTcl and the current platform is OS X, warn that this is bad.
    if is_darwin:
        _warn_if_activetcl_or_teapot_installed(tcl_root, tcltree)

    return (tcltree + tktree)
示例#6
0
def _collect_tcl_tk_files(hook_api):
    """
    Get a list of TOC-style 3-tuples describing all external Tcl/Tk data files.

    Returns
    -------
    Tree
        Such list.
    """
    # Workaround for broken Tcl/Tk detection in virtualenv on Windows.
    _handle_broken_tcl_tk()

    tcl_root, tk_root = _find_tcl_tk(hook_api)

    # TODO Shouldn't these be fatal exceptions?
    if not tcl_root:
        logger.error('Tcl/Tk improperly installed on this system.')
        return []
    if not os.path.isdir(tcl_root):
        logger.error('Tcl data directory "%s" not found.', tcl_root)
        return []
    if not os.path.isdir(tk_root):
        logger.error('Tk data directory "%s" not found.', tk_root)
        return []

    tcltree = Tree(tcl_root,
                   prefix='tcl',
                   excludes=['demos', '*.lib', 'tclConfig.sh'])
    tktree = Tree(tk_root,
                  prefix='tk',
                  excludes=['demos', '*.lib', 'tkConfig.sh'])

    # If the current Tcl installation is a Teapot-distributed version of
    # ActiveTcl and the current platform is OS X, warn that this is bad.
    if is_darwin:
        _warn_if_activetcl_or_teapot_installed(tcl_root, tcltree)

    return (tcltree + tktree)
示例#7
0
def _collect_tcl_tk_files(hook_api):
    """
    Get a list of TOC-style 3-tuples describing all external Tcl/Tk data files.

    Returns
    -------
    Tree
        Such list.
    """
    # Workaround for broken Tcl/Tk detection in virtualenv on Windows.
    _handle_broken_tcl_tk()

    tcl_root, tk_root = _find_tcl_tk(hook_api)

    # TODO Shouldn't these be fatal exceptions?
    if not tcl_root:
        logger.error('Tcl/Tk improperly installed on this system.')
        return []
    if not os.path.isdir(tcl_root):
        logger.error('Tcl data directory "%s" not found.', tcl_root)
        return []
    if not os.path.isdir(tk_root):
        logger.error('Tk data directory "%s" not found.', tk_root)
        return []

    tcltree = Tree(
        tcl_root, prefix='tclResources', excludes=['demos', '*.lib', 'tclConfig.sh'])
    tktree = Tree(
        tk_root, prefix='tkResources', excludes=['demos', '*.lib', 'tkConfig.sh'])

    # If the current Tcl installation is a Teapot-distributed version of
    # ActiveTcl and the current platform is OS X, warn that this is bad.
    if is_darwin:
        _warn_if_activetcl_or_teapot_installed(tcl_root, tcltree)

    return (tcltree + tktree)
示例#8
0
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
    import os
    import re
    import logging
    logger = logging.getLogger(__name__)

    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 + '/', 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(['../../../'])

    ## 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
示例#9
0
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
    import os
    import re
    import logging
    logger = logging.getLogger(__name__)

    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 + '/', 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(['../../../'])

    ## 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