We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Numpy v1.26.4 build fails: Module "features" does not exist
@ryanking13 @mattip @seberg

v1.26.3 build succeeds

Failure for v1.26.4:

<details><summary>Details</summary>
<p>

```
The Meson build system
Version: 1.3.1
Source dir: /home/hood/Documents/programming/pyodide/packages/numpy/build/numpy-1.26.4
Build dir: /home/hood/Documents/programming/pyodide/packages/numpy/build/numpy-1.26.4/.mesonpy-c6ekdglk
Build type: cross build
Project name: NumPy
Project version: 1.26.4
Cross compiler sanity tests disabled via the cross file.
C compiler for the host machine: /tmp/tmpqfmd_k4r/cc (emscripten 3.1.52 "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.52 (fa478400df3351f7153c0279bc638784d3d90334)")
C linker for the host machine: /tmp/tmpqfmd_k4r/cc ld.wasm 18.0.0
C++ compiler for the host machine: /tmp/tmpqfmd_k4r/c++ (emscripten 3.1.52 "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.52 (fa478400df3351f7153c0279bc638784d3d90334)")
C++ linker for the host machine: /tmp/tmpqfmd_k4r/c++ ld.wasm 18.0.0
Cython compiler for the host machine: cython (cython 3.0.8)
C compiler for the build machine: ccache cc (gcc 9.4.0 "cc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0")
C linker for the build machine: cc ld.bfd 2.34
C++ compiler for the build machine: ccache c++ (gcc 9.4.0 "c++ (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0")
C++ linker for the build machine: c++ ld.bfd 2.34
Cython compiler for the build machine: cython (cython 3.0.8)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Host machine cpu family: wasm32
Host machine cpu: wasm
Target machine cpu family: wasm32
Target machine cpu: wasm
Program python3 found: YES (/tmp/build-env-mr2vjazb/bin/python)
Found pkg-config: YES (/usr/bin/pkg-config) 0.29.1
Run-time dependency python found: YES 3.12
Has header "Python.h" with dependency python: YES 
Compiler for C supports arguments -fno-strict-aliasing: YES 

../meson_cpu/x86/meson.build:2:15: ERROR: Module "features" does not exist
```

</p>
</details> 





