imphookapi.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2005-2021, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. #-----------------------------------------------------------------------------
  11. """
  12. Classes facilitating communication between PyInstaller and import hooks.
  13. PyInstaller passes instances of classes defined by this module to corresponding
  14. functions defined by external import hooks, which commonly modify the contents
  15. of these instances before returning. PyInstaller then detects and converts these
  16. modifications into appropriate operations on the current `PyiModuleGraph`
  17. instance, thus modifying which modules will be frozen into the executable.
  18. """
  19. from PyInstaller.lib.modulegraph.modulegraph import RuntimeModule, \
  20. RuntimePackage
  21. from PyInstaller.building.datastruct import TOC
  22. from PyInstaller.building.utils import format_binaries_and_datas
  23. class PreSafeImportModuleAPI(object):
  24. """
  25. Metadata communicating changes made by the current **pre-safe import module
  26. hook** (i.e., hook run immediately _before_ a call to
  27. `ModuleGraph._safe_import_module()` recursively adding the hooked module,
  28. package, or C extension and all transitive imports thereof to the module
  29. graph) back to PyInstaller.
  30. Pre-safe import module hooks _must_ define a `pre_safe_import_module()`
  31. function accepting an instance of this class, whose attributes describe the
  32. subsequent `ModuleGraph._safe_import_module()` call creating the hooked
  33. module's graph node.
  34. Each pre-safe import module hook is run _only_ on the first attempt to
  35. create the hooked module's graph node and then subsequently ignored. If this
  36. hook successfully creates that graph node, the subsequent
  37. `ModuleGraph._safe_import_module()` call will observe this fact and silently
  38. return without attempting to recreate that graph node.
  39. Pre-safe import module hooks are typically used to create graph nodes for
  40. **runtime modules** (i.e., modules dynamically defined at runtime). Most
  41. modules are physically defined in external `.py`-suffixed scripts. Some
  42. modules, however, are dynamically defined at runtime (e.g., `six.moves`,
  43. dynamically defined by the physically defined `six.py` module). However,
  44. `ModuleGraph` only parses `import` statements residing in external scripts.
  45. `ModuleGraph` is _not_ a full-fledged, Turing-complete Python interpreter
  46. and hence has no means of parsing `import` statements performed by runtime
  47. modules existing only in-memory.
  48. 'With great power comes great responsibility.'
  49. Attributes (Immutable)
  50. ----------------------------
  51. The following attributes are **immutable** (i.e., read-only). For
  52. safety, any attempts to change these attributes _will_ result in a
  53. raised exception:
  54. module_graph : PyiModuleGraph
  55. Current module graph.
  56. parent_package : Package
  57. Graph node for the package providing this module _or_ `None` if this
  58. module is a top-level module.
  59. Attributes (Mutable)
  60. -----------------------------
  61. The following attributes are editable.
  62. module_basename : str
  63. Unqualified name of the module to be imported (e.g., `text`).
  64. module_name : str
  65. Fully-qualified name of this module (e.g., `email.mime.text`).
  66. """
  67. def __init__(self, module_graph, module_basename, module_name,
  68. parent_package):
  69. self._module_graph = module_graph
  70. self.module_basename = module_basename
  71. self.module_name = module_name
  72. self._parent_package = parent_package
  73. # Immutable properties. No corresponding setters are defined.
  74. @property
  75. def module_graph(self):
  76. """Current module graph"""
  77. return self._module_graph
  78. @property
  79. def parent_package(self):
  80. """Parent Package of this node"""
  81. return self._parent_package
  82. def add_runtime_module(self, module_name):
  83. """
  84. Add a graph node representing a non-package Python module with the
  85. passed name dynamically defined at runtime.
  86. Most modules are statically defined on-disk as standard Python files.
  87. Some modules, however, are dynamically defined in-memory at runtime
  88. (e.g., `gi.repository.Gst`, dynamically defined by the statically
  89. defined `gi.repository.__init__` module).
  90. This method adds a graph node representing such a runtime module. Since
  91. this module is _not_ a package, all attempts to import submodules from
  92. this module in `from`-style import statements (e.g., the `queue`
  93. submodule in `from six.moves import queue`) will be silently ignored. To
  94. circumvent this, simply call `add_runtime_package()` instead.
  95. Parameters
  96. ----------
  97. module_name : str
  98. Fully-qualified name of this module (e.g., `gi.repository.Gst`).
  99. Examples
  100. ----------
  101. This method is typically called by `pre_safe_import_module()` hooks:
  102. e.g.,
  103. def pre_safe_import_module(api):
  104. api.add_runtime_module(api.module_name)
  105. """
  106. self._module_graph.add_module(RuntimeModule(module_name))
  107. def add_runtime_package(self, package_name):
  108. """
  109. Add a graph node representing a non-namespace Python package with the
  110. passed name dynamically defined at runtime.
  111. Most packages are statically defined on-disk as standard subdirectories
  112. containing `__init__.py` files. Some packages, however, are dynamically
  113. defined in-memory at runtime (e.g., `six.moves`, dynamically defined by
  114. the statically defined `six` module).
  115. This method adds a graph node representing such a runtime package. All
  116. attributes imported from this package in `from`-style import statements
  117. that are submodules of this package (e.g., the `queue` submodule in
  118. `from six.moves import queue`) will be imported rather than ignored.
  119. Parameters
  120. ----------
  121. package_name : str
  122. Fully-qualified name of this package (e.g., `six.moves`).
  123. Examples
  124. ----------
  125. This method is typically called by `pre_safe_import_module()` hooks:
  126. e.g.,
  127. def pre_safe_import_module(api):
  128. api.add_runtime_package(api.module_name)
  129. """
  130. self._module_graph.add_module(RuntimePackage(package_name))
  131. def add_alias_module(self, real_module_name, alias_module_name):
  132. """
  133. Alias the source module to the target module with the passed names.
  134. This method ensures that the next call to findNode() given the target
  135. module name will resolve this alias. This includes importing and adding
  136. a graph node for the source module if needed as well as adding a
  137. reference from the target to the source module.
  138. Parameters
  139. ----------
  140. real_module_name : str
  141. Fully-qualified name of the **existing module** (i.e., the
  142. module being aliased).
  143. alias_module_name : str
  144. Fully-qualified name of the **non-existent module** (i.e.,
  145. the alias to be created).
  146. """
  147. self._module_graph.alias_module(real_module_name, alias_module_name)
  148. def append_package_path(self, directory):
  149. """
  150. Modulegraph does a good job at simulating Python's, but it cannot
  151. handle packagepath `__path__` modifications packages make at runtime.
  152. Therefore there is a mechanism whereby you can register extra paths
  153. in this map for a package, and it will be honored.
  154. Parameters
  155. ----------
  156. directory : str
  157. Absolute or relative path of the directory to be appended to this
  158. package's `__path__` attribute.
  159. """
  160. self._module_graph.append_package_path(self.module_name, directory)
  161. class PreFindModulePathAPI(object):
  162. """
  163. Metadata communicating changes made by the current **pre-find module
  164. path hook** (i.e., hook run immediately _before_ a call to
  165. `ModuleGraph._find_module_path()` finding the hooked module's absolute
  166. path) back to PyInstaller.
  167. Pre-find module path hooks _must_ define a `pre_find_module_path()`
  168. function accepting an instance of this class, whose attributes describe the
  169. subsequent `ModuleGraph._find_module_path()` call to be performed.
  170. Pre-find module path hooks are typically used to change the absolute
  171. path from which a module will be subsequently imported and thus frozen into
  172. the executable. To do so, hooks may overwrite the default `search_dirs` list
  173. of the absolute paths of all directories to be searched for that module:
  174. e.g.,
  175. def pre_find_module_path(api):
  176. api.search_dirs = ['/the/one/true/package/providing/this/module']
  177. Each pre-find module path hook is run _only_ on the first call to
  178. `ModuleGraph._find_module_path()` for the corresponding module.
  179. Attributes
  180. ----------
  181. The following attributes are **mutable** (i.e., modifiable). All changes to
  182. these attributes will be immediately respected by PyInstaller:
  183. search_dirs : list
  184. List of the absolute paths of all directories to be searched for this
  185. module (in order). Searching will halt at the first directory containing
  186. this module.
  187. Attributes (Immutable)
  188. ----------
  189. The following attributes are **immutable** (i.e., read-only). For safety,
  190. any attempts to change these attributes _will_ result in a raised exception:
  191. module_name : str
  192. Fully-qualified name of this module.
  193. module_graph : PyiModuleGraph
  194. Current module graph. For efficiency, this attribute is technically
  195. mutable. To preserve graph integrity, this attribute should nonetheless
  196. _never_ be modified. While read-only `PyiModuleGraph` methods (e.g.,
  197. `findNode()`) are safely callable from within pre-find module path
  198. hooks, methods modifying the graph are _not_. If graph modifications are
  199. required, consider an alternative type of hook (e.g., pre-import module
  200. hooks).
  201. """
  202. def __init__(
  203. self,
  204. module_graph,
  205. module_name,
  206. search_dirs,
  207. ):
  208. # Mutable attributes.
  209. self.search_dirs = search_dirs
  210. # Immutable attributes.
  211. self._module_graph = module_graph
  212. self._module_name = module_name
  213. # Immutable properties. No corresponding setters are defined.
  214. @property
  215. def module_graph(self):
  216. """
  217. Current module graph
  218. """
  219. return self._module_graph
  220. @property
  221. def module_name(self):
  222. """
  223. Fully-qualified name of this module.
  224. """
  225. return self._module_name
  226. class PostGraphAPI(object):
  227. """
  228. Metadata communicating changes made by the current **post-graph hook**
  229. (i.e., hook run for a specific module transitively imported by the current
  230. application _after_ the module graph of all `import` statements performed by
  231. this application has been constructed) back to PyInstaller.
  232. Post-graph hooks may optionally define a `post_graph()` function accepting
  233. an instance of this class, whose attributes describe the current state of
  234. the module graph and the hooked module's graph node.
  235. Attributes (Mutable)
  236. ----------
  237. The following attributes are **mutable** (i.e., modifiable). All changes to
  238. these attributes will be immediately respected by PyInstaller:
  239. module_graph : PyiModuleGraph
  240. Current module graph.
  241. module : Node
  242. Graph node for the currently hooked module.
  243. 'With great power comes great responsibility.'
  244. Attributes (Immutable)
  245. ----------
  246. The following attributes are **immutable** (i.e., read-only). For safety,
  247. any attempts to change these attributes _will_ result in a raised exception:
  248. __name__ : str
  249. Fully-qualified name of this module (e.g., `six.moves.tkinter`).
  250. __file__ : str
  251. Absolute path of this module. If this module is:
  252. * A standard (rather than namespace) package, this is the absolute path
  253. of this package's directory.
  254. * A namespace (rather than standard) package, this is the abstract
  255. placeholder `-`. (Don't ask. Don't tell.)
  256. * A non-package module or C extension, this is the absolute path of the
  257. corresponding file.
  258. __path__ : list
  259. List of the absolute paths of all directories comprising this package
  260. if this module is a package _or_ `None` otherwise. If this module is a
  261. standard (rather than namespace) package, this list contains only the
  262. absolute path of this package's directory.
  263. co : code
  264. Code object compiled from the contents of `__file__` (e.g., via the
  265. `compile()` builtin).
  266. analysis: build_main.Analysis
  267. The Analysis that load the hook.
  268. Attributes (Private)
  269. ----------
  270. The following attributes are technically mutable but private, and hence
  271. should _never_ be externally accessed or modified by hooks. Call the
  272. corresponding public methods instead:
  273. _added_datas : list
  274. List of the `(name, path)` 2-tuples or TOC objects of all
  275. external data files required by the current hook, defaulting to the
  276. empty list. This is equivalent to the global `datas` hook attribute.
  277. _added_imports : list
  278. List of the fully-qualified names of all modules imported by the current
  279. hook, defaulting to the empty list. This is equivalent to the global
  280. `hiddenimports` hook attribute.
  281. _added_binaries : list
  282. List of the `(name, path)` 2-tuples or TOC objects of all
  283. external C extensions imported by the current hook, defaulting to the
  284. empty list. This is equivalent to the global
  285. `binaries` hook attribute.
  286. """
  287. def __init__(self, module_name, module_graph, analysis):
  288. # Mutable attributes.
  289. self.module_graph = module_graph
  290. self.module = module_graph.find_node(module_name)
  291. assert self.module is not None # should not occur
  292. # Immutable attributes.
  293. self.___name__ = module_name
  294. self.___file__ = self.module.filename
  295. self._co = self.module.code
  296. self._analysis = analysis
  297. # To enforce immutability, convert this module's package path if any
  298. # into an immutable tuple.
  299. self.___path__ = tuple(self.module.packagepath) \
  300. if self.module.packagepath is not None else None
  301. #FIXME: Refactor "_added_datas", "_added_binaries", and
  302. #"_deleted_imports" into sets. Since order of importation is
  303. #significant, "_added_imports" must remain a list.
  304. # Private attributes.
  305. self._added_binaries = []
  306. self._added_datas = []
  307. self._added_imports = []
  308. self._deleted_imports = []
  309. # Immutable properties. No corresponding setters are defined.
  310. @property
  311. def __file__(self):
  312. """
  313. Absolute path of this module's file.
  314. """
  315. return self.___file__
  316. @property
  317. def __path__(self):
  318. """
  319. List of the absolute paths of all directories comprising this package
  320. if this module is a package _or_ `None` otherwise. If this module is a
  321. standard (rather than namespace) package, this list contains only the
  322. absolute path of this package's directory.
  323. """
  324. return self.___path__
  325. @property
  326. def __name__(self):
  327. """
  328. Fully-qualified name of this module (e.g., `six.moves.tkinter`).
  329. """
  330. return self.___name__
  331. @property
  332. def co(self):
  333. """
  334. Code object compiled from the contents of `__file__` (e.g., via the
  335. `compile()` builtin).
  336. """
  337. return self._co
  338. @property
  339. def analysis(self):
  340. """
  341. build_main.Analysis that calls the hook
  342. """
  343. return self._analysis
  344. # Obsolete immutable properties provided to preserve backward compatibility.
  345. @property
  346. def name(self):
  347. """
  348. Fully-qualified name of this module (e.g., `six.moves.tkinter`).
  349. **This property has been deprecated by the `__name__` property.**
  350. """
  351. return self.___name__
  352. @property
  353. def graph(self):
  354. """
  355. Current module graph.
  356. **This property has been deprecated by the `module_graph` property.**
  357. """
  358. return self.module_graph
  359. @property
  360. def node(self):
  361. """
  362. Graph node for the currently hooked module.
  363. **This property has been deprecated by the `module` property.**
  364. """
  365. return self.module
  366. # TODO: This incorrectly returns the list of the graph nodes of all modules
  367. # *TRANSITIVELY* (rather than directly) imported by this module.
  368. # Unfortunately, this implies that most uses of this property are currently
  369. # broken (e.g., "hook-PIL.SpiderImagePlugin.py"). We only require this for
  370. # the aforementioned hook, so contemplate alternative approaches.
  371. @property
  372. def imports(self):
  373. """
  374. List of the graph nodes of all modules directly imported by this module.
  375. """
  376. return self.module_graph.iter_graph(start=self.module)
  377. def add_imports(self, *module_names):
  378. """
  379. Add all Python modules whose fully-qualified names are in the passed
  380. list as "hidden imports" upon which the current module depends.
  381. This is equivalent to appending such names to the hook-specific
  382. `hiddenimports` attribute.
  383. """
  384. # Append such names to the current list of all such names.
  385. self._added_imports.extend(module_names)
  386. def del_imports(self, *module_names):
  387. """
  388. Remove the named fully-qualified modules from the set of
  389. imports (either hidden or visible) upon which the current
  390. module depends.
  391. This is equivalent to appending such names to the hook-specific
  392. `excludedimports` attribute.
  393. """
  394. self._deleted_imports.extend(module_names)
  395. def add_binaries(self, list_of_tuples):
  396. """
  397. Add all external dynamic libraries in the passed list of
  398. `(name, path)` 2-tuples as dependencies of the current module.
  399. This is equivalent to adding to the global `binaries` hook
  400. attribute.
  401. For convenience, the `list_of_tuples` may also be a single TOC
  402. or TREE instance.
  403. """
  404. if isinstance(list_of_tuples, TOC):
  405. self._added_binaries.extend(i[:2] for i in list_of_tuples)
  406. else:
  407. self._added_binaries.extend(format_binaries_and_datas(list_of_tuples))
  408. def add_datas(self, list_of_tuples):
  409. """
  410. Add all external data files in the passed list of `(name,
  411. path)` 2-tuples as dependencies of the current module. This is
  412. equivalent to adding to the global `datas` hook attribute.
  413. For convenience, the `list_of_tuples` may also be a single TOC
  414. or TREE instance.
  415. """
  416. if isinstance(list_of_tuples, TOC):
  417. self._added_datas.extend(i[:2] for i in list_of_tuples)
  418. else:
  419. self._added_datas.extend(format_binaries_and_datas(list_of_tuples))