Thanks to visit codestin.com
Credit goes to github.com

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
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions doc/release/upcoming_changes/24532.new_feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
``meson`` backend for ``f2py``
------------------------------
``f2py`` in compile mode (i.e. ``f2py -c``) now accepts the ``--backend meson`` option. This is the default option
for Python ``3.12`` on-wards. Older versions will still default to ``--backend
distutils``.

To support this in realistic use-cases, in compile mode ``f2py`` takes a
``--dep`` flag one or many times which maps to ``dependency()`` calls in the
``meson`` backend, and does nothing in the ``distutils`` backend.


There are no changes for users of ``f2py`` only as a code generator, i.e. without ``-c``.
8 changes: 4 additions & 4 deletions numpy/distutils/command/build_src.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ def f2py_sources(self, sources, extension):
if (self.force or newer_group(depends, target_file, 'newer')) \
and not skip_f2py:
log.info("f2py: %s" % (source))
import numpy.f2py
numpy.f2py.run_main(f2py_options
from numpy.f2py import f2py2e
f2py2e.run_main(f2py_options
+ ['--build-dir', target_dir, source])
else:
log.debug(" skipping '%s' f2py interface (up-to-date)" % (source))
Expand All @@ -558,8 +558,8 @@ def f2py_sources(self, sources, extension):
and not skip_f2py:
log.info("f2py:> %s" % (target_file))
self.mkpath(target_dir)
import numpy.f2py
numpy.f2py.run_main(f2py_options + ['--lower',
from numpy.f2py import f2py2e
f2py2e.run_main(f2py_options + ['--lower',
'--build-dir', target_dir]+\
['-m', ext_name]+f_sources)
else:
Expand Down
9 changes: 9 additions & 0 deletions numpy/f2py/_backends/__init__.py
Original file line number Diff line number Diff line change
@@ -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}")
46 changes: 46 additions & 0 deletions numpy/f2py/_backends/_backend.py
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions numpy/f2py/_backends/_distutils.py
Original file line number Diff line number Diff line change
@@ -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(
"distutils has been deprecated since NumPy 1.26."
"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)
157 changes: 157 additions & 0 deletions numpy/f2py/_backends/_meson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from __future__ import annotations

import errno
import shutil
import subprocess
from pathlib import Path

from ._backend import Backend
from string import Template

import warnings


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

def __init__(
self,
modulename: str,
sources: list[Path],
deps: list[str],
object_files: list[Path],
linker_args: list[str],
c_args: list[str],
build_type: str,
):
self.modulename = modulename
self.build_template_path = (
Path(__file__).parent.absolute() / "meson.build.template"
)
self.sources = sources
self.deps = deps
self.substitutions = {}
self.objects = object_files
self.pipeline = [
self.initialize_template,
self.sources_substitution,
self.deps_substitution,
]
self.build_type = build_type

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

def sources_substitution(self) -> None:
indent = " " * 21
self.substitutions["source_list"] = f",\n{indent}".join(
[f"'{source}'" for source in self.sources]
)

def deps_substitution(self) -> None:
indent = " " * 21
self.substitutions["dep_list"] = f",\n{indent}".join(
[f"dependency('{dep}')" for dep in self.deps]
)

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


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"
)

def _move_exec_to_root(self, build_dir: Path):
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())

def _get_build_command(self):
return [
"meson",
"setup",
self.meson_build_dir,
]

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.extra_objects,
self.flib_flags,
self.fc_flags,
self.build_type,
)
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_meson(self, build_dir: Path):
completed_process = subprocess.run(self._get_build_command(), cwd=build_dir)
if completed_process.returncode != 0:
raise subprocess.CalledProcessError(
completed_process.returncode, completed_process.args
)
completed_process = subprocess.run(
["meson", "compile", "-C", self.meson_build_dir], cwd=build_dir
)
if completed_process.returncode != 0:
raise subprocess.CalledProcessError(
completed_process.returncode, completed_process.args
)

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(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
42 changes: 42 additions & 0 deletions numpy/f2py/_backends/meson.build.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
project('${modulename}',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting in this file is a little off - may be worth fixing in a next PR. Normal indentation is 2 or 4 spaces for function calls like project(...) or py.extension_module(...).

['c', 'fortran'],
version : '0.1',
meson_version: '>= 1.1.0',
default_options : [
'warning_level=1',
'buildtype=${buildtype}'
])

py = import('python').find_installation(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)

py.extension_module('${modulename}',
[
${source_list},
fortranobject_c
],
include_directories: [inc_np],
dependencies : [
py_dep,
${dep_list}
],
install : true)
Loading