Instead of trying to convince setup.py to do what we want, the wheel
package empowers us to unpack and directly manipulate the wheel.
In particular this is important because `--skip-build` really does
not work.

First we build a native wheel. If skip_host is True, then this
"native" wheel has defective empty files as binaries, if it's False,
we build a real native wheel. If skip_host is False, then we use
pip to install our native wheel into host directory.

Then we unpack the wheel, replace the platform tag with
wasm32_emscripten, remove the non-metadata files, and copy in the
new files with the cross compiled binaries.

Of course, this is all only necessary for binary extensions.


Hopefully this will help get us towards using alternative build
backends. The one concern is that this approach depends on
implementation details of where the build backend puts the files
into the directory tree before copying them into the wheel. If
the build backend moved the linked binaries after building them,
we would be in trouble. Or worse if the build backend deleted the
files after packing them into the wheel, we'd be in trouble.

Basically, it seems clear that we can only support alternative
build backends on a case-by-case basis.

@henryiii I'm curious what your thoughts are cross compilation + arbitrary build backends.

Better package cross compilation. Instead of trying to convince setup.py to do what we want, the wheel
package empowers us to unpack and directly manipulate the wheel.
In particular this is important because `--skip-build` really does
not work.

First we build a native wheel. If skip_host is True, then this
"native" wheel has defective empty files as binaries, if it's False,
we build a real native wheel. If skip_host is False, then we use
pip to install our native wheel into host directory.

Then we unpack the wheel, replace the platform tag with
wasm32_emscripten, remove the non-metadata files, and copy in the
new files with the cross compiled binaries.

Of course, this is all only necessary for binary extensions.


Hopefully this will help get us towards using alternative build
backends. The one concern is that this approach depends on
implementation details of where the build backend puts the files
into the directory tree before copying them into the wheel. If
the build backend moved the linked binaries after building them,
we would be in trouble. Or worse if the build backend deleted the
files after packing them into the wheel, we'd be in trouble.

Basically, it seems clear that we can only support alternative
build backends on a case-by-case basis.

@henryiii I'm curious what your thoughts are cross compilation + arbitrary build backends.

from pyodide_build.testing import run_in_pyodide


@run_in_pyodide(packages=["wrapt"])
def test_wrapt():
    import inspect
    import unittest

    import wrapt

    @wrapt.decorator
    def passthru_decorator(wrapped, instance, args, kwargs):
        return wrapped(*args, **kwargs)

    def function1(arg):
        """documentation"""
        return arg

    function1o = function1
    function1d = passthru_decorator(function1)
    assert function1d is not function1o

    class TestNamingFunction(unittest.TestCase):
        def test_object_name(self):
            # Test preservation of function __name__ attribute.

            self.assertEqual(function1d.__name__, function1o.__name__)

        def test_object_qualname(self):
            # Test preservation of function __qualname__ attribute.

            try:
                __qualname__ = function1o.__qualname__
            except AttributeError:
                pass
            else:
                self.assertEqual(function1d.__qualname__, __qualname__)

        def test_module_name(self):
            # Test preservation of function __module__ attribute.

            self.assertEqual(function1d.__module__, __name__)

        def test_doc_string(self):
            # Test preservation of function __doc__ attribute.

            self.assertEqual(function1d.__doc__, function1o.__doc__)

        def test_argspec(self):
            # Test preservation of function argument specification.

            function1o_argspec = inspect.getargspec(function1o)
            function1d_argspec = inspect.getargspec(function1d)
            self.assertEqual(function1o_argspec, function1d_argspec)

        def test_isinstance(self):
            # Test preservation of isinstance() checks.

            self.assertTrue(isinstance(function1d, type(function1o)))

    class TestCallingFunction(unittest.TestCase):
        def test_call_function(self):
            _args = (1, 2)
            _kwargs = {"one": 1, "two": 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                self.assertEqual(instance, None)
                self.assertEqual(args, _args)
                self.assertEqual(kwargs, _kwargs)
                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            result = _function(*_args, **_kwargs)

            self.assertEqual(result, (_args, _kwargs))

    # Run tests
    with unittest.TestCase().assertRaisesRegex(SystemExit, "False"):
        unittest.main()

from pyodide_build.testing import run_in_pyodide


@run_in_pyodide(packages=["wrapt"])
def test_wrapt():
    import inspect
    import unittest

    import wrapt

    @wrapt.decorator
    def passthru_decorator(wrapped, instance, args, kwargs):
        return wrapped(*args, **kwargs)

    def function1(arg):
        """documentation"""
        return arg

    function1o = function1
    function1d = passthru_decorator(function1)
    assert function1d is not function1o

    class TestNamingFunction(unittest.TestCase):
        def test_object_name(self):
            # Test preservation of function __name__ attribute.

            self.assertEqual(function1d.__name__, function1o.__name__)

        def test_object_qualname(self):
            # Test preservation of function __qualname__ attribute.

            try:
                __qualname__ = function1o.__qualname__
            except AttributeError:
                pass
            else:
                self.assertEqual(function1d.__qualname__, __qualname__)

        def test_module_name(self):
            # Test preservation of function __module__ attribute.

            self.assertEqual(function1d.__module__, __name__)

        def test_doc_string(self):
            # Test preservation of function __doc__ attribute.

            self.assertEqual(function1d.__doc__, function1o.__doc__)

        def test_argspec(self):
            # Test preservation of function argument specification.

            function1o_argspec = inspect.getargspec(function1o)
            function1d_argspec = inspect.getargspec(function1d)
            self.assertEqual(function1o_argspec, function1d_argspec)

        def test_isinstance(self):
            # Test preservation of isinstance() checks.

            self.assertTrue(isinstance(function1d, type(function1o)))

    class TestCallingFunction(unittest.TestCase):
        def test_call_function(self):
            _args = (1, 2)
            _kwargs = {"one": 1, "two": 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                self.assertEqual(instance, None)
                self.assertEqual(args, _args)
                self.assertEqual(kwargs, _kwargs)
                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            result = _function(*_args, **_kwargs)

            self.assertEqual(result, (_args, _kwargs))

    # Run tests
    with unittest.TestCase().assertRaisesRegex(SystemExit, "False"):
        unittest.main()
