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