-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
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
ENH: meson
backend for f2py
#24532
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 6b1475e
FIX: Import f2py2e instead of f2py
NamamiShanker 91f3825
ENH: Add F2PY back-end work from gh-22225
HaoZeke a0b01c3
ENH: Add meson skeleton from gh-2225
HaoZeke 384aa96
MAINT: Trim backend.py down to f2py2e flags
HaoZeke 26b224f
ENH: Add a factory function for backends
HaoZeke 3a5c43d
ENH: Add a distutils backend
HaoZeke d03d28d
ENH: Handle --backends in f2py
HaoZeke 1d279bc
DOC: Add some minor comments in f2py2e
HaoZeke 0f0e3ac
MAINT: Refactor and rework meson.build.src
HaoZeke 323fa5b
MAINT: Add objects
HaoZeke 8d4368b
MAINT: Cleanup distutils backend
HaoZeke 7bd8c34
MAINT: Refactor to add everything back to backend
HaoZeke d7a1ea9
MAINT: Fix overly long line
HaoZeke a95e71a
BUG: Construct wrappers for meson backend
HaoZeke 0621032
MAINT: Rework, simplify template massively
HaoZeke 677e42a
ENH: Truncate meson.build to skeleton only
HaoZeke 2517afe
MAINT: Minor backend housekeeping, name changes
HaoZeke 1dc2733
MAINT: Less absolute paths, update setup.py [f2py]
HaoZeke 42a15ac
MAINT: Move f2py module name functionality
HaoZeke 6776dba
ENH: Handle .pyf files
HaoZeke 0357ab8
TST: Fix typo in isoFortranEnvMap.f90
HaoZeke 5faec2f
MAINT: Typo in f2py2e support for pyf files
HaoZeke 5b79487
DOC: Add release note for --backend
HaoZeke faadb6d
MAINT: Conditional switch for Python 3.12 [f2py]
HaoZeke 82a4f8f
MAINT: No absolute paths in backend [f2py-meson]
HaoZeke 6585458
MAINT: Prettier generated meson.build files [f2py]
HaoZeke f85581e
ENH: Add meson's dependency(blah) to f2py
HaoZeke 16f22ee
DOC: Document the new flag
HaoZeke c0c6bf1
MAINT: Simplify and rename backend template [f2py]
HaoZeke fa06f8d
ENH: Support build_type via --debug [f2py-meson]
HaoZeke 8841647
MAINT,DOC: Reduce warn,rework doc [f2py-meson]
HaoZeke 8f214a0
ENH: Rework deps: to --dep calls [f2py-meson]
HaoZeke bc37684
MAINT,DOC: Add --backend to argparse, add docs
HaoZeke 4e3336b
MAINT: Rename meson template [f2py-meson]
HaoZeke 518074e
MAINT: Add meson.build for f2py
HaoZeke cc92d2c
BLD: remove duplicate f2py handling in meson.build files
rgommers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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``. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
project('${modulename}', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
['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) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.