Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
def f2py_build_generator(name):
|
||||
if name == "meson":
|
||||
from ._meson import MesonBackend
|
||||
return MesonBackend
|
||||
elif name == "distutils":
|
||||
from ._distutils import DistutilsBackend
|
||||
return DistutilsBackend
|
||||
else:
|
||||
raise ValueError(f"Unknown backend: {name}")
|
@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Backend(ABC):
|
||||
def __init__(
|
||||
self,
|
||||
modulename,
|
||||
sources,
|
||||
extra_objects,
|
||||
build_dir,
|
||||
include_dirs,
|
||||
library_dirs,
|
||||
libraries,
|
||||
define_macros,
|
||||
undef_macros,
|
||||
f2py_flags,
|
||||
sysinfo_flags,
|
||||
fc_flags,
|
||||
flib_flags,
|
||||
setup_flags,
|
||||
remove_build_dir,
|
||||
extra_dat,
|
||||
):
|
||||
self.modulename = modulename
|
||||
self.sources = sources
|
||||
self.extra_objects = extra_objects
|
||||
self.build_dir = build_dir
|
||||
self.include_dirs = include_dirs
|
||||
self.library_dirs = library_dirs
|
||||
self.libraries = libraries
|
||||
self.define_macros = define_macros
|
||||
self.undef_macros = undef_macros
|
||||
self.f2py_flags = f2py_flags
|
||||
self.sysinfo_flags = sysinfo_flags
|
||||
self.fc_flags = fc_flags
|
||||
self.flib_flags = flib_flags
|
||||
self.setup_flags = setup_flags
|
||||
self.remove_build_dir = remove_build_dir
|
||||
self.extra_dat = extra_dat
|
||||
|
||||
@abstractmethod
|
||||
def compile(self) -> None:
|
||||
"""Compile the wrapper."""
|
||||
pass
|
@ -0,0 +1,75 @@
|
||||
from ._backend import Backend
|
||||
|
||||
from numpy.distutils.core import setup, Extension
|
||||
from numpy.distutils.system_info import get_info
|
||||
from numpy.distutils.misc_util import dict_append
|
||||
from numpy.exceptions import VisibleDeprecationWarning
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import warnings
|
||||
|
||||
|
||||
class DistutilsBackend(Backend):
|
||||
def __init__(sef, *args, **kwargs):
|
||||
warnings.warn(
|
||||
"\ndistutils has been deprecated since NumPy 1.26.x\n"
|
||||
"Use the Meson backend instead, or generate wrappers"
|
||||
" without -c and use a custom build script",
|
||||
VisibleDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def compile(self):
|
||||
num_info = {}
|
||||
if num_info:
|
||||
self.include_dirs.extend(num_info.get("include_dirs", []))
|
||||
ext_args = {
|
||||
"name": self.modulename,
|
||||
"sources": self.sources,
|
||||
"include_dirs": self.include_dirs,
|
||||
"library_dirs": self.library_dirs,
|
||||
"libraries": self.libraries,
|
||||
"define_macros": self.define_macros,
|
||||
"undef_macros": self.undef_macros,
|
||||
"extra_objects": self.extra_objects,
|
||||
"f2py_options": self.f2py_flags,
|
||||
}
|
||||
|
||||
if self.sysinfo_flags:
|
||||
for n in self.sysinfo_flags:
|
||||
i = get_info(n)
|
||||
if not i:
|
||||
print(
|
||||
f"No {repr(n)} resources found"
|
||||
"in system (try `f2py --help-link`)"
|
||||
)
|
||||
dict_append(ext_args, **i)
|
||||
|
||||
ext = Extension(**ext_args)
|
||||
|
||||
sys.argv = [sys.argv[0]] + self.setup_flags
|
||||
sys.argv.extend(
|
||||
[
|
||||
"build",
|
||||
"--build-temp",
|
||||
self.build_dir,
|
||||
"--build-base",
|
||||
self.build_dir,
|
||||
"--build-platlib",
|
||||
".",
|
||||
"--disable-optimization",
|
||||
]
|
||||
)
|
||||
|
||||
if self.fc_flags:
|
||||
sys.argv.extend(["config_fc"] + self.fc_flags)
|
||||
if self.flib_flags:
|
||||
sys.argv.extend(["build_ext"] + self.flib_flags)
|
||||
|
||||
setup(ext_modules=[ext])
|
||||
|
||||
if self.remove_build_dir and os.path.exists(self.build_dir):
|
||||
print(f"Removing build directory {self.build_dir}")
|
||||
shutil.rmtree(self.build_dir)
|
234
lib/python3.13/site-packages/numpy/f2py/_backends/_meson.py
Normal file
234
lib/python3.13/site-packages/numpy/f2py/_backends/_meson.py
Normal file
@ -0,0 +1,234 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import errno
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from ._backend import Backend
|
||||
from string import Template
|
||||
from itertools import chain
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
class MesonTemplate:
|
||||
"""Template meson build file generation class."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
modulename: str,
|
||||
sources: list[Path],
|
||||
deps: list[str],
|
||||
libraries: list[str],
|
||||
library_dirs: list[Path],
|
||||
include_dirs: list[Path],
|
||||
object_files: list[Path],
|
||||
linker_args: list[str],
|
||||
fortran_args: list[str],
|
||||
build_type: str,
|
||||
python_exe: str,
|
||||
):
|
||||
self.modulename = modulename
|
||||
self.build_template_path = (
|
||||
Path(__file__).parent.absolute() / "meson.build.template"
|
||||
)
|
||||
self.sources = sources
|
||||
self.deps = deps
|
||||
self.libraries = libraries
|
||||
self.library_dirs = library_dirs
|
||||
if include_dirs is not None:
|
||||
self.include_dirs = include_dirs
|
||||
else:
|
||||
self.include_dirs = []
|
||||
self.substitutions = {}
|
||||
self.objects = object_files
|
||||
# Convert args to '' wrapped variant for meson
|
||||
self.fortran_args = [
|
||||
f"'{x}'" if not (x.startswith("'") and x.endswith("'")) else x
|
||||
for x in fortran_args
|
||||
]
|
||||
self.pipeline = [
|
||||
self.initialize_template,
|
||||
self.sources_substitution,
|
||||
self.deps_substitution,
|
||||
self.include_substitution,
|
||||
self.libraries_substitution,
|
||||
self.fortran_args_substitution,
|
||||
]
|
||||
self.build_type = build_type
|
||||
self.python_exe = python_exe
|
||||
self.indent = " " * 21
|
||||
|
||||
def meson_build_template(self) -> str:
|
||||
if not self.build_template_path.is_file():
|
||||
raise FileNotFoundError(
|
||||
errno.ENOENT,
|
||||
"Meson build template"
|
||||
f" {self.build_template_path.absolute()}"
|
||||
" does not exist.",
|
||||
)
|
||||
return self.build_template_path.read_text()
|
||||
|
||||
def initialize_template(self) -> None:
|
||||
self.substitutions["modulename"] = self.modulename
|
||||
self.substitutions["buildtype"] = self.build_type
|
||||
self.substitutions["python"] = self.python_exe
|
||||
|
||||
def sources_substitution(self) -> None:
|
||||
self.substitutions["source_list"] = ",\n".join(
|
||||
[f"{self.indent}'''{source}'''," for source in self.sources]
|
||||
)
|
||||
|
||||
def deps_substitution(self) -> None:
|
||||
self.substitutions["dep_list"] = f",\n{self.indent}".join(
|
||||
[f"{self.indent}dependency('{dep}')," for dep in self.deps]
|
||||
)
|
||||
|
||||
def libraries_substitution(self) -> None:
|
||||
self.substitutions["lib_dir_declarations"] = "\n".join(
|
||||
[
|
||||
f"lib_dir_{i} = declare_dependency(link_args : ['''-L{lib_dir}'''])"
|
||||
for i, lib_dir in enumerate(self.library_dirs)
|
||||
]
|
||||
)
|
||||
|
||||
self.substitutions["lib_declarations"] = "\n".join(
|
||||
[
|
||||
f"{lib.replace('.','_')} = declare_dependency(link_args : ['-l{lib}'])"
|
||||
for lib in self.libraries
|
||||
]
|
||||
)
|
||||
|
||||
self.substitutions["lib_list"] = f"\n{self.indent}".join(
|
||||
[f"{self.indent}{lib.replace('.','_')}," for lib in self.libraries]
|
||||
)
|
||||
self.substitutions["lib_dir_list"] = f"\n{self.indent}".join(
|
||||
[f"{self.indent}lib_dir_{i}," for i in range(len(self.library_dirs))]
|
||||
)
|
||||
|
||||
def include_substitution(self) -> None:
|
||||
self.substitutions["inc_list"] = f",\n{self.indent}".join(
|
||||
[f"{self.indent}'''{inc}'''," for inc in self.include_dirs]
|
||||
)
|
||||
|
||||
def fortran_args_substitution(self) -> None:
|
||||
if self.fortran_args:
|
||||
self.substitutions["fortran_args"] = (
|
||||
f"{self.indent}fortran_args: [{', '.join([arg for arg in self.fortran_args])}],"
|
||||
)
|
||||
else:
|
||||
self.substitutions["fortran_args"] = ""
|
||||
|
||||
def generate_meson_build(self):
|
||||
for node in self.pipeline:
|
||||
node()
|
||||
template = Template(self.meson_build_template())
|
||||
meson_build = template.substitute(self.substitutions)
|
||||
meson_build = re.sub(r",,", ",", meson_build)
|
||||
return meson_build
|
||||
|
||||
|
||||
class MesonBackend(Backend):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dependencies = self.extra_dat.get("dependencies", [])
|
||||
self.meson_build_dir = "bbdir"
|
||||
self.build_type = (
|
||||
"debug" if any("debug" in flag for flag in self.fc_flags) else "release"
|
||||
)
|
||||
self.fc_flags = _get_flags(self.fc_flags)
|
||||
|
||||
def _move_exec_to_root(self, build_dir: Path):
|
||||
walk_dir = Path(build_dir) / self.meson_build_dir
|
||||
path_objects = chain(
|
||||
walk_dir.glob(f"{self.modulename}*.so"),
|
||||
walk_dir.glob(f"{self.modulename}*.pyd"),
|
||||
)
|
||||
# Same behavior as distutils
|
||||
# https://github.com/numpy/numpy/issues/24874#issuecomment-1835632293
|
||||
for path_object in path_objects:
|
||||
dest_path = Path.cwd() / path_object.name
|
||||
if dest_path.exists():
|
||||
dest_path.unlink()
|
||||
shutil.copy2(path_object, dest_path)
|
||||
os.remove(path_object)
|
||||
|
||||
def write_meson_build(self, build_dir: Path) -> None:
|
||||
"""Writes the meson build file at specified location"""
|
||||
meson_template = MesonTemplate(
|
||||
self.modulename,
|
||||
self.sources,
|
||||
self.dependencies,
|
||||
self.libraries,
|
||||
self.library_dirs,
|
||||
self.include_dirs,
|
||||
self.extra_objects,
|
||||
self.flib_flags,
|
||||
self.fc_flags,
|
||||
self.build_type,
|
||||
sys.executable,
|
||||
)
|
||||
src = meson_template.generate_meson_build()
|
||||
Path(build_dir).mkdir(parents=True, exist_ok=True)
|
||||
meson_build_file = Path(build_dir) / "meson.build"
|
||||
meson_build_file.write_text(src)
|
||||
return meson_build_file
|
||||
|
||||
def _run_subprocess_command(self, command, cwd):
|
||||
subprocess.run(command, cwd=cwd, check=True)
|
||||
|
||||
def run_meson(self, build_dir: Path):
|
||||
setup_command = ["meson", "setup", self.meson_build_dir]
|
||||
self._run_subprocess_command(setup_command, build_dir)
|
||||
compile_command = ["meson", "compile", "-C", self.meson_build_dir]
|
||||
self._run_subprocess_command(compile_command, build_dir)
|
||||
|
||||
def compile(self) -> None:
|
||||
self.sources = _prepare_sources(self.modulename, self.sources, self.build_dir)
|
||||
self.write_meson_build(self.build_dir)
|
||||
self.run_meson(self.build_dir)
|
||||
self._move_exec_to_root(self.build_dir)
|
||||
|
||||
|
||||
def _prepare_sources(mname, sources, bdir):
|
||||
extended_sources = sources.copy()
|
||||
Path(bdir).mkdir(parents=True, exist_ok=True)
|
||||
# Copy sources
|
||||
for source in sources:
|
||||
if Path(source).exists() and Path(source).is_file():
|
||||
shutil.copy(source, bdir)
|
||||
generated_sources = [
|
||||
Path(f"{mname}module.c"),
|
||||
Path(f"{mname}-f2pywrappers2.f90"),
|
||||
Path(f"{mname}-f2pywrappers.f"),
|
||||
]
|
||||
bdir = Path(bdir)
|
||||
for generated_source in generated_sources:
|
||||
if generated_source.exists():
|
||||
shutil.copy(generated_source, bdir / generated_source.name)
|
||||
extended_sources.append(generated_source.name)
|
||||
generated_source.unlink()
|
||||
extended_sources = [
|
||||
Path(source).name
|
||||
for source in extended_sources
|
||||
if not Path(source).suffix == ".pyf"
|
||||
]
|
||||
return extended_sources
|
||||
|
||||
|
||||
def _get_flags(fc_flags):
|
||||
flag_values = []
|
||||
flag_pattern = re.compile(r"--f(77|90)flags=(.*)")
|
||||
for flag in fc_flags:
|
||||
match_result = flag_pattern.match(flag)
|
||||
if match_result:
|
||||
values = match_result.group(2).strip().split()
|
||||
values = [val.strip("'\"") for val in values]
|
||||
flag_values.extend(values)
|
||||
# Hacky way to preserve order of flags
|
||||
unique_flags = list(dict.fromkeys(flag_values))
|
||||
return unique_flags
|
@ -0,0 +1,55 @@
|
||||
project('${modulename}',
|
||||
['c', 'fortran'],
|
||||
version : '0.1',
|
||||
meson_version: '>= 1.1.0',
|
||||
default_options : [
|
||||
'warning_level=1',
|
||||
'buildtype=${buildtype}'
|
||||
])
|
||||
fc = meson.get_compiler('fortran')
|
||||
|
||||
py = import('python').find_installation('''${python}''', pure: false)
|
||||
py_dep = py.dependency()
|
||||
|
||||
incdir_numpy = run_command(py,
|
||||
['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
|
||||
check : true
|
||||
).stdout().strip()
|
||||
|
||||
incdir_f2py = run_command(py,
|
||||
['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
|
||||
check : true
|
||||
).stdout().strip()
|
||||
|
||||
inc_np = include_directories(incdir_numpy)
|
||||
np_dep = declare_dependency(include_directories: inc_np)
|
||||
|
||||
incdir_f2py = incdir_numpy / '..' / '..' / 'f2py' / 'src'
|
||||
inc_f2py = include_directories(incdir_f2py)
|
||||
fortranobject_c = incdir_f2py / 'fortranobject.c'
|
||||
|
||||
inc_np = include_directories(incdir_numpy, incdir_f2py)
|
||||
# gh-25000
|
||||
quadmath_dep = fc.find_library('quadmath', required: false)
|
||||
|
||||
${lib_declarations}
|
||||
${lib_dir_declarations}
|
||||
|
||||
py.extension_module('${modulename}',
|
||||
[
|
||||
${source_list},
|
||||
fortranobject_c
|
||||
],
|
||||
include_directories: [
|
||||
inc_np,
|
||||
${inc_list}
|
||||
],
|
||||
dependencies : [
|
||||
py_dep,
|
||||
quadmath_dep,
|
||||
${dep_list}
|
||||
${lib_list}
|
||||
${lib_dir_list}
|
||||
],
|
||||
${fortran_args}
|
||||
install : true)
|
Reference in New Issue
Block a user