8000 ENH: ``meson`` backend for ``f2py`` by HaoZeke · Pull Request #24532 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

ENH: meson backend for f2py #24532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Sep 5, 2023
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fe49281
FIX: Import f2py2e rather than f2py for run_main
NamamiShanker Jul 11, 2022
6b1475e
FIX: Import f2py2e instead of f2py
NamamiShanker Jul 12, 2022
91f3825
ENH: Add F2PY back-end work from gh-22225
HaoZeke Aug 24, 2023
a0b01c3
ENH: Add meson skeleton from gh-2225
HaoZeke Aug 24, 2023
384aa96
MAINT: Trim backend.py down to f2py2e flags
HaoZeke Aug 25, 2023
26b224f
ENH: Add a factory function for backends
HaoZeke Aug 25, 2023
3a5c43d
ENH: Add a distutils backend
HaoZeke Aug 25, 2023
d03d28d
ENH: Handle --backends in f2py
HaoZeke Aug 25, 2023
1d279bc
DOC: Add some minor comments in f2py2e
HaoZeke Aug 25, 2023
0f0e3ac
MAINT: Refactor and rework meson.build.src
HaoZeke Aug 25, 2023
323fa5b
MAINT: Add objects
HaoZeke Aug 25, 2023
8d4368b
MAINT: Cleanup distutils backend
HaoZeke Aug 25, 2023
7bd8c34
MAINT: Refactor to add everything back to backend
HaoZeke Aug 25, 2023
d7a1ea9
MAINT: Fix overly long line
HaoZeke Aug 25, 2023
a95e71a
BUG: Construct wrappers for meson backend
HaoZeke Aug 25, 2023
0621032
MAINT: Rework, simplify template massively
HaoZeke Aug 25, 2023
677e42a
ENH: Truncate meson.build to skeleton only
HaoZeke Aug 25, 2023
2517afe
MAINT: Minor backend housekeeping, name changes
HaoZeke Aug 25, 2023
1dc2733
MAINT: Less absolute paths, update setup.py [f2py]
HaoZeke Aug 25, 2023
42a15ac
MAINT: Move f2py module name functionality
HaoZeke Aug 25, 2023
6776dba
ENH: Handle .pyf files
HaoZeke Aug 25, 2023
0357ab8
TST: Fix typo in isoFortranEnvMap.f90
HaoZeke Aug 25, 2023
5faec2f
MAINT: Typo in f2py2e support for pyf files
HaoZeke Aug 25, 2023
5b79487
DOC: Add release note for --backend
HaoZeke Aug 25, 2023
faadb6d
MAINT: Conditional switch for Python 3.12 [f2py]
HaoZeke Aug 25, 2023
82a4f8f
MAINT: No absolute paths in backend [f2py-meson]
HaoZeke Aug 25, 2023
6585458
MAINT: Prettier generated meson.build files [f2py]
HaoZeke Aug 25, 2023
f85581e
ENH: Add meson's dependency(blah) to f2py
HaoZeke Aug 25, 2023
16f22ee
DOC: Document the new flag
HaoZeke Aug 25, 2023
c0c6bf1
MAINT: Simplify and rename backend template [f2py]
HaoZeke Aug 26, 2023
fa06f8d
ENH: Support build_type via --debug [f2py-meson]
HaoZeke Aug 26, 2023
8841647
MAINT,DOC: Reduce warn,rework doc [f2py-meson]
HaoZeke Aug 26, 2023
8f214a0
ENH: Rework deps: to --dep calls [f2py-meson]
HaoZeke Aug 26, 2023
bc37684
MAINT,DOC: Add --backend to argparse, add docs
HaoZeke Aug 26, 2023
4e3336b
MAINT: Rename meson template [f2py-meson]
HaoZeke Aug 26, 2023
518074e
MAINT: Add meson.build for f2py
HaoZeke Aug 29, 2023
cc92d2c
BLD: remove duplicate f2py handling in meson.build files
rgommers Sep 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ENH: Truncate meson.build to skeleton only
  • Loading branch information
HaoZeke committed Sep 2, 2023
commit 677e42a50efe28cf6b26f4e2b6127ddb4deb3df7
293 changes: 61 additions & 232 deletions numpy/f2py/backends/meson_backend.py
8000
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import os
import errno
import shutil
import subprocess
Expand All @@ -9,228 +8,71 @@
from .backend import Backend
from string import Template

import warnings


class MesonTemplate:
"""Template meson build file generation class."""