--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pyodide-build/pyodide_build/pypabuild.py`
Content:
```
1 import json
2 import os
3 import shutil
4 import subprocess as sp
5 import sys
6 import traceback
7 from collections.abc import Callable, Iterator, Mapping, Sequence
8 from contextlib import contextmanager
9 from itertools import chain
10 from pathlib import Path
11 from tempfile import TemporaryDirectory
12 
13 from build import BuildBackendException, ConfigSettingsType
14 from build.env import DefaultIsolatedEnv
15 from packaging.requirements import Requirement
16 
17 from . import common, pywasmcross
18 from .build_env import (
19     get_build_flag,
20     get_hostsitepackages,
21     get_pyversion,
22     get_unisolated_packages,
23     platform,
24 )
25 from .io import _BuildSpecExports
26 from .vendor._pypabuild import (
27     _STYLES,
28     _DefaultIsolatedEnv,
29     _error,
30     _handle_build_error,
31     _ProjectBuilder,
32 )
33 
34 AVOIDED_REQUIREMENTS = [
35     # We don't want to install cmake Python package inside the isolated env as it will shadow
36     # the pywasmcross cmake wrapper.
37     # TODO: Find a way to make scikit-build use the pywasmcross cmake wrapper.
38     "cmake",
39     # mesonpy installs patchelf in linux platform but we don't want it.
40     "patchelf",
41 ]
42 
43 
44 def _gen_runner(
45     cross_build_env: Mapping[str, str],
46     isolated_build_env: DefaultIsolatedEnv,
47 ) -> Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]:
48     """
49     This returns a slightly modified version of default subprocess runner that pypa/build uses.
50     pypa/build prepends the virtual environment's bin directory to the PATH environment variable.
51     This is problematic because it shadows the pywasmcross compiler wrappers for cmake, meson, etc.
52 
53     This function prepends the compiler wrapper directory to the PATH again so that our compiler wrappers
54     are searched first.
55 
56     Parameters
57     ----------
58     cross_build_env
59         The cross build environment for pywasmcross.
60     isolated_build_env
61         The isolated build environment created by pypa/build.
62     """
63 
64     def _runner(cmd, cwd=None, extra_environ=None):
65         env = os.environ.copy()
66         if extra_environ:
67             env.update(extra_environ)
68 
69         # Some build dependencies like cmake, meson installs binaries to this directory
70         # and we should add it to the PATH so that they can be found.
71         env["BUILD_ENV_SCRIPTS_DIR"] = isolated_build_env._scripts_dir
72         env["PATH"] = f"{cross_build_env['COMPILER_WRAPPER_DIR']}:{env['PATH']}"
73         # For debugging: Uncomment the following line to print the build command
74         # print("Build backend call:", " ".join(str(x) for x in cmd), file=sys.stderr)
75         sp.check_call(cmd, cwd=cwd, env=env)
76 
77     return _runner
78 
79 
80 def symlink_unisolated_packages(env: DefaultIsolatedEnv) -> None:
81     pyversion = get_pyversion()
82     site_packages_path = f"lib/{pyversion}/site-packages"
83     env_site_packages = Path(env.path) / site_packages_path
84     sysconfigdata_name = get_build_flag("SYSCONFIG_NAME")
85     sysconfigdata_path = (
86         Path(get_build_flag("TARGETINSTALLDIR"))
87         / f"sysconfigdata/{sysconfigdata_name}.py"
88     )
89 
90     env_site_packages.mkdir(parents=True, exist_ok=True)
91     shutil.copy(sysconfigdata_path, env_site_packages)
92     host_site_packages = Path(get_hostsitepackages())
93     for name in get_unisolated_packages():
94         for path in chain(
95             host_site_packages.glob(f"{name}*"), host_site_packages.glob(f"_{name}*")
96         ):
97             (env_site_packages / path.name).unlink(missing_ok=True)
98             (env_site_packages / path.name).symlink_to(path)
99 
100 
101 def remove_avoided_requirements(
102     requires: set[str], avoided_requirements: set[str] | list[str]
103 ) -> set[str]:
104     for reqstr in list(requires):
105         req = Requirement(reqstr)
106         for avoid_name in set(avoided_requirements):
107             if avoid_name in req.name.lower():
108                 requires.remove(reqstr)
109     return requires
110 
111 
112 def install_reqs(env: DefaultIsolatedEnv, reqs: set[str]) -> None:
113     env.install(
114         remove_avoided_requirements(
115             reqs,
116             get_unisolated_packages() + AVOIDED_REQUIREMENTS,
117         )
118     )
119 
120 
121 def _build_in_isolated_env(
122     build_env: Mapping[str, str],
123     srcdir: Path,
124     outdir: str,
125     distribution: str,
126     config_settings: ConfigSettingsType,
127 ) -> str:
128     # For debugging: The following line disables removal of the isolated venv.
129     # It will be left in the /tmp folder and can be inspected or entered as
130     # needed.
131     # _DefaultIsolatedEnv.__exit__ = lambda *args: None
132     with _DefaultIsolatedEnv() as env:
133         builder = _ProjectBuilder.from_isolated_env(
134             env,
135             srcdir,
136             runner=_gen_runner(build_env, env),
137         )
138 
139         # first install the build dependencies
140         symlink_unisolated_packages(env)
141         install_reqs(env, builder.build_system_requires)
142         installed_requires_for_build = False
143         try:
144             build_reqs = builder.get_requires_for_build(
145                 distribution,
146             )
147         except BuildBackendException:
148             pass
149         else:
150             install_reqs(env, build_reqs)
151             installed_requires_for_build = True
152 
153         with common.replace_env(build_env):
154             if not installed_requires_for_build:
155                 build_reqs = builder.get_requires_for_build(
156                     distribution,
157                     config_settings,
158                 )
159                 install_reqs(env, build_reqs)
160 
161             return builder.build(distribution, outdir, config_settings)
162 
163 
164 def parse_backend_flags(backend_flags: str | list[str]) -> ConfigSettingsType:
165     config_settings: dict[str, str | list[str]] = {}
166 
167     if isinstance(backend_flags, str):
168         backend_flags = backend_flags.split()
169 
170     for arg in backend_flags:
171         setting, _, value = arg.partition("=")
172         if setting not in config_settings:
173             config_settings[setting] = value
174             continue
175 
176         cur_value = config_settings[setting]
177         if isinstance(cur_value, str):
178             config_settings[setting] = [cur_value, value]
179         else:
180             cur_value.append(value)
181     return config_settings
182 
183 
184 def make_command_wrapper_symlinks(symlink_dir: Path) -> dict[str, str]:
185     """
186     Create symlinks that make pywasmcross look like a compiler.
187 
188     Parameters
189     ----------
190     symlink_dir
191         The directory where the symlinks will be created.
192 
193     Returns
194     -------
195     The dictionary of compiler environment variables that points to the symlinks.
196     """
197 
198     pywasmcross_exe = symlink_dir / "pywasmcross.py"
199     shutil.copy2(pywasmcross.__file__, pywasmcross_exe)
200     pywasmcross_exe.chmod(0o755)
201 
202     env = {}
203     for symlink in pywasmcross.SYMLINKS:
204         symlink_path = symlink_dir / symlink
205         if os.path.lexists(symlink_path) and not symlink_path.exists():
206             # remove broken symlink so it can be re-created
207             symlink_path.unlink()
208 
209         symlink_path.symlink_to(pywasmcross_exe)
210         if symlink == "c++":
211             var = "CXX"
212         elif symlink == "gfortran":
213             var = "FC"  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables
214         else:
215             var = symlink.upper()
216         env[var] = str(symlink_path)
217 
218     return env
219 
220 
221 @contextmanager
222 def get_build_env(
223     env: dict[str, str],
224     *,
225     pkgname: str,
226     cflags: str,
227     cxxflags: str,
228     ldflags: str,
229     target_install_dir: str,
230     exports: _BuildSpecExports,
231 ) -> Iterator[dict[str, str]]:
232     """
233     Returns a dict of environment variables that should be used when building
234     a package with pypa/build.
235     """
236 
237     kwargs = dict(
238         pkgname=pkgname,
239         cflags=cflags,
240         cxxflags=cxxflags,
241         ldflags=ldflags,
242         target_install_dir=target_install_dir,
243     )
244 
245     args = common.environment_substitute_args(kwargs, env)
246     args["builddir"] = str(Path(".").absolute())
247     args["exports"] = exports
248     env = env.copy()
249 
250     with TemporaryDirectory() as symlink_dir_str:
251         symlink_dir = Path(symlink_dir_str)
252         env.update(make_command_wrapper_symlinks(symlink_dir))
253 
254         sysconfig_dir = Path(get_build_flag("TARGETINSTALLDIR")) / "sysconfigdata"
255         args["PYTHONPATH"] = sys.path + [str(sysconfig_dir)]
256         args["orig__name__"] = __name__
257         args["pythoninclude"] = get_build_flag("PYTHONINCLUDE")
258         args["PATH"] = env["PATH"]
259 
260         pywasmcross_env = json.dumps(args)
261         # Store into environment variable and to disk. In most cases we will
262         # load from the environment variable but if some other tool filters
263         # environment variables we will load from disk instead.
264         env["PYWASMCROSS_ARGS"] = pywasmcross_env
265         (symlink_dir / "pywasmcross_env.json").write_text(pywasmcross_env)
266 
267         env["_PYTHON_HOST_PLATFORM"] = platform()
268         env["_PYTHON_SYSCONFIGDATA_NAME"] = get_build_flag("SYSCONFIG_NAME")
269         env["PYTHONPATH"] = str(sysconfig_dir)
270         env["COMPILER_WRAPPER_DIR"] = str(symlink_dir)
271 
272         yield env
273 
274 
275 def build(
276     srcdir: Path,
277     outdir: Path,
278     build_env: Mapping[str, str],
279     config_settings: ConfigSettingsType,
280 ) -> str:
281     distribution = "wheel"
282     try:
283         with _handle_build_error():
284             built = _build_in_isolated_env(
285                 build_env, srcdir, str(outdir), distribution, config_settings
286             )
287             print("{bold}{green}Successfully built {}{reset}".format(built, **_STYLES))
288             return built
289     except Exception as e:  # pragma: no cover
290         tb = traceback.format_exc().strip("\n")
291         print("\n{dim}{}{reset}\n".format(tb, **_STYLES))
292         _error(str(e))
293         sys.exit(1)
294
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pyodide-build/pyodide_build/pypabuild.py b/pyodide-build/pyodide_build/pypabuild.py
--- a/pyodide-build/pyodide_build/pypabuild.py
+++ b/pyodide-build/pyodide_build/pypabuild.py
@@ -40,6 +40,19 @@
     "patchelf",
 ]
 
