Repository: pyodide/pyodide

Issue:
Compilation error with out-of-tree builds with 0.24.0a1: LONG_BIT definition appears wrong for platform
## 🐛 Bug

Trying to use out-of-tree builds with 0.24.0a1 yields a compilation error: LONG_BIT definition appears wrong for platform

Maybe this is not supposed to work with the alpha version but will work fine with the 0.24 release?

I have found this same error in other places in the issue tracker e.g. https://github.com/pyodide/pyodide/discussions/2186#discussioncomment-2784855 or https://github.com/pyodide/pyodide/issues/775#issuecomment-717527769 but without a clear solution.

<details>
<summary>Excerpt of the build log with the error</summary>

```
building 'sklearn.__check_build._check_build' extension
creating build/temp.emscripten_3_1_44_wasm32-cpython-311/sklearn/__check_build
/tmp/tmp_co55dq1/cc -DNDEBUG -g -fwrapv -O3 -Wall -O2 -g0 -fPIC -DPY_CALL_TRAMPOLINE -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION -I/tmp/build-env-o9s9adj9/include -I/opt/conda/envs/env/include/python3.11 -c sklearn/__check_build/_check_build.c -o build/temp.emscripten_3_1_44_wasm32-cpython-311/sklearn/__check_build/_check_build.o -g0 -O2
In file included from sklearn/__check_build/_check_build.c:35:
In file included from /opt/conda/envs/env/include/python3.11/Python.h:38:
/opt/conda/envs/env/include/python3.11/pyport.h:601:2: error: "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
  601 | #error "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
      |  ^
sklearn/__check_build/_check_build.c:415:39: error: expression is not an integer constant expression
  415 |     enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
      |                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sklearn/__check_build/_check_build.c:415:41: note: division by zero
  415 |     enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
      |                                         ^
2 errors generated.
emcc: error: '/emsdk/upstream/bin/clang -target wasm32-unknown-emscripten -fignore-exceptions -fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -DEMSCRIPTEN -Werror=implicit-function-declaration --sysroot=/emsdk/upstream/emscripten/cache/sysroot -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat -DNDEBUG -g3 -fwrapv -O3 -Wall -O2 -g0 -fPIC -DPY_CALL_TRAMPOLINE -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION -I/tmp/build-env-o9s9adj9/include -I/opt/conda/envs/env/include/python3.11 -c -g0 -O2 -Werror=implicit-function-declaration -Werror=mismatched-parameter-types -Werror=return-type -O2 -g0 -fPIC -I/.pyodide-xbuildenv/xbuildenv/pyodide-root/cpython/installs/python-3.11.3/include/python3.11 -I/.pyodide-xbuildenv/xbuildenv/pyodide-root/cpython/installs/python-3.11.3/include/python3.11 sklearn/__check_build/_check_build.c -o build/temp.emscripten_3_1_44_wasm32-cpython-311/sklearn/__check_build/_check_build.o' failed (returned 1)
error: command '/tmp/tmp_co55dq1/cc' failed with exit code 1
```

</summary>
</details>

### To Reproduce

```sh
# Need set up emscripten 3.1.44 the usual way before
pip install pyodide-build==0.24.0a1
pyodide build scikit-learn
```

### Expected behavior

Builds a Pyodide wheel for scikit-learn

### Environment

- Pyodide Version<!-- (e.g. 1.8.1) -->: 0.24.0a1
- Browser version<!-- (e.g. Chrome 95.0.4638.54) -->:
- Any other relevant information:

### Additional context

With Pyodide 0.24 the scikit-learn test suite fully passes, except some tests due to Pyodide limitations.

I saw the 0.24 release was approaching and I wanted to give a go to start running the scikit-learn tests inside Pyodide and in the scikit-learn CI.


Discussion:
Thanks for the report @lesteve! It looks like a regression. The same error doesn't occur in 0.23.4?
This works fine with 0.23.4 indeed.

Note this is not specific to scikit-learn, for example you get the same error with simplejson or msgpack. Note that both of these packages have a fall-back to a pure Python implementation which makes the wheel building succeed despite the compilation error.
That's very interesting. I think Pyodide side didn't change very much, so maybe there was a regression between Emscripten 3.1.32 (Pyodide 0.23.4) and 3.1.44 (Pyodide 0.24.0a1).

