Our package build process currently has a significant flaw: we first run `setup.py`, recording all compilation commands, then we rewrite these compilation commands to invoke `emcc` and replay them, and then we pray that the cross compiled executables ended up in the right place to go into the wheel. This is not a good strategy because the build script is allowed to implement arbitrary logic, and if it moves, renames, etc any of the output files then we lose track of them. This has repeatedly caused difficulty for us.

However, we also make no particularly significant use of the two pass approach. We can just do the simpler thing: capture the compiler commands as they occur, modify them as needed, and then run the fixed command.

I also added a patch to fix the numpy feature detection for wasm so that we don't have to include `_npyconfig.h` and `config.h`, numpy can generate them in the way it would for a native build. I opened a numpy PR that would fix the detection for us upstream:
https://github.com/numpy/numpy/pull/21154

This clears the way for us to switch to using pypa/build (as @henryiii has suggested) by removing our dependence on specific setuptools behavior.

This is on top of #2238.
No replay. Our package build process currently has a significant flaw: we first run `setup.py`, recording all compilation commands, then we rewrite these compilation commands to invoke `emcc` and replay them, and then we pray that the cross compiled executables ended up in the right place to go into the wheel. This is not a good strategy because the build script is allowed to implement arbitrary logic, and if it moves, renames, etc any of the output files then we lose track of them. This has repeatedly caused difficulty for us.

However, we also make no particularly significant use of the two pass approach. We can just do the simpler thing: capture the compiler commands as they occur, modify them as needed, and then run the fixed command.

I also added a patch to fix the numpy feature detection for wasm so that we don't have to include `_npyconfig.h` and `config.h`, numpy can generate them in the way it would for a native build. I opened a numpy PR that would fix the detection for us upstream:
https://github.com/numpy/numpy/pull/21154

This clears the way for us to switch to using pypa/build (as @henryiii has suggested) by removing our dependence on specific setuptools behavior.

This is on top of #2238.
import pytest


@pytest.fixture(scope="session")
def print_info():
    headings = [
        "browser",
        "py_limit",
        "py_usage",
        "js_depth",
        "py_depth",
        "js_depth/py_usage",
        "js_depth/py_depth",
    ]
    fmt = "## {{:{:d}s}}  {{:{:d}g}}  {{:{:d}.2f}}  {{:{:d}g}}  {{:{:d}g}}  {{:{:d}g}}  {{:{:d}g}}".format(
        *map(len, headings)
    )
    printed_heading = False

    def print_info(*args):
        nonlocal printed_heading
        if not printed_heading:
            printed_heading = True
            print("## " + "  ".join(headings))
        print(fmt.format(*args))

    yield print_info


@pytest.mark.skip_refcount_check
@pytest.mark.skip_pyproxy_check
def test_stack_usage(selenium, print_info):
    res = selenium.run_js(
        """
        self.measure_available_js_stack_depth = () => {
            let depth = 0;
            function recurse() { depth += 1; recurse(); }
            try { recurse(); } catch (err) { }
            return depth;
        };
        let py_limit = pyodide.runPython("import sys; sys.getrecursionlimit()");
        let py_usage = pyodide.runPython(`
            from js import measure_available_js_stack_depth
            def recurse(n):
                return measure_available_js_stack_depth() if n==0 else recurse(n-1)
            (recurse(0)-recurse(100))/100
        `);
        let js_depth = measure_available_js_stack_depth();
        self.py_depth = [0];
        try {
        pyodide.runPython(`
        import sys
        from js import py_depth
        sys.setrecursionlimit(2000)
        def infiniterecurse():
            py_depth[0] += 1
            infiniterecurse()
        infiniterecurse()
        `);
        } catch {}

        py_depth = py_depth[0];
        return [
            py_limit,
            py_usage,
            js_depth,
            py_depth,
            Math.floor(js_depth/py_usage),
            Math.floor(js_depth/py_depth),
        ]
        """
    )
    # "py_usage",
    # "js_depth",
    # "py_depth",
    # "js_depth/py_usage",
    # "js_depth/py_depth",

    print_info(selenium.browser, *res)

    selenium.clean_logs()

import pytest


@pytest.fixture(scope="session")
def print_info():
    headings = [
        "browser",
        "py_limit",
        "py_usage",
        "js_depth",
        "py_depth",
        "js_depth/py_usage",
        "js_depth/py_depth",
    ]
    fmt = "## {{:{:d}s}}  {{:{:d}g}}  {{:{:d}.2f}}  {{:{:d}g}}  {{:{:d}g}}  {{:{:d}g}}  {{:{:d}g}}".format(
        *map(len, headings)
    )
    printed_heading = False

    def print_info(*args):
        nonlocal printed_heading
        if not printed_heading:
            printed_heading = True
            print("## " + "  ".join(headings))
        print(fmt.format(*args))

    yield print_info


@pytest.mark.skip_refcount_check
@pytest.mark.skip_pyproxy_check
def test_stack_usage(selenium, print_info):
    res = selenium.run_js(
        """
        self.measure_available_js_stack_depth = () => {
            let depth = 0;
            function recurse() { depth += 1; recurse(); }
            try { recurse(); } catch (err) { }
            return depth;
        };
        let py_limit = pyodide.runPython("import sys; sys.getrecursionlimit()");
        let py_usage = pyodide.runPython(`
            from js import measure_available_js_stack_depth
            def recurse(n):
                return measure_available_js_stack_depth() if n==0 else recurse(n-1)
            (recurse(0)-recurse(100))/100
        `);
        let js_depth = measure_available_js_stack_depth();
        self.py_depth = [0];
        try {
        pyodide.runPython(`
        import sys
        from js import py_depth
        sys.setrecursionlimit(2000)
        def infiniterecurse():
            py_depth[0] += 1
            infiniterecurse()
        infiniterecurse()
        `);
        } catch {}

        py_depth = py_depth[0];
        return [
            py_limit,
            py_usage,
            js_depth,
            py_depth,
            Math.floor(js_depth/py_usage),
            Math.floor(js_depth/py_depth),
        ]
        """
    )
    # "py_usage",
    # "js_depth",
    # "py_depth",
    # "js_depth/py_usage",
    # "js_depth/py_depth",

    print_info(selenium.browser, *res)

    selenium.clean_logs()