+# corresponding env variables for symlinks
+SYMLINK_ENV_VARS = {
+    "cc": "CC",
+    "c++": "CXX",
+    "ld": "LD",
+    "lld": "LLD",
+    "ar": "AR",
+    "gcc": "GCC",
+    "ranlib": "RANLIB",
+    "strip": "STRIP",
+    "gfortran": "FC",  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables
+}
+
 
 def _gen_runner(
     cross_build_env: Mapping[str, str],
@@ -207,13 +220,8 @@
             symlink_path.unlink()
 
         symlink_path.symlink_to(pywasmcross_exe)
-        if symlink == "c++":
-            var = "CXX"
-        elif symlink == "gfortran":
-            var = "FC"  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables
-        else:
-            var = symlink.upper()
-        env[var] = str(symlink_path)
+        if symlink in SYMLINK_ENV_VARS:
+            env[SYMLINK_ENV_VARS[symlink]] = str(symlink_path)
 
     return env

{"golden_diff": "diff --git a/pyodide-build/pyodide_build/pypabuild.py b/pyodide-build/pyodide_build/pypabuild.py\n--- a/pyodide-build/pyodide_build/pypabuild.py\n+++ b/pyodide-build/pyodide_build/pypabuild.py\n@@ -40,6 +40,19 @@\n     \"patchelf\",\n ]\n \n+# corresponding env variables for symlinks\n+SYMLINK_ENV_VARS = {\n+    \"cc\": \"CC\",\n+    \"c++\": \"CXX\",\n+    \"ld\": \"LD\",\n+    \"lld\": \"LLD\",\n+    \"ar\": \"AR\",\n+    \"gcc\": \"GCC\",\n+    \"ranlib\": \"RANLIB\",\n+    \"strip\": \"STRIP\",\n+    \"gfortran\": \"FC\",  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables\n+}\n+\n \n def _gen_runner(\n     cross_build_env: Mapping[str, str],\n@@ -207,13 +220,8 @@\n             symlink_path.unlink()\n \n         symlink_path.symlink_to(pywasmcross_exe)\n-        if symlink == \"c++\":\n-            var = \"CXX\"\n-        elif symlink == \"gfortran\":\n-            var = \"FC\"  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables\n-        else:\n-            var = symlink.upper()\n-        env[var] = str(symlink_path)\n+        if symlink in SYMLINK_ENV_VARS:\n+            env[SYMLINK_ENV_VARS[symlink]] = str(symlink_path)\n \n     return env\n", "issue": "Numpy v1.26.4 build fails: Module \"features\" does not exist\n@ryanking13 @mattip @seberg\r\n\r\nv1.26.3 build succeeds\r\n\r\nFailure for v1.26.4:\r\n\r\n<details><summary>Details</summary>\r\n<p>\r\n\r\n```\r\nThe Meson build system\r\nVersion: 1.3.1\r\nSource dir: /home/hood/Documents/programming/pyodide/packages/numpy/build/numpy-1.26.4\r\nBuild dir: /home/hood/Documents/programming/pyodide/packages/numpy/build/numpy-1.26.4/.mesonpy-c6ekdglk\r\nBuild type: cross build\r\nProject name: NumPy\r\nProject version: 1.26.4\r\nCross compiler sanity tests disabled via the cross file.\r\nC compiler for the host machine: /tmp/tmpqfmd_k4r/cc (emscripten 3.1.52 \"emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.52 (fa478400df3351f7153c0279bc638784d3d90334)\")\r\nC linker for the host machine: /tmp/tmpqfmd_k4r/cc ld.wasm 18.0.0\r\nC++ compiler for the host machine: /tmp/tmpqfmd_k4r/c++ (emscripten 3.1.52 \"emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.52 (fa478400df3351f7153c0279bc638784d3d90334)\")\r\nC++ linker for the host machine: /tmp/tmpqfmd_k4r/c++ ld.wasm 18.0.0\r\nCython compiler for the host machine: cython (cython 3.0.8)\r\nC compiler for the build machine: ccache cc (gcc 9.4.0 \"cc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0\")\r\nC linker for the build machine: cc ld.bfd 2.34\r\nC++ compiler for the build machine: ccache c++ (gcc 9.4.0 \"c++ (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0\")\r\nC++ linker for the build machine: c++ ld.bfd 2.34\r\nCython compiler for the build machine: cython (cython 3.0.8)\r\nBuild machine cpu family: x86_64\r\nBuild machine cpu: x86_64\r\nHost machine cpu family: wasm32\r\nHost machine cpu: wasm\r\nTarget machine cpu family: wasm32\r\nTarget machine cpu: wasm\r\nProgram python3 found: YES (/tmp/build-env-mr2vjazb/bin/python)\r\nFound pkg-config: YES (/usr/bin/pkg-config) 0.29.1\r\nRun-time dependency python found: YES 3.12\r\nHas header \"Python.h\" with dependency python: YES \r\nCompiler for C supports arguments -fno-strict-aliasing: YES \r\n\r\n../meson_cpu/x86/meson.build:2:15: ERROR: Module \"features\" does not exist\r\n```\r\n\r\n</p>\r\n</details> \r\n\r\n\r\n\r\n\n", "before_files": [{"content": "import json\nimport os\nimport shutil\nimport subprocess as sp\nimport sys\nimport traceback\nfrom collections.abc import Callable, Iterator, Mapping, Sequence\nfrom contextlib import contextmanager\nfrom itertools import chain\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nfrom build import BuildBackendException, ConfigSettingsType\nfrom build.env import DefaultIsolatedEnv\nfrom packaging.requirements import Requirement\n\nfrom . import common, pywasmcross\nfrom .build_env import (\n    get_build_flag,\n    get_hostsitepackages,\n    get_pyversion,\n    get_unisolated_packages,\n    platform,\n)\nfrom .io import _BuildSpecExports\nfrom .vendor._pypabuild import (\n    _STYLES,\n    _DefaultIsolatedEnv,\n    _error,\n    _handle_build_error,\n    _ProjectBuilder,\n)\n\nAVOIDED_REQUIREMENTS = [\n    # We don't want to install cmake Python package inside the isolated env as it will shadow\n    # the pywasmcross cmake wrapper.\n    # TODO: Find a way to make scikit-build use the pywasmcross cmake wrapper.\n    \"cmake\",\n    # mesonpy installs patchelf in linux platform but we don't want it.\n    \"patchelf\",\n]\n\n\ndef _gen_runner(\n    cross_build_env: Mapping[str, str],\n    isolated_build_env: DefaultIsolatedEnv,\n) -> Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]:\n    \"\"\"\n    This returns a slightly modified version of default subprocess runner that pypa/build uses.\n    pypa/build prepends the virtual environment's bin directory to the PATH environment variable.\n    This is problematic because it shadows the pywasmcross compiler wrappers for cmake, meson, etc.\n\n    This function prepends the compiler wrapper directory to the PATH again so that our compiler wrappers\n    are searched first.\n\n    Parameters\n    ----------\n    cross_build_env\n        The cross build environment for pywasmcross.\n    isolated_build_env\n        The isolated build environment created by pypa/build.\n    \"\"\"\n\n    def _runner(cmd, cwd=None, extra_environ=None):\n        env = os.environ.copy()\n        if extra_environ:\n            env.update(extra_environ)\n\n        # Some build dependencies like cmake, meson installs binaries to this directory\n        # and we should add it to the PATH so that they can be found.\n        env[\"BUILD_ENV_SCRIPTS_DIR\"] = isolated_build_env._scripts_dir\n        env[\"PATH\"] = f\"{cross_build_env['COMPILER_WRAPPER_DIR']}:{env['PATH']}\"\n        # For debugging: Uncomment the following line to print the build command\n        # print(\"Build backend call:\", \" \".join(str(x) for x in cmd), file=sys.stderr)\n        sp.check_call(cmd, cwd=cwd, env=env)\n\n    return _runner\n\n\ndef symlink_unisolated_packages(env: DefaultIsolatedEnv) -> None:\n    pyversion = get_pyversion()\n    site_packages_path = f\"lib/{pyversion}/site-packages\"\n    env_site_packages = Path(env.path) / site_packages_path\n    sysconfigdata_name = get_build_flag(\"SYSCONFIG_NAME\")\n    sysconfigdata_path = (\n        Path(get_build_flag(\"TARGETINSTALLDIR\"))\n        / f\"sysconfigdata/{sysconfigdata_name}.py\"\n    )\n\n    env_site_packages.mkdir(parents=True, exist_ok=True)\n    shutil.copy(sysconfigdata_path, env_site_packages)\n    host_site_packages = Path(get_hostsitepackages())\n    for name in get_unisolated_packages():\n        for path in chain(\n            host_site_packages.glob(f\"{name}*\"), host_site_packages.glob(f\"_{name}*\")\n        ):\n            (env_site_packages / path.name).unlink(missing_ok=True)\n            (env_site_packages / path.name).symlink_to(path)\n\n\ndef remove_avoided_requirements(\n    requires: set[str], avoided_requirements: set[str] | list[str]\n) -> set[str]:\n    for reqstr in list(requires):\n        req = Requirement(reqstr)\n        for avoid_name in set(avoided_requirements):\n            if avoid_name in req.name.lower():\n                requires.remove(reqstr)\n    return requires\n\n\ndef install_reqs(env: DefaultIsolatedEnv, reqs: set[str]) -> None:\n    env.install(\n        remove_avoided_requirements(\n            reqs,\n            get_unisolated_packages() + AVOIDED_REQUIREMENTS,\n        )\n    )\n\n\ndef _build_in_isolated_env(\n    build_env: Mapping[str, str],\n    srcdir: Path,\n    outdir: str,\n    distribution: str,\n    config_settings: ConfigSettingsType,\n) -> str:\n    # For debugging: The following line disables removal of the isolated venv.\n    # It will be left in the /tmp folder and can be inspected or entered as\n    # needed.\n    # _DefaultIsolatedEnv.__exit__ = lambda *args: None\n    with _DefaultIsolatedEnv() as env:\n        builder = _ProjectBuilder.from_isolated_env(\n            env,\n            srcdir,\n            runner=_gen_runner(build_env, env),\n        )\n\n        # first install the build dependencies\n        symlink_unisolated_packages(env)\n        install_reqs(env, builder.build_system_requires)\n        installed_requires_for_build = False\n        try:\n            build_reqs = builder.get_requires_for_build(\n                distribution,\n            )\n        except BuildBackendException:\n            pass\n        else:\n            install_reqs(env, build_reqs)\n            installed_requires_for_build = True\n\n        with common.replace_env(build_env):\n            if not installed_requires_for_build:\n                build_reqs = builder.get_requires_for_build(\n                    distribution,\n                    config_settings,\n                )\n                install_reqs(env, build_reqs)\n\n            return builder.build(distribution, outdir, config_settings)\n\n\ndef parse_backend_flags(backend_flags: str | list[str]) -> ConfigSettingsType:\n    config_settings: dict[str, str | list[str]] = {}\n\n    if isinstance(backend_flags, str):\n        backend_flags = backend_flags.split()\n\n    for arg in backend_flags:\n        setting, _, value = arg.partition(\"=\")\n        if setting not in config_settings:\n            config_settings[setting] = value\n            continue\n\n        cur_value = config_settings[setting]\n        if isinstance(cur_value, str):\n            config_settings[setting] = [cur_value, value]\n        else:\n            cur_value.append(value)\n    return config_settings\n\n\ndef make_command_wrapper_symlinks(symlink_dir: Path) -> dict[str, str]:\n    \"\"\"\n    Create symlinks that make pywasmcross look like a compiler.\n\n    Parameters\n    ----------\n    symlink_dir\n        The directory where the symlinks will be created.\n\n    Returns\n    -------\n    The dictionary of compiler environment variables that points to the symlinks.\n    \"\"\"\n\n    pywasmcross_exe = symlink_dir / \"pywasmcross.py\"\n    shutil.copy2(pywasmcross.__file__, pywasmcross_exe)\n    pywasmcross_exe.chmod(0o755)\n\n    env = {}\n    for symlink in pywasmcross.SYMLINKS:\n        symlink_path = symlink_dir / symlink\n        if os.path.lexists(symlink_path) and not symlink_path.exists():\n            # remove broken symlink so it can be re-created\n            symlink_path.unlink()\n\n        symlink_path.symlink_to(pywasmcross_exe)\n        if symlink == \"c++\":\n            var = \"CXX\"\n        elif symlink == \"gfortran\":\n            var = \"FC\"  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables\n        else:\n            var = symlink.upper()\n        env[var] = str(symlink_path)\n\n    return env\n\n\n@contextmanager\ndef get_build_env(\n    env: dict[str, str],\n    *,\n    pkgname: str,\n    cflags: str,\n    cxxflags: str,\n    ldflags: str,\n    target_install_dir: str,\n    exports: _BuildSpecExports,\n) -> Iterator[dict[str, str]]:\n    \"\"\"\n    Returns a dict of environment variables that should be used when building\n    a package with pypa/build.\n    \"\"\"\n\n    kwargs = dict(\n        pkgname=pkgname,\n        cflags=cflags,\n        cxxflags=cxxflags,\n        ldflags=ldflags,\n        target_install_dir=target_install_dir,\n    )\n\n    args = common.environment_substitute_args(kwargs, env)\n    args[\"builddir\"] = str(Path(\".\").absolute())\n    args[\"exports\"] = exports\n    env = env.copy()\n\n    with TemporaryDirectory() as symlink_dir_str:\n        symlink_dir = Path(symlink_dir_str)\n        env.update(make_command_wrapper_symlinks(symlink_dir))\n\n        sysconfig_dir = Path(get_build_flag(\"TARGETINSTALLDIR\")) / \"sysconfigdata\"\n        args[\"PYTHONPATH\"] = sys.path + [str(sysconfig_dir)]\n        args[\"orig__name__\"] = __name__\n        args[\"pythoninclude\"] = get_build_flag(\"PYTHONINCLUDE\")\n        args[\"PATH\"] = env[\"PATH\"]\n\n        pywasmcross_env = json.dumps(args)\n        # Store into environment variable and to disk. In most cases we will\n        # load from the environment variable but if some other tool filters\n        # environment variables we will load from disk instead.\n        env[\"PYWASMCROSS_ARGS\"] = pywasmcross_env\n        (symlink_dir / \"pywasmcross_env.json\").write_text(pywasmcross_env)\n\n        env[\"_PYTHON_HOST_PLATFORM\"] = platform()\n        env[\"_PYTHON_SYSCONFIGDATA_NAME\"] = get_build_flag(\"SYSCONFIG_NAME\")\n        env[\"PYTHONPATH\"] = str(sysconfig_dir)\n        env[\"COMPILER_WRAPPER_DIR\"] = str(symlink_dir)\n\n        yield env\n\n\ndef build(\n    srcdir: Path,\n    outdir: Path,\n    build_env: Mapping[str, str],\n    config_settings: ConfigSettingsType,\n) -> str:\n    distribution = \"wheel\"\n    try:\n        with _handle_build_error():\n            built = _build_in_isolated_env(\n                build_env, srcdir, str(outdir), distribution, config_settings\n            )\n            print(\"{bold}{green}Successfully built {}{reset}\".format(built, **_STYLES))\n            return built\n    except Exception as e:  # pragma: no cover\n        tb = traceback.format_exc().strip(\"\\n\")\n        print(\"\\n{dim}{}{reset}\\n\".format(tb, **_STYLES))\n        _error(str(e))\n        sys.exit(1)\n", "path": "pyodide-build/pyodide_build/pypabuild.py"}], "after_files": [{"content": "import json\nimport os\nimport shutil\nimport subprocess as sp\nimport sys\nimport traceback\nfrom collections.abc import Callable, Iterator, Mapping, Sequence\nfrom contextlib import contextmanager\nfrom itertools import chain\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nfrom build import BuildBackendException, ConfigSettingsType\nfrom build.env import DefaultIsolatedEnv\nfrom packaging.requirements import Requirement\n\nfrom . import common, pywasmcross\nfrom .build_env import (\n    get_build_flag,\n    get_hostsitepackages,\n    get_pyversion,\n    get_unisolated_packages,\n    platform,\n)\nfrom .io import _BuildSpecExports\nfrom .vendor._pypabuild import (\n    _STYLES,\n    _DefaultIsolatedEnv,\n    _error,\n    _handle_build_error,\n    _ProjectBuilder,\n)\n\nAVOIDED_REQUIREMENTS = [\n    # We don't want to install cmake Python package inside the isolated env as it will shadow\n    # the pywasmcross cmake wrapper.\n    # TODO: Find a way to make scikit-build use the pywasmcross cmake wrapper.\n    \"cmake\",\n    # mesonpy installs patchelf in linux platform but we don't want it.\n    \"patchelf\",\n]\n\n# corresponding env variables for symlinks\nSYMLINK_ENV_VARS = {\n    \"cc\": \"CC\",\n    \"c++\": \"CXX\",\n    \"ld\": \"LD\",\n    \"lld\": \"LLD\",\n    \"ar\": \"AR\",\n    \"gcc\": \"GCC\",\n    \"ranlib\": \"RANLIB\",\n    \"strip\": \"STRIP\",\n    \"gfortran\": \"FC\",  # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables\n}\n\n\ndef _gen_runner(\n    cross_build_env: Mapping[str, str],\n    isolated_build_env: DefaultIsolatedEnv,\n) -> Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]:\n    \"\"\"\n    This returns a slightly modified version of default subprocess runner that pypa/build uses.\n    pypa/build prepends the virtual environment's bin directory to the PATH environment variable.\n    This is problematic because it shadows the pywasmcross compiler wrappers for cmake, meson, etc.\n\n    This function prepends the compiler wrapper directory to the PATH again so that our compiler wrappers\n    are searched first.\n\n    Parameters\n    ----------\n    cross_build_env\n        The cross build environment for pywasmcross.\n    isolated_build_env\n        The isolated build environment created by pypa/build.\n    \"\"\"\n\n    def _runner(cmd, cwd=None, extra_environ=None):\n        env = os.environ.copy()\n        if extra_environ:\n            env.update(extra_environ)\n\n        # Some build dependencies like cmake, meson installs binaries to this directory\n        # and we should add it to the PATH so that they can be found.\n        env[\"BUILD_ENV_SCRIPTS_DIR\"] = isolated_build_env._scripts_dir\n        env[\"PATH\"] = f\"{cross_build_env['COMPILER_WRAPPER_DIR']}:{env['PATH']}\"\n        # For debugging: Uncomment the following line to print the build command\n        # print(\"Build backend call:\", \" \".join(str(x) for x in cmd), file=sys.stderr)\n        sp.check_call(cmd, cwd=cwd, env=env)\n\n    return _runner\n\n\ndef symlink_unisolated_packages(env: DefaultIsolatedEnv) -> None:\n    pyversion = get_pyversion()\n    site_packages_path = f\"lib/{pyversion}/site-packages\"\n    env_site_packages = Path(env.path) / site_packages_path\n    sysconfigdata_name = get_build_flag(\"SYSCONFIG_NAME\")\n    sysconfigdata_path = (\n        Path(get_build_flag(\"TARGETINSTALLDIR\"))\n        / f\"sysconfigdata/{sysconfigdata_name}.py\"\n    )\n\n    env_site_packages.mkdir(parents=True, exist_ok=True)\n    shutil.copy(sysconfigdata_path, env_site_packages)\n    host_site_packages = Path(get_hostsitepackages())\n    for name in get_unisolated_packages():\n        for path in chain(\n            host_site_packages.glob(f\"{name}*\"), host_site_packages.glob(f\"_{name}*\")\n        ):\n            (env_site_packages / path.name).unlink(missing_ok=True)\n            (env_site_packages / path.name).symlink_to(path)\n\n\ndef remove_avoided_requirements(\n    requires: set[str], avoided_requirements: set[str] | list[str]\n) -> set[str]:\n    for reqstr in list(requires):\n        req = Requirement(reqstr)\n        for avoid_name in set(avoided_requirements):\n            if avoid_name in req.name.lower():\n                requires.remove(reqstr)\n    return requires\n\n\ndef install_reqs(env: DefaultIsolatedEnv, reqs: set[str]) -> None:\n    env.install(\n        remove_avoided_requirements(\n            reqs,\n            get_unisolated_packages() + AVOIDED_REQUIREMENTS,\n        )\n    )\n\n\ndef _build_in_isolated_env(\n    build_env: Mapping[str, str],\n    srcdir: Path,\n    outdir: str,\n    distribution: str,\n    config_settings: ConfigSettingsType,\n) -> str:\n    # For debugging: The following line disables removal of the isolated venv.\n    # It will be left in the /tmp folder and can be inspected or entered as\n    # needed.\n    # _DefaultIsolatedEnv.__exit__ = lambda *args: None\n    with _DefaultIsolatedEnv() as env:\n        builder = _ProjectBuilder.from_isolated_env(\n            env,\n            srcdir,\n            runner=_gen_runner(build_env, env),\n        )\n\n        # first install the build dependencies\n        symlink_unisolated_packages(env)\n        install_reqs(env, builder.build_system_requires)\n        installed_requires_for_build = False\n        try:\n            build_reqs = builder.get_requires_for_build(\n                distribution,\n            )\n        except BuildBackendException:\n            pass\n        else:\n            install_reqs(env, build_reqs)\n            installed_requires_for_build = True\n\n        with common.replace_env(build_env):\n            if not installed_requires_for_build:\n                build_reqs = builder.get_requires_for_build(\n                    distribution,\n                    config_settings,\n                )\n                install_reqs(env, build_reqs)\n\n            return builder.build(distribution, outdir, config_settings)\n\n\ndef parse_backend_flags(backend_flags: str | list[str]) -> ConfigSettingsType:\n    config_settings: dict[str, str | list[str]] = {}\n\n    if isinstance(backend_flags, str):\n        backend_flags = backend_flags.split()\n\n    for arg in backend_flags:\n        setting, _, value = arg.partition(\"=\")\n        if setting not in config_settings:\n            config_settings[setting] = value\n            continue\n\n        cur_value = config_settings[setting]\n        if isinstance(cur_value, str):\n            config_settings[setting] = [cur_value, value]\n        else:\n            cur_value.append(value)\n    return config_settings\n\n\ndef make_command_wrapper_symlinks(symlink_dir: Path) -> dict[str, str]:\n    \"\"\"\n    Create symlinks that make pywasmcross look like a compiler.\n\n    Parameters\n    ----------\n    symlink_dir\n        The directory where the symlinks will be created.\n\n    Returns\n    -------\n    The dictionary of compiler environment variables that points to the symlinks.\n    \"\"\"\n\n    pywasmcross_exe = symlink_dir / \"pywasmcross.py\"\n    shutil.copy2(pywasmcross.__file__, pywasmcross_exe)\n    pywasmcross_exe.chmod(0o755)\n\n    env = {}\n    for symlink in pywasmcross.SYMLINKS:\n        symlink_path = symlink_dir / symlink\n        if os.path.lexists(symlink_path) and not symlink_path.exists():\n            # remove broken symlink so it can be re-created\n            symlink_path.unlink()\n\n        symlink_path.symlink_to(pywasmcross_exe)\n        if symlink in SYMLINK_ENV_VARS:\n            env[SYMLINK_ENV_VARS[symlink]] = str(symlink_path)\n\n    return env\n\n\n@contextmanager\ndef get_build_env(\n    env: dict[str, str],\n    *,\n    pkgname: str,\n    cflags: str,\n    cxxflags: str,\n    ldflags: str,\n    target_install_dir: str,\n    exports: _BuildSpecExports,\n) -> Iterator[dict[str, str]]:\n    \"\"\"\n    Returns a dict of environment variables that should be used when building\n    a package with pypa/build.\n    \"\"\"\n\n    kwargs = dict(\n        pkgname=pkgname,\n        cflags=cflags,\n        cxxflags=cxxflags,\n        ldflags=ldflags,\n        target_install_dir=target_install_dir,\n    )\n\n    args = common.environment_substitute_args(kwargs, env)\n    args[\"builddir\"] = str(Path(\".\").absolute())\n    args[\"exports\"] = exports\n    env = env.copy()\n\n    with TemporaryDirectory() as symlink_dir_str:\n        symlink_dir = Path(symlink_dir_str)\n        env.update(make_command_wrapper_symlinks(symlink_dir))\n\n        sysconfig_dir = Path(get_build_flag(\"TARGETINSTALLDIR\")) / \"sysconfigdata\"\n        args[\"PYTHONPATH\"] = sys.path + [str(sysconfig_dir)]\n        args[\"orig__name__\"] = __name__\n        args[\"pythoninclude\"] = get_build_flag(\"PYTHONINCLUDE\")\n        args[\"PATH\"] = env[\"PATH\"]\n\n        pywasmcross_env = json.dumps(args)\n        # Store into environment variable and to disk. In most cases we will\n        # load from the environment variable but if some other tool filters\n        # environment variables we will load from disk instead.\n        env[\"PYWASMCROSS_ARGS\"] = pywasmcross_env\n        (symlink_dir / \"pywasmcross_env.json\").write_text(pywasmcross_env)\n\n        env[\"_PYTHON_HOST_PLATFORM\"] = platform()\n        env[\"_PYTHON_SYSCONFIGDATA_NAME\"] = get_build_flag(\"SYSCONFIG_NAME\")\n        env[\"PYTHONPATH\"] = str(sysconfig_dir)\n        env[\"COMPILER_WRAPPER_DIR\"] = str(symlink_dir)\n\n        yield env\n\n\ndef build(\n    srcdir: Path,\n    outdir: Path,\n    build_env: Mapping[str, str],\n    config_settings: ConfigSettingsType,\n) -> str:\n    distribution = \"wheel\"\n    try:\n        with _handle_build_error():\n            built = _build_in_isolated_env(\n                build_env, srcdir, str(outdir), distribution, config_settings\n            )\n            print(\"{bold}{green}Successfully built {}{reset}\".format(built, **_STYLES))\n            return built\n    except Exception as e:  # pragma: no cover\n        tb = traceback.format_exc().strip(\"\\n\")\n        print(\"\\n{dim}{}{reset}\\n\".format(tb, **_STYLES))\n        _error(str(e))\n        sys.exit(1)\n", "path": "pyodide-build/pyodide_build/pypabuild.py"}]}