We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Emscripten.cmake missing from pyodide-build wheel
## 🐛 Bug

It seems like `Emscripten.cmake` is not bundled with `pyodide-build` wheels any more (since version 0.24.0) and I wonder whether this is a bug or an intended change. (I haven't found anything in the changelog). We were relying on the presence of the toolchain file over there in https://github.com/igraph/python-igraph/ to build a wasm wheel in CI and it is not working any more.

### To Reproduce

See, e.g., https://github.com/igraph/python-igraph/actions/runs/6459502630/job/17535393423

### Expected behavior

`pyodide-build` wheels should keep on bundling `Emscripten.cmake` so we can simply `pip install pyodide-build` in CI and then build `python-igraph`'s wasm wheels as before.

### Environment

- Pyodide Version<!-- (e.g. 1.8.1) -->: 0.24.1
- Browser version<!-- (e.g. Chrome 95.0.4638.54) -->: not applicable


--- 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/build_env.py`
Content:
```
1 # This file contains functions for managing the Pyodide build environment.
2 
3 import functools
4 import os
5 import re
6 import subprocess
7 import sys
8 from collections.abc import Iterator
9 from contextlib import nullcontext, redirect_stdout
10 from io import StringIO
11 from pathlib import Path
12 
13 if sys.version_info < (3, 11, 0):
14     import tomli as tomllib
15 else:
16     import tomllib
17 
18 from packaging.tags import Tag, compatible_tags, cpython_tags
19 
20 from .common import exit_with_stdio
21 from .logger import logger
22 from .recipe import load_all_recipes
23 
24 RUST_BUILD_PRELUDE = """
25 rustup toolchain install ${RUST_TOOLCHAIN} && rustup default ${RUST_TOOLCHAIN}
26 rustup target add wasm32-unknown-emscripten --toolchain ${RUST_TOOLCHAIN}
27 """
28 
29 
30 BUILD_VARS: set[str] = {
31     "CARGO_BUILD_TARGET",
32     "CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER",
33     "HOME",
34     "HOSTINSTALLDIR",
35     "HOSTSITEPACKAGES",
36     "NUMPY_LIB",
37     "PATH",
38     "PKG_CONFIG_PATH",
39     "PLATFORM_TRIPLET",
40     "PIP_CONSTRAINT",
41     "PYMAJOR",
42     "PYMICRO",
43     "PYMINOR",
44     "PYO3_CROSS_INCLUDE_DIR",
45     "PYO3_CROSS_LIB_DIR",
46     "PYODIDE_EMSCRIPTEN_VERSION",
47     "PYODIDE_JOBS",
48     "PYODIDE_PACKAGE_ABI",
49     "PYODIDE_ROOT",
50     "PYTHON_ARCHIVE_SHA256",
51     "PYTHON_ARCHIVE_URL",
52     "PYTHONINCLUDE",
53     "PYTHONPATH",
54     "PYVERSION",
55     "RUSTFLAGS",
56     "RUST_TOOLCHAIN",
57     "SIDE_MODULE_CFLAGS",
58     "SIDE_MODULE_CXXFLAGS",
59     "SIDE_MODULE_LDFLAGS",
60     "STDLIB_MODULE_CFLAGS",
61     "SYSCONFIGDATA_DIR",
62     "SYSCONFIG_NAME",
63     "TARGETINSTALLDIR",
64     "WASM_LIBRARY_DIR",
65 }
66 
67 
68 def init_environment(*, quiet: bool = False) -> None:
69     """
70     Initialize Pyodide build environment.
71     This function needs to be called before any other Pyodide build functions.
72 
73     Parameters
74     ----------
75     quiet
76         If True, do not print any messages
77     """
78 
79     # Already initialized
80     if "PYODIDE_ROOT" in os.environ:
81         return
82 
83     try:
84         root = search_pyodide_root(Path.cwd())
85     except FileNotFoundError:  # Not in Pyodide tree
86         root = _init_xbuild_env(quiet=quiet)
87 
88     os.environ["PYODIDE_ROOT"] = str(root)
89 
90 
91 def _init_xbuild_env(*, quiet: bool = False) -> Path:
92     """
93     Initialize the build environment for out-of-tree builds.
94 
95     Parameters
96     ----------
97     quiet
98         If True, do not print any messages
99 
100     Returns
101     -------
102         The path to the Pyodide root directory inside the xbuild environment
103     """
104     from . import install_xbuildenv  # avoid circular import
105 
106     # TODO: Do not hardcode the path
107     # TODO: Add version numbers to the path
108     xbuildenv_path = Path(".pyodide-xbuildenv").resolve()
109 
110     context = redirect_stdout(StringIO()) if quiet else nullcontext()
111     with context:
112         return install_xbuildenv.install(xbuildenv_path, download=True)
113 
114 
115 @functools.cache
116 def get_pyodide_root() -> Path:
117     init_environment()
118     return Path(os.environ["PYODIDE_ROOT"])
119 
120 
121 def search_pyodide_root(curdir: str | Path, *, max_depth: int = 5) -> Path:
122     """
123     Recursively search for the root of the Pyodide repository,
124     by looking for the pyproject.toml file in the parent directories
125     which contains [tool.pyodide] section.
126     """
127 
128     # We want to include "curdir" in parent_dirs, so add a garbage suffix
129     parent_dirs = (Path(curdir) / "garbage").parents[:max_depth]
130 
131     for base in parent_dirs:
132         pyproject_file = base / "pyproject.toml"
133 
134         if not pyproject_file.is_file():
135             continue
136 
137         try:
138             with pyproject_file.open("rb") as f:
139                 configs = tomllib.load(f)
140         except tomllib.TOMLDecodeError as e:
141             raise ValueError(f"Could not parse {pyproject_file}.") from e
142 
143         if "tool" in configs and "pyodide" in configs["tool"]:
144             return base
145 
146     raise FileNotFoundError(
147         "Could not find Pyodide root directory. If you are not in the Pyodide directory, set `PYODIDE_ROOT=<pyodide-root-directory>`."
148     )
149 
150 
151 def in_xbuildenv() -> bool:
152     pyodide_root = get_pyodide_root()
153     return pyodide_root.name == "pyodide-root"
154 
155 
156 @functools.cache
157 def get_build_environment_vars() -> dict[str, str]:
158     """
159     Get common environment variables for the in-tree and out-of-tree build.
160     """
161     env = _get_make_environment_vars().copy()
162 
163     # Allow users to overwrite the build environment variables by setting
164     # host environment variables.
165     # TODO: Add modifiable configuration file instead.
166     # (https://github.com/pyodide/pyodide/pull/3737/files#r1161247201)
167     env.update({key: os.environ[key] for key in BUILD_VARS if key in os.environ})
168     env["PYODIDE"] = "1"
169 
170     tools_dir = Path(__file__).parent / "tools"
171 
172     env["CMAKE_TOOLCHAIN_FILE"] = str(
173         tools_dir / "cmake/Modules/Platform/Emscripten.cmake"
174     )
175     env["PYO3_CONFIG_FILE"] = str(tools_dir / "pyo3_config.ini")
176 
177     hostsitepackages = env["HOSTSITEPACKAGES"]
178     pythonpath = [
179         hostsitepackages,
180     ]
181     env["PYTHONPATH"] = ":".join(pythonpath)
182 
183     return env
184 
185 
186 def _get_make_environment_vars(*, pyodide_root: Path | None = None) -> dict[str, str]:
187     """Load environment variables from Makefile.envs
188 
189     This allows us to set all build vars in one place
190 
191     Parameters
192     ----------
193     pyodide_root
194         The root directory of the Pyodide repository. If None, this will be inferred.
195     """
196 
197     PYODIDE_ROOT = get_pyodide_root() if pyodide_root is None else pyodide_root
198     environment = {}
199     result = subprocess.run(
200         ["make", "-f", str(PYODIDE_ROOT / "Makefile.envs"), ".output_vars"],
201         capture_output=True,
202         text=True,
203         env={"PYODIDE_ROOT": str(PYODIDE_ROOT)},
204     )
205 
206     if result.returncode != 0:
207         logger.error("ERROR: Failed to load environment variables from Makefile.envs")
208         exit_with_stdio(result)
209 
210     for line in result.stdout.splitlines():
211         equalPos = line.find("=")
212         if equalPos != -1:
213             varname = line[0:equalPos]
214 
215             if varname not in BUILD_VARS:
216                 continue
217 
218             value = line[equalPos + 1 :]
219             value = value.strip("'").strip()
220             environment[varname] = value
221     return environment
222 
223 
224 def get_build_flag(name: str) -> str:
225     """
226     Get a value of a build flag.
227     """
228     build_vars = get_build_environment_vars()
229     if name not in build_vars:
230         raise ValueError(f"Unknown build flag: {name}")
231 
232     return build_vars[name]
233 
234 
235 def get_pyversion_major() -> str:
236     return get_build_flag("PYMAJOR")
237 
238 
239 def get_pyversion_minor() -> str:
240     return get_build_flag("PYMINOR")
241 
242 
243 def get_pyversion_major_minor() -> str:
244     return f"{get_pyversion_major()}.{get_pyversion_minor()}"
245 
246 
247 def get_pyversion() -> str:
248     return f"python{get_pyversion_major_minor()}"
249 
250 
251 def get_hostsitepackages() -> str:
252     return get_build_flag("HOSTSITEPACKAGES")
253 
254 
255 @functools.cache
256 def get_unisolated_packages() -> list[str]:
257     PYODIDE_ROOT = get_pyodide_root()
258 
259     unisolated_file = PYODIDE_ROOT / "unisolated.txt"
260     if unisolated_file.exists():
261         # in xbuild env, read from file
262         unisolated_packages = unisolated_file.read_text().splitlines()
263     else:
264         unisolated_packages = []
265         recipe_dir = PYODIDE_ROOT / "packages"
266         recipes = load_all_recipes(recipe_dir)
267         for name, config in recipes.items():
268             if config.build.cross_build_env:
269                 unisolated_packages.append(name)
270 
271     return unisolated_packages
272 
273 
274 def platform() -> str:
275     emscripten_version = get_build_flag("PYODIDE_EMSCRIPTEN_VERSION")
276     version = emscripten_version.replace(".", "_")
277     return f"emscripten_{version}_wasm32"
278 
279 
280 def pyodide_tags() -> Iterator[Tag]:
281     """
282     Returns the sequence of tag triples for the Pyodide interpreter.
283 
284     The sequence is ordered in decreasing specificity.
285     """
286     PYMAJOR = get_pyversion_major()
287     PYMINOR = get_pyversion_minor()
288     PLATFORM = platform()
289     python_version = (int(PYMAJOR), int(PYMINOR))
290     yield from cpython_tags(platforms=[PLATFORM], python_version=python_version)
291     yield from compatible_tags(platforms=[PLATFORM], python_version=python_version)
292     # Following line can be removed once packaging 22.0 is released and we update to it.
293     yield Tag(interpreter=f"cp{PYMAJOR}{PYMINOR}", abi="none", platform="any")
294 
295 
296 def replace_so_abi_tags(wheel_dir: Path) -> None:
297     """Replace native abi tag with emscripten abi tag in .so file names"""
298     import sysconfig
299 
300     build_soabi = sysconfig.get_config_var("SOABI")
301     assert build_soabi
302     ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
303     assert ext_suffix
304     build_triplet = "-".join(build_soabi.split("-")[2:])
305     host_triplet = get_build_flag("PLATFORM_TRIPLET")
306     for file in wheel_dir.glob(f"**/*{ext_suffix}"):
307         file.rename(file.with_name(file.name.replace(build_triplet, host_triplet)))
308 
309 
310 def emscripten_version() -> str:
311     return get_build_flag("PYODIDE_EMSCRIPTEN_VERSION")
312 
313 
314 def get_emscripten_version_info() -> str:
315     """Extracted for testing purposes."""
316     return subprocess.run(["emcc", "-v"], capture_output=True, encoding="utf8").stderr
317 
318 
319 def check_emscripten_version() -> None:
320     needed_version = emscripten_version()
321     try:
322         version_info = get_emscripten_version_info()
323     except FileNotFoundError:
324         raise RuntimeError(
325             f"No Emscripten compiler found. Need Emscripten version {needed_version}"
326         ) from None
327     installed_version = None
328     try:
329         for x in reversed(version_info.partition("\n")[0].split(" ")):
330             if re.match(r"[0-9]+\.[0-9]+\.[0-9]+", x):
331                 installed_version = x
332                 break
333     except Exception:
334         raise RuntimeError("Failed to determine Emscripten version.") from None
335     if installed_version is None:
336         raise RuntimeError("Failed to determine Emscripten version.")
337     if installed_version != needed_version:
338         raise RuntimeError(
339             f"Incorrect Emscripten version {installed_version}. Need Emscripten version {needed_version}"
340         )
341
```
--- 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/build_env.py b/pyodide-build/pyodide_build/build_env.py
--- a/pyodide-build/pyodide_build/build_env.py
+++ b/pyodide-build/pyodide_build/build_env.py
@@ -62,6 +62,8 @@
     "SYSCONFIG_NAME",
     "TARGETINSTALLDIR",
     "WASM_LIBRARY_DIR",
+    "CMAKE_TOOLCHAIN_FILE",
+    "PYO3_CONFIG_FILE",
 }
 
 
@@ -169,10 +171,13 @@
 
     tools_dir = Path(__file__).parent / "tools"
 
-    env["CMAKE_TOOLCHAIN_FILE"] = str(
-        tools_dir / "cmake/Modules/Platform/Emscripten.cmake"
-    )
-    env["PYO3_CONFIG_FILE"] = str(tools_dir / "pyo3_config.ini")
+    if "CMAKE_TOOLCHAIN_FILE" not in env:
+        env["CMAKE_TOOLCHAIN_FILE"] = str(
+            tools_dir / "cmake/Modules/Platform/Emscripten.cmake"
+        )
+
+    if "PYO3_CONFIG_FILE" not in env:
+        env["PYO3_CONFIG_FILE"] = str(tools_dir / "pyo3_config.ini")
 
     hostsitepackages = env["HOSTSITEPACKAGES"]
     pythonpath = [

{"golden_diff": "diff --git a/pyodide-build/pyodide_build/build_env.py b/pyodide-build/pyodide_build/build_env.py\n--- a/pyodide-build/pyodide_build/build_env.py\n+++ b/pyodide-build/pyodide_build/build_env.py\n@@ -62,6 +62,8 @@\n     \"SYSCONFIG_NAME\",\n     \"TARGETINSTALLDIR\",\n     \"WASM_LIBRARY_DIR\",\n+    \"CMAKE_TOOLCHAIN_FILE\",\n+    \"PYO3_CONFIG_FILE\",\n }\n \n \n@@ -169,10 +171,13 @@\n \n     tools_dir = Path(__file__).parent / \"tools\"\n \n-    env[\"CMAKE_TOOLCHAIN_FILE\"] = str(\n-        tools_dir / \"cmake/Modules/Platform/Emscripten.cmake\"\n-    )\n-    env[\"PYO3_CONFIG_FILE\"] = str(tools_dir / \"pyo3_config.ini\")\n+    if \"CMAKE_TOOLCHAIN_FILE\" not in env:\n+        env[\"CMAKE_TOOLCHAIN_FILE\"] = str(\n+            tools_dir / \"cmake/Modules/Platform/Emscripten.cmake\"\n+        )\n+\n+    if \"PYO3_CONFIG_FILE\" not in env:\n+        env[\"PYO3_CONFIG_FILE\"] = str(tools_dir / \"pyo3_config.ini\")\n \n     hostsitepackages = env[\"HOSTSITEPACKAGES\"]\n     pythonpath = [\n", "issue": "Emscripten.cmake missing from pyodide-build wheel\n## \ud83d\udc1b Bug\r\n\r\nIt seems like `Emscripten.cmake` is not bundled with `pyodide-build` wheels any more (since version 0.24.0) and I wonder whether this is a bug or an intended change. (I haven't found anything in the changelog). We were relying on the presence of the toolchain file over there in https://github.com/igraph/python-igraph/ to build a wasm wheel in CI and it is not working any more.\r\n\r\n### To Reproduce\r\n\r\nSee, e.g., https://github.com/igraph/python-igraph/actions/runs/6459502630/job/17535393423\r\n\r\n### Expected behavior\r\n\r\n`pyodide-build` wheels should keep on bundling `Emscripten.cmake` so we can simply `pip install pyodide-build` in CI and then build `python-igraph`'s wasm wheels as before.\r\n\r\n### Environment\r\n\r\n- Pyodide Version<!-- (e.g. 1.8.1) -->: 0.24.1\r\n- Browser version<!-- (e.g. Chrome 95.0.4638.54) -->: not applicable\r\n\n", "before_files": [{"content": "# This file contains functions for managing the Pyodide build environment.\n\nimport functools\nimport os\nimport re\nimport subprocess\nimport sys\nfrom collections.abc import Iterator\nfrom contextlib import nullcontext, redirect_stdout\nfrom io import StringIO\nfrom pathlib import Path\n\nif sys.version_info < (3, 11, 0):\n    import tomli as tomllib\nelse:\n    import tomllib\n\nfrom packaging.tags import Tag, compatible_tags, cpython_tags\n\nfrom .common import exit_with_stdio\nfrom .logger import logger\nfrom .recipe import load_all_recipes\n\nRUST_BUILD_PRELUDE = \"\"\"\nrustup toolchain install ${RUST_TOOLCHAIN} && rustup default ${RUST_TOOLCHAIN}\nrustup target add wasm32-unknown-emscripten --toolchain ${RUST_TOOLCHAIN}\n\"\"\"\n\n\nBUILD_VARS: set[str] = {\n    \"CARGO_BUILD_TARGET\",\n    \"CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER\",\n    \"HOME\",\n    \"HOSTINSTALLDIR\",\n    \"HOSTSITEPACKAGES\",\n    \"NUMPY_LIB\",\n    \"PATH\",\n    \"PKG_CONFIG_PATH\",\n    \"PLATFORM_TRIPLET\",\n    \"PIP_CONSTRAINT\",\n    \"PYMAJOR\",\n    \"PYMICRO\",\n    \"PYMINOR\",\n    \"PYO3_CROSS_INCLUDE_DIR\",\n    \"PYO3_CROSS_LIB_DIR\",\n    \"PYODIDE_EMSCRIPTEN_VERSION\",\n    \"PYODIDE_JOBS\",\n    \"PYODIDE_PACKAGE_ABI\",\n    \"PYODIDE_ROOT\",\n    \"PYTHON_ARCHIVE_SHA256\",\n    \"PYTHON_ARCHIVE_URL\",\n    \"PYTHONINCLUDE\",\n    \"PYTHONPATH\",\n    \"PYVERSION\",\n    \"RUSTFLAGS\",\n    \"RUST_TOOLCHAIN\",\n    \"SIDE_MODULE_CFLAGS\",\n    \"SIDE_MODULE_CXXFLAGS\",\n    \"SIDE_MODULE_LDFLAGS\",\n    \"STDLIB_MODULE_CFLAGS\",\n    \"SYSCONFIGDATA_DIR\",\n    \"SYSCONFIG_NAME\",\n    \"TARGETINSTALLDIR\",\n    \"WASM_LIBRARY_DIR\",\n}\n\n\ndef init_environment(*, quiet: bool = False) -> None:\n    \"\"\"\n    Initialize Pyodide build environment.\n    This function needs to be called before any other Pyodide build functions.\n\n    Parameters\n    ----------\n    quiet\n        If True, do not print any messages\n    \"\"\"\n\n    # Already initialized\n    if \"PYODIDE_ROOT\" in os.environ:\n        return\n\n    try:\n        root = search_pyodide_root(Path.cwd())\n    except FileNotFoundError:  # Not in Pyodide tree\n        root = _init_xbuild_env(quiet=quiet)\n\n    os.environ[\"PYODIDE_ROOT\"] = str(root)\n\n\ndef _init_xbuild_env(*, quiet: bool = False) -> Path:\n    \"\"\"\n    Initialize the build environment for out-of-tree builds.\n\n    Parameters\n    ----------\n    quiet\n        If True, do not print any messages\n\n    Returns\n    -------\n        The path to the Pyodide root directory inside the xbuild environment\n    \"\"\"\n    from . import install_xbuildenv  # avoid circular import\n\n    # TODO: Do not hardcode the path\n    # TODO: Add version numbers to the path\n    xbuildenv_path = Path(\".pyodide-xbuildenv\").resolve()\n\n    context = redirect_stdout(StringIO()) if quiet else nullcontext()\n    with context:\n        return install_xbuildenv.install(xbuildenv_path, download=True)\n\n\n@functools.cache\ndef get_pyodide_root() -> Path:\n    init_environment()\n    return Path(os.environ[\"PYODIDE_ROOT\"])\n\n\ndef search_pyodide_root(curdir: str | Path, *, max_depth: int = 5) -> Path:\n    \"\"\"\n    Recursively search for the root of the Pyodide repository,\n    by looking for the pyproject.toml file in the parent directories\n    which contains [tool.pyodide] section.\n    \"\"\"\n\n    # We want to include \"curdir\" in parent_dirs, so add a garbage suffix\n    parent_dirs = (Path(curdir) / \"garbage\").parents[:max_depth]\n\n    for base in parent_dirs:\n        pyproject_file = base / \"pyproject.toml\"\n\n        if not pyproject_file.is_file():\n            continue\n\n        try:\n            with pyproject_file.open(\"rb\") as f:\n                configs = tomllib.load(f)\n        except tomllib.TOMLDecodeError as e:\n            raise ValueError(f\"Could not parse {pyproject_file}.\") from e\n\n        if \"tool\" in configs and \"pyodide\" in configs[\"tool\"]:\n            return base\n\n    raise FileNotFoundError(\n        \"Could not find Pyodide root directory. If you are not in the Pyodide directory, set `PYODIDE_ROOT=<pyodide-root-directory>`.\"\n    )\n\n\ndef in_xbuildenv() -> bool:\n    pyodide_root = get_pyodide_root()\n    return pyodide_root.name == \"pyodide-root\"\n\n\n@functools.cache\ndef get_build_environment_vars() -> dict[str, str]:\n    \"\"\"\n    Get common environment variables for the in-tree and out-of-tree build.\n    \"\"\"\n    env = _get_make_environment_vars().copy()\n\n    # Allow users to overwrite the build environment variables by setting\n    # host environment variables.\n    # TODO: Add modifiable configuration file instead.\n    # (https://github.com/pyodide/pyodide/pull/3737/files#r1161247201)\n    env.update({key: os.environ[key] for key in BUILD_VARS if key in os.environ})\n    env[\"PYODIDE\"] = \"1\"\n\n    tools_dir = Path(__file__).parent / \"tools\"\n\n    env[\"CMAKE_TOOLCHAIN_FILE\"] = str(\n        tools_dir / \"cmake/Modules/Platform/Emscripten.cmake\"\n    )\n    env[\"PYO3_CONFIG_FILE\"] = str(tools_dir / \"pyo3_config.ini\")\n\n    hostsitepackages = env[\"HOSTSITEPACKAGES\"]\n    pythonpath = [\n        hostsitepackages,\n    ]\n    env[\"PYTHONPATH\"] = \":\".join(pythonpath)\n\n    return env\n\n\ndef _get_make_environment_vars(*, pyodide_root: Path | None = None) -> dict[str, str]:\n    \"\"\"Load environment variables from Makefile.envs\n\n    This allows us to set all build vars in one place\n\n    Parameters\n    ----------\n    pyodide_root\n        The root directory of the Pyodide repository. If None, this will be inferred.\n    \"\"\"\n\n    PYODIDE_ROOT = get_pyodide_root() if pyodide_root is None else pyodide_root\n    environment = {}\n    result = subprocess.run(\n        [\"make\", \"-f\", str(PYODIDE_ROOT / \"Makefile.envs\"), \".output_vars\"],\n        capture_output=True,\n        text=True,\n        env={\"PYODIDE_ROOT\": str(PYODIDE_ROOT)},\n    )\n\n    if result.returncode != 0:\n        logger.error(\"ERROR: Failed to load environment variables from Makefile.envs\")\n        exit_with_stdio(result)\n\n    for line in result.stdout.splitlines():\n        equalPos = line.find(\"=\")\n        if equalPos != -1:\n            varname = line[0:equalPos]\n\n            if varname not in BUILD_VARS:\n                continue\n\n            value = line[equalPos + 1 :]\n            value = value.strip(\"'\").strip()\n            environment[varname] = value\n    return environment\n\n\ndef get_build_flag(name: str) -> str:\n    \"\"\"\n    Get a value of a build flag.\n    \"\"\"\n    build_vars = get_build_environment_vars()\n    if name not in build_vars:\n        raise ValueError(f\"Unknown build flag: {name}\")\n\n    return build_vars[name]\n\n\ndef get_pyversion_major() -> str:\n    return get_build_flag(\"PYMAJOR\")\n\n\ndef get_pyversion_minor() -> str:\n    return get_build_flag(\"PYMINOR\")\n\n\ndef get_pyversion_major_minor() -> str:\n    return f\"{get_pyversion_major()}.{get_pyversion_minor()}\"\n\n\ndef get_pyversion() -> str:\n    return f\"python{get_pyversion_major_minor()}\"\n\n\ndef get_hostsitepackages() -> str:\n    return get_build_flag(\"HOSTSITEPACKAGES\")\n\n\n@functools.cache\ndef get_unisolated_packages() -> list[str]:\n    PYODIDE_ROOT = get_pyodide_root()\n\n    unisolated_file = PYODIDE_ROOT / \"unisolated.txt\"\n    if unisolated_file.exists():\n        # in xbuild env, read from file\n        unisolated_packages = unisolated_file.read_text().splitlines()\n    else:\n        unisolated_packages = []\n        recipe_dir = PYODIDE_ROOT / \"packages\"\n        recipes = load_all_recipes(recipe_dir)\n        for name, config in recipes.items():\n            if config.build.cross_build_env:\n                unisolated_packages.append(name)\n\n    return unisolated_packages\n\n\ndef platform() -> str:\n    emscripten_version = get_build_flag(\"PYODIDE_EMSCRIPTEN_VERSION\")\n    version = emscripten_version.replace(\".\", \"_\")\n    return f\"emscripten_{version}_wasm32\"\n\n\ndef pyodide_tags() -> Iterator[Tag]:\n    \"\"\"\n    Returns the sequence of tag triples for the Pyodide interpreter.\n\n    The sequence is ordered in decreasing specificity.\n    \"\"\"\n    PYMAJOR = get_pyversion_major()\n    PYMINOR = get_pyversion_minor()\n    PLATFORM = platform()\n    python_version = (int(PYMAJOR), int(PYMINOR))\n    yield from cpython_tags(platforms=[PLATFORM], python_version=python_version)\n    yield from compatible_tags(platforms=[PLATFORM], python_version=python_version)\n    # Following line can be removed once packaging 22.0 is released and we update to it.\n    yield Tag(interpreter=f\"cp{PYMAJOR}{PYMINOR}\", abi=\"none\", platform=\"any\")\n\n\ndef replace_so_abi_tags(wheel_dir: Path) -> None:\n    \"\"\"Replace native abi tag with emscripten abi tag in .so file names\"\"\"\n    import sysconfig\n\n    build_soabi = sysconfig.get_config_var(\"SOABI\")\n    assert build_soabi\n    ext_suffix = sysconfig.get_config_var(\"EXT_SUFFIX\")\n    assert ext_suffix\n    build_triplet = \"-\".join(build_soabi.split(\"-\")[2:])\n    host_triplet = get_build_flag(\"PLATFORM_TRIPLET\")\n    for file in wheel_dir.glob(f\"**/*{ext_suffix}\"):\n        file.rename(file.with_name(file.name.replace(build_triplet, host_triplet)))\n\n\ndef emscripten_version() -> str:\n    return get_build_flag(\"PYODIDE_EMSCRIPTEN_VERSION\")\n\n\ndef get_emscripten_version_info() -> str:\n    \"\"\"Extracted for testing purposes.\"\"\"\n    return subprocess.run([\"emcc\", \"-v\"], capture_output=True, encoding=\"utf8\").stderr\n\n\ndef check_emscripten_version() -> None:\n    needed_version = emscripten_version()\n    try:\n        version_info = get_emscripten_version_info()\n    except FileNotFoundError:\n        raise RuntimeError(\n            f\"No Emscripten compiler found. Need Emscripten version {needed_version}\"\n        ) from None\n    installed_version = None\n    try:\n        for x in reversed(version_info.partition(\"\\n\")[0].split(\" \")):\n            if re.match(r\"[0-9]+\\.[0-9]+\\.[0-9]+\", x):\n                installed_version = x\n                break\n    except Exception:\n        raise RuntimeError(\"Failed to determine Emscripten version.\") from None\n    if installed_version is None:\n        raise RuntimeError(\"Failed to determine Emscripten version.\")\n    if installed_version != needed_version:\n        raise RuntimeError(\n            f\"Incorrect Emscripten version {installed_version}. Need Emscripten version {needed_version}\"\n        )\n", "path": "pyodide-build/pyodide_build/build_env.py"}], "after_files": [{"content": "# This file contains functions for managing the Pyodide build environment.\n\nimport functools\nimport os\nimport re\nimport subprocess\nimport sys\nfrom collections.abc import Iterator\nfrom contextlib import nullcontext, redirect_stdout\nfrom io import StringIO\nfrom pathlib import Path\n\nif sys.version_info < (3, 11, 0):\n    import tomli as tomllib\nelse:\n    import tomllib\n\nfrom packaging.tags import Tag, compatible_tags, cpython_tags\n\nfrom .common import exit_with_stdio\nfrom .logger import logger\nfrom .recipe import load_all_recipes\n\nRUST_BUILD_PRELUDE = \"\"\"\nrustup toolchain install ${RUST_TOOLCHAIN} && rustup default ${RUST_TOOLCHAIN}\nrustup target add wasm32-unknown-emscripten --toolchain ${RUST_TOOLCHAIN}\n\"\"\"\n\n\nBUILD_VARS: set[str] = {\n    \"CARGO_BUILD_TARGET\",\n    \"CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER\",\n    \"HOME\",\n    \"HOSTINSTALLDIR\",\n    \"HOSTSITEPACKAGES\",\n    \"NUMPY_LIB\",\n    \"PATH\",\n    \"PKG_CONFIG_PATH\",\n    \"PLATFORM_TRIPLET\",\n    \"PIP_CONSTRAINT\",\n    \"PYMAJOR\",\n    \"PYMICRO\",\n    \"PYMINOR\",\n    \"PYO3_CROSS_INCLUDE_DIR\",\n    \"PYO3_CROSS_LIB_DIR\",\n    \"PYODIDE_EMSCRIPTEN_VERSION\",\n    \"PYODIDE_JOBS\",\n    \"PYODIDE_PACKAGE_ABI\",\n    \"PYODIDE_ROOT\",\n    \"PYTHON_ARCHIVE_SHA256\",\n    \"PYTHON_ARCHIVE_URL\",\n    \"PYTHONINCLUDE\",\n    \"PYTHONPATH\",\n    \"PYVERSION\",\n    \"RUSTFLAGS\",\n    \"RUST_TOOLCHAIN\",\n    \"SIDE_MODULE_CFLAGS\",\n    \"SIDE_MODULE_CXXFLAGS\",\n    \"SIDE_MODULE_LDFLAGS\",\n    \"STDLIB_MODULE_CFLAGS\",\n    \"SYSCONFIGDATA_DIR\",\n    \"SYSCONFIG_NAME\",\n    \"TARGETINSTALLDIR\",\n    \"WASM_LIBRARY_DIR\",\n    \"CMAKE_TOOLCHAIN_FILE\",\n    \"PYO3_CONFIG_FILE\",\n}\n\n\ndef init_environment(*, quiet: bool = False) -> None:\n    \"\"\"\n    Initialize Pyodide build environment.\n    This function needs to be called before any other Pyodide build functions.\n\n    Parameters\n    ----------\n    quiet\n        If True, do not print any messages\n    \"\"\"\n\n    # Already initialized\n    if \"PYODIDE_ROOT\" in os.environ:\n        return\n\n    try:\n        root = search_pyodide_root(Path.cwd())\n    except FileNotFoundError:  # Not in Pyodide tree\n        root = _init_xbuild_env(quiet=quiet)\n\n    os.environ[\"PYODIDE_ROOT\"] = str(root)\n\n\ndef _init_xbuild_env(*, quiet: bool = False) -> Path:\n    \"\"\"\n    Initialize the build environment for out-of-tree builds.\n\n    Parameters\n    ----------\n    quiet\n        If True, do not print any messages\n\n    Returns\n    -------\n        The path to the Pyodide root directory inside the xbuild environment\n    \"\"\"\n    from . import install_xbuildenv  # avoid circular import\n\n    # TODO: Do not hardcode the path\n    # TODO: Add version numbers to the path\n    xbuildenv_path = Path(\".pyodide-xbuildenv\").resolve()\n\n    context = redirect_stdout(StringIO()) if quiet else nullcontext()\n    with context:\n        return install_xbuildenv.install(xbuildenv_path, download=True)\n\n\n@functools.cache\ndef get_pyodide_root() -> Path:\n    init_environment()\n    return Path(os.environ[\"PYODIDE_ROOT\"])\n\n\ndef search_pyodide_root(curdir: str | Path, *, max_depth: int = 5) -> Path:\n    \"\"\"\n    Recursively search for the root of the Pyodide repository,\n    by looking for the pyproject.toml file in the parent directories\n    which contains [tool.pyodide] section.\n    \"\"\"\n\n    # We want to include \"curdir\" in parent_dirs, so add a garbage suffix\n    parent_dirs = (Path(curdir) / \"garbage\").parents[:max_depth]\n\n    for base in parent_dirs:\n        pyproject_file = base / \"pyproject.toml\"\n\n        if not pyproject_file.is_file():\n            continue\n\n        try:\n            with pyproject_file.open(\"rb\") as f:\n                configs = tomllib.load(f)\n        except tomllib.TOMLDecodeError as e:\n            raise ValueError(f\"Could not parse {pyproject_file}.\") from e\n\n        if \"tool\" in configs and \"pyodide\" in configs[\"tool\"]:\n            return base\n\n    raise FileNotFoundError(\n        \"Could not find Pyodide root directory. If you are not in the Pyodide directory, set `PYODIDE_ROOT=<pyodide-root-directory>`.\"\n    )\n\n\ndef in_xbuildenv() -> bool:\n    pyodide_root = get_pyodide_root()\n    return pyodide_root.name == \"pyodide-root\"\n\n\n@functools.cache\ndef get_build_environment_vars() -> dict[str, str]:\n    \"\"\"\n    Get common environment variables for the in-tree and out-of-tree build.\n    \"\"\"\n    env = _get_make_environment_vars().copy()\n\n    # Allow users to overwrite the build environment variables by setting\n    # host environment variables.\n    # TODO: Add modifiable configuration file instead.\n    # (https://github.com/pyodide/pyodide/pull/3737/files#r1161247201)\n    env.update({key: os.environ[key] for key in BUILD_VARS if key in os.environ})\n    env[\"PYODIDE\"] = \"1\"\n\n    tools_dir = Path(__file__).parent / \"tools\"\n\n    if \"CMAKE_TOOLCHAIN_FILE\" not in env:\n        env[\"CMAKE_TOOLCHAIN_FILE\"] = str(\n            tools_dir / \"cmake/Modules/Platform/Emscripten.cmake\"\n        )\n\n    if \"PYO3_CONFIG_FILE\" not in env:\n        env[\"PYO3_CONFIG_FILE\"] = str(tools_dir / \"pyo3_config.ini\")\n\n    hostsitepackages = env[\"HOSTSITEPACKAGES\"]\n    pythonpath = [\n        hostsitepackages,\n    ]\n    env[\"PYTHONPATH\"] = \":\".join(pythonpath)\n\n    return env\n\n\ndef _get_make_environment_vars(*, pyodide_root: Path | None = None) -> dict[str, str]:\n    \"\"\"Load environment variables from Makefile.envs\n\n    This allows us to set all build vars in one place\n\n    Parameters\n    ----------\n    pyodide_root\n        The root directory of the Pyodide repository. If None, this will be inferred.\n    \"\"\"\n\n    PYODIDE_ROOT = get_pyodide_root() if pyodide_root is None else pyodide_root\n    environment = {}\n    result = subprocess.run(\n        [\"make\", \"-f\", str(PYODIDE_ROOT / \"Makefile.envs\"), \".output_vars\"],\n        capture_output=True,\n        text=True,\n        env={\"PYODIDE_ROOT\": str(PYODIDE_ROOT)},\n    )\n\n    if result.returncode != 0:\n        logger.error(\"ERROR: Failed to load environment variables from Makefile.envs\")\n        exit_with_stdio(result)\n\n    for line in result.stdout.splitlines():\n        equalPos = line.find(\"=\")\n        if equalPos != -1:\n            varname = line[0:equalPos]\n\n            if varname not in BUILD_VARS:\n                continue\n\n            value = line[equalPos + 1 :]\n            value = value.strip(\"'\").strip()\n            environment[varname] = value\n    return environment\n\n\ndef get_build_flag(name: str) -> str:\n    \"\"\"\n    Get a value of a build flag.\n    \"\"\"\n    build_vars = get_build_environment_vars()\n    if name not in build_vars:\n        raise ValueError(f\"Unknown build flag: {name}\")\n\n    return build_vars[name]\n\n\ndef get_pyversion_major() -> str:\n    return get_build_flag(\"PYMAJOR\")\n\n\ndef get_pyversion_minor() -> str:\n    return get_build_flag(\"PYMINOR\")\n\n\ndef get_pyversion_major_minor() -> str:\n    return f\"{get_pyversion_major()}.{get_pyversion_minor()}\"\n\n\ndef get_pyversion() -> str:\n    return f\"python{get_pyversion_major_minor()}\"\n\n\ndef get_hostsitepackages() -> str:\n    return get_build_flag(\"HOSTSITEPACKAGES\")\n\n\n@functools.cache\ndef get_unisolated_packages() -> list[str]:\n    PYODIDE_ROOT = get_pyodide_root()\n\n    unisolated_file = PYODIDE_ROOT / \"unisolated.txt\"\n    if unisolated_file.exists():\n        # in xbuild env, read from file\n        unisolated_packages = unisolated_file.read_text().splitlines()\n    else:\n        unisolated_packages = []\n        recipe_dir = PYODIDE_ROOT / \"packages\"\n        recipes = load_all_recipes(recipe_dir)\n        for name, config in recipes.items():\n            if config.build.cross_build_env:\n                unisolated_packages.append(name)\n\n    return unisolated_packages\n\n\ndef platform() -> str:\n    emscripten_version = get_build_flag(\"PYODIDE_EMSCRIPTEN_VERSION\")\n    version = emscripten_version.replace(\".\", \"_\")\n    return f\"emscripten_{version}_wasm32\"\n\n\ndef pyodide_tags() -> Iterator[Tag]:\n    \"\"\"\n    Returns the sequence of tag triples for the Pyodide interpreter.\n\n    The sequence is ordered in decreasing specificity.\n    \"\"\"\n    PYMAJOR = get_pyversion_major()\n    PYMINOR = get_pyversion_minor()\n    PLATFORM = platform()\n    python_version = (int(PYMAJOR), int(PYMINOR))\n    yield from cpython_tags(platforms=[PLATFORM], python_version=python_version)\n    yield from compatible_tags(platforms=[PLATFORM], python_version=python_version)\n    # Following line can be removed once packaging 22.0 is released and we update to it.\n    yield Tag(interpreter=f\"cp{PYMAJOR}{PYMINOR}\", abi=\"none\", platform=\"any\")\n\n\ndef replace_so_abi_tags(wheel_dir: Path) -> None:\n    \"\"\"Replace native abi tag with emscripten abi tag in .so file names\"\"\"\n    import sysconfig\n\n    build_soabi = sysconfig.get_config_var(\"SOABI\")\n    assert build_soabi\n    ext_suffix = sysconfig.get_config_var(\"EXT_SUFFIX\")\n    assert ext_suffix\n    build_triplet = \"-\".join(build_soabi.split(\"-\")[2:])\n    host_triplet = get_build_flag(\"PLATFORM_TRIPLET\")\n    for file in wheel_dir.glob(f\"**/*{ext_suffix}\"):\n        file.rename(file.with_name(file.name.replace(build_triplet, host_triplet)))\n\n\ndef emscripten_version() -> str:\n    return get_build_flag(\"PYODIDE_EMSCRIPTEN_VERSION\")\n\n\ndef get_emscripten_version_info() -> str:\n    \"\"\"Extracted for testing purposes.\"\"\"\n    return subprocess.run([\"emcc\", \"-v\"], capture_output=True, encoding=\"utf8\").stderr\n\n\ndef check_emscripten_version() -> None:\n    needed_version = emscripten_version()\n    try:\n        version_info = get_emscripten_version_info()\n    except FileNotFoundError:\n        raise RuntimeError(\n            f\"No Emscripten compiler found. Need Emscripten version {needed_version}\"\n        ) from None\n    installed_version = None\n    try:\n        for x in reversed(version_info.partition(\"\\n\")[0].split(\" \")):\n            if re.match(r\"[0-9]+\\.[0-9]+\\.[0-9]+\", x):\n                installed_version = x\n                break\n    except Exception:\n        raise RuntimeError(\"Failed to determine Emscripten version.\") from None\n    if installed_version is None:\n        raise RuntimeError(\"Failed to determine Emscripten version.\")\n    if installed_version != needed_version:\n        raise RuntimeError(\n            f\"Incorrect Emscripten version {installed_version}. Need Emscripten version {needed_version}\"\n        )\n", "path": "pyodide-build/pyodide_build/build_env.py"}]}