def hook(hook_api): """ SQLAlchemy 0.9 introduced the decorator 'util.dependencies'. This decorator does imports. eg: @util.dependencies("sqlalchemy.sql.schema") This hook scans for included SQLAlchemy modules and then scans those modules for any util.dependencies and marks those modules as hidden imports. """ if not is_module_satisfies('sqlalchemy >= 0.9'): return # this parser is very simplistic but seems to catch all cases as of V1.1 depend_regex = re.compile(r'@util.dependencies\([\'"](.*?)[\'"]\)') hidden_imports_set = set() known_imports = set() for node in hook_api.module_graph.flatten(start=hook_api.module): if isinstance(node, SourceModule) and \ node.identifier.startswith('sqlalchemy.'): known_imports.add(node.identifier) with open(node.filename) as f: for match in depend_regex.findall(f.read()): hidden_imports_set.add(match) hidden_imports_set -= known_imports if len(hidden_imports_set): logger.info(" Found %d sqlalchemy hidden imports", len(hidden_imports_set)) hook_api.add_imports(*list(hidden_imports_set))
def get_matplotlib_backend_module_names(): """ List the names of all matplotlib backend modules importable under the current Python installation. Returns ---------- list List of the fully-qualified names of all such modules. """ # Statement safely importing a single backend module. import_statement = """ import os, sys # Preserve stdout. sys_stdout = sys.stdout try: # Redirect output printed by this importation to "/dev/null", preventing # such output from being erroneously interpreted as an error. with open(os.devnull, 'w') as dev_null: sys.stdout = dev_null __import__('%s') # If this is an ImportError, print this exception's message without a traceback. # ImportError messages are human-readable and require no additional context. except ImportError as exc: sys.stdout = sys_stdout print(exc) # Else, print this exception preceded by a traceback. traceback.print_exc() # prints to stderr rather than stdout and must not be called here! except Exception: sys.stdout = sys_stdout import traceback print(traceback.format_exc()) """ # List of the human-readable names of all available backends. backend_names = eval_statement( 'import matplotlib; print(matplotlib.rcsetup.all_backends)') # List of the fully-qualified names of all importable backend modules. module_names = [] # If the current system is not OS X and the "CocoaAgg" backend is available, # remove this backend from consideration. Attempting to import this backend # on non-OS X systems halts the current subprocess without printing output # or raising exceptions, preventing its reliable detection. if not is_darwin and 'CocoaAgg' in backend_names: backend_names.remove('CocoaAgg') # For safety, attempt to import each backend in a unique subprocess. for backend_name in backend_names: module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() stdout = exec_statement(import_statement % module_name) # If no output was printed, this backend is importable. if not stdout: module_names.append(module_name) logger.info(' Matplotlib backend "%s": added' % backend_name) else: logger.info(' Matplotlib backend "%s": ignored\n %s' % (backend_name, stdout)) return module_names
def hook(hook_api): """ SQLAlchemy 0.9 introduced the decorator 'util.dependencies'. This decorator does imports. eg: @util.dependencies("sqlalchemy.sql.schema") This hook scans for included SQLAlchemy modules and then scans those modules for any util.dependencies and marks those modules as hidden imports. """ if not is_module_satisfies('sqlalchemy >= 0.9'): return # this parser is very simplistic but seems to catch all cases as of V1.1 depend_regex = re.compile(r'@util.dependencies\([\'"](.*?)[\'"]\)') hidden_imports_set = set() known_imports = set() for node in hook_api.module_graph.flatten(start=hook_api.module): if isinstance(node, SourceModule) and \ node.identifier.startswith('sqlalchemy.'): known_imports.add(node.identifier) with open(node.filename) as f: for match in depend_regex.findall(f.read()): hidden_imports_set.add(match) hidden_imports_set -= known_imports if len(hidden_imports_set): logger.info(" Found %d sqlalchemy hidden imports", len(hidden_imports_set)) hook_api.add_imports(*list(hidden_imports_set))
def pre_find_module_path(api): # FIXME: for reusability, move this into a new PyInstaller.configure.get_fake_modules_dir() utility function. # Absolute path of the faked sub-package. fake_dir = os.path.join(PACKAGEPATH, 'fake-modules') api.search_dirs = [fake_dir] logger.info('site: retargeting to fake-dir %r', fake_dir)
def pre_find_module_path(api): #FIXME: For reusability, move this into a new #PyInstaller.configure.get_fake_modules_dir() utility function. # Absolute path of the faked sub-package. fake_dir = os.path.join(PACKAGEPATH, 'fake-modules') api.search_dirs = [fake_dir] logger.info('site: retargeting to fake-dir %r', fake_dir)
def pre_find_module_path(api): # Absolute path of the system-wide "distutils" package when run from within # a venv or None otherwise. distutils_dir = getattr(distutils, 'distutils_path', None) if distutils_dir is not None: # Find this package in its parent directory. api.search_dirs = [os.path.dirname(distutils_dir)] logger.info('distutils: retargeting to non-venv dir %r' % distutils_dir)
def pre_find_module_path(api): # Absolute path of the system-wide "distutils" package when run from within # a venv or None otherwise. distutils_dir = getattr(distutils, 'distutils_path', None) if distutils_dir is not None: # Find this package in its parent directory. api.search_dirs = [os.path.dirname(distutils_dir)] logger.info('distutils: retargeting to non-venv dir %r' % distutils_dir)
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 pre_find_module_path(api): # Absolute path of the system-wide "distutils" package when run from within a venv or None otherwise. # opcode is not a virtualenv module, so we can use it to find the stdlib. Technique taken from virtualenv's # "distutils" package detection at # https://github.com/pypa/virtualenv/blob/16.3.0/virtualenv_embedded/distutils-init.py#L5 import opcode system_module_path = os.path.normpath(os.path.dirname(opcode.__file__)) loaded_module_path = os.path.normpath(os.path.dirname(distutils.__file__)) if system_module_path != loaded_module_path: # Find this package in its parent directory. api.search_dirs = [system_module_path] logger.info('distutils: retargeting to non-venv dir %r', system_module_path)
def hook(hook_api): module_versions = get_hook_config(hook_api, 'gi', 'module-versions') if module_versions: version = module_versions.get('GtkSource', '3.0') else: version = '3.0' logger.info(f'GtkSource version is {version}') binaries, datas, hiddenimports = get_gi_typelibs('GtkSource', version) datas += collect_glib_share_files(f'gtksourceview-{version}') hook_api.add_datas(datas) hook_api.add_binaries(binaries) hook_api.add_imports(*hiddenimports)
def pre_find_module_path(api): try: # Test if a module named 'pyi_splash' is locally installed. # This prevents that a potentially required dependency is not # packed import pyi_splash # noqa: F401 except ImportError: module_dir = os.path.join(PACKAGEPATH, 'fake-modules') api.search_dirs = [module_dir] logger.info('Adding pyi_splash module to application dependencies.') else: logger.info('A local module named "pyi_splash" is installed. ' 'Use the installed one instead.') return
def pre_find_module_path(api): # Absolute path of the system-wide "distutils" package when run from within # a venv or None otherwise. distutils_dir = getattr(distutils, 'distutils_path', None) if distutils_dir is not None: # workaround for https://github.com/pyinstaller/pyinstaller/issues/4064 if distutils_dir.endswith('__init__.py'): distutils_dir = os.path.dirname(distutils_dir) # Find this package in its parent directory. api.search_dirs = [os.path.dirname(distutils_dir)] logger.info( '>>>>>>> CUSTOM >>>>>>>>> distutils: retargeting to non-venv dir %r' % distutils_dir)
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 if PyInstaller.compat.is_darwin: logger.info("Monkey-patching bindep._getImports_macholib") PyInstaller.depend.bindepend._getImports_macholib = getImports_macholib
def pre_find_module_path(api): # Absolute path of the faked sub-package. fake_dir = os.path.join(PACKAGEPATH, "fake-modules") api.search_dirs = [fake_dir] logger.info("site: retargeting to fake-dir %r", fake_dir)
from PyInstaller.compat import is_darwin from PyInstaller.utils.hooks import (eval_statement, exec_statement, logger) logger.info('######removing unused mpl-backend ######') excl_backend_names = eval_statement( 'import matplotlib; print(matplotlib.rcsetup.all_backends)') excl_backend_names.remove('WX') excl_backend_names.remove('WXAgg') print("Entering custom hook for matplotlib-backends") excl_module_names = [] for backend_name in excl_backend_names: module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() excl_module_names.append(module_name) logger.info('## Matplotlib backend "%s": removed' % module_name) logger.info('###### end mpl-backend removal #########') excludedimports = excl_module_names
def pre_safe_import_module(api): logger.info("Pre-safe import module hook for pyi_hooksample was executed.")
def get_matplotlib_backend_module_names(): """ List the names of all matplotlib backend modules importable under the current Python installation. Returns ---------- list List of the fully-qualified names of all such modules. """ # Statement safely importing a single backend module. import_statement = """ import os, sys # Preserve stdout. sys_stdout = sys.stdout try: # Redirect output printed by this importation to "/dev/null", preventing # such output from being erroneously interpreted as an error. with open(os.devnull, 'w') as dev_null: sys.stdout = dev_null __import__('%s') # If this is an ImportError, print this exception's message without a traceback. # ImportError messages are human-readable and require no additional context. except ImportError as exc: sys.stdout = sys_stdout print(exc) # Else, print this exception preceded by a traceback. traceback.print_exc() # prints to stderr rather than stdout and must not be called here! except Exception: sys.stdout = sys_stdout import traceback print(traceback.format_exc()) """ # List of the human-readable names of all available backends. backend_names = eval_statement( 'import matplotlib; print(matplotlib.rcsetup.all_backends)') # List of the fully-qualified names of all importable backend modules. module_names = [] # If the current system is not OS X and the "CocoaAgg" backend is available, # remove this backend from consideration. Attempting to import this backend # on non-OS X systems halts the current subprocess without printing output # or raising exceptions, preventing its reliable detection. if not is_darwin and 'CocoaAgg' in backend_names: backend_names.remove('CocoaAgg') # For safety, attempt to import each backend in a unique subprocess. for backend_name in backend_names: module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() stdout = exec_statement(import_statement % module_name) # If no output was printed, this backend is importable. if not stdout: module_names.append(module_name) logger.info(' Matplotlib backend "%s": added' % backend_name) else: logger.info(' Matplotlib backend "%s": ignored\n %s' % (backend_name, stdout)) return module_names
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 if PyInstaller.compat.is_darwin: logger.info("Monkey-patching bindep._getImports_macholib") PyInstaller.depend.bindepend._getImports_macholib = getImports_macholib
def pre_find_module_path(api): logger.info("Running pre-find module path hook for pyi_hooksample.")
def pre_find_module_path(api): # Absolute path of the faked sub-package. fake_dir = os.path.join(PACKAGEPATH, 'fake-modules') api.search_dirs = [fake_dir] logger.info('site: retargeting to fake-dir %r', fake_dir)