-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathsetup.py
More file actions
172 lines (143 loc) · 6.03 KB
/
setup.py
File metadata and controls
172 lines (143 loc) · 6.03 KB
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
"""Setup routines to enable cf-units' Cython elements.
All other setup configuration is in `pyproject.toml`.
"""
from distutils.sysconfig import get_config_var
from os import environ, getenv
from pathlib import Path
from shutil import copy
import sys
import sysconfig
from setuptools import Command, Extension, setup
from setuptools.command import build_ext
# Default to using cython, but use the .c files if it doesn't exist.
# Supports the widest possible range of developer setups.
try:
from Cython.Build import cythonize
except ImportError:
cythonize = False
USE_PY_LIMITED_API = (
# Builds are not ABI3 default
getenv("CF_UNITS_LIMITED_API", "0") == "1"
and sys.version_info >= (3, 11)
# Free-threaded builds do not support ABI3
and not sysconfig.get_config_var("Py_GIL_DISABLED")
)
COMPILER_DIRECTIVES = {}
# This Cython macro disables a build warning, obsolete with Cython>=3
# see : https://cython.readthedocs.io/en/latest/src/userguide/migrating_to_cy30.html#numpy-c-api
DEFINE_MACROS = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")]
if USE_PY_LIMITED_API:
# 0x030B0000 -> 3.11
DEFINE_MACROS.append(("Py_LIMITED_API", "0x030B0000"))
FLAG_COVERAGE = "--cython-coverage" # custom flag enabling Cython line tracing
BASEDIR = Path(__file__).resolve().parent
PACKAGE = "cf_units"
CFUNITS_DIR = BASEDIR / PACKAGE
class CleanCython(Command):
description = "Purge artifacts built by Cython"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
for path in CFUNITS_DIR.rglob("*"):
if path.suffix in (".pyc", ".pyo", ".c", ".so"):
msg = f"clean: removing file {path}"
print(msg)
path.unlink()
def get_dirs(env_var: str, config_var: str):
"""Get a directory from an env variable or a distutils config variable."""
result = environ.get(env_var) or get_config_var(config_var)
return [result] if result else []
def get_package_data():
"""Find and correctly package the UDUNITS2 XML files for a wheel build."""
package_data = {}
# Determine whether we're building a wheel.
if "bdist_wheel" in sys.argv:
# The protocol is that the UDUNITS2_XML_PATH environment variable
# identifies the root UDUNITS2 XML file and parent directory containing
# all the XML resource files that require to be bundled within this
# wheel. Note that, this should match the UDUNITS2 distribution for the
# UDUNITS2 library cf-units is linking against.
xml_env = "UDUNITS2_XML_PATH"
xml_database = environ.get(xml_env)
if xml_database is None:
emsg = f"Require to set {xml_env} for a cf-units wheel build."
raise ValueError(emsg)
xml_database = Path(xml_database)
if not xml_database.is_file():
emsg = (
f"Can't open {xml_env} file {xml_database} during cf-units wheel build."
)
raise ValueError(emsg)
# We have a valid XML file, so copy the distro bundle into the
# cf_units/etc/share directory.
xml_dir = xml_database.expanduser().resolve().parent
share_base = Path("etc") / "share"
share_dir = CFUNITS_DIR / share_base
if not share_dir.is_dir():
share_dir.mkdir(parents=True)
else:
# Purge any existing XML share files.
[fname.unlink() for fname in share_dir.glob("*.xml")]
# Bundle the UDUNITS2 XML file/s for the wheel.
[copy(fname, share_dir) for fname in xml_dir.glob("*.xml")]
# Register our additional wheel content.
package_data = {PACKAGE: [str(share_base / "*.xml")]}
return package_data
class NumpyBuildExt(build_ext.build_ext):
"""Subclass of build_ext to make NumPy headers available for the Cython layer."""
def finalize_options(self):
# See https://github.com/SciTools/cf-units/issues/151
def _set_builtin(name, value):
if isinstance(__builtins__, dict):
__builtins__[name] = value
else:
setattr(__builtins__, name, value)
super().finalize_options()
_set_builtin("__NUMPY_SETUP__", False)
import numpy # noqa: PLC0415
self.include_dirs.append(numpy.get_include())
if FLAG_COVERAGE in sys.argv or environ.get("CYTHON_COVERAGE", None):
COMPILER_DIRECTIVES = {"linetrace": True}
DEFINE_MACROS += [("CYTHON_TRACE", "1"), ("CYTHON_TRACE_NOGIL", "1")]
if FLAG_COVERAGE in sys.argv:
sys.argv.remove(FLAG_COVERAGE)
print('enable: "linetrace" Cython compiler directive')
include_dirs = get_dirs("UDUNITS2_INCDIR", "INCLUDEDIR")
library_dirs = get_dirs("UDUNITS2_LIBDIR", "LIBDIR")
# Some of the complexity MUST remain in setup.py due to its dynamic nature. To
# reduce confusion, the Extension is 100% defined here, rather than splitting
# between setup.py and pyproject.toml `ext-modules`.
udunits_ext = Extension(
f"{PACKAGE}._udunits2",
[str(Path(f"{PACKAGE}") / f"_udunits2.{'pyx' if cythonize else 'c'}")],
include_dirs=include_dirs,
library_dirs=library_dirs,
libraries=["udunits2"],
runtime_library_dirs=(None if sys.platform.startswith("win") else library_dirs),
define_macros=DEFINE_MACROS,
py_limited_api=True,
)
if cythonize:
# https://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules
[udunits_ext] = cythonize(
udunits_ext,
compiler_directives=COMPILER_DIRECTIVES,
# Assert python 3 source syntax: Currently required to suppress a
# warning, even though this is now the default (as-of Cython v3).
language_level="3str",
)
cmdclass = {"clean_cython": CleanCython, "build_ext": NumpyBuildExt}
if USE_PY_LIMITED_API:
SETUP_OPTIONS = {"bdist_wheel": {"py_limited_api": "cp311"}}
else:
SETUP_OPTIONS = {}
kwargs = {
"cmdclass": cmdclass,
"ext_modules": [udunits_ext],
"package_data": get_package_data(),
"options": SETUP_OPTIONS,
}
setup(**kwargs)