Repository: pyodide/pyodide

Issue:
WIP: build scipy with meson
Tries to continue @hoodmane 's work in  https://github.com/pyodide/pyodide/pull/3037 


Discussion:
So there are a few issues that happen here,

1. 
```
Dependency lookup for OpenBLAS with method 'pkgconfig' failed: Pkg-config binary for machine 1 not  
found. Giving up.
```
as discussed in the parent issue. pkg-config is installed and setting `export PKG_CONFIG_LIBDIR=$WASM_PKG_CONFIG_PATH` should fix the path, so I don't know why it cannot find it.


2. `WARNING: Unknown CPU family wasm, please report this at https://github.com/mesonbuild/meson/issues/new`
     Maybe a false positive, the config looks correct.
     
3. ` ERROR: Compiler /usr/bin/gfortran can not compile programs.` depending on what one puts in the config of `tools/emscripten.meson.cross`. 

Initially, I felt the quickest might be to monkeypatch the [compiler checks in meson](https://github.com/mesonbuild/meson/blob/master/mesonbuild/compilers/fortran.py) but since it's run in a separate process by `meson-python` we can't easily modify that code at runtime, short of installing a modified version of meson.

So we would indeed likely need to make current checks pass with our compiler wrappers, as Hood was suggesting.

I'll likely not continue this PR in the near future. This was mostly so we have a separate PR on scipy with meson, independent from scipy. Feel free to take it over.
BTW, it looks like the last version of scikit-image also migrated to meson. So maybe that would be an easier package to start, since it only has Cython with a little C.
There are too many build systems in the world 😂
Indeed :) But with distutils deprecations, many of Python scientific packages (or packages with C extensions) will move to meson. So that's one we likely can't avoid.

Resolving patch:
diff --git a/pyodide-build/pyodide_build/_f2c_fixes.py b/pyodide-build/pyodide_build/_f2c_fixes.py
--- a/pyodide-build/pyodide_build/_f2c_fixes.py
+++ b/pyodide-build/pyodide_build/_f2c_fixes.py
@@ -487,12 +487,28 @@ def scipy_fix_cfile(path: str) -> None:
 
     for lib in ["lapack", "blas"]:
         if path.endswith(f"cython_{lib}.c"):
-            header_path = Path(path).with_name(f"_{lib}_subroutines.h")
+            header_name = f"_{lib}_subroutines.h"
+            header_dir = Path(path).parent
+            header_path = find_header(header_dir, header_name)
+
             header_text = header_path.read_text()
             header_text = header_text.replace("void F_FUNC", "int F_FUNC")
             header_path.write_text(header_text)
 
 
+def find_header(source_dir: Path, header_name: str) -> Path:
+    """
+    Find the header file that corresponds to a source file.
+    """
+    while not (header_path := source_dir / header_name).exists():
+        # meson copies the source files into a subdirectory of the build
+        source_dir = source_dir.parent
+        if source_dir == Path("/"):
+            raise RuntimeError(f"Could not find header file {header_name}")
+
+    return header_path
+
+
 def scipy_fixes(args: list[str]) -> None:
     for arg in args:
         if arg.endswith(".c"):
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
@@ -35,7 +35,6 @@
     "HOSTSITEPACKAGES",
     "NUMPY_LIB",
     "PATH",
-    "PKG_CONFIG_PATH",
     "PLATFORM_TRIPLET",
     "PIP_CONSTRAINT",
     "PYMAJOR",
@@ -65,6 +64,7 @@
     "CMAKE_TOOLCHAIN_FILE",
     "PYO3_CONFIG_FILE",
     "MESON_CROSS_FILE",
+    "PKG_CONFIG_LIBDIR",
 }
 
 
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
@@ -62,7 +62,7 @@
 import shutil
 import subprocess
 from collections.abc import Iterable, Iterator
-from typing import Literal, NoReturn
+from typing import Literal
 
 
 @dataclasses.dataclass(eq=False, order=False, kw_only=True)
@@ -165,7 +165,6 @@ def replay_f2c(args: list[str], dryrun: bool = False) -> list[str] | None:
         found_source = True
 
     if not found_source:
