File: builder.py

package info (click to toggle)
mpi4py 4.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,560 kB
  • sloc: python: 35,306; ansic: 16,482; sh: 837; makefile: 618; cpp: 193; f90: 178
file content (236 lines) | stat: -rw-r--r-- 6,514 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import contextlib
import importlib
import os
import pathlib
import shutil
import sys

# ---

BACKENDS = {
    "setuptools": "setuptools.build_meta",
    "skbuild": "scikit_build_core.setuptools.build_meta",
    "mesonpy": "mesonpy",
}


def get_build_backend_name(name=None):
    if name is None:
        name = os.environ.get("MPI4PY_BUILD_BACKEND", "")
        name = name.lower().replace("_", "-")
    if name in ("default", ""):
        for name, module_name in BACKENDS.items():
            if name != "setuptools":
                if module_name in sys.modules:
                    return name
        return "setuptools"
    if name in ("setuptools", "setup"):
        return "setuptools"
    if name in ("scikit-build-core", "scikit-build", "skbuild", "cmake"):
        return "skbuild"
    if name in ("meson-python", "mesonpy", "meson"):
        return "mesonpy"
    raise RuntimeError(f"Unknown build backend {name!r}")


def build_backend(name=None):
    name = get_build_backend_name(name)
    return importlib.import_module(BACKENDS[name])


def read_build_requires(name):
    confdir = pathlib.Path(__file__).resolve().parent
    basename = f"requirements-build-{name}.txt"
    filename = confdir / basename
    with filename.open(encoding="utf-8") as f:
        return [req for req in map(str.strip, f) if req]


def get_backend_requires_fast(backend, dist, config_settings=None):
    if backend is None or isinstance(backend, str):
        try:
            backend = build_backend(backend)
        except ImportError:
            return None
    requires = []
    get_requires = getattr(backend, f"get_requires_for_build_{dist}", None)
    if get_requires is not None:
        requires += get_requires(config_settings)
    return requires


def get_backend_requires_hook(name, dist, config_settings=None):
    try:
        from pyproject_hooks import BuildBackendHookCaller
    except ImportError:
        from pep517.wrappers import Pep517HookCaller as BuildBackendHookCaller
    try:
        from build.env import DefaultIsolatedEnv
    except ImportError:
        from build.env import IsolatedEnvBuilder

        class DefaultIsolatedEnv(IsolatedEnvBuilder):
            def __enter__(self):
                env = super().__enter__()
                env.python_executable = env.executable

                def make_extra_environ():
                    path = os.environ.get("PATH")
                    return {
                        "PATH": os.pathsep.join([env.scripts_dir, path])
                        if path is not None
                        else env.scripts_dir,
                    }

                env.make_extra_environ = make_extra_environ
                return env

    @contextlib.contextmanager
    def environment(path):
        environ_prev = [("PATH", os.environ["PATH"])]
        for prefix in ("_PYPROJECT_HOOKS", "PEP517"):
            for suffix in ("BUILD_BACKEND", "BACKEND_PATH"):
                key = f"{prefix}_{suffix}"
                if key in os.environ:
                    val = os.environ.pop(key)
                    environ_prev.append((key, val))
        os.environ["PATH"] = path
        try:
            yield None
        finally:
            os.environ.update(environ_prev)

    requires = read_build_requires(name)
    with DefaultIsolatedEnv() as env:
        path = env.make_extra_environ()["PATH"]
        python_executable = env.python_executable
        with environment(path):
            env.install(requires)
            hook = BuildBackendHookCaller(
                source_dir=pathlib.Path.cwd(),
                build_backend=BACKENDS[name],
                python_executable=python_executable,
            )
            requires += get_backend_requires_fast(hook, dist, config_settings)
    return requires


def get_requires_for_build(dist, config_settings=None):
    name = get_build_backend_name()
    requires = get_backend_requires_fast(name, dist, config_settings)
    if requires is None:
        requires = get_backend_requires_hook(name, dist, config_settings)
    if dist in ("wheel", "editable"):
        requires += read_build_requires("cython")
    return requires


# ---


def get_requires_for_build_sdist(config_settings=None):
    return get_requires_for_build("sdist", config_settings)


def build_sdist(
    sdist_directory,
    config_settings=None,
):
    return build_backend().build_sdist(
        sdist_directory,
        config_settings,
    )


# ---


def get_requires_for_build_wheel(config_settings=None):
    return get_requires_for_build("wheel", config_settings)


def prepare_metadata_for_build_wheel(
    metadata_directory,
    config_settings=None,
):
    return build_backend().prepare_metadata_for_build_wheel(
        metadata_directory,
        config_settings,
    )


def build_wheel(
    wheel_directory,
    config_settings=None,
    metadata_directory=None,
):
    return build_backend().build_wheel(
        wheel_directory,
        config_settings,
        metadata_directory,
    )


# ---


def get_requires_for_build_editable(config_settings=None):
    return get_requires_for_build("editable", config_settings)


def prepare_metadata_for_build_editable(
    metadata_directory,
    config_settings=None,
):
    return build_backend().prepare_metadata_for_build_editable(
        metadata_directory,
        config_settings,
    )


def build_editable(
    wheel_directory,
    config_settings=None,
    metadata_directory=None,
):
    return build_backend().build_editable(
        wheel_directory,
        config_settings,
        metadata_directory,
    )


# ---


def setup_env_mpicc():
    mpicc = shutil.which("mpicc")
    mpicc = os.environ.get("MPICC", mpicc)
    mpicc = os.environ.get("MPI4PY_BUILD_MPICC", mpicc)
    if not mpicc:
        return
    if " " in mpicc:
        mpicc = f'"{mpicc}"'
    if "CC" not in os.environ:
        os.environ["CC"] = mpicc


if get_build_backend_name() == "setuptools":
    try:
        import setuptools.build_meta as st_bm
    except ImportError:
        st_bm = None
    if not hasattr(st_bm, "get_requires_for_build_editable"):
        del get_requires_for_build_editable
    if not hasattr(st_bm, "prepare_metadata_for_build_editable"):
        del prepare_metadata_for_build_editable
    if not hasattr(st_bm, "build_editable"):
        del build_editable
    del st_bm

if get_build_backend_name() == "mesonpy":
    setup_env_mpicc()
    del prepare_metadata_for_build_wheel
    del prepare_metadata_for_build_editable

# ---