"""Cython-based Python Module""" import re from pathlib import Path from buildutils import * Import('env', 'build', 'install') localenv = env.Clone() dataFiles = localenv.RecursiveInstall("cantera/data", "#build/data") build(dataFiles) # Install Python samples install(localenv.RecursiveInstall, "$inst_sampledir/python", "#samples/python") setup_python_env(localenv) # Python module shouldn't explicitly link to Python library (added in other cases to # support Python-based extensions), except when using MinGW if localenv["toolchain"] != "mingw": localenv["LIBS"] = [lib for lib in localenv["LIBS"] if not lib.startswith("python")] 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"]) directives = {"binding": True} if env["coverage"]: directives["linetrace"] = True localenv.Append(CPPDEFINES={"CYTHON_TRACE": 1}) # Build the Python module cython_obj = [] for pyxfile in multi_glob(localenv, "cantera", "pyx"): cythonized = localenv.Command( f"cantera/{pyxfile.name.replace('.pyx', '.cpp')}", pyxfile, f'''${{python_cmd}} -c "import Cython.Build; Cython.Build.cythonize(r'${{SOURCE}}', compiler_directives={directives!r})"''' ) for pxd in multi_glob(localenv, "cantera", "pxd"): localenv.Depends(cythonized, pxd) obj = localenv.SharedObject( f"#build/temp-py/{pyxfile.name.split('.')[0]}", cythonized) cython_obj.append(obj) cython_obj.extend(env['python_ext_objects']) module_ext = localenv["py_module_ext"] ext = localenv.LoadableModule(f"cantera/_cantera{module_ext}", cython_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") wheel_name = ("Cantera-${cantera_version}-cp${py_version_nodot}" "-cp${py_version_nodot}-${py_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, setup_cfg, readme, license, "setup.py", "pyproject.toml", "cantera/test/README.txt", "cantera/examples/README.txt"]) if env['OS'] == 'Windows': # On Windows, the cantera library directory is likely not to be on the path. # However, Windows does search the directory containing a library (i.e. the # Python extension module) for DLL dependencies. dll = [f for f in localenv['cantera_shlib'] if f.name.endswith('.dll')][0] copy_dll = localenv.Command(f'cantera/{dll.name}', dll, Copy("$TARGET", "$SOURCE")) localenv.Depends(ext, copy_dll) else: localenv.Depends(ext, localenv['cantera_shlib']) for f in (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"] # Check for existing Python module installation. Allow pip to remove an existing # installation only if we're installing to the same location. Also disable # uninstallation if we're installing to a staging directory. if env["stage_dir"]: install_cmd.append("--ignore-installed") else: info = get_command_output(localenv["python_cmd"], "-m", "pip", "show", "cantera", ignore_errors=True) if user_install: test_prefix = Path(localenv["user_site_packages"]).parents[2] elif python_prefix is None: test_prefix = Path(localenv["site_packages"][0]).parents[2] else: test_prefix = Path(python_prefix) match = re.search(r"Location: (.*)\n", info, re.MULTILINE) existing_prefix = Path(match.group(1)).parents[2] if match else None if existing_prefix and existing_prefix != test_prefix: install_cmd.append("--ignore-installed") 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"] = Path(install_locs["platlib"]).as_posix() env["ct_pyscriptdir"] = Path(install_locs["scripts"]).as_posix() 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"] = ""