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


def test_init(selenium_standalone):
    assert "Python initialization complete" in selenium_standalone.logs.splitlines()


def test_webbrowser(selenium):
    if selenium.browser == "node":
        pytest.xfail("Webbrowser doesn't work in node")
    selenium.run_async("import antigravity")
    assert len(selenium.driver.window_handles) == 2


def test_print(selenium):
    selenium.run("print('This should be logged')")
    assert "This should be logged" in selenium.logs.splitlines()


def test_import_js(selenium):
    if selenium.browser == "node":
        pytest.xfail("No window in node")
    result = selenium.run(
        """
        import js
        js.window.title = 'Foo'
        js.window.title
        """
    )
    assert result == "Foo"
    result = selenium.run(
        """
        dir(js)
        """
    )
    assert len(result) > 100
    assert "document" in result
    assert "window" in result


def test_globals_get_multiple(selenium):
    """See #1151"""
    selenium.run_js(
        """
        pyodide.runPython("v = 0.123");
        pyodide.globals.get('v')
        pyodide.globals.get('v')
        """
    )


def test_load_package_after_convert_string(selenium):
    """
    See #93.
    """
    selenium.run("import sys; x = sys.version")
    selenium.run_js("let x = pyodide.runPython('x'); console.log(x);")
    selenium.load_package("pytz")
    selenium.run("import pytz")


def test_version_info(selenium):
    from distutils.version import LooseVersion

    version_py_str = selenium.run("import pyodide; pyodide.__version__")
    version_py = LooseVersion(version_py_str)
    assert version_py > LooseVersion("0.0.1")

    version_js_str = selenium.run_js("return pyodide.version;")
    assert version_py_str == version_js_str


def test_runpythonasync(selenium_standalone):
    output = selenium_standalone.run_async(
        """
        import pyparsing
        pyparsing.__version__
        """
    )
    assert isinstance(output, str)


def test_runpythonasync_no_imports(selenium_standalone):
    output = selenium_standalone.run_async(
        """
        42
        """
    )
    assert output == 42


def test_runpythonasync_missing_import(selenium_standalone):
    msg = "ModuleNotFoundError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            import foo
            """
        )


def test_runpythonasync_exception(selenium_standalone):
    msg = "ZeroDivisionError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            42 / 0
            """
        )


def test_runpythonasync_exception_after_import(selenium_standalone):
    msg = "ZeroDivisionError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            import pyparsing
            42 / 0
            """
        )


def test_py(selenium_standalone):
    selenium_standalone.run_js(
        """
        pyodide.runPython(`
            def func():
                return 42
        `);
        let func = pyodide.globals.get('func');
        assert(() => func() === 42);
        func.destroy();
        """
    )


def test_eval_nothing(selenium):
    assert selenium.run("# comment") is None
    assert selenium.run("") is None


def test_unknown_attribute(selenium):
    selenium.run_async(
        """
        from unittest import TestCase
        raises = TestCase().assertRaisesRegex
        import js
        with raises(AttributeError, "asdf"):
            js.asdf
        """
    )

import pytest


def test_init(selenium_standalone):
    assert "Python initialization complete" in selenium_standalone.logs.splitlines()


def test_webbrowser(selenium):
    if selenium.browser == "node":
        pytest.xfail("Webbrowser doesn't work in node")
    selenium.run_async("import antigravity")
    assert len(selenium.driver.window_handles) == 2


def test_print(selenium):
    selenium.run("print('This should be logged')")
    assert "This should be logged" in selenium.logs.splitlines()


def test_import_js(selenium):
    if selenium.browser == "node":
        pytest.xfail("No window in node")
    result = selenium.run(
        """
        import js
        js.window.title = 'Foo'
        js.window.title
        """
    )
    assert result == "Foo"
    result = selenium.run(
        """
        dir(js)
        """
    )
    assert len(result) > 100
    assert "document" in result
    assert "window" in result


def test_globals_get_multiple(selenium):
    """See #1151"""
    selenium.run_js(
        """
        pyodide.runPython("v = 0.123");
        pyodide.globals.get('v')
        pyodide.globals.get('v')
        """
    )


def test_load_package_after_convert_string(selenium):
    """
    See #93.
    """
    selenium.run("import sys; x = sys.version")
    selenium.run_js("let x = pyodide.runPython('x'); console.log(x);")
    selenium.load_package("pytz")
    selenium.run("import pytz")


def test_version_info(selenium):
    from distutils.version import LooseVersion

    version_py_str = selenium.run("import pyodide; pyodide.__version__")
    version_py = LooseVersion(version_py_str)
    assert version_py > LooseVersion("0.0.1")

    version_js_str = selenium.run_js("return pyodide.version;")
    assert version_py_str == version_js_str


def test_runpythonasync(selenium_standalone):
    output = selenium_standalone.run_async(
        """
        import pyparsing
        pyparsing.__version__
        """
    )
    assert isinstance(output, str)


def test_runpythonasync_no_imports(selenium_standalone):
    output = selenium_standalone.run_async(
        """
        42
        """
    )
    assert output == 42


def test_runpythonasync_missing_import(selenium_standalone):
    msg = "ModuleNotFoundError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            import foo
            """
        )


def test_runpythonasync_exception(selenium_standalone):
    msg = "ZeroDivisionError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            42 / 0
            """
        )


def test_runpythonasync_exception_after_import(selenium_standalone):
    msg = "ZeroDivisionError"
    with pytest.raises(selenium_standalone.JavascriptException, match=msg):
        selenium_standalone.run_async(
            """
            import pyparsing
            42 / 0
            """
        )


def test_py(selenium_standalone):
    selenium_standalone.run_js(
        """
        pyodide.runPython(`
            def func():
                return 42
        `);
        let func = pyodide.globals.get('func');
        assert(() => func() === 42);
        func.destroy();
        """
    )


def test_eval_nothing(selenium):
    assert selenium.run("# comment") is None
    assert selenium.run("") is None


def test_unknown_attribute(selenium):
    selenium.run_async(
        """
        from unittest import TestCase
        raises = TestCase().assertRaisesRegex
        import js
        with raises(AttributeError, "asdf"):
            js.asdf
        """
    )