-        print(f"f2c: source not found, skipping: {new_args_str}")
         return None
     return new_args
 
@@ -328,8 +327,15 @@ def replay_genargs_handle_argument(arg: str) -> str | None:
         '-fno-strict-overflow',  # warning: argument unused during compilation
         "-mno-sse2", # warning: argument unused during compilation
         "-mno-avx2", # warning: argument unused during compilation
+        "-std=legacy", # fortran flag that clang does not support
     ]:
         return None
+
+    if arg.startswith((
+        "-J",  # fortran flag that clang does not support
+    )):
+        return None
+
     # fmt: on
     return arg
 
@@ -658,7 +664,7 @@ def handle_command_generate_args(  # noqa: C901
 def handle_command(
     line: list[str],
     build_args: BuildArgs,
-) -> NoReturn:
+) -> int:
     """Handle a compilation command. Exit with an appropriate exit code when done.
 
     Parameters
@@ -672,11 +678,12 @@ def handle_command(
     is_link_cmd = get_library_output(line) is not None
 
     if line[0] == "gfortran":
-        if "-dumpversion" in line:
-            sys.exit(subprocess.run(line).returncode)
         tmp = replay_f2c(line)
         if tmp is None:
-            sys.exit(0)
+            # No source file, it's a query for information about the compiler. Pretend we're
+            # gfortran by letting gfortran handle it
+            return subprocess.run(line).returncode
+
         line = tmp
 
     new_args = handle_command_generate_args(line, build_args, is_link_cmd)
@@ -686,9 +693,8 @@ def handle_command(
 
         scipy_fixes(new_args)
 
-    returncode = subprocess.run(new_args).returncode
-
-    sys.exit(returncode)
+    result = subprocess.run(new_args)
+    return result.returncode
 
 
 def compiler_main():


Test patch:
diff --git a/pyodide-build/pyodide_build/tests/test_build_env.py b/pyodide-build/pyodide_build/tests/test_build_env.py
--- a/pyodide-build/pyodide_build/tests/test_build_env.py
+++ b/pyodide-build/pyodide_build/tests/test_build_env.py
@@ -60,8 +60,10 @@ def test_in_xbuildenv(self, reset_env_vars, reset_cache):
 
     def test_get_build_environment_vars(self, reset_env_vars, reset_cache):
         build_vars = build_env.get_build_environment_vars()
+
+        # extra variables that does not come from Makefile.envs but are added by build_env.py
         extra_vars = set(
-            ["PYODIDE", "PKG_CONFIG_PATH", "CMAKE_TOOLCHAIN_FILE", "PYO3_CONFIG_FILE"]
+            ["PYODIDE", "MESON_CROSS_FILE", "CMAKE_TOOLCHAIN_FILE", "PYO3_CONFIG_FILE"]
         )
 
         for var in build_vars:
@@ -98,18 +100,18 @@ def test_get_build_environment_vars_host_env(
 
         monkeypatch.setenv("HOME", "/home/user")
         monkeypatch.setenv("PATH", "/usr/bin:/bin")
-        monkeypatch.setenv("PKG_CONFIG_PATH", "/x/y/z:/c/d/e")
+        monkeypatch.setenv("PKG_CONFIG_LIBDIR", "/x/y/z:/c/d/e")
 
         build_env.get_build_environment_vars.cache_clear()
 
         e_host = build_env.get_build_environment_vars()
         assert e_host.get("HOME") == os.environ.get("HOME")
         assert e_host.get("PATH") == os.environ.get("PATH")
-        assert e_host["PKG_CONFIG_PATH"].endswith("/x/y/z:/c/d/e")
+        assert e_host["PKG_CONFIG_LIBDIR"].endswith("/x/y/z:/c/d/e")
 
         assert e_host.get("HOME") != e.get("HOME")
         assert e_host.get("PATH") != e.get("PATH")
-        assert e_host.get("PKG_CONFIG_PATH") != e.get("PKG_CONFIG_PATH")
+        assert e_host.get("PKG_CONFIG_LIBDIR") != e.get("PKG_CONFIG_LIBDIR")
 
         build_env.get_build_environment_vars.cache_clear()