hook-gribapi.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. # ------------------------------------------------------------------
  2. # Copyright (c) 2024 PyInstaller Development Team.
  3. #
  4. # This file is distributed under the terms of the GNU General Public
  5. # License (version 2.0 or later).
  6. #
  7. # The full license is available in LICENSE, distributed with
  8. # this software.
  9. #
  10. # SPDX-License-Identifier: GPL-2.0-or-later
  11. # ------------------------------------------------------------------
  12. import os
  13. import pathlib
  14. from PyInstaller import isolated
  15. from PyInstaller.utils.hooks import collect_data_files, logger
  16. # Collect the headers (eccodes.h, gribapi.h) that are bundled with the package.
  17. datas = collect_data_files('gribapi')
  18. # Collect the eccodes shared library. Starting with eccodes 2.37.0, binary wheels with bundled shared library are
  19. # provided for linux and macOS, and since 2.39.0, also for Windows.
  20. # NOTE: custom isolated function is used here instead of `get_module_attribute('gribapi.bindings', 'library_path')`
  21. # hook utility function because with eccodes 2.37.0, `eccodes` needs to be imported before `gribapi` to avoid circular
  22. # imports... Also, this way, we can obtain the root directory of eccodes package at the same time.
  23. @isolated.decorate
  24. def get_eccodes_library_path():
  25. import eccodes
  26. import gribapi.bindings
  27. return (
  28. # Path to eccodes shared library used by the gribapi bindings.
  29. str(gribapi.bindings.library_path),
  30. # Path to eccodes package (implicitly assumed to be next to the gribapi package, since they are part of the
  31. # same eccodes dist).
  32. str(eccodes.__path__[0]),
  33. )
  34. binaries = []
  35. hiddenimports = []
  36. try:
  37. library_path, package_path = get_eccodes_library_path()
  38. except Exception:
  39. logger.warning("hook-gribapi: failed to query gribapi.bindings.library_path!", exc_info=True)
  40. library_path = None
  41. if library_path:
  42. if not os.path.isabs(library_path):
  43. from PyInstaller.depend.utils import _resolveCtypesImports
  44. resolved_binary = _resolveCtypesImports([os.path.basename(library_path)])
  45. if resolved_binary:
  46. library_path = resolved_binary[0][1]
  47. else:
  48. logger.warning("hook-gribapi: failed to resolve shared library name %r!", library_path)
  49. library_path = None
  50. else:
  51. logger.warning("hook-gribapi: could not determine path to eccodes shared library!")
  52. if library_path:
  53. # If we are collecting eccodes shared library that is bundled with eccodes >= 2.37.0 binary wheel, attempt to
  54. # preserve its parent directory layout. This ensures that the library is found at run-time, but implicitly requires
  55. # PyInstaller 6.x, whose binary dependency analysis (that might also pick up this shared library) also preserves the
  56. # parent directory layout of discovered shared libraries. With PyInstaller 5.x, this will result in duplication
  57. # because binary dependency analysis collects into top-level application directory, but that copy will not be
  58. # discovered at run-time, so duplication is unavoidable.
  59. library_parent_path = pathlib.PurePath(library_path).parent
  60. package_parent_path = pathlib.PurePath(package_path).parent
  61. if package_parent_path in library_parent_path.parents:
  62. # Should end up being `eccodes.libs` on Linux, `eccodes/.dylib` on macOS, and `eccodes` on Windows.
  63. dest_dir = str(library_parent_path.relative_to(package_parent_path))
  64. else:
  65. # External copy; collect into top-level application directory.
  66. dest_dir = '.'
  67. logger.info(
  68. "hook-gribapi: collecting eccodes shared library %r to destination directory %r", library_path, dest_dir
  69. )
  70. binaries.append((library_path, dest_dir))
  71. # If the shared library is available in the stand-alone `eccodeslib` package, add this package to to hidden imports,
  72. # so that `findlibs.find()` can import it and query its `__file__` attribute.
  73. if 'eccodeslib' in library_parent_path.parts:
  74. hiddenimports += ['eccodeslib']