Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_meson.py245 linesDownload Raw Back to _backends
1import errno
2import os
3import re
4import shutil
5import subprocess
6import sys
7from itertools import chain
8from pathlib import Path
9from string import Template
10
11from ._backend import Backend
12
13
14class MesonTemplate:
15    """Template meson build file generation class."""
16
17    def __init__(
18        self,
19        modulename: str,
20        sources: list[Path],
21        deps: list[str],
22        libraries: list[str],
23        library_dirs: list[Path],
24        include_dirs: list[Path],
25        object_files: list[Path],
26        linker_args: list[str],
27        fortran_args: list[str],
28        build_type: str,
29        python_exe: str,
30    ):
31        self.modulename = modulename
32        self.build_template_path = (
33            Path(__file__).parent.absolute() / "meson.build.template"
34        )
35        self.sources = sources
36        self.deps = deps
37        self.libraries = libraries
38        self.library_dirs = library_dirs
39        if include_dirs is not None:
40            self.include_dirs = include_dirs
41        else:
42            self.include_dirs = []
43        self.substitutions = {}
44        self.objects = object_files
45        # Convert args to '' wrapped variant for meson
46        self.fortran_args = [
47            f"'{x}'" if not (x.startswith("'") and x.endswith("'")) else x
48            for x in fortran_args
49        ]
50        self.pipeline = [
51            self.initialize_template,
52            self.sources_substitution,
53            self.objects_substitution,
54            self.deps_substitution,
55            self.include_substitution,
56            self.libraries_substitution,
57            self.fortran_args_substitution,
58        ]
59        self.build_type = build_type
60        self.python_exe = python_exe
61        self.indent = " " * 21
62
63    def meson_build_template(self) -> str:
64        if not self.build_template_path.is_file():
65            raise FileNotFoundError(
66                errno.ENOENT,
67                "Meson build template"
68                f" {self.build_template_path.absolute()}"
69                " does not exist.",
70            )
71        return self.build_template_path.read_text()
72
73    def initialize_template(self) -> None:
74        self.substitutions["modulename"] = self.modulename
75        self.substitutions["buildtype"] = self.build_type
76        self.substitutions["python"] = self.python_exe
77
78    def sources_substitution(self) -> None:
79        self.substitutions["source_list"] = ",\n".join(
80            [f"{self.indent}'''{source}'''," for source in self.sources]
81        )
82
83    def objects_substitution(self) -> None:
84        self.substitutions["obj_list"] = ",\n".join(
85            [f"{self.indent}'''{obj}'''," for obj in self.objects]
86        )
87
88    def deps_substitution(self) -> None:
89        self.substitutions["dep_list"] = f",\n{self.indent}".join(
90            [f"{self.indent}dependency('{dep}')," for dep in self.deps]
91        )
92
93    def libraries_substitution(self) -> None:
94        self.substitutions["lib_dir_declarations"] = "\n".join(
95            [
96                f"lib_dir_{i} = declare_dependency(link_args : ['''-L{lib_dir}'''])"
97                for i, lib_dir in enumerate(self.library_dirs)
98            ]
99        )
100
101        self.substitutions["lib_declarations"] = "\n".join(
102            [
103                f"{lib.replace('.', '_')} = declare_dependency(link_args : ['-l{lib}'])"
104                for lib in self.libraries
105            ]
106        )
107
108        self.substitutions["lib_list"] = f"\n{self.indent}".join(
109            [f"{self.indent}{lib.replace('.', '_')}," for lib in self.libraries]
110        )
111        self.substitutions["lib_dir_list"] = f"\n{self.indent}".join(
112            [f"{self.indent}lib_dir_{i}," for i in range(len(self.library_dirs))]
113        )
114
115    def include_substitution(self) -> None:
116        self.substitutions["inc_list"] = f",\n{self.indent}".join(
117            [f"{self.indent}'''{inc}'''," for inc in self.include_dirs]
118        )
119
120    def fortran_args_substitution(self) -> None:
121        if self.fortran_args:
122            self.substitutions["fortran_args"] = (
123                f"{self.indent}fortran_args: [{', '.join(list(self.fortran_args))}],"
124            )
125        else:
126            self.substitutions["fortran_args"] = ""
127
128    def generate_meson_build(self):
129        for node in self.pipeline:
130            node()
131        template = Template(self.meson_build_template())
132        meson_build = template.substitute(self.substitutions)
133        meson_build = meson_build.replace(",,", ",")
134        return meson_build
135
136
137class MesonBackend(Backend):
138    def __init__(self, *args, **kwargs):
139        super().__init__(*args, **kwargs)
140        self.dependencies = self.extra_dat.get("dependencies", [])
141        self.meson_build_dir = "bbdir"
142        self.build_type = (
143            "debug" if any("debug" in flag for flag in self.fc_flags) else "release"
144        )
145        self.fc_flags = _get_flags(self.fc_flags)
146
147    def _move_exec_to_root(self, build_dir: Path):
148        walk_dir = Path(build_dir) / self.meson_build_dir
149        path_objects = chain(
150            walk_dir.glob(f"{self.modulename}*.so"),
151            walk_dir.glob(f"{self.modulename}*.pyd"),
152            walk_dir.glob(f"{self.modulename}*.dll"),
153        )
154        # Same behavior as distutils
155        # https://github.com/numpy/numpy/issues/24874#issuecomment-1835632293
156        for path_object in path_objects:
157            dest_path = Path.cwd() / path_object.name
158            if dest_path.exists():
159                dest_path.unlink()
160            shutil.copy2(path_object, dest_path)
161            os.remove(path_object)
162
163    def write_meson_build(self, build_dir: Path) -> None:
164        """Writes the meson build file at specified location"""
165        meson_template = MesonTemplate(
166            self.modulename,
167            self.sources,
168            self.dependencies,
169            self.libraries,
170            self.library_dirs,
171            self.include_dirs,
172            self.extra_objects,
173            self.flib_flags,
174            self.fc_flags,
175            self.build_type,
176            sys.executable,
177        )
178        src = meson_template.generate_meson_build()
179        Path(build_dir).mkdir(parents=True, exist_ok=True)
180        meson_build_file = Path(build_dir) / "meson.build"
181        meson_build_file.write_text(src)
182        return meson_build_file
183
184    def _run_subprocess_command(self, command, cwd):
185        subprocess.run(command, cwd=cwd, check=True)
186
187    def run_meson(self, build_dir: Path):
188        setup_command = ["meson", "setup", self.meson_build_dir]
189        self._run_subprocess_command(setup_command, build_dir)
190        compile_command = ["meson", "compile", "-C", self.meson_build_dir]
191        self._run_subprocess_command(compile_command, build_dir)
192
193    def compile(self) -> None:
194        self.sources = _prepare_sources(self.modulename, self.sources, self.build_dir)
195        _prepare_objects(self.modulename, self.extra_objects, self.build_dir)
196        self.write_meson_build(self.build_dir)
197        self.run_meson(self.build_dir)
198        self._move_exec_to_root(self.build_dir)
199
200
201def _prepare_sources(mname, sources, bdir):
202    extended_sources = sources.copy()
203    Path(bdir).mkdir(parents=True, exist_ok=True)
204    # Copy sources
205    for source in sources:
206        if Path(source).exists() and Path(source).is_file():
207            shutil.copy(source, bdir)
208    generated_sources = [
209        Path(f"{mname}module.c"),
210        Path(f"{mname}-f2pywrappers2.f90"),
211        Path(f"{mname}-f2pywrappers.f"),
212    ]
213    bdir = Path(bdir)
214    for generated_source in generated_sources:
215        if generated_source.exists():
216            shutil.copy(generated_source, bdir / generated_source.name)
217            extended_sources.append(generated_source.name)
218            generated_source.unlink()
219    extended_sources = [
220        Path(source).name
221        for source in extended_sources
222        if not Path(source).suffix == ".pyf"
223    ]
224    return extended_sources
225
226def _prepare_objects(mname, objects, bdir):
227    Path(bdir).mkdir(parents=True, exist_ok=True)
228    # Copy objects
229    for obj in objects:
230        if Path(obj).exists() and Path(obj).is_file():
231            shutil.copy(obj, bdir)
232
233def _get_flags(fc_flags):
234    flag_values = []
235    flag_pattern = re.compile(r"--f(77|90)flags=(.*)")
236    for flag in fc_flags:
237        match_result = flag_pattern.match(flag)
238        if match_result:
239            values = match_result.group(2).strip().split()
240            values = [val.strip("'\"") for val in values]
241            flag_values.extend(values)
242    # Hacky way to preserve order of flags
243    unique_flags = list(dict.fromkeys(flag_values))
244    return unique_flags
245 
codekingpro/portable-devtools · Team Ai