hook-shapely.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # ------------------------------------------------------------------
  2. # Copyright (c) 2020 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. from ctypes.util import find_library
  14. from PyInstaller.utils.hooks import get_package_paths
  15. from PyInstaller.utils.hooks import is_module_satisfies
  16. from PyInstaller import compat
  17. # Necessary when using the vectorized subpackage
  18. hiddenimports = ['shapely.prepared']
  19. if is_module_satisfies('shapely >= 2.0.0'):
  20. # An import made in the `shapely.geometry_helpers` extension; both `shapely.geometry_helpers` and `shapely._geos`
  21. # extensions were introduced in v2.0.0.
  22. hiddenimports += ['shapely._geos']
  23. pkg_base, pkg_dir = get_package_paths('shapely')
  24. binaries = []
  25. datas = []
  26. if compat.is_win:
  27. geos_c_dll_found = False
  28. # Search conda directory if conda is active, then search standard
  29. # directory. This is the same order of precidence used in shapely.
  30. standard_path = os.path.join(pkg_dir, 'DLLs')
  31. lib_paths = [standard_path, os.environ['PATH']]
  32. if compat.is_conda:
  33. conda_path = os.path.join(compat.base_prefix, 'Library', 'bin')
  34. lib_paths.insert(0, conda_path)
  35. original_path = os.environ['PATH']
  36. try:
  37. os.environ['PATH'] = os.pathsep.join(lib_paths)
  38. dll_path = find_library('geos_c') or find_library('libgeos_c')
  39. finally:
  40. os.environ['PATH'] = original_path
  41. if dll_path is not None:
  42. binaries += [(dll_path, '.')]
  43. geos_c_dll_found = True
  44. # Starting with shapely 1.8.1, the DLLs shipped with PyPI wheels are stored in
  45. # site-packages/Shapely.libs instead of sub-directory in site-packages/shapely.
  46. if is_module_satisfies("shapely >= 1.8.1"):
  47. lib_dir = os.path.join(pkg_base, "Shapely.libs")
  48. if os.path.isdir(lib_dir):
  49. # We collect DLLs as data files instead of binaries to suppress binary
  50. # analysis, which would result in duplicates (because it collects a copy
  51. # into the top-level directory instead of preserving the original layout).
  52. # In addition to DLls, this also collects .load-order* file (required on
  53. # python < 3.8), and ensures that Shapely.libs directory exists (required
  54. # on python >= 3.8 due to os.add_dll_directory call).
  55. datas += [
  56. (os.path.join(lib_dir, lib_file), 'Shapely.libs')
  57. for lib_file in os.listdir(lib_dir)
  58. ]
  59. geos_c_dll_found |= any([
  60. os.path.basename(lib_file).startswith("geos_c")
  61. for lib_file, _ in datas
  62. ])
  63. if not geos_c_dll_found:
  64. raise SystemExit(
  65. "Error: geos_c.dll not found, required by hook-shapely.py.\n"
  66. "Please check your installation or provide a pull request to "
  67. "PyInstaller to update hook-shapely.py.")
  68. elif compat.is_linux and is_module_satisfies('shapely < 1.7'):
  69. # This duplicates the libgeos*.so* files in the build. PyInstaller will
  70. # copy them into the root of the build by default, but shapely cannot load
  71. # them from there in linux IF shapely was installed via a whl file. The
  72. # whl bundles its own libgeos with a different name, something like
  73. # libgeos_c-*.so.* but shapely tries to load libgeos_c.so if there isn't a
  74. # ./libs directory under its package.
  75. #
  76. # The fix for this (https://github.com/Toblerity/Shapely/pull/485) has
  77. # been available in shapely since version 1.7.
  78. lib_dir = os.path.join(pkg_dir, '.libs')
  79. dest_dir = os.path.join('shapely', '.libs')
  80. binaries += [(os.path.join(lib_dir, f), dest_dir) for f in os.listdir(lib_dir)]
  81. elif compat.is_darwin and is_module_satisfies('shapely >= 1.8.1'):
  82. # In shapely 1.8.1, the libgeos_c library bundled in macOS PyPI wheels is not
  83. # called libgeos.1.dylib anymore, but rather has a fullly-versioned name
  84. # (e.g., libgeos_c.1.16.0.dylib).
  85. # Shapely fails to find such a library unless it is located in the .dylibs
  86. # directory. So we need to ensure that the libraries are collected into
  87. # .dylibs directory; however, this will result in duplication due to binary
  88. # analysis of the python extensions that are linked against these libraries
  89. # as well (as that will copy the libraries to top-level directory).
  90. lib_dir = os.path.join(pkg_dir, '.dylibs')
  91. dest_dir = os.path.join('shapely', '.dylibs')
  92. if os.path.isdir(lib_dir):
  93. binaries += [(os.path.join(lib_dir, f), dest_dir) for f in os.listdir(lib_dir)]