def __init__(
self,
module_name: str,
numpy_install_path,
numpy_get_include: Path,
f2py_get_include: Path,
wrappers: list[Path],
fortran_sources: list[Path],
dependencies: list[str],
include_path: list[Path],
optimization_flags: list[str],
architecture_flags: list[str],
f77_flags: list[str],
f90_flags: list[str],
linker_libpath: list[Path],
linker_libname: list[str],
define_macros: list[tuple[str, str]],
undef_macros: list[str],
modulename: str,
sources: list[Path],
object_files: list[Path],
linker_args: list[str],
c_args: list[str],
):
self.module_name = module_name
self.numpy_install_path = numpy_install_path
self.modulename = modulename
self.build_template_path = (
numpy_install_path / "f2py" / "backends" / "src" / "meson.build.src"
Path(__file__).parent.absolute() / "src" / "meson.build.src"
)
self.sources = fortran_sources
self.numpy_get_include = numpy_get_include
self.f2py_get_include = f2py_get_include
self.wrappers = wrappers
self.dependencies = dependencies
self.include_directories = include_path
self.sources = sources
self.substitutions = {}
self.optimization_flags = optimization_flags
self.architecture_flags = architecture_flags
self.fortran_flags = f77_flags + f90_flags
self.linker_libpath = linker_libpath
self.linker_libname = linker_libname
self.define_macros = define_macros
self.undef_macros = undef_macros
self.objects = object_files
self.pipeline = [
self.initialize_template,
self.global_flags_substitution,
self.sources_substitution,
self.dependencies_substitution,
self.include_directories_subtitution,
self.linker_substitution,
self.macros_substitution,
]

@property
def meson_build_template(self) -> str:
if not self.build_template_path.is_file():
raise FileNotFoundError(
errno.ENOENT,
f"Meson build template {self.build_template_path.absolute()} does not exist.",
"Meson build template"
f" {self.build_template_path.absolute()}"
" does not exist.",
)
return self.build_template_path.read_text()

def initialize_template(self) -> None:
"""Initialize with module name and external NumPy and F2PY C libraries."""
self.substitutions["modulename"] = self.module_name
self.substitutions["numpy_get_include"] = self.numpy_get_include.absolute()
self.substitutions["f2py_get_include"] = self.f2py_get_include.absolute()
"""Initialize with module name."""
self.substitutions["modulename"] = self.modulename

def sources_substitution(self) -> None:
self.substitutions["source_list"] = ",".join(
["'" + str(source.absolute()) + "'" for source in self.sources]
)
self.substitutions["wrappers"] = ",".join(
["'" + str(wrapper.absolute()) + "'" for wrapper in self.wrappers]
)

def dependencies_substitution(self) -> None:
self.substitutions["dependencies_list"] = ", ".join(
[f"dependency('{dependecy}')" for dependecy in self.dependencies]
)

def include_directories_subtitution(self) -> None:
self.substitutions["include_directories_list"] = ", ".join(
[
f"include_directories('{include_directory}')"
for include_directory in self.include_directories
]
)

