206 lines
6.8 KiB
Python
206 lines
6.8 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
import os
|
||
|
import errno
|
||
|
import shutil
|
||
|
import subprocess
|
||
|
import sys
|
||
|
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],
|
||
|
c_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
|
||
|
self.pipeline = [
|
||
|
self.initialize_template,
|
||
|
self.sources_substitution,
|
||
|
self.deps_substitution,
|
||
|
self.include_substitution,
|
||
|
self.libraries_substitution,
|
||
|
]
|
||
|
self.build_type = build_type
|
||
|
self.python_exe = python_exe
|
||
|
|
||
|
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:
|
||
|
indent = " " * 21
|
||
|
self.substitutions["source_list"] = f",\n{indent}".join(
|
||
|
[f"{indent}'{source}'" for source in self.sources]
|
||
|
)
|
||
|
|
||
|
def deps_substitution(self) -> None:
|
||
|
indent = " " * 21
|
||
|
self.substitutions["dep_list"] = f",\n{indent}".join(
|
||
|
[f"{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} = declare_dependency(link_args : ['-l{lib}'])"
|
||
|
for lib in self.libraries
|
||
|
]
|
||
|
)
|
||
|
|
||
|
indent = " " * 21
|
||
|
self.substitutions["lib_list"] = f"\n{indent}".join(
|
||
|
[f"{indent}{lib}," for lib in self.libraries]
|
||
|
)
|
||
|
self.substitutions["lib_dir_list"] = f"\n{indent}".join(
|
||
|
[f"{indent}lib_dir_{i}," for i in range(len(self.library_dirs))]
|
||
|
)
|
||
|
|
||
|
def include_substitution(self) -> None:
|
||
|
indent = " " * 21
|
||
|
self.substitutions["inc_list"] = f",\n{indent}".join(
|
||
|
[f"{indent}'{inc}'" for inc in self.include_dirs]
|
||
|
)
|
||
|
|
||
|
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 = 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
|