I tried to reproduce the error with Pyodide 0.24.0a1 and scikit-learn 1.3.0 but failed (it build successfully).
I was also able to build scikit-build main branch with Pyodide 0.24.0a1. So probably this is not an Emscripten issue.

I also tested with both Python 3.11.2 and Python 3.11.3... so I guess the host Python version is not an issue either.

Build log: [out.log](https://github.com/pyodide/pyodide/files/12550211/out.log)

Hmmm indeed I tried in a Debian-based Docker container and it works fine. It doesn't work on my machine which is an ArchLinux-variant. Maybe I should consider myself lucky that it worked until Pyodide 0.23.

This is a little bit annoying but at the same time I can fully understand there are higher priorities for Pyodide ... I'll leave this issue open just in case someone has some insights and I'll close after some time if not.

For reference:
```
❯ cat /etc/os-release       
NAME="EndeavourOS"
PRETTY_NAME="EndeavourOS"
ID="endeavouros"
ID_LIKE="arch"
BUILD_ID="2023.03.26"
ANSI_COLOR="38;2;23;147;209"
HOME_URL="https://endeavouros.com"
DOCUMENTATION_URL="https://discovery.endeavouros.com"
SUPPORT_URL="https://forum.endeavouros.com"
BUG_REPORT_URL="https://forum.endeavouros.com/c/arch-based-related-questions/bug-reports"
PRIVACY_POLICY_URL="https://endeavouros.com/privacy-policy-2"
LOGO="endeavouros"
```
Yes, let's keep this issue open until we find the root cause.
I looked a bit more, for some reason this happens only in a conda environment. I can build fine on my ArchLinux machine in a Python virtual env (`python -m venv my-venv`) and I reproduce the issue using the `condaforge/miniforge3` Docker image based on Ubuntu 20.04.
I can reproduce the issue in a CI see [scikit-learn PR](https://github.com/scikit-learn/scikit-learn/pull/27346) and [CI build](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=58949&view=logs&jobId=6fac3219-cc32-5595-eb73-7f086a643b12&j=6fac3219-cc32-5595-eb73-7f086a643b12&t=6856d197-9931-5ad8-f897-5714e4bdfa31).

My hunch is that you get this problem as soon as you are not using your system Python (which is the case for the above CI on Azure or conda environment). I suspect there are things in `pywasmcross.py` like https://github.com/pyodide/pyodide/blob/3b428ceb6b7b9b3abfeab7a75fbf0fac6c9d76bb/pyodide-build/pyodide_build/pywasmcross.py#L234-L236 that removes the system Python includes which make it work in this case (edit: it works in a venv using system python so it may not be as simple as this ...)
 
If you look at the failing command, you will see first the host Python (`-I/opt/conda/envs/env/include/python3.11`) and then xbuildenv one (`-I/.pyodide-xbuildenv/xbuildenv/pyodide-root/cpython/installs/python-3.11.3/include/python3.11`). This seems in the wrong order since you want first the xbuildenv one unless I am missing something. With some quick and dirty print statement, it seems like the include order is the one I am expecting in Pyodide 0.23.3.

Here is the command with a new line on each `-I` for better readability:
```
emcc: error: '/emsdk/upstream/bin/clang -target wasm32-unknown-emscripten -fignore-exceptions -fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -DEMSCRIPTEN -Werror=implicit-function-declaration --sysroot=/emsdk/upstream/emscripten/cache/sysroot -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat -DNDEBUG -g3 -fwrapv -O3 -Wall -O2 -g0 -fPIC -DPY_CALL_TRAMPOLINE -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
-I/tmp/build-env-o9s9adj9/include
-I/opt/conda/envs/env/include/python3.11 -c -g0 -O2 -Werror=implicit-function-declaration -Werror=mismatched-parameter-types -Werror=return-type -O2 -g0 -fPIC
-I/.pyodide-xbuildenv/xbuildenv/pyodide-root/cpython/installs/python-3.11.3/include/python3.11
-I/.pyodide-xbuildenv/xbuildenv/pyodide-root/cpython/installs/python-3.11.3/include/python3.11 sklearn/__check_build/_check_build.c -o build/temp.emscripten_3_1_44_wasm32-cpython-311/sklearn/__check_build/_check_build.o' failed (returned 1)
```


Thanks for the investigation @lesteve! Indeed I changed the order of arguments in #3866.

Probably we should update this part to filter out other local Python include directories as well.

https://github.com/pyodide/pyodide/blob/3b428ceb6b7b9b3abfeab7a75fbf0fac6c9d76bb/pyodide-build/pyodide_build/pywasmcross.py#L234-L236

Resolving patch:
diff --git a/pyodide-build/pyodide_build/out_of_tree/build.py b/pyodide-build/pyodide_build/out_of_tree/build.py
--- a/pyodide-build/pyodide_build/out_of_tree/build.py
+++ b/pyodide-build/pyodide_build/out_of_tree/build.py
@@ -15,6 +15,9 @@ def run(
     cxxflags += f" {os.environ.get('CXXFLAGS', '')}"
     ldflags = build_env.get_build_flag("SIDE_MODULE_LDFLAGS")
     ldflags += f" {os.environ.get('LDFLAGS', '')}"
+    target_install_dir = os.environ.get(
+        "TARGETINSTALLDIR", build_env.get_build_flag("TARGETINSTALLDIR")
+    )
     env = os.environ.copy()
     env.update(build_env.get_build_environment_vars())
 
@@ -24,7 +27,7 @@ def run(
         cflags=cflags,
         cxxflags=cxxflags,
         ldflags=ldflags,
-        target_install_dir="",
+        target_install_dir=target_install_dir,
         exports=exports,
     )
 
diff --git a/pyodide-build/pyodide_build/pywasmcross.py b/pyodide-build/pyodide_build/pywasmcross.py
--- a/pyodide-build/pyodide_build/pywasmcross.py
+++ b/pyodide-build/pyodide_build/pywasmcross.py
@@ -226,14 +226,19 @@ def replay_genargs_handle_dashI(arg: str, target_install_dir: str) -> str | None
         The new argument, or None to delete the argument.
     """
     assert arg.startswith("-I")
-    if (
-        str(Path(arg[2:]).resolve()).startswith(sys.prefix + "/include/python")
-        and "site-packages" not in arg
-    ):
-        return arg.replace("-I" + sys.prefix, "-I" + target_install_dir)
+
     # Don't include any system directories
     if arg[2:].startswith("/usr"):
         return None
+
+    # Replace local Python include paths with the cross compiled ones
+    include_path = str(Path(arg[2:]).resolve())
+    if include_path.startswith(sys.prefix + "/include/python"):
+        return arg.replace("-I" + sys.prefix, "-I" + target_install_dir)
+
+    if include_path.startswith(sys.base_prefix + "/include/python"):
+        return arg.replace("-I" + sys.base_prefix, "-I" + target_install_dir)
+
     return arg
 
 


Test patch:
diff --git a/pyodide-build/pyodide_build/tests/test_pywasmcross.py b/pyodide-build/pyodide_build/tests/test_pywasmcross.py
--- a/pyodide-build/pyodide_build/tests/test_pywasmcross.py
+++ b/pyodide-build/pyodide_build/tests/test_pywasmcross.py
@@ -10,6 +10,7 @@
     get_library_output,
     handle_command_generate_args,
     replay_f2c,
+    replay_genargs_handle_dashI,
 )
 
 
@@ -145,6 +146,30 @@ def test_handle_command_ldflags(build_args):
     )
 
 
+def test_replay_genargs_handle_dashI(monkeypatch):
+    import sys
+
+    mock_prefix = "/mock_prefix"
+    mock_base_prefix = "/mock_base_prefix"
+    monkeypatch.setattr(sys, "prefix", mock_prefix)
+    monkeypatch.setattr(sys, "base_prefix", mock_base_prefix)
+
+    target_dir = "/target"
+    target_cpython_include = "/target/include/python3.11"
+
+    assert replay_genargs_handle_dashI("-I/usr/include", target_dir) is None
+    assert (
+        replay_genargs_handle_dashI(f"-I{mock_prefix}/include/python3.11", target_dir)
+        == f"-I{target_cpython_include}"
+    )
+    assert (
+        replay_genargs_handle_dashI(
+            f"-I{mock_base_prefix}/include/python3.11", target_dir
+        )
+        == f"-I{target_cpython_include}"
+    )
+
+
 def test_f2c():
     assert f2c_wrap("gfortran test.f") == "gcc test.c"
     assert f2c_wrap("gcc test.c") is None