"""Cython-based Python Module""" import re from pathlib import Path from pkg_resources import parse_version import json from buildutils import * 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) # This must be the path to the real pxd file, not a file node pointing at the # possibly non-existent file in the build directory pxd_file = File("#interfaces/cython/cantera/_cantera.pxd").abspath for line in Path(pxd_file).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 -v --no-build-isolation --no-deps " "--wheel-dir=build/python/dist build/python") plat = info['plat'].replace('-', '_').replace('.', '_') wheel_name = (f"Cantera-{env['cantera_version']}-cp{py_version_nodot}" f"-cp{py_version_nodot}-{plat}.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"] user_install = False python_prefix = 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"]: # A specific location for the Cantera python module has been given install_cmd.append(f"--prefix={localenv.subst('$python_prefix')}") python_prefix = localenv.subst("$python_prefix") elif not env["default_prefix"]: install_cmd.append(f"--prefix={env['prefix']}") python_prefix = env["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()}") 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_pip_install_location(localenv["python_cmd"], user_install, python_prefix) 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"] = ""