1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
"""Setuptools build."""
import logging
import os
import subprocess
import sys
from pathlib import Path
from setuptools import Extension, setup
# ensure the current directory is on sys.path so versioneer can be imported
# when pip uses PEP 517/518 build rules.
# https://github.com/python-versioneer/python-versioneer/issues/193
sys.path.insert(0, os.path.dirname(__file__))
import versioneer
# Skip Cython build if not available
try:
import cython
from Cython.Build import cythonize
except ImportError:
cythonize = None
log = logging.getLogger(__name__)
ch = logging.StreamHandler()
log.addHandler(ch)
MIN_GEOS_VERSION = "3.9"
if "all" in sys.warnoptions:
# show GEOS messages in console with: python -W all
log.setLevel(logging.DEBUG)
def get_geos_config(option):
"""Get configuration option from the `geos-config` development utility.
The PATH environment variable should include the path where geos-config is
located, or the GEOS_CONFIG environment variable should point to the
executable.
"""
cmd = os.environ.get("GEOS_CONFIG", "geos-config")
try:
proc = subprocess.run([cmd, option], capture_output=True, text=True, check=True)
except OSError:
return
if proc.stderr and not proc.stdout:
log.warning("geos-config %s returned '%s'", option, proc.stderr.strip())
return
result = proc.stdout.strip()
log.debug("geos-config %s returned '%s'", option, result)
return result
def get_ext_options():
"""Get Extension options to build using GEOS and NumPy C API.
First the presence of the GEOS_INCLUDE_PATH and GEOS_INCLUDE_PATH environment
variables is checked. If they are both present, these are taken.
If one of the two paths was not present, geos-config is called (it should be on the
PATH variable). geos-config provides all the paths.
If geos-config was not found, no additional paths are provided to the extension.
It is still possible to compile in this case using custom arguments to setup.py.
"""
import numpy
opts = {
"define_macros": [
# avoid accidental use of non-reentrant functions
("GEOS_USE_ONLY_R_API", None),
# silence warnings
("NPY_NO_DEPRECATED_API", "0"),
# minimum numpy version
("NPY_TARGET_VERSION", "NPY_1_20_API_VERSION"),
],
"include_dirs": ["./src"],
"library_dirs": [],
"extra_link_args": [],
"libraries": [],
}
if include_dir := numpy.get_include():
opts["include_dirs"].append(include_dir)
# Without geos-config (e.g. MSVC), specify these two vars
include_dir = os.environ.get("GEOS_INCLUDE_PATH")
library_dir = os.environ.get("GEOS_LIBRARY_PATH")
if include_dir and library_dir:
opts["include_dirs"].append(include_dir)
opts["library_dirs"].append(library_dir)
opts["libraries"].append("geos_c")
return opts
geos_version = get_geos_config("--version")
if not geos_version:
log.warning(
"Could not find geos-config executable. Either append the path to "
"geos-config to PATH or manually provide the include_dirs, library_dirs, "
"libraries and other link args for compiling against a GEOS version >=%s.",
MIN_GEOS_VERSION,
)
return {}
def version_tuple(ver):
return tuple(int(itm) if itm.isnumeric() else itm for itm in ver.split("."))
if version_tuple(geos_version) < version_tuple(MIN_GEOS_VERSION):
raise ImportError(
f"GEOS version should be >={MIN_GEOS_VERSION}, found {geos_version}"
)
for item in get_geos_config("--cflags").split():
if item.startswith("-I"):
opts["include_dirs"].extend(item[2:].split(":"))
for item in get_geos_config("--clibs").split():
if item.startswith("-L"):
opts["library_dirs"].extend(item[2:].split(":"))
elif item.startswith("-l"):
opts["libraries"].append(item[2:])
else:
opts["extra_link_args"].append(item)
return opts
ext_modules = []
if "clean" in sys.argv:
# delete any previously Cythonized or compiled files in pygeos
p = Path(".")
for pattern in [
"build/lib.*/shapely/*.so",
"shapely/*.c",
"shapely/*.so",
"shapely/*.pyd",
]:
for filename in p.glob(pattern):
print(f"removing '{filename}'")
filename.unlink()
elif "sdist" in sys.argv:
if Path("LICENSE_GEOS").exists() or Path("LICENSE_win32").exists():
raise FileExistsError(
"Source distributions should not pack LICENSE_GEOS or LICENSE_win32. "
"Please remove the files."
)
else:
ext_options = get_ext_options()
ext_modules = [
Extension(
"shapely.lib",
sources=[
"src/c_api.c",
"src/coords.c",
"src/geos.c",
"src/lib.c",
"src/pygeom.c",
"src/pygeos.c",
"src/strtree.c",
"src/ufuncs.c",
"src/vector.c",
],
**ext_options,
)
]
# Cython is required
if not cythonize:
sys.exit("ERROR: Cython is required to build shapely from source.")
cython_modules = [
Extension(
"shapely._geometry_helpers",
["shapely/_geometry_helpers.pyx"],
**ext_options,
),
Extension(
"shapely._geos",
["shapely/_geos.pyx"],
**ext_options,
),
]
compiler_directives = {"language_level": "3"}
if cython.__version__ >= "3.1.0":
compiler_directives.update({"freethreading_compatible": True})
ext_modules += cythonize(
cython_modules,
compiler_directives=compiler_directives,
)
cmdclass = versioneer.get_cmdclass()
# see pyproject.toml for static project metadata
setup(
version=versioneer.get_version(),
ext_modules=ext_modules,
cmdclass=cmdclass,
)
|