__init__.py 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2005-2023, 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. from __future__ import annotations
  12. import copy
  13. import os
  14. import textwrap
  15. import fnmatch
  16. from pathlib import Path
  17. from collections import deque
  18. from typing import Callable
  19. import packaging.requirements
  20. from PyInstaller import HOMEPATH, compat
  21. from PyInstaller import log as logging
  22. from PyInstaller.depend.imphookapi import PostGraphAPI
  23. from PyInstaller import isolated
  24. from PyInstaller.compat import importlib_metadata
  25. logger = logging.getLogger(__name__)
  26. # These extensions represent Python executables and should therefore be ignored when collecting data files.
  27. # NOTE: .dylib files are not Python executable and should not be in this list.
  28. PY_IGNORE_EXTENSIONS = set(compat.ALL_SUFFIXES)
  29. # Some hooks need to save some values. This is the dict that can be used for that.
  30. #
  31. # When running tests this variable should be reset before every test.
  32. #
  33. # For example the 'wx' module needs variable 'wxpubsub'. This tells PyInstaller which protocol of the wx module
  34. # should be bundled.
  35. hook_variables = {}
  36. def __exec_python_cmd(cmd, env=None, capture_stdout=True):
  37. """
  38. Executes an externally spawned Python interpreter. If capture_stdout is set to True, returns anything that was
  39. emitted in the standard output as a single string. Otherwise, returns the exit code.
  40. """
  41. # 'PyInstaller.config' cannot be imported as other top-level modules.
  42. from PyInstaller.config import CONF
  43. if env is None:
  44. env = {}
  45. # Update environment. Defaults to 'os.environ'
  46. pp_env = copy.deepcopy(os.environ)
  47. pp_env.update(env)
  48. # Prepend PYTHONPATH with pathex.
  49. # Some functions use some PyInstaller code in subprocess, so add PyInstaller HOMEPATH to sys.path as well.
  50. pp = os.pathsep.join(CONF['pathex'] + [HOMEPATH])
  51. # PYTHONPATH might be already defined in the 'env' argument or in the original 'os.environ'. Prepend it.
  52. if 'PYTHONPATH' in pp_env:
  53. pp = os.pathsep.join([pp_env.get('PYTHONPATH'), pp])
  54. pp_env['PYTHONPATH'] = pp
  55. if capture_stdout:
  56. txt = compat.exec_python(*cmd, env=pp_env)
  57. return txt.strip()
  58. else:
  59. return compat.exec_python_rc(*cmd, env=pp_env)
  60. def __exec_statement(statement, capture_stdout=True):
  61. statement = textwrap.dedent(statement)
  62. cmd = ['-c', statement]
  63. return __exec_python_cmd(cmd, capture_stdout=capture_stdout)
  64. def exec_statement(statement: str):
  65. """
  66. Execute a single Python statement in an externally-spawned interpreter, and return the resulting standard output
  67. as a string.
  68. Examples::
  69. tk_version = exec_statement("from _tkinter import TK_VERSION; print(TK_VERSION)")
  70. mpl_data_dir = exec_statement("import matplotlib; print(matplotlib.get_data_path())")
  71. datas = [ (mpl_data_dir, "") ]
  72. Notes:
  73. As of v5.0, usage of this function is discouraged in favour of the
  74. new :mod:`PyInstaller.isolated` module.
  75. """
  76. return __exec_statement(statement, capture_stdout=True)
  77. def exec_statement_rc(statement: str):
  78. """
  79. Executes a Python statement in an externally spawned interpreter, and returns the exit code.
  80. """
  81. return __exec_statement(statement, capture_stdout=False)
  82. def eval_statement(statement: str):
  83. """
  84. Execute a single Python statement in an externally-spawned interpreter, and :func:`eval` its output (if any).
  85. Example::
  86. databases = eval_statement('''
  87. import sqlalchemy.databases
  88. print(sqlalchemy.databases.__all__)
  89. ''')
  90. for db in databases:
  91. hiddenimports.append("sqlalchemy.databases." + db)
  92. Notes:
  93. As of v5.0, usage of this function is discouraged in favour of the
  94. new :mod:`PyInstaller.isolated` module.
  95. """
  96. txt = exec_statement(statement).strip()
  97. if not txt:
  98. # Return an empty string, which is "not true" but is iterable.
  99. return ''
  100. return eval(txt)
  101. @isolated.decorate
  102. def get_pyextension_imports(module_name: str):
  103. """
  104. Return list of modules required by binary (C/C++) Python extension.
  105. Python extension files ends with .so (Unix) or .pyd (Windows). It is almost impossible to analyze binary extension
  106. and its dependencies.
  107. Module cannot be imported directly.
  108. Let's at least try import it in a subprocess and observe the difference in module list from sys.modules.
  109. This function could be used for 'hiddenimports' in PyInstaller hooks files.
  110. """
  111. import sys
  112. import importlib
  113. original = set(sys.modules.keys())
  114. # When importing this module - sys.modules gets updated.
  115. importlib.import_module(module_name)
  116. # Find and return which new modules have been loaded.
  117. return list(set(sys.modules.keys()) - original - {module_name})
  118. def get_homebrew_path(formula: str = ''):
  119. """
  120. Return the homebrew path to the requested formula, or the global prefix when called with no argument.
  121. Returns the path as a string or None if not found.
  122. """
  123. import subprocess
  124. brewcmd = ['brew', '--prefix']
  125. path = None
  126. if formula:
  127. brewcmd.append(formula)
  128. dbgstr = 'homebrew formula "%s"' % formula
  129. else:
  130. dbgstr = 'homebrew prefix'
  131. try:
  132. path = subprocess.check_output(brewcmd).strip()
  133. logger.debug('Found %s at "%s"' % (dbgstr, path))
  134. except OSError:
  135. logger.debug('Detected homebrew not installed')
  136. except subprocess.CalledProcessError:
  137. logger.debug('homebrew formula "%s" not installed' % formula)
  138. if path:
  139. return path.decode('utf8') # macOS filenames are UTF-8
  140. else:
  141. return None
  142. def remove_prefix(string: str, prefix: str):
  143. """
  144. This function removes the given prefix from a string, if the string does indeed begin with the prefix; otherwise,
  145. it returns the original string.
  146. """
  147. if string.startswith(prefix):
  148. return string[len(prefix):]
  149. else:
  150. return string
  151. def remove_suffix(string: str, suffix: str):
  152. """
  153. This function removes the given suffix from a string, if the string does indeed end with the suffix; otherwise,
  154. it returns the original string.
  155. """
  156. # Special case: if suffix is empty, string[:0] returns ''. So, test for a non-empty suffix.
  157. if suffix and string.endswith(suffix):
  158. return string[:-len(suffix)]
  159. else:
  160. return string
  161. # TODO: Do we really need a helper for this? This is pretty trivially obvious.
  162. def remove_file_extension(filename: str):
  163. """
  164. This function returns filename without its extension.
  165. For Python C modules it removes even whole '.cpython-34m.so' etc.
  166. """
  167. for suff in compat.EXTENSION_SUFFIXES:
  168. if filename.endswith(suff):
  169. return filename[0:filename.rfind(suff)]
  170. # Fallback to ordinary 'splitext'.
  171. return os.path.splitext(filename)[0]
  172. def can_import_module(module_name: str):
  173. """
  174. Check if the specified module can be imported.
  175. Intended as a silent module availability check, as it does not print ModuleNotFoundError traceback to stderr when
  176. the module is unavailable.
  177. Parameters
  178. ----------
  179. module_name : str
  180. Fully-qualified name of the module.
  181. Returns
  182. ----------
  183. bool
  184. Boolean indicating whether the module can be imported or not.
  185. """
  186. # Run the check in isolated sub-process, so we can gracefully handle cases when importing the module ends up
  187. # crashing python interpreter.
  188. @isolated.decorate
  189. def _can_import_module(module_name):
  190. try:
  191. __import__(module_name)
  192. return True
  193. except Exception:
  194. return False
  195. try:
  196. return _can_import_module(module_name)
  197. except isolated.SubprocessDiedError:
  198. return False
  199. # TODO: Replace most calls to exec_statement() with calls to this function.
  200. def get_module_attribute(module_name: str, attr_name: str):
  201. """
  202. Get the string value of the passed attribute from the passed module if this attribute is defined by this module
  203. _or_ raise `AttributeError` otherwise.
  204. Since modules cannot be directly imported during analysis, this function spawns a subprocess importing this module
  205. and returning the string value of this attribute in this module.
  206. Parameters
  207. ----------
  208. module_name : str
  209. Fully-qualified name of this module.
  210. attr_name : str
  211. Name of the attribute in this module to be retrieved.
  212. Returns
  213. ----------
  214. str
  215. String value of this attribute.
  216. Raises
  217. ----------
  218. AttributeError
  219. If this attribute is undefined.
  220. """
  221. @isolated.decorate
  222. def _get_module_attribute(module_name, attr_name):
  223. import importlib
  224. module = importlib.import_module(module_name)
  225. return getattr(module, attr_name)
  226. # Return AttributeError on any kind of errors, to preserve old behavior.
  227. try:
  228. return _get_module_attribute(module_name, attr_name)
  229. except Exception as e:
  230. raise AttributeError(f"Failed to retrieve attribute {attr_name} from module {module_name}") from e
  231. def get_module_file_attribute(package: str):
  232. """
  233. Get the absolute path to the specified module or package.
  234. Modules and packages *must not* be directly imported in the main process during the analysis. Therefore, to
  235. avoid leaking the imports, this function uses an isolated subprocess when it needs to import the module and
  236. obtain its ``__file__`` attribute.
  237. Parameters
  238. ----------
  239. package : str
  240. Fully-qualified name of module or package.
  241. Returns
  242. ----------
  243. str
  244. Absolute path of this module.
  245. """
  246. # First, try to use 'importlib.util.find_spec' and obtain loader from the spec (and filename from the loader).
  247. # It is the fastest way, but does not work on certain modules in pywin32 that replace all module attributes with
  248. # those of the .dll. In addition, we need to avoid it for submodules/subpackages, because it ends up importing
  249. # their parent package, which would cause an import leak during the analysis.
  250. filename: str | None = None
  251. if '.' not in package:
  252. try:
  253. import importlib.util
  254. loader = importlib.util.find_spec(package).loader
  255. filename = loader.get_filename(package)
  256. # Apparently in the past, ``None`` could be returned for built-in ``datetime`` module. Just in case this
  257. # is still possible, return only if filename is valid.
  258. if filename:
  259. return filename
  260. except (ImportError, AttributeError, TypeError, ValueError):
  261. pass
  262. # Second attempt: try to obtain module/package's __file__ attribute in an isolated subprocess.
  263. @isolated.decorate
  264. def _get_module_file_attribute(package):
  265. # First, try to use 'importlib.util.find_spec' and obtain loader from the spec (and filename from the loader).
  266. # This should return the filename even if the module or package cannot be imported (e.g., a C-extension module
  267. # with missing dependencies).
  268. try:
  269. import importlib.util
  270. loader = importlib.util.find_spec(package).loader
  271. filename = loader.get_filename(package)
  272. # Safe-guard against ``None`` being returned (see comment in the non-isolated codepath).
  273. if filename:
  274. return filename
  275. except (ImportError, AttributeError, TypeError, ValueError):
  276. pass
  277. # Fall back to import attempt
  278. import importlib
  279. p = importlib.import_module(package)
  280. return p.__file__
  281. # The old behavior was to return ImportError (and that is what the test are also expecting...).
  282. try:
  283. filename = _get_module_file_attribute(package)
  284. except Exception as e:
  285. raise ImportError(f"Failed to obtain the __file__ attribute of package/module {package}!") from e
  286. return filename
  287. def get_pywin32_module_file_attribute(module_name):
  288. """
  289. Get the absolute path of the PyWin32 DLL specific to the PyWin32 module with the passed name (`pythoncom`
  290. or `pywintypes`).
  291. On import, each PyWin32 module:
  292. * Imports a DLL specific to that module.
  293. * Overwrites the values of all module attributes with values specific to that DLL. This includes that module's
  294. `__file__` attribute, which then provides the absolute path of that DLL.
  295. This function imports the module in isolated subprocess and retrieves its `__file__` attribute.
  296. """
  297. # NOTE: we cannot use `get_module_file_attribute` as it does not account for the __file__ rewriting magic
  298. # done by the module. Use `get_module_attribute` instead.
  299. return get_module_attribute(module_name, '__file__')
  300. def check_requirement(requirement: str):
  301. """
  302. Check if a :pep:`0508` requirement is satisfied. Usually used to check if a package distribution is installed,
  303. or if it is installed and satisfies the specified version requirement.
  304. Parameters
  305. ----------
  306. requirement : str
  307. Requirement string in :pep:`0508` format.
  308. Returns
  309. ----------
  310. bool
  311. Boolean indicating whether the requirement is satisfied or not.
  312. Examples
  313. --------
  314. ::
  315. # Assume Pillow 10.0.0 is installed.
  316. >>> from PyInstaller.utils.hooks import check_requirement
  317. >>> check_requirement('Pillow')
  318. True
  319. >>> check_requirement('Pillow < 9.0')
  320. False
  321. >>> check_requirement('Pillow >= 9.0, < 11.0')
  322. True
  323. """
  324. parsed_requirement = packaging.requirements.Requirement(requirement)
  325. # Fetch the actual version of the specified dist
  326. try:
  327. version = importlib_metadata.version(parsed_requirement.name)
  328. except importlib_metadata.PackageNotFoundError:
  329. return False # Not available at all
  330. # If specifier is not given, the only requirement is that dist is available
  331. if not parsed_requirement.specifier:
  332. return True
  333. # Parse specifier, and compare version. Enable pre-release matching,
  334. # because we need "package >= 2.0.0" to match "2.5.0b1".
  335. return parsed_requirement.specifier.contains(version, prereleases=True)
  336. # Keep the `is_module_satisfies` as an alias for backwards compatibility with existing hooks. The old fallback
  337. # to module version check does not work any more, though.
  338. def is_module_satisfies(
  339. requirements: str,
  340. version: None = None,
  341. version_attr: None = None,
  342. ):
  343. """
  344. A compatibility wrapper for :func:`check_requirement`, intended for backwards compatibility with existing hooks.
  345. In contrast to original implementation from PyInstaller < 6, this implementation only checks the specified
  346. :pep:`0508` requirement string; i.e., it tries to retrieve the distribution metadata, and compare its version
  347. against optional version specifier(s). It does not attempt to fall back to checking the module's version attribute,
  348. nor does it support ``version`` and ``version_attr`` arguments.
  349. Parameters
  350. ----------
  351. requirements : str
  352. Requirements string passed to the :func:`check_requirement`.
  353. version : None
  354. Deprecated and unsupported. Must be ``None``.
  355. version_attr : None
  356. Deprecated and unsupported. Must be ``None``.
  357. Returns
  358. ----------
  359. bool
  360. Boolean indicating whether the requirement is satisfied or not.
  361. Raises
  362. ----------
  363. ValueError
  364. If either ``version`` or ``version_attr`` are specified and are not None.
  365. """
  366. if version is not None:
  367. raise ValueError("Calling is_module_satisfies with version argument is not supported anymore.")
  368. if version_attr is not None:
  369. raise ValueError("Calling is_module_satisfies with version argument_attr is not supported anymore.")
  370. return check_requirement(requirements)
  371. def is_package(module_name: str):
  372. """
  373. Check if a Python module is really a module or is a package containing other modules, without importing anything
  374. in the main process.
  375. :param module_name: Module name to check.
  376. :return: True if module is a package else otherwise.
  377. """
  378. def _is_package(module_name: str):
  379. """
  380. Determines whether the given name represents a package or not. If the name represents a top-level module or
  381. a package, it is not imported. If the name represents a sub-module or a sub-package, its parent is imported.
  382. In such cases, this function should be called from an isolated suprocess.
  383. NOTE: the fallback check for `__init__.py` is there because `_distutils_hack.DistutilsMetaFinder` from
  384. `setuptools` does not set spec.submodule_search_locations for `distutils` / `setuptools._distutils` even though
  385. it is a package. The alternative would be to always perform full import, and check for the `__path__` attribute,
  386. but that would also always require full isolation.
  387. """
  388. try:
  389. import importlib.util
  390. spec = importlib.util.find_spec(module_name)
  391. return bool(spec.submodule_search_locations) or spec.origin.endswith('__init__.py')
  392. except Exception:
  393. return False
  394. # For top-level packages/modules, we can perform check in the main process; otherwise, we need to isolate the
  395. # call to prevent import leaks in the main process.
  396. if '.' not in module_name:
  397. return _is_package(module_name)
  398. else:
  399. return isolated.call(_is_package, module_name)
  400. def get_all_package_paths(package: str):
  401. """
  402. Given a package name, return all paths associated with the package. Typically, packages have a single location
  403. path, but PEP 420 namespace packages may be split across multiple locations. Returns an empty list if the specified
  404. package is not found or is not a package.
  405. """
  406. def _get_package_paths(package: str):
  407. """
  408. Retrieve package path(s), as advertised by submodule_search_paths attribute of the spec obtained via
  409. importlib.util.find_spec(package). If the name represents a top-level package, the package is not imported.
  410. If the name represents a sub-module or a sub-package, its parent is imported. In such cases, this function
  411. should be called from an isolated suprocess. Returns an empty list if specified package is not found or is not
  412. a package.
  413. """
  414. try:
  415. import importlib.util
  416. spec = importlib.util.find_spec(package)
  417. if not spec or not spec.submodule_search_locations:
  418. return []
  419. return [str(path) for path in spec.submodule_search_locations]
  420. except Exception:
  421. return []
  422. # For top-level packages/modules, we can perform check in the main process; otherwise, we need to isolate the
  423. # call to prevent import leaks in the main process.
  424. if '.' not in package:
  425. pkg_paths = _get_package_paths(package)
  426. else:
  427. pkg_paths = isolated.call(_get_package_paths, package)
  428. return pkg_paths
  429. def package_base_path(package_path: str, package: str):
  430. """
  431. Given a package location path and package name, return the package base path, i.e., the directory in which the
  432. top-level package is located. For example, given the path ``/abs/path/to/python/libs/pkg/subpkg`` and
  433. package name ``pkg.subpkg``, the function returns ``/abs/path/to/python/libs``.
  434. """
  435. return remove_suffix(package_path, package.replace('.', os.sep)) # Base directory
  436. def get_package_paths(package: str):
  437. """
  438. Given a package, return the path to packages stored on this machine and also returns the path to this particular
  439. package. For example, if pkg.subpkg lives in /abs/path/to/python/libs, then this function returns
  440. ``(/abs/path/to/python/libs, /abs/path/to/python/libs/pkg/subpkg)``.
  441. NOTE: due to backwards compatibility, this function returns only one package path along with its base directory.
  442. In case of PEP 420 namespace package with multiple location, only first location is returned. To obtain all
  443. package paths, use the ``get_all_package_paths`` function and obtain corresponding base directories using the
  444. ``package_base_path`` helper.
  445. """
  446. pkg_paths = get_all_package_paths(package)
  447. if not pkg_paths:
  448. raise ValueError(f"Package '{package}' does not exist or is not a package!")
  449. if len(pkg_paths) > 1:
  450. logger.warning(
  451. "get_package_paths - package %s has multiple paths (%r); returning only first one!", package, pkg_paths
  452. )
  453. pkg_dir = pkg_paths[0]
  454. pkg_base = package_base_path(pkg_dir, package)
  455. return pkg_base, pkg_dir
  456. def collect_submodules(
  457. package: str,
  458. filter: Callable[[str], bool] = lambda name: True,
  459. on_error: str = "warn once",
  460. ):
  461. """
  462. List all submodules of a given package.
  463. Arguments:
  464. package:
  465. An ``import``-able package.
  466. filter:
  467. Filter the submodules found: A callable that takes a submodule name and returns True if it should be
  468. included.
  469. on_error:
  470. The action to take when a submodule fails to import. May be any of:
  471. - raise: Errors are reraised and terminate the build.
  472. - warn: Errors are downgraded to warnings.
  473. - warn once: The first error issues a warning but all
  474. subsequent errors are ignored to minimise *stderr pollution*. This
  475. is the default.
  476. - ignore: Skip all errors. Don't warn about anything.
  477. Returns:
  478. All submodules to be assigned to ``hiddenimports`` in a hook.
  479. This function is intended to be used by hook scripts, not by main PyInstaller code.
  480. Examples::
  481. # Collect all submodules of Sphinx don't contain the word ``test``.
  482. hiddenimports = collect_submodules(
  483. "Sphinx", ``filter=lambda name: 'test' not in name)
  484. .. versionchanged:: 4.5
  485. Add the **on_error** parameter.
  486. """
  487. # Accept only strings as packages.
  488. if not isinstance(package, str):
  489. raise TypeError('package must be a str')
  490. if on_error not in ("ignore", "warn once", "warn", "raise"):
  491. raise ValueError(
  492. f"Invalid on-error action '{on_error}': Must be one of ('ignore', 'warn once', 'warn', 'raise')"
  493. )
  494. logger.debug('Collecting submodules for %s', package)
  495. # Skip a module which is not a package.
  496. if not is_package(package):
  497. logger.debug('collect_submodules - %s is not a package.', package)
  498. # If module is importable, return its name in the list, in order to keep behavior consistent with the
  499. # one we have for packages (i.e., we include the package in the list of returned names)
  500. if can_import_module(package):
  501. return [package]
  502. return []
  503. # Determine the filesystem path(s) to the specified package.
  504. package_submodules = []
  505. todo = deque()
  506. todo.append(package)
  507. with isolated.Python() as isolated_python:
  508. while todo:
  509. # Scan the given (sub)package
  510. name = todo.pop()
  511. modules, subpackages, on_error = isolated_python.call(_collect_submodules, name, on_error)
  512. # Add modules to the list of all submodules
  513. package_submodules += [module for module in modules if filter(module)]
  514. # Add sub-packages to deque for subsequent recursion
  515. for subpackage_name in subpackages:
  516. if filter(subpackage_name):
  517. todo.append(subpackage_name)
  518. package_submodules = sorted(package_submodules)
  519. logger.debug("collect_submodules - found submodules: %s", package_submodules)
  520. return package_submodules
  521. # This function is called in an isolated sub-process via `isolated.Python.call`.
  522. def _collect_submodules(name, on_error):
  523. import sys
  524. import pkgutil
  525. from traceback import format_exception_only
  526. from PyInstaller.utils.hooks import logger
  527. logger.debug("collect_submodules - scanning (sub)package %s", name)
  528. modules = []
  529. subpackages = []
  530. # Resolve package location(s)
  531. try:
  532. __import__(name)
  533. except Exception as ex:
  534. # Catch all errors and either raise, warn, or ignore them as determined by the *on_error* parameter.
  535. if on_error in ("warn", "warn once"):
  536. from PyInstaller.log import logger
  537. ex = "".join(format_exception_only(type(ex), ex)).strip()
  538. logger.warning(f"Failed to collect submodules for '{name}' because importing '{name}' raised: {ex}")
  539. if on_error == "warn once":
  540. on_error = "ignore"
  541. return modules, subpackages, on_error
  542. elif on_error == "raise":
  543. raise ImportError(f"Unable to load subpackage '{name}'.") from ex
  544. # Do not attempt to recurse into package if it did not make it into sys.modules.
  545. if name not in sys.modules:
  546. return modules, subpackages, on_error
  547. # Or if it does not have __path__ attribute.
  548. paths = getattr(sys.modules[name], '__path__', None) or []
  549. if not paths:
  550. return modules, subpackages, on_error
  551. # Package was successfully imported - include it in the list of modules.
  552. modules.append(name)
  553. # Iterate package contents
  554. logger.debug("collect_submodules - scanning (sub)package %s in location(s): %s", name, paths)
  555. for importer, name, ispkg in pkgutil.iter_modules(paths, name + '.'):
  556. if not ispkg:
  557. modules.append(name)
  558. else:
  559. subpackages.append(name)
  560. return modules, subpackages, on_error
  561. def is_module_or_submodule(name: str, mod_or_submod: str):
  562. """
  563. This helper function is designed for use in the ``filter`` argument of :func:`collect_submodules`, by returning
  564. ``True`` if the given ``name`` is a module or a submodule of ``mod_or_submod``.
  565. Examples:
  566. The following excludes ``foo.test`` and ``foo.test.one`` but not ``foo.testifier``. ::
  567. collect_submodules('foo', lambda name: not is_module_or_submodule(name, 'foo.test'))``
  568. """
  569. return name.startswith(mod_or_submod + '.') or name == mod_or_submod
  570. # Patterns of dynamic library filenames that might be bundled with some installed Python packages.
  571. PY_DYLIB_PATTERNS = [
  572. '*.dll',
  573. '*.dylib',
  574. 'lib*.so',
  575. ]
  576. def collect_dynamic_libs(package: str, destdir: str | None = None, search_patterns: list = PY_DYLIB_PATTERNS):
  577. """
  578. This function produces a list of (source, dest) of dynamic library files that reside in package. Its output can be
  579. directly assigned to ``binaries`` in a hook script. The package parameter must be a string which names the package.
  580. :param destdir: Relative path to ./dist/APPNAME where the libraries should be put.
  581. :param search_patterns: List of dynamic library filename patterns to collect.
  582. """
  583. logger.debug('Collecting dynamic libraries for %s' % package)
  584. # Accept only strings as packages.
  585. if not isinstance(package, str):
  586. raise TypeError('package must be a str')
  587. # Skip a module which is not a package.
  588. if not is_package(package):
  589. logger.warning(
  590. "collect_dynamic_libs - skipping library collection for module '%s' as it is not a package.", package
  591. )
  592. return []
  593. pkg_dirs = get_all_package_paths(package)
  594. dylibs = []
  595. for pkg_dir in pkg_dirs:
  596. pkg_base = package_base_path(pkg_dir, package)
  597. # Recursively glob for all file patterns in the package directory
  598. for pattern in search_patterns:
  599. files = Path(pkg_dir).rglob(pattern)
  600. for source in files:
  601. # Produce the tuple ('/abs/path/to/source/mod/submod/file.pyd', 'mod/submod')
  602. if destdir:
  603. # Put libraries in the specified target directory.
  604. dest = destdir
  605. else:
  606. # Preserve original directory hierarchy.
  607. dest = source.parent.relative_to(pkg_base)
  608. logger.debug(' %s, %s' % (source, dest))
  609. dylibs.append((str(source), str(dest)))
  610. return dylibs
  611. def collect_data_files(
  612. package: str,
  613. include_py_files: bool = False,
  614. subdir: str | os.PathLike | None = None,
  615. excludes: list | None = None,
  616. includes: list | None = None,
  617. ):
  618. r"""
  619. This function produces a list of ``(source, dest)`` entries for data files that reside in ``package``.
  620. Its output can be directly assigned to ``datas`` in a hook script; for example, see ``hook-sphinx.py``.
  621. The data files are all files that are not shared libraries / binary python extensions (based on extension
  622. check) and are not python source (.py) files or byte-compiled modules (.pyc). Collection of the .py and .pyc
  623. files can be toggled via the ``include_py_files`` flag.
  624. Parameters:
  625. - The ``package`` parameter is a string which names the package.
  626. - By default, python source files and byte-compiled modules (files with ``.py`` and ``.pyc`` suffix) are not
  627. collected; setting the ``include_py_files`` argument to ``True`` collects these files as well. This is typically
  628. used when a package requires source .py files to be available; for example, JIT compilation used in
  629. deep-learning frameworks, code that requires access to .py files (for example, to check their date), or code
  630. that tries to extend `sys.path` with subpackage paths in a way that is incompatible with PyInstaller's frozen
  631. importer.. However, in contemporary PyInstaller versions, the preferred way of collecting source .py files is by
  632. using the **module collection mode** setting (which enables collection of source .py files in addition to or
  633. in lieu of collecting byte-compiled modules into PYZ archive).
  634. - The ``subdir`` argument gives a subdirectory relative to ``package`` to search, which is helpful when submodules
  635. are imported at run-time from a directory lacking ``__init__.py``.
  636. - The ``excludes`` argument contains a sequence of strings or Paths. These provide a list of
  637. `globs <https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob>`_
  638. to exclude from the collected data files; if a directory matches the provided glob, all files it contains will
  639. be excluded as well. All elements must be relative paths, which are relative to the provided package's path
  640. (/ ``subdir`` if provided).
  641. Therefore, ``*.txt`` will exclude only ``.txt`` files in ``package``\ 's path, while ``**/*.txt`` will exclude
  642. all ``.txt`` files in ``package``\ 's path and all its subdirectories. Likewise, ``**/__pycache__`` will exclude
  643. all files contained in any subdirectory named ``__pycache__``.
  644. - The ``includes`` function like ``excludes``, but only include matching paths. ``excludes`` override
  645. ``includes``: a file or directory in both lists will be excluded.
  646. This function does not work on zipped Python eggs.
  647. This function is intended to be used by hook scripts, not by main PyInstaller code.
  648. """
  649. logger.debug('Collecting data files for %s' % package)
  650. # Accept only strings as packages.
  651. if not isinstance(package, str):
  652. raise TypeError('package must be a str')
  653. # Skip a module which is not a package.
  654. if not is_package(package):
  655. logger.warning("collect_data_files - skipping data collection for module '%s' as it is not a package.", package)
  656. return []
  657. # Make sure the excludes are a list; this also makes a copy, so we don't modify the original.
  658. excludes = list(excludes) if excludes else []
  659. # These excludes may contain directories which need to be searched.
  660. excludes_len = len(excludes)
  661. # Including py files means don't exclude them. This pattern will search any directories for containing files, so
  662. # do not modify ``excludes_len``.
  663. if not include_py_files:
  664. excludes += ['**/*' + s for s in compat.ALL_SUFFIXES]
  665. else:
  666. # include_py_files should collect only .py and .pyc files, and not the extensions / shared libs.
  667. excludes += ['**/*' + s for s in compat.ALL_SUFFIXES if s not in {'.py', '.pyc'}]
  668. # Never, ever, collect .pyc files from __pycache__.
  669. excludes.append('**/__pycache__/*.pyc')
  670. # If not specified, include all files. Follow the same process as the excludes.
  671. includes = list(includes) if includes else ["**/*"]
  672. includes_len = len(includes)
  673. # A helper function to glob the in/ex "cludes", adding a wildcard to refer to all files under a subdirectory if a
  674. # subdirectory is matched by the first ``clude_len`` patterns. Otherwise, it in/excludes the matched file.
  675. # **This modifies** ``cludes``.
  676. def clude_walker(
  677. # Package directory to scan
  678. pkg_dir,
  679. # A list of paths relative to ``pkg_dir`` to in/exclude.
  680. cludes,
  681. # The number of ``cludes`` for which matching directories should be searched for all files under them.
  682. clude_len,
  683. # True if the list is includes, False for excludes.
  684. is_include
  685. ):
  686. for i, c in enumerate(cludes):
  687. for g in Path(pkg_dir).glob(c):
  688. # The path obtained from `pathlib.Path.glob()` should already be an instance of `pathlib.Path`; however,
  689. # under msys2/mingw python 3.13, it uses inconsistent mix of POSIX-style separators (for base part) and
  690. # Windows separators (for the globbed part), which causes issues further down the road. Explicitly
  691. # constructing a `Path` instance seems to normalize the separators and works around the problem.
  692. g = Path(g)
  693. if g.is_dir():
  694. # Only files are sources. Subdirectories are not.
  695. if i < clude_len:
  696. # In/exclude all files under a matching subdirectory.
  697. cludes.append(str((g / "**/*").relative_to(pkg_dir)))
  698. else:
  699. # In/exclude a matching file.
  700. sources.add(g) if is_include else sources.discard(g)
  701. # Obtain all paths for the specified package, and process each path independently.
  702. datas = []
  703. pkg_dirs = get_all_package_paths(package)
  704. for pkg_dir in pkg_dirs:
  705. sources = set() # Reset sources set
  706. pkg_base = package_base_path(pkg_dir, package)
  707. if subdir:
  708. pkg_dir = os.path.join(pkg_dir, subdir)
  709. # Process the package path with clude walker
  710. clude_walker(pkg_dir, includes, includes_len, True)
  711. clude_walker(pkg_dir, excludes, excludes_len, False)
  712. # Transform the sources into tuples for ``datas``.
  713. datas += [(str(s), str(s.parent.relative_to(pkg_base))) for s in sources]
  714. logger.debug("collect_data_files - Found files: %s", datas)
  715. return datas
  716. def collect_system_data_files(path: str, destdir: str | os.PathLike | None = None, include_py_files: bool = False):
  717. """
  718. This function produces a list of (source, dest) non-Python (i.e., data) files that reside somewhere on the system.
  719. Its output can be directly assigned to ``datas`` in a hook script.
  720. This function is intended to be used by hook scripts, not by main PyInstaller code.
  721. """
  722. # Accept only strings as paths.
  723. if not isinstance(path, str):
  724. raise TypeError('path must be a str')
  725. # Walk through all file in the given package, looking for data files.
  726. datas = []
  727. for dirpath, dirnames, files in os.walk(path):
  728. for f in files:
  729. extension = os.path.splitext(f)[1]
  730. if include_py_files or (extension not in PY_IGNORE_EXTENSIONS):
  731. # Produce the tuple: (/abs/path/to/source/mod/submod/file.dat, mod/submod/destdir)
  732. source = os.path.join(dirpath, f)
  733. dest = str(Path(dirpath).relative_to(path))
  734. if destdir is not None:
  735. dest = os.path.join(destdir, dest)
  736. datas.append((source, dest))
  737. return datas
  738. def copy_metadata(package_name: str, recursive: bool = False):
  739. """
  740. Collect distribution metadata so that ``importlib.metadata.distribution()`` or ``pkg_resources.get_distribution()``
  741. can find it.
  742. This function returns a list to be assigned to the ``datas`` global variable. This list instructs PyInstaller to
  743. copy the metadata for the given package to the frozen application's data directory.
  744. Parameters
  745. ----------
  746. package_name : str
  747. Specifies the name of the package for which metadata should be copied.
  748. recursive : bool
  749. If true, collect metadata for the package's dependencies too. This enables use of
  750. ``importlib.metadata.requires('package')`` or ``pkg_resources.require('package')`` inside the frozen
  751. application.
  752. Returns
  753. -------
  754. list
  755. This should be assigned to ``datas``.
  756. Examples
  757. --------
  758. >>> from PyInstaller.utils.hooks import copy_metadata
  759. >>> copy_metadata('sphinx')
  760. [('c:\\python27\\lib\\site-packages\\Sphinx-1.3.2.dist-info',
  761. 'Sphinx-1.3.2.dist-info')]
  762. Some packages rely on metadata files accessed through the ``importlib.metadata`` (or the now-deprecated
  763. ``pkg_resources``) module. PyInstaller does not collect these metadata files by default.
  764. If a package fails without the metadata (either its own, or of another package that it depends on), you can use this
  765. function in a hook to collect the corresponding metadata files into the frozen application. The tuples in the
  766. returned list contain two strings. The first is the full path to the package's metadata directory on the system. The
  767. second is the destination name, which typically corresponds to the basename of the metadata directory. Adding these
  768. tuples the the ``datas`` hook global variable, the metadata is collected into top-level application directory (where
  769. it is usually searched for).
  770. .. versionchanged:: 4.3.1
  771. Prevent ``dist-info`` metadata folders being renamed to ``egg-info`` which broke ``pkg_resources.require`` with
  772. *extras* (see :issue:`#3033`).
  773. .. versionchanged:: 4.4.0
  774. Add the **recursive** option.
  775. """
  776. from collections import deque
  777. todo = deque([package_name])
  778. done = set()
  779. out = []
  780. while todo:
  781. package_name = todo.pop()
  782. if package_name in done:
  783. continue
  784. dist = importlib_metadata.distribution(package_name)
  785. # We support only `importlib_metadata.PathDistribution`, since we need to rely on its private `_path` attribute
  786. # to obtain the path to metadata file/directory. But we need to account for possible sub-classes and vendored
  787. # variants (`setuptools._vendor.importlib_metadata.PathDistribution`), so just check that `_path` is available.
  788. if not hasattr(dist, '_path'):
  789. raise RuntimeError(
  790. f"Unsupported distribution type {type(dist)} for {package_name} - does not have _path attribute"
  791. )
  792. src_path = dist._path
  793. # We expect the `_path` attribute to be an instance of `pathlib.Path`. This assumption is violated when the
  794. # package happens to be installed as a zipped egg. In such case, `_path` is an instance of either `zipp.Path`
  795. # (when using `importlib.metadata` from `importlib-metadata`, which in turn uses 3rd party `zipp` package) or
  796. # `zipfile.Path` (when using stdlib's `importlib.metadata`). While we could attempt to read the metadata
  797. # from the zip, we dropped geberal support for zipped eggs from PyInstaller in 6.0, so raise an error.
  798. if not isinstance(src_path, Path):
  799. # NOTE: `src_path.parent` is also an instance of `zipfile.Path` or `zipp.Path`, and calling its `is_file()`
  800. # method returns False, because the root of zip file is (rightfully) considered a directory. Therefore, we
  801. # convert the path to `pathlib.Path` by taking the parent of `src_path.parent` (which turns out to be a
  802. # `pathlib.Path`) and add to it the name of the `src_path.parent` (the name of .egg file).
  803. try:
  804. src_parent = src_path.parent.parent / src_path.parent.name
  805. except Exception:
  806. src_parent = src_path.parent
  807. if src_parent.is_file() and src_parent.name.endswith('.egg'):
  808. raise RuntimeError(
  809. f"Cannot collect metadata from path {str(src_path)!r}, which appears to be inside a zipped egg. "
  810. f"PyInstaller >= 6.0 does not support zipped eggs anymore. Please reinstall {package_name!r} "
  811. "using modern package installation method instead of deprecated 'python setup.py install'. "
  812. "For example, if you are using pip package manager:\n"
  813. "1. uninstall the zipped egg:\n"
  814. f" pip uninstall {package_name}\n"
  815. "2. make sure pip and its dependencies are up-to-date:\n"
  816. " python -m pip install --upgrade pip setuptools\n"
  817. "3. install the package:\n"
  818. f" pip install {package_name}\n"
  819. "To install a package from source, pass the path to the source directory to 'pip install' command."
  820. )
  821. else:
  822. # Generic message for unforeseen cases.
  823. raise RuntimeError(
  824. f"Cannot collect metadata from path {src_path!r}, which is of unsupported type {type(src_path)}."
  825. )
  826. if src_path.is_dir():
  827. # The metadata is stored in a directory (.egg-info, .dist-info), so collect the whole directory. If the
  828. # package is installed as an egg, the metadata directory is ([...]/package_name-version.egg/EGG-INFO),
  829. # and requires special handling (as of PyInstaller v6, we support only non-zipped eggs).
  830. if src_path.name == 'EGG-INFO' and src_path.parent.name.endswith('.egg'):
  831. dest_path = os.path.join(*src_path.parts[-2:])
  832. else:
  833. dest_path = src_path.name
  834. elif src_path.is_file():
  835. # The metadata is stored in a single file. Collect it into top-level application directory.
  836. # The .egg-info file is commonly used by Debian/Ubuntu when packaging python packages.
  837. dest_path = '.'
  838. else:
  839. raise RuntimeError(
  840. f"Distribution metadata path {src_path!r} for {package_name} is neither file nor directory!"
  841. )
  842. # Hack for metadata from packages vendored by setuptools >= 71. If source path is rooted in setuptools/_vendor,
  843. # prepend the same to the destination path and avoid collecting into top-level directory.
  844. if src_path.parent.name == '_vendor' and src_path.parent.parent.name == 'setuptools':
  845. dest_path = os.path.join('setuptools', '_vendor', dest_path)
  846. out.append((str(src_path), str(dest_path)))
  847. if not recursive:
  848. return out
  849. done.add(package_name)
  850. # Process requirements; `importlib.metadata` has no API for parsing requirements, so we need to use
  851. # `packaging.requirements`. This is necessary to discard requirements with markers that do not match the
  852. # environment (e.g., `python_version`, `sys_platform`).
  853. requirements = [packaging.requirements.Requirement(req) for req in dist.requires or []]
  854. requirements = [req.name for req in requirements if req.marker is None or req.marker.evaluate()]
  855. todo += requirements
  856. return out
  857. def get_installer(dist_name: str):
  858. """
  859. Try to find which package manager installed the specified distribution (e.g., pip, conda, rpm) by reading INSTALLER
  860. file from distribution's metadata.
  861. If the specified distribution does not exist, fall back to treating the passed name as importable package/module
  862. name, and attempt to look up its associated distribution name; this matches the behavior of implementation found
  863. in older PyInstaller versions (<= v6.12.0).
  864. :param dist_name: Name of distribution to look up
  865. :return: Name of package manager or None
  866. .. versionchanged:: 6.13
  867. The passed name is now first treated as a distribution name (direct look-up), and only if that fails, it is
  868. treated as importable package/module name.
  869. """
  870. # First, perform direct look-up via the passed name, treating it as distribution name.
  871. try:
  872. dist = importlib_metadata.distribution(dist_name)
  873. installer_text = dist.read_text('INSTALLER')
  874. if installer_text is not None:
  875. return installer_text.strip()
  876. else:
  877. # Distribution exists, but does not have an INSTALLER file; stop the search here.
  878. return None
  879. except importlib_metadata.PackageNotFoundError:
  880. pass
  881. # Fall back to treating the passed name as importable package/module name, and try to resolve its associated
  882. # distribution name (e.g., enchant -> pyenchant). This requires distributions to explicitly list their top-level
  883. # importable names in `top_level.txt` file in metadata, or `importlib.metadata` that can inferring top-level
  884. # importable names (available in stdlib for python >= 3.11, or in importlib-metadata >= 4.8.1).
  885. module_name = dist_name
  886. pkg_to_dist = importlib_metadata.packages_distributions()
  887. dist_names = pkg_to_dist.get(module_name)
  888. # A namespace package might result in multiple dists; take the first one that has INSTALLER file available...
  889. for dist_name in dist_names or []:
  890. try:
  891. dist = importlib_metadata.distribution(dist_name)
  892. installer_text = dist.read_text('INSTALLER')
  893. if installer_text is not None:
  894. return installer_text.strip()
  895. except importlib_metadata.PackageNotFoundError:
  896. # This might happen with eggs if the egg directory name does not match the dist name declared in the
  897. # metadata.
  898. pass
  899. return None
  900. def collect_all(
  901. package_name: str,
  902. include_py_files: bool = True,
  903. filter_submodules: Callable = lambda name: True,
  904. exclude_datas: list | None = None,
  905. include_datas: list | None = None,
  906. on_error: str = "warn once",
  907. ):
  908. """
  909. Collect everything for a given package name.
  910. Arguments:
  911. package_name:
  912. An ``import``-able package name.
  913. include_py_files:
  914. Forwarded to :func:`collect_data_files`.
  915. filter_submodules:
  916. Forwarded to :func:`collect_submodules`.
  917. exclude_datas:
  918. Forwarded to :func:`collect_data_files`.
  919. include_datas:
  920. Forwarded to :func:`collect_data_files`.
  921. on_error:
  922. Forwarded onto :func:`collect_submodules`.
  923. Returns:
  924. tuple: A ``(datas, binaries, hiddenimports)`` triplet containing:
  925. - All data files, raw Python files (if **include_py_files**), and distribution metadata directories (if
  926. applicable).
  927. - All dynamic libraries as returned by :func:`collect_dynamic_libs`.
  928. - All submodules of **package_name**.
  929. Typical use::
  930. datas, binaries, hiddenimports = collect_all('my_package_name')
  931. """
  932. datas = collect_data_files(package_name, include_py_files, excludes=exclude_datas, includes=include_datas)
  933. binaries = collect_dynamic_libs(package_name)
  934. hiddenimports = collect_submodules(package_name, on_error=on_error, filter=filter_submodules)
  935. # `copy_metadata` requires a dist name instead of importable/package name.
  936. # A namespace package might belong to multiple distributions, so process all of them.
  937. pkg_to_dist = importlib_metadata.packages_distributions()
  938. dist_names = set(pkg_to_dist.get(package_name, []))
  939. for dist_name in dist_names:
  940. # Copy metadata
  941. try:
  942. datas += copy_metadata(dist_name)
  943. except Exception:
  944. pass
  945. return datas, binaries, hiddenimports
  946. def collect_entry_point(name: str):
  947. """
  948. Collect modules and metadata for all exporters of a given entry point.
  949. Args:
  950. name:
  951. The name of the entry point. Check the documentation for the library that uses the entry point to find
  952. its name.
  953. Returns:
  954. A ``(datas, hiddenimports)`` pair that should be assigned to the ``datas`` and ``hiddenimports``, respectively.
  955. For libraries, such as ``pytest`` or ``keyring``, that rely on plugins to extend their behaviour.
  956. Examples:
  957. Pytest uses an entry point called ``'pytest11'`` for its extensions.
  958. To collect all those extensions use::
  959. datas, hiddenimports = collect_entry_point("pytest11")
  960. These values may be used in a hook or added to the ``datas`` and ``hiddenimports`` arguments in the ``.spec``
  961. file. See :ref:`using spec files`.
  962. .. versionadded:: 4.3
  963. """
  964. datas = []
  965. imports = []
  966. for entry_point in importlib_metadata.entry_points(group=name):
  967. datas += copy_metadata(entry_point.dist.name)
  968. imports.append(entry_point.module)
  969. return datas, imports
  970. def get_hook_config(hook_api: PostGraphAPI, module_name: str, key: str):
  971. """
  972. Get user settings for hooks.
  973. Args:
  974. module_name:
  975. The module/package for which the key setting belong to.
  976. key:
  977. A key for the config.
  978. Returns:
  979. The value for the config. ``None`` if not set.
  980. The ``get_hook_config`` function will lookup settings in the ``Analysis.hooksconfig`` dict.
  981. The hook settings can be added to ``.spec`` file in the form of::
  982. a = Analysis(["my-app.py"],
  983. ...
  984. hooksconfig = {
  985. "gi": {
  986. "icons": ["Adwaita"],
  987. "themes": ["Adwaita"],
  988. "languages": ["en_GB", "zh_CN"],
  989. },
  990. },
  991. ...
  992. )
  993. """
  994. config = hook_api.analysis.hooksconfig
  995. value = None
  996. if module_name in config and key in config[module_name]:
  997. value = config[module_name][key]
  998. return value
  999. def include_or_exclude_file(
  1000. filename: str,
  1001. include_list: list | None = None,
  1002. exclude_list: list | None = None,
  1003. ):
  1004. """
  1005. Generic inclusion/exclusion decision function based on filename and list of include and exclude patterns.
  1006. Args:
  1007. filename:
  1008. Filename considered for inclusion.
  1009. include_list:
  1010. List of inclusion file patterns.
  1011. exclude_list:
  1012. List of exclusion file patterns.
  1013. Returns:
  1014. A boolean indicating whether the file should be included or not.
  1015. If ``include_list`` is provided, True is returned only if the filename matches one of include patterns (and does not
  1016. match any patterns in ``exclude_list``, if provided). If ``include_list`` is not provided, True is returned if
  1017. filename does not match any patterns in ``exclude list``, if provided. If neither list is provided, True is
  1018. returned for any filename.
  1019. """
  1020. if include_list is not None:
  1021. for pattern in include_list:
  1022. if fnmatch.fnmatch(filename, pattern):
  1023. break
  1024. else:
  1025. return False # Not explicitly included; exclude
  1026. if exclude_list is not None:
  1027. for pattern in exclude_list:
  1028. if fnmatch.fnmatch(filename, pattern):
  1029. return False # Explicitly excluded
  1030. return True
  1031. def collect_delvewheel_libs_directory(package_name, libdir_name=None, datas=None, binaries=None):
  1032. """
  1033. Collect data files and binaries from the .libs directory of a delvewheel-enabled python wheel. Such wheels ship
  1034. their shared libraries in a .libs directory that is located next to the package directory, and therefore falls
  1035. outside the purview of the collect_dynamic_libs() utility function.
  1036. Args:
  1037. package_name:
  1038. Name of the package (e.g., scipy).
  1039. libdir_name:
  1040. Optional name of the .libs directory (e.g., scipy.libs). If not provided, ".libs" is added to
  1041. ``package_name``.
  1042. datas:
  1043. Optional list of datas to which collected data file entries are added. The combined result is retuned
  1044. as part of the output tuple.
  1045. binaries:
  1046. Optional list of binaries to which collected binaries entries are added. The combined result is retuned
  1047. as part of the output tuple.
  1048. Returns:
  1049. tuple: A ``(datas, binaries)`` pair that should be assigned to the ``datas`` and ``binaries``, respectively.
  1050. Examples:
  1051. Collect the ``scipy.libs`` delvewheel directory belonging to the Windows ``scipy`` wheel::
  1052. datas, binaries = collect_delvewheel_libs_directory("scipy")
  1053. When the collected entries should be added to existing ``datas`` and ``binaries`` listst, the following form
  1054. can be used to avoid using intermediate temporary variables and merging those into existing lists::
  1055. datas, binaries = collect_delvewheel_libs_directory("scipy", datas=datas, binaries=binaries)
  1056. .. versionadded:: 5.6
  1057. """
  1058. datas = datas or []
  1059. binaries = binaries or []
  1060. if libdir_name is None:
  1061. libdir_name = package_name + '.libs'
  1062. # delvewheel is applicable only to Windows wheels
  1063. if not compat.is_win:
  1064. return datas, binaries
  1065. # Get package's parent path
  1066. pkg_base, pkg_dir = get_package_paths(package_name)
  1067. pkg_base = Path(pkg_base)
  1068. libs_dir = pkg_base / libdir_name
  1069. if not libs_dir.is_dir():
  1070. return datas, binaries
  1071. # Collect all dynamic libs - collect them as binaries in order to facilitate proper binary dependency analysis
  1072. # (for example, to ensure that system-installed VC runtime DLLs are collected, if needed).
  1073. # As of PyInstaller 5.4, this should be safe (should not result in duplication), because binary dependency
  1074. # analysis attempts to preserve the DLL directory structure.
  1075. binaries += [(str(dll_file), str(dll_file.parent.relative_to(pkg_base))) for dll_file in libs_dir.glob('*.dll')]
  1076. # Collect the .load-order file; strictly speaking, this should be necessary only under python < 3.8, but let us
  1077. # collect it for completeness sake. Differently named variants have been observed: `.load_order`, `.load-order`,
  1078. # and `.load-order-Name`.
  1079. datas += [(str(load_order_file), str(load_order_file.parent.relative_to(pkg_base)))
  1080. for load_order_file in libs_dir.glob('.load[-_]order*')]
  1081. return datas, binaries
  1082. if compat.is_pure_conda:
  1083. from PyInstaller.utils.hooks import conda as conda_support # noqa: F401
  1084. elif compat.is_conda:
  1085. from PyInstaller.utils.hooks.conda import CONDA_META_DIR as _tmp
  1086. logger.warning(
  1087. "Assuming this is not an Anaconda environment or an additional venv/pipenv/... environment manager is being "
  1088. "used on top, because the conda-meta folder %s does not exist.", _tmp
  1089. )
  1090. del _tmp