1import builtins 2import logging 3import os 4import subprocess 5import sys 6from distutils.version import LooseVersion 7from pathlib import Path 8 9from setuptools import Extension, setup, find_packages 10from setuptools.command.build_ext import build_ext as _build_ext 11 12import versioneer 13 14# Skip Cython build if not available 15try: 16 from Cython.Build import cythonize 17except ImportError: 18 cythonize = None 19 20 21log = logging.getLogger(__name__) 22ch = logging.StreamHandler() 23log.addHandler(ch) 24 25MIN_GEOS_VERSION = "3.5" 26 27if "all" in sys.warnoptions: 28 # show GEOS messages in console with: python -W all 29 log.setLevel(logging.DEBUG) 30 31 32def get_geos_config(option): 33 """Get configuration option from the `geos-config` development utility 34 35 The PATH environment variable should include the path where geos-config is 36 located, or the GEOS_CONFIG environment variable should point to the 37 executable. 38 """ 39 cmd = os.environ.get("GEOS_CONFIG", "geos-config") 40 try: 41 stdout, stderr = subprocess.Popen( 42 [cmd, option], stdout=subprocess.PIPE, stderr=subprocess.PIPE 43 ).communicate() 44 except OSError: 45 return 46 if stderr and not stdout: 47 log.warning("geos-config %s returned '%s'", option, stderr.decode().strip()) 48 return 49 result = stdout.decode().strip() 50 log.debug("geos-config %s returned '%s'", option, result) 51 return result 52 53 54def get_geos_paths(): 55 """Obtain the paths for compiling and linking with the GEOS C-API 56 57 First the presence of the GEOS_INCLUDE_PATH and GEOS_INCLUDE_PATH environment 58 variables is checked. If they are both present, these are taken. 59 60 If one of the two paths was not present, geos-config is called (it should be on the 61 PATH variable). geos-config provides all the paths. 62 63 If geos-config was not found, no additional paths are provided to the extension. It is 64 still possible to compile in this case using custom arguments to setup.py. 65 """ 66 include_dir = os.environ.get("GEOS_INCLUDE_PATH") 67 library_dir = os.environ.get("GEOS_LIBRARY_PATH") 68 if include_dir and library_dir: 69 return { 70 "include_dirs": ["./src", include_dir], 71 "library_dirs": [library_dir], 72 "libraries": ["geos_c"], 73 } 74 75 geos_version = get_geos_config("--version") 76 if not geos_version: 77 log.warning( 78 "Could not find geos-config executable. Either append the path to geos-config" 79 " to PATH or manually provide the include_dirs, library_dirs, libraries and " 80 "other link args for compiling against a GEOS version >=%s.", 81 MIN_GEOS_VERSION, 82 ) 83 return {} 84 85 if LooseVersion(geos_version) < LooseVersion(MIN_GEOS_VERSION): 86 raise ImportError( 87 "GEOS version should be >={}, found {}".format( 88 MIN_GEOS_VERSION, geos_version 89 ) 90 ) 91 92 libraries = [] 93 library_dirs = [] 94 include_dirs = ["./src"] 95 extra_link_args = [] 96 for item in get_geos_config("--cflags").split(): 97 if item.startswith("-I"): 98 include_dirs.extend(item[2:].split(":")) 99 100 for item in get_geos_config("--clibs").split(): 101 if item.startswith("-L"): 102 library_dirs.extend(item[2:].split(":")) 103 elif item.startswith("-l"): 104 libraries.append(item[2:]) 105 else: 106 extra_link_args.append(item) 107 108 return { 109 "include_dirs": include_dirs, 110 "library_dirs": library_dirs, 111 "libraries": libraries, 112 "extra_link_args": extra_link_args, 113 } 114 115 116class build_ext(_build_ext): 117 def finalize_options(self): 118 _build_ext.finalize_options(self) 119 120 # Add numpy include dirs without importing numpy on module level. 121 # derived from scikit-hep: 122 # https://github.com/scikit-hep/root_numpy/pull/292 123 124 # Prevent numpy from thinking it is still in its setup process: 125 try: 126 del builtins.__NUMPY_SETUP__ 127 except AttributeError: 128 pass 129 130 import numpy 131 132 self.include_dirs.append(numpy.get_include()) 133 134 135ext_modules = [] 136 137if "clean" in sys.argv: 138 # delete any previously Cythonized or compiled files in pygeos 139 p = Path(".") 140 for pattern in [ 141 "build/lib.*/pygeos/*.so", 142 "pygeos/*.c", 143 "pygeos/*.so", 144 "pygeos/*.pyd", 145 ]: 146 for filename in p.glob(pattern): 147 print("removing '{}'".format(filename)) 148 filename.unlink() 149elif "sdist" in sys.argv: 150 if Path("LICENSE_GEOS").exists() or Path("LICENSE_win32").exists(): 151 raise FileExistsError( 152 "Source distributions should not pack LICENSE_GEOS or LICENSE_win32. Please remove the files." 153 ) 154else: 155 ext_options = get_geos_paths() 156 157 ext_modules = [ 158 Extension( 159 "pygeos.lib", 160 sources=[ 161 "src/c_api.c", 162 "src/coords.c", 163 "src/geos.c", 164 "src/lib.c", 165 "src/pygeom.c", 166 "src/strtree.c", 167 "src/ufuncs.c", 168 "src/vector.c", 169 ], 170 **ext_options, 171 ) 172 ] 173 174 # Cython is required 175 if not cythonize: 176 sys.exit("ERROR: Cython is required to build pygeos from source.") 177 178 cython_modules = [ 179 Extension( 180 "pygeos._geometry", 181 [ 182 "pygeos/_geometry.pyx", 183 ], 184 **ext_options, 185 ), 186 Extension( 187 "pygeos._geos", 188 [ 189 "pygeos/_geos.pyx", 190 ], 191 **ext_options, 192 ), 193 ] 194 195 ext_modules += cythonize( 196 cython_modules, 197 compiler_directives={"language_level": "3"}, 198 # enable once Cython >= 0.3 is released 199 # define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], 200 ) 201 202 203try: 204 descr = open(os.path.join(os.path.dirname(__file__), "README.rst")).read() 205except IOError: 206 descr = "" 207 208 209version = versioneer.get_version() 210cmdclass = versioneer.get_cmdclass() 211cmdclass["build_ext"] = build_ext 212 213 214setup( 215 name="pygeos", 216 version=version, 217 description="GEOS wrapped in numpy ufuncs", 218 long_description=descr, 219 url="https://github.com/pygeos/pygeos", 220 author="Casper van der Wel", 221 author_email="caspervdw@gmail.com", 222 license="BSD 3-Clause", 223 packages=find_packages(include=["pygeos", "pygeos.*"]), 224 install_requires=["numpy>=1.13"], 225 extras_require={ 226 "test": ["pytest"], 227 "docs": ["sphinx", "numpydoc"], 228 }, 229 python_requires=">=3.6", 230 include_package_data=True, 231 ext_modules=ext_modules, 232 classifiers=[ 233 "Programming Language :: Python :: 3", 234 "Intended Audience :: Science/Research", 235 "Intended Audience :: Developers", 236 "Development Status :: 4 - Beta", 237 "Topic :: Scientific/Engineering", 238 "Topic :: Software Development", 239 "Operating System :: Unix", 240 "Operating System :: MacOS", 241 "Operating System :: Microsoft :: Windows", 242 ], 243 cmdclass=cmdclass, 244) 245