def global_flags_substitution(self) -> None:
fortran_compiler_flags = (
self.fortran_flags + self.optimization_flags + self.architecture_flags
self.substitutions["source_list"] = ", ".join(
[f"'{source}'" for source in self.sources]
)
c_compiler_flags = self.optimization_flags + self.architecture_flags
self.substitutions["fortran_global_args"] = fortran_compiler_flags
self.substitutions["c_global_args"] = c_compiler_flags

def macros_substitution(self) -> None:
self.substitutions["macros"] = ""
if self.define_macros:
self.substitutions["macros"] = ",".join(
f"'-D{macro[0]}={macro[1]}'" if macro[1] else f"-D{macro[0]}"
for macro in self.define_macros
)
if self.undef_macros:
self.substitutions["macros"] += "," + ",".join(
f"'-U{macro}'" for macro in self.undef_macros
)

def linker_substitution(self) -> None:
self.substitutions["linker_args"] = ""
if self.linker_libpath:
linker_libpath_subs = ",".join(
f"-L{libpath}" for libpath in self.linker_libpath
)
self.substitutions["linker_args"] += linker_libpath_subs
if self.linker_libname:
linker_libname_subs = ",".join(
f"-l{libname}" for libname in self.linker_libname
)
self.substitutions["linker_args"] += f",{linker_libname_subs}"

def generate_meson_build(self) -> str:
def generate_meson_build(self):
for node in self.pipeline:
node()
template = Template(self.meson_build_template)
template = Template(self.meson_build_template())
return template.substitute(self.substitutions)


class MesonBackend(Backend):
def __init__(
self,
module_name: str = "untitled",
fortran_compiler: str = None,
c_compiler: str = None,
f77exec: Path = None,
f90exec: Path = None,
f77_flags: list[str] = None,
f90_flags: list[str] = None,
include_paths: list[Path] = None,
include_dirs: list[Path] = None,
external_resources: list[str] = None,
linker_libpath: list[Path] = None,
linker_libname: list[str] = None,
define_macros: list[tuple[str, str]] = None,
undef_macros: list[str] = None,
debug: bool = False,
opt_flags: list[str] = None,
arch_flags: list[str] = None,
no_opt: bool = False,
no_arch: bool = False,
) -> None:
self.meson_build_dir = "builddir"
if f77_flags is None:
f77_flags = []
if include_paths is None:
include_paths = []
if include_dirs is None:
include_dirs = []
if external_resources is None:
external_resources = []
if linker_libpath is None:
linker_libpath = []
if linker_libname is None:
linker_libname = []
if define_macros is None:
define_macros = []
if undef_macros is None:
undef_macros = []
if f77_flags is None:
f77_flags = []
if f90_flags is None:
f90_flags = []
if opt_flags is None:
opt_flags = []
if arch_flags is None:
arch_flags = []
super().__init__(
module_name,
fortran_compiler,
c_compiler,
f77exec,
f90exec,
f77_flags,
f90_flags,
include_paths,
include_dirs,
external_resources,
linker_libpath,
linker_libname,
define_macros,
undef_macros,
debug,
opt_flags,
arch_flags,
no_opt,
no_arch,
def __init__(self, *args, **kwargs):
warnings.warn(
"The MesonBackend is experimental and may change in the future."
"--build-dir should be used to customize the generated skeleton.",
stacklevel=2,
)

self.wrappers: list[Path] = []
self.fortran_sources: list[Path] = []
self.template = Template(self.fortran_sources)

def _get_optimization_level(self):
if self.no_arch and not self.no_opt:
return 2
elif self.no_opt:
return 0
return 3

def _set_environment_variables(self) -> None:
if self.fortran_compiler:
os.putenv("FC", self.fortran_compiler)
elif self.f77exec:
os.putenv("FC", self.f77exec)
elif self.f90exec:
os.putenv("FC", self.f90exec)
if self.c_compiler:
os.putenv("CC", self.c_compiler)
super().__init__(*args, **kwargs)
self.meson_build_dir = "bbdir"

def _move_exec_to_root(self, build_dir: Path):
walk_dir = build_dir / self.meson_build_dir
path_objects = walk_dir.glob(f"{self.module_name}*.so")
walk_dir = Path(build_dir) / self.meson_build_dir
path_objects = walk_dir.glob(f"{self.modulename}*.so")
for path_object in path_objects:
shutil.move(path_object, Path.cwd())

Expand All @@ -239,49 +81,24 @@ def _get_build_command(self):
"meson",
"setup",
self.meson_build_dir,
"-Ddebug=true" if self.debug else "-Ddebug=false",
f"-Doptimization={str(self._get_optimization_level())}",
]

def load_wrapper(self, wrappers: list[Path]) -> None:
self.wrappers = wrappers

def load_sources(self, fortran_sources: list[Path]) -> None:
for fortran_source in fortran_sources:
fortran_source = Path(fortran_source)
if not fortran_source.is_file():
raise FileNotFoundError(
errno.ENOENT, f"{fortran_source.absolute()} does not exist."
)
self.fortran_sources.append(fortran_source)

def write_meson_build(self, build_dir: Path) -> None:
"""Writes the meson build file at specified location"""
meson_template = MesonTemplate(
self.module_name,
super().numpy_install_path(),
self.numpy_get_include(),
self.f2py_get_include(),
self.wrappers,
self.fortran_sources,
self.external_resources,
self.include_paths + self.include_dirs,
optimization_flags=self.opt_flags,
architecture_flags=self.arch_flags,
f77_flags=self.f77_flags,
f90_flags=self.f90_flags,
linker_libpath=self.linker_libpath,
linker_libname=self.linker_libname,
define_macros=self.define_macros,
undef_macros=self.undef_macros,
self.modulename,
self.sources,
self.extra_objects,
self.flib_flags,
self.fc_flags,
)
src = meson_template.generate_meson_build()
meson_build_file = build_dir / "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_meson(self, build_dir: Path):
self._set_environment_variables()
completed_process = subprocess.run(self._get_build_command(), cwd=build_dir)
if completed_process.returncode != 0:
raise subprocess.CalledProcessError(
Expand All @@ -295,16 +112,28 @@ def run_meson(self, build_dir: Path):
completed_process.returncode, completed_process.args
)

def compile(
self,
f77_sources: list[Path],
f90_sources: list[Path],
object_files: list[Path],
wrappers: list[Path],
build_dir: Path,
) -> None:
self.load_wrapper(wrappers)
self.load_sources(f77_sources + f90_sources + object_files)
self.write_meson_build(build_dir)
self.run_meson(build_dir)
self._move_exec_to_root(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:
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(bdir / generated_source.name)
generated_source.unlink()
return extended_sources
0