mirror of
https://github.com/Cantera/cantera.git
synced 2025-02-25 18:55:29 -06:00
Setuptools versions greater than 60.0.0 deprecate using setup.py install commands, in preference for PEP 517/518 build backends/frontends. In this case, we're using pip as the frontend and setuptools as the backend. This also uses pip to determine the installation location for the module. It uses pip's internal API which is subject to change. However, pip does not expose an external API to get this information, and since Pip is the one determining where files will be installed, this seems the most reasonable approach for now.
201 lines
7.9 KiB
Python
201 lines
7.9 KiB
Python
"""Cython-based Python Module"""
|
|
from __future__ import annotations
|
|
import re
|
|
from pathlib import Path
|
|
from pkg_resources import parse_version
|
|
import json
|
|
from buildutils import *
|
|
from textwrap import dedent
|
|
|
|
Import('env', 'build', 'install')
|
|
|
|
localenv = env.Clone()
|
|
|
|
cythonized = localenv.Command(
|
|
'cantera/_cantera.cpp',
|
|
'cantera/_cantera.pyx',
|
|
'''${python_cmd} -c "import Cython.Build; Cython.Build.cythonize('${SOURCE}')"''')
|
|
|
|
for f in multi_glob(localenv, 'cantera', 'pyx', 'pxd'):
|
|
localenv.Depends(cythonized, f)
|
|
|
|
for line in Path(File('#interfaces/cython/cantera/_cantera.pxd').abspath).read_text().splitlines():
|
|
m = re.search(r'from "(cantera.*?)"', line)
|
|
if m:
|
|
localenv.Depends('cantera/_cantera.cpp', '#include/' + m.group(1))
|
|
|
|
dataFiles = localenv.RecursiveInstall('cantera/data',
|
|
'#build/data')
|
|
build(dataFiles)
|
|
|
|
testFiles = localenv.RecursiveInstall('cantera/test/data',
|
|
'#test/data')
|
|
build(testFiles)
|
|
|
|
# Get information needed to build the Python module
|
|
script = """\
|
|
from sysconfig import *
|
|
import numpy
|
|
import json
|
|
vars = get_config_vars()
|
|
vars["plat"] = get_platform()
|
|
vars["numpy_include"] = numpy.get_include()
|
|
print(json.dumps(vars))
|
|
"""
|
|
info = json.loads(get_command_output(localenv["python_cmd"], "-c", script))
|
|
module_ext = info["EXT_SUFFIX"]
|
|
inc = info["INCLUDEPY"]
|
|
pylib = info.get("LDLIBRARY")
|
|
prefix = info["prefix"]
|
|
py_version_short = parse_version(info["py_version_short"])
|
|
py_version_full = parse_version(info["py_version"])
|
|
py_version_nodot = info['py_version_nodot']
|
|
numpy_include = info["numpy_include"]
|
|
localenv.Prepend(CPPPATH=[Dir('#include'), inc, numpy_include])
|
|
localenv.Prepend(LIBS=localenv['cantera_libs'])
|
|
|
|
# Fix the module extension for Windows from the sysconfig library.
|
|
# See https://github.com/python/cpython/pull/22088 and
|
|
# https://bugs.python.org/issue39825
|
|
if (
|
|
py_version_full < parse_version("3.8.7")
|
|
and localenv["OS"] == "Windows"
|
|
and module_ext == ".pyd"
|
|
):
|
|
module_ext = f".cp{py_version_nodot}-{info['plat'].replace('-', '_')}.pyd"
|
|
|
|
# Don't print deprecation warnings for internal Python changes.
|
|
# Only applies to Python 3.8. The field that is deprecated in Python 3.8
|
|
# and causes the warnings to appear will be removed in Python 3.9 so no
|
|
# further warnings should be issued.
|
|
if localenv["HAS_CLANG"] and py_version_short == parse_version("3.8"):
|
|
localenv.Append(CXXFLAGS='-Wno-deprecated-declarations')
|
|
|
|
if "icc" in localenv["CC"]:
|
|
localenv.Append(CPPDEFINES={"CYTHON_FALLTHROUGH":" __attribute__((fallthrough))"})
|
|
|
|
if localenv['OS'] == 'Darwin':
|
|
localenv.Append(LINKFLAGS='-undefined dynamic_lookup')
|
|
elif localenv['OS'] == 'Windows':
|
|
localenv.Append(LIBPATH=prefix+'/libs')
|
|
if localenv['toolchain'] == 'mingw':
|
|
localenv.Append(LIBS=f"python{py_version_nodot}")
|
|
if localenv['OS_BITS'] == 64:
|
|
localenv.Append(CPPDEFINES='MS_WIN64')
|
|
# Fix for https://bugs.python.org/issue11566. Fixed in 3.7.3 and higher.
|
|
# See https://github.com/python/cpython/pull/11283
|
|
if py_version_full < parse_version("3.7.3"):
|
|
localenv.Append(CPPDEFINES={"_hypot": "hypot"})
|
|
elif localenv['OS'] == 'Cygwin':
|
|
# extract 'pythonX.Y' from 'libpythonX.Y.dll.a'
|
|
localenv.Append(LIBS=pylib[3:-6])
|
|
|
|
localenv["module_ext"] = module_ext
|
|
setup_cfg = localenv.SubstFile("setup.cfg", "setup.cfg.in")
|
|
readme = localenv.Command("README.rst", "#README.rst", Copy("$TARGET", "$SOURCE"))
|
|
license = localenv.Command("LICENSE.txt", "#build/ext/LICENSE.txt",
|
|
Copy("$TARGET", "$SOURCE"))
|
|
localenv.Depends(license, localenv["license_target"])
|
|
|
|
# Build the Python module
|
|
obj = localenv.SharedObject('#build/temp-py/_cantera', 'cantera/_cantera.cpp')
|
|
ext = localenv.LoadableModule(f"cantera/_cantera{module_ext}",
|
|
obj, LIBPREFIX="", SHLIBSUFFIX=module_ext,
|
|
SHLIBPREFIX="", LIBSUFFIXES=[module_ext])
|
|
|
|
build_cmd = ("$python_cmd_esc -m pip wheel --no-build-isolation --no-deps "
|
|
"--wheel-dir=build/python/dist build/python")
|
|
wheel_name = (f"Cantera-{env['cantera_version']}-cp{py_version_nodot}"
|
|
f"-cp{py_version_nodot}-{info['plat'].replace('-', '_')}.whl")
|
|
mod = build(localenv.Command(f"#build/python/dist/{wheel_name}", "setup.cfg", build_cmd))
|
|
env['python_module'] = mod
|
|
env['python_extension'] = ext
|
|
|
|
localenv.Depends(mod, [ext, dataFiles, testFiles, setup_cfg, readme, license,
|
|
"setup.py", "pyproject.toml"])
|
|
localenv.Depends(ext, localenv['cantera_staticlib'])
|
|
|
|
for f in (multi_glob(localenv, 'cantera', 'py') +
|
|
multi_glob(localenv, 'cantera/*', 'py') +
|
|
multi_glob(localenv, 'cantera/*/*', 'py')):
|
|
localenv.Depends(mod, f)
|
|
|
|
# Determine installation path and install the Python module
|
|
install_cmd = ["$python_cmd_esc", "-m", "pip", "install"]
|
|
|
|
|
|
def get_install_location(
|
|
user: bool = False,
|
|
prefix: str | None = None,
|
|
root: str | None = None
|
|
) -> dict[str, str]:
|
|
"""Determine the location where pip will install files.
|
|
|
|
This relies on pip's internal API so it may break in future versions.
|
|
Unfortunately, I don't really see another way to determine this information
|
|
reliably.
|
|
"""
|
|
# These need to be quoted if they're not None, even if they're a falsey value
|
|
# like the empty string. Otherwise, we want the literal None value.
|
|
prefix = quoted(prefix) if prefix is not None else None
|
|
root = quoted(root) if root is not None else None
|
|
install_script = dedent(f"""
|
|
from pip import __version__ as pip_version
|
|
from pkg_resources import parse_version
|
|
import pip
|
|
import json
|
|
pip_version = parse_version(pip_version)
|
|
if pip_version < parse_version("10.0.0"):
|
|
from pip.locations import distutils_scheme
|
|
scheme = distutils_scheme("Cantera", user={user}, root={root},
|
|
prefix={prefix})
|
|
else:
|
|
from pip._internal.locations import get_scheme
|
|
scheme = get_scheme("Cantera", user={user}, root={root},
|
|
prefix={prefix})
|
|
|
|
if not isinstance(scheme, dict):
|
|
scheme = {{k: getattr(scheme, k) for k in dir(scheme)
|
|
if not k.startswith("_")}}
|
|
scheme["pip"] = pip.__file__
|
|
print(json.dumps(scheme))
|
|
""")
|
|
return json.loads(get_command_output(localenv["python_cmd"], "-c", install_script))
|
|
|
|
|
|
user_install = False
|
|
python_prefix = None
|
|
python_root = None
|
|
if localenv['python_prefix'] == 'USER':
|
|
# Install to the OS-dependent user site-packages directory
|
|
install_cmd.append("--user")
|
|
user_install = True
|
|
elif localenv['python_prefix']:
|
|
install_cmd.append(f"--prefix={localenv.subst('python_prefix')}")
|
|
python_prefix = localenv.subst("python_prefix")
|
|
|
|
if env["stage_dir"]:
|
|
# Get the absolute path to the stage directory. If the stage directory is a relative
|
|
# path, consider it to be relative to the root of the Cantera source directory.
|
|
stage_dir = Path(env["stage_dir"])
|
|
if not stage_dir.is_absolute():
|
|
stage_dir = Path(Dir("#").abspath) / stage_dir
|
|
|
|
install_cmd.append(f"--root={stage_dir.resolve()}")
|
|
python_root = stage_dir.resolve()
|
|
|
|
install_cmd.extend(("--no-build-isolation", "--no-deps", "-v", "--force-reinstall", "build/python"))
|
|
if localenv['PYTHON_INSTALLER'] == 'direct':
|
|
mod_inst = install(localenv.Command, 'dummy', mod,
|
|
" ".join(install_cmd))
|
|
env["install_python_action"] = mod_inst
|
|
install_locs = get_install_location(user_install, python_prefix, python_root)
|
|
env["python_module_loc"] = install_locs["platlib"]
|
|
env["ct_pyscriptdir"] = install_locs["scripts"]
|
|
elif localenv['PYTHON_INSTALLER'] == 'debian':
|
|
install(localenv.Command, 'dummy', mod,
|
|
'cd build/python && '
|
|
'$python_cmd_esc setup.py build --build-lib=. '
|
|
'install --install-layout=deb --no-compile --root=${python_prefix}')
|
|
env["python_module_loc"] = "<unspecified>"
|