# ------------------------------------------------------------------ # Copyright (c) 2020 PyInstaller Development Team. # # This file is distributed under the terms of the GNU General Public # License (version 2.0 or later). # # The full license is available in LICENSE.GPL.txt, distributed with # this software. # # SPDX-License-Identifier: GPL-2.0-or-later # ------------------------------------------------------------------ import os from ctypes.util import find_library from PyInstaller.utils.hooks import get_package_paths from PyInstaller.utils.hooks import is_module_satisfies from PyInstaller import compat # Necessary when using the vectorized subpackage hiddenimports = ['shapely.prepared'] if is_module_satisfies('shapely >= 2.0.0'): # An import made in the `shapely.geometry_helpers` extension; both `shapely.geometry_helpers` and `shapely._geos` # extensions were introduced in v2.0.0. hiddenimports += ['shapely._geos'] pkg_base, pkg_dir = get_package_paths('shapely') binaries = [] datas = [] if compat.is_win: geos_c_dll_found = False # Search conda directory if conda is active, then search standard # directory. This is the same order of precidence used in shapely. standard_path = os.path.join(pkg_dir, 'DLLs') lib_paths = [standard_path, os.environ['PATH']] if compat.is_conda: conda_path = os.path.join(compat.base_prefix, 'Library', 'bin') lib_paths.insert(0, conda_path) original_path = os.environ['PATH'] try: os.environ['PATH'] = os.pathsep.join(lib_paths) dll_path = find_library('geos_c') finally: os.environ['PATH'] = original_path if dll_path is not None: binaries += [(dll_path, '.')] geos_c_dll_found = True # Starting with shapely 1.8.1, the DLLs shipped with PyPI wheels are stored in # site-packages/Shapely.libs instead of sub-directory in site-packages/shapely. if is_module_satisfies("shapely >= 1.8.1"): lib_dir = os.path.join(pkg_base, "Shapely.libs") if os.path.isdir(lib_dir): # We collect DLLs as data files instead of binaries to suppress binary # analysis, which would result in duplicates (because it collects a copy # into the top-level directory instead of preserving the original layout). # In addition to DLls, this also collects .load-order* file (required on # python < 3.8), and ensures that Shapely.libs directory exists (required # on python >= 3.8 due to os.add_dll_directory call). datas += [ (os.path.join(lib_dir, lib_file), 'Shapely.libs') for lib_file in os.listdir(lib_dir) ] geos_c_dll_found |= any([ os.path.basename(lib_file).startswith("geos_c") for lib_file, _ in datas ]) if not geos_c_dll_found: raise SystemExit( "Error: geos_c.dll not found, required by hook-shapely.py.\n" "Please check your installation or provide a pull request to " "PyInstaller to update hook-shapely.py.") elif compat.is_linux and is_module_satisfies('shapely < 1.7'): # This duplicates the libgeos*.so* files in the build. PyInstaller will # copy them into the root of the build by default, but shapely cannot load # them from there in linux IF shapely was installed via a whl file. The # whl bundles its own libgeos with a different name, something like # libgeos_c-*.so.* but shapely tries to load libgeos_c.so if there isn't a # ./libs directory under its package. # # The fix for this (https://github.com/Toblerity/Shapely/pull/485) has # been available in shapely since version 1.7. lib_dir = os.path.join(pkg_dir, '.libs') dest_dir = os.path.join('shapely', '.libs') binaries += [(os.path.join(lib_dir, f), dest_dir) for f in os.listdir(lib_dir)] elif compat.is_darwin and is_module_satisfies('shapely >= 1.8.1'): # In shapely 1.8.1, the libgeos_c library bundled in macOS PyPI wheels is not # called libgeos.1.dylib anymore, but rather has a fullly-versioned name # (e.g., libgeos_c.1.16.0.dylib). # Shapely fails to find such a library unless it is located in the .dylibs # directory. So we need to ensure that the libraries are collected into # .dylibs directory; however, this will result in duplication due to binary # analysis of the python extensions that are linked against these libraries # as well (as that will copy the libraries to top-level directory). lib_dir = os.path.join(pkg_dir, '.dylibs') dest_dir = os.path.join('shapely', '.dylibs') if os.path.isdir(lib_dir): binaries += [(os.path.join(lib_dir, f), dest_dir) for f in os.listdir(lib_dir)]