123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2005-2021, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- """
- Classes facilitating communication between PyInstaller and import hooks.
- PyInstaller passes instances of classes defined by this module to corresponding functions defined by external import
- hooks, which commonly modify the contents of these instances before returning. PyInstaller then detects and converts
- these modifications into appropriate operations on the current `PyiModuleGraph` instance, thus modifying which
- modules will be frozen into the executable.
- """
- from PyInstaller.building.datastruct import TOC
- from PyInstaller.building.utils import format_binaries_and_datas
- from PyInstaller.lib.modulegraph.modulegraph import (RuntimeModule, RuntimePackage)
- class PreSafeImportModuleAPI(object):
- """
- Metadata communicating changes made by the current **pre-safe import module hook** (i.e., hook run immediately
- _before_ a call to `ModuleGraph._safe_import_module()` recursively adding the hooked module, package,
- or C extension and all transitive imports thereof to the module graph) back to PyInstaller.
- Pre-safe import module hooks _must_ define a `pre_safe_import_module()` function accepting an instance of this
- class, whose attributes describe the subsequent `ModuleGraph._safe_import_module()` call creating the hooked
- module's graph node.
- Each pre-safe import module hook is run _only_ on the first attempt to create the hooked module's graph node and
- then subsequently ignored. If this hook successfully creates that graph node, the subsequent
- `ModuleGraph._safe_import_module()` call will observe this fact and silently return without attempting to
- recreate that graph node.
- Pre-safe import module hooks are typically used to create graph nodes for **runtime modules** (i.e.,
- modules dynamically defined at runtime). Most modules are physically defined in external `.py`-suffixed scripts.
- Some modules, however, are dynamically defined at runtime (e.g., `six.moves`, dynamically defined by the
- physically defined `six.py` module). However, `ModuleGraph` only parses `import` statements residing in external
- scripts. `ModuleGraph` is _not_ a full-fledged, Turing-complete Python interpreter and hence has no means of
- parsing `import` statements performed by runtime modules existing only in-memory.
- 'With great power comes great responsibility.'
- Attributes (Immutable)
- ----------------------------
- The following attributes are **immutable** (i.e., read-only). For safety, any attempts to change these attributes
- _will_ result in a raised exception:
- module_graph : PyiModuleGraph
- Current module graph.
- parent_package : Package
- Graph node for the package providing this module _or_ `None` if this module is a top-level module.
- Attributes (Mutable)
- -----------------------------
- The following attributes are editable.
- module_basename : str
- Unqualified name of the module to be imported (e.g., `text`).
- module_name : str
- Fully-qualified name of this module (e.g., `email.mime.text`).
- """
- def __init__(self, module_graph, module_basename, module_name, parent_package):
- self._module_graph = module_graph
- self.module_basename = module_basename
- self.module_name = module_name
- self._parent_package = parent_package
- # Immutable properties. No corresponding setters are defined.
- @property
- def module_graph(self):
- """
- Current module graph.
- """
- return self._module_graph
- @property
- def parent_package(self):
- """
- Parent Package of this node.
- """
- return self._parent_package
- def add_runtime_module(self, module_name):
- """
- Add a graph node representing a non-package Python module with the passed name dynamically defined at runtime.
- Most modules are statically defined on-disk as standard Python files. Some modules, however, are dynamically
- defined in-memory at runtime (e.g., `gi.repository.Gst`, dynamically defined by the statically defined
- `gi.repository.__init__` module).
- This method adds a graph node representing such a runtime module. Since this module is _not_ a package,
- all attempts to import submodules from this module in `from`-style import statements (e.g., the `queue`
- submodule in `from six.moves import queue`) will be silently ignored. To circumvent this, simply call
- `add_runtime_package()` instead.
- Parameters
- ----------
- module_name : str
- Fully-qualified name of this module (e.g., `gi.repository.Gst`).
- Examples
- ----------
- This method is typically called by `pre_safe_import_module()` hooks, e.g.:
- def pre_safe_import_module(api):
- api.add_runtime_module(api.module_name)
- """
- self._module_graph.add_module(RuntimeModule(module_name))
- def add_runtime_package(self, package_name):
- """
- Add a graph node representing a non-namespace Python package with the passed name dynamically defined at
- runtime.
- Most packages are statically defined on-disk as standard subdirectories containing `__init__.py` files. Some
- packages, however, are dynamically defined in-memory at runtime (e.g., `six.moves`, dynamically defined by
- the statically defined `six` module).
- This method adds a graph node representing such a runtime package. All attributes imported from this package
- in `from`-style import statements that are submodules of this package (e.g., the `queue` submodule in `from
- six.moves import queue`) will be imported rather than ignored.
- Parameters
- ----------
- package_name : str
- Fully-qualified name of this package (e.g., `six.moves`).
- Examples
- ----------
- This method is typically called by `pre_safe_import_module()` hooks, e.g.:
- def pre_safe_import_module(api):
- api.add_runtime_package(api.module_name)
- """
- self._module_graph.add_module(RuntimePackage(package_name))
- def add_alias_module(self, real_module_name, alias_module_name):
- """
- Alias the source module to the target module with the passed names.
- This method ensures that the next call to findNode() given the target module name will resolve this alias.
- This includes importing and adding a graph node for the source module if needed as well as adding a reference
- from the target to the source module.
- Parameters
- ----------
- real_module_name : str
- Fully-qualified name of the **existing module** (i.e., the module being aliased).
- alias_module_name : str
- Fully-qualified name of the **non-existent module** (i.e., the alias to be created).
- """
- self._module_graph.alias_module(real_module_name, alias_module_name)
- def append_package_path(self, directory):
- """
- Modulegraph does a good job at simulating Python's, but it cannot handle packagepath `__path__` modifications
- packages make at runtime.
- Therefore there is a mechanism whereby you can register extra paths in this map for a package, and it will be
- honored.
- Parameters
- ----------
- directory : str
- Absolute or relative path of the directory to be appended to this package's `__path__` attribute.
- """
- self._module_graph.append_package_path(self.module_name, directory)
- class PreFindModulePathAPI(object):
- """
- Metadata communicating changes made by the current **pre-find module path hook** (i.e., hook run immediately
- _before_ a call to `ModuleGraph._find_module_path()` finding the hooked module's absolute path) back to PyInstaller.
- Pre-find module path hooks _must_ define a `pre_find_module_path()` function accepting an instance of this class,
- whose attributes describe the subsequent `ModuleGraph._find_module_path()` call to be performed.
- Pre-find module path hooks are typically used to change the absolute path from which a module will be
- subsequently imported and thus frozen into the executable. To do so, hooks may overwrite the default
- `search_dirs` list of the absolute paths of all directories to be searched for that module: e.g.,
- def pre_find_module_path(api):
- api.search_dirs = ['/the/one/true/package/providing/this/module']
- Each pre-find module path hook is run _only_ on the first call to `ModuleGraph._find_module_path()` for the
- corresponding module.
- Attributes
- ----------
- The following attributes are **mutable** (i.e., modifiable). All changes to these attributes will be immediately
- respected by PyInstaller:
- search_dirs : list
- List of the absolute paths of all directories to be searched for this module (in order). Searching will halt
- at the first directory containing this module.
- Attributes (Immutable)
- ----------
- The following attributes are **immutable** (i.e., read-only). For safety, any attempts to change these attributes
- _will_ result in a raised exception:
- module_name : str
- Fully-qualified name of this module.
- module_graph : PyiModuleGraph
- Current module graph. For efficiency, this attribute is technically mutable. To preserve graph integrity,
- this attribute should nonetheless _never_ be modified. While read-only `PyiModuleGraph` methods (e.g.,
- `findNode()`) are safely callable from within pre-find module path hooks, methods modifying the graph are
- _not_. If graph modifications are required, consider an alternative type of hook (e.g., pre-import module
- hooks).
- """
- def __init__(
- self,
- module_graph,
- module_name,
- search_dirs,
- ):
- # Mutable attributes.
- self.search_dirs = search_dirs
- # Immutable attributes.
- self._module_graph = module_graph
- self._module_name = module_name
- # Immutable properties. No corresponding setters are defined.
- @property
- def module_graph(self):
- """
- Current module graph.
- """
- return self._module_graph
- @property
- def module_name(self):
- """
- Fully-qualified name of this module.
- """
- return self._module_name
- class PostGraphAPI(object):
- """
- Metadata communicating changes made by the current **post-graph hook** (i.e., hook run for a specific module
- transitively imported by the current application _after_ the module graph of all `import` statements performed by
- this application has been constructed) back to PyInstaller.
- Post-graph hooks may optionally define a `post_graph()` function accepting an instance of this class,
- whose attributes describe the current state of the module graph and the hooked module's graph node.
- Attributes (Mutable)
- ----------
- The following attributes are **mutable** (i.e., modifiable). All changes to these attributes will be immediately
- respected by PyInstaller:
- module_graph : PyiModuleGraph
- Current module graph.
- module : Node
- Graph node for the currently hooked module.
- 'With great power comes great responsibility.'
- Attributes (Immutable)
- ----------
- The following attributes are **immutable** (i.e., read-only). For safety, any attempts to change these attributes
- _will_ result in a raised exception:
- __name__ : str
- Fully-qualified name of this module (e.g., `six.moves.tkinter`).
- __file__ : str
- Absolute path of this module. If this module is:
- * A standard (rather than namespace) package, this is the absolute path of this package's directory.
- * A namespace (rather than standard) package, this is the abstract placeholder `-`. (Don't ask. Don't tell.)
- * A non-package module or C extension, this is the absolute path of the corresponding file.
- __path__ : list
- List of the absolute paths of all directories comprising this package if this module is a package _or_ `None`
- otherwise. If this module is a standard (rather than namespace) package, this list contains only the absolute
- path of this package's directory.
- co : code
- Code object compiled from the contents of `__file__` (e.g., via the `compile()` builtin).
- analysis: build_main.Analysis
- The Analysis that load the hook.
- Attributes (Private)
- ----------
- The following attributes are technically mutable but private, and hence should _never_ be externally accessed or
- modified by hooks. Call the corresponding public methods instead:
- _added_datas : list
- List of the `(name, path)` 2-tuples or TOC objects of all external data files required by the current hook,
- defaulting to the empty list. This is equivalent to the global `datas` hook attribute.
- _added_imports : list
- List of the fully-qualified names of all modules imported by the current hook, defaulting to the empty list.
- This is equivalent to the global `hiddenimports` hook attribute.
- _added_binaries : list
- List of the `(name, path)` 2-tuples or TOC objects of all external C extensions imported by the current hook,
- defaulting to the empty list. This is equivalent to the global `binaries` hook attribute.
- """
- def __init__(self, module_name, module_graph, analysis):
- # Mutable attributes.
- self.module_graph = module_graph
- self.module = module_graph.find_node(module_name)
- assert self.module is not None # should not occur
- # Immutable attributes.
- self.___name__ = module_name
- self.___file__ = self.module.filename
- self._co = self.module.code
- self._analysis = analysis
- # To enforce immutability, convert this module's package path if any into an immutable tuple.
- self.___path__ = tuple(self.module.packagepath) \
- if self.module.packagepath is not None else None
- #FIXME: Refactor "_added_datas", "_added_binaries", and "_deleted_imports" into sets. Since order of
- #import is important, "_added_imports" must remain a list.
- # Private attributes.
- self._added_binaries = []
- self._added_datas = []
- self._added_imports = []
- self._deleted_imports = []
- # Immutable properties. No corresponding setters are defined.
- @property
- def __file__(self):
- """
- Absolute path of this module's file.
- """
- return self.___file__
- @property
- def __path__(self):
- """
- List of the absolute paths of all directories comprising this package if this module is a package _or_ `None`
- otherwise. If this module is a standard (rather than namespace) package, this list contains only the absolute
- path of this package's directory.
- """
- return self.___path__
- @property
- def __name__(self):
- """
- Fully-qualified name of this module (e.g., `six.moves.tkinter`).
- """
- return self.___name__
- @property
- def co(self):
- """
- Code object compiled from the contents of `__file__` (e.g., via the `compile()` builtin).
- """
- return self._co
- @property
- def analysis(self):
- """
- build_main.Analysis that calls the hook.
- """
- return self._analysis
- # Obsolete immutable properties provided to preserve backward compatibility.
- @property
- def name(self):
- """
- Fully-qualified name of this module (e.g., `six.moves.tkinter`).
- **This property has been deprecated by the `__name__` property.**
- """
- return self.___name__
- @property
- def graph(self):
- """
- Current module graph.
- **This property has been deprecated by the `module_graph` property.**
- """
- return self.module_graph
- @property
- def node(self):
- """
- Graph node for the currently hooked module.
- **This property has been deprecated by the `module` property.**
- """
- return self.module
- # TODO: This incorrectly returns the list of the graph nodes of all modules *TRANSITIVELY* (rather than directly)
- # imported by this module. Unfortunately, this implies that most uses of this property are currently broken
- # (e.g., "hook-PIL.SpiderImagePlugin.py"). We only require this for the aforementioned hook, so contemplate
- # alternative approaches.
- @property
- def imports(self):
- """
- List of the graph nodes of all modules directly imported by this module.
- """
- return self.module_graph.iter_graph(start=self.module)
- def add_imports(self, *module_names):
- """
- Add all Python modules whose fully-qualified names are in the passed list as "hidden imports" upon which the
- current module depends.
- This is equivalent to appending such names to the hook-specific `hiddenimports` attribute.
- """
- # Append such names to the current list of all such names.
- self._added_imports.extend(module_names)
- def del_imports(self, *module_names):
- """
- Remove the named fully-qualified modules from the set of imports (either hidden or visible) upon which the
- current module depends.
- This is equivalent to appending such names to the hook-specific `excludedimports` attribute.
- """
- self._deleted_imports.extend(module_names)
- def add_binaries(self, list_of_tuples):
- """
- Add all external dynamic libraries in the passed list of `(name, path)` 2-tuples as dependencies of the
- current module. This is equivalent to adding to the global `binaries` hook attribute.
- For convenience, the `list_of_tuples` may also be a single TOC or TREE instance.
- """
- if isinstance(list_of_tuples, TOC):
- self._added_binaries.extend(i[:2] for i in list_of_tuples)
- else:
- self._added_binaries.extend(format_binaries_and_datas(list_of_tuples))
- def add_datas(self, list_of_tuples):
- """
- Add all external data files in the passed list of `(name, path)` 2-tuples as dependencies of the current
- module. This is equivalent to adding to the global `datas` hook attribute.
- For convenience, the `list_of_tuples` may also be a single TOC or TREE instance.
- """
- if isinstance(list_of_tuples, TOC):
- self._added_datas.extend(i[:2] for i in list_of_tuples)
- else:
- self._added_datas.extend(format_binaries_and_datas(list_of_tuples))
|