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.
#!/usr/bin/env bash

PYODIDE_IMAGE_REPO="pyodide"
PYODIDE_IMAGE_TAG="20220213-chrome97-firefox96"
PYODIDE_PREBUILT_IMAGE_TAG="0.19.0"
DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}"
DEFAULT_PYODIDE_SYSTEM_PORT="none"
DOCKER_COMMAND="/bin/bash"
DOCKER_INTERACTIVE="--interactive"


USER_HOME="/src/.docker_home"
USER_NAME="$(id -u -n)"
USER_PASS="x"
USER_ID="$(id -u)"
USER_GID="$(id -g)"
USER_COMMENT_FIELD="${USER_NAME} pyodide user alias"
USER_INTERPRETER="/sbin/nologin"
USER_ACCOUNT_INFO="${USER_NAME}:${USER_PASS}:${USER_ID}:${USER_GID}:${USER_COMMENT_FIELD}:${USER_HOME}:${USER_INTERPRETER}"
USER_FLAG=("--user" "$USER_ID:$USER_GID")

set -eo pipefail


function usage() {
  cat > /dev/stdout <<EOF
Usage: run_docker [OPTIONS] [COMMAND] [ARG...]

Runs COMMAND in a new Pyodide docker container. If no COMMAND is provided, starts a bash
shell in the container.

Options:
  -h, --help                  Show this information and exit.
  --pre-built                 Use the prebuilt Pyodide image.
                              This is ignored if the env var PYODIDE_DOCKER_IMAGE is set.
  -p, --port <port>           System port to which to forward.
                              This is ignored if the env var PYODIDE_SYSTEM_PORT is set.
                              If set to 'none', docker instance will not bind to any port.
  --non-interactive           Run docker without the --interactive flag.
                              Useful for running in headless mode on CI server.
  --root                      Run as root user inside the container

Prerequisites:
  Docker has to be set up on your system.
EOF
}

function error() {
  usage
  exit 255
}

while [[ $# -gt 0 ]]
do
  key="$1"
  case $key in
      -h|--help)
        usage
        exit 0
      ;;
      --pre-built)
        if [[ -n ${PYODIDE_DOCKER_IMAGE} ]]; then
          echo "WARNING: will use the env var PYODIDE_DOCKER_IMAGE=${PYODIDE_DOCKER_IMAGE},
          the flag --pre-built has no effect"
        fi
        DEFAULT_PYODIDE_DOCKER_IMAGE="pyodide/pyodide:${PYODIDE_PREBUILT_IMAGE_TAG}"
        shift
      ;;
      -p|--port)
        if [ "$#" -lt 2 ]; then
          >&2 echo "port cannot be empty"
          error
        fi
        if [[ -n ${PYODIDE_SYSTEM_PORT} ]]; then
          echo "WARNING: will use the env var PYODIDE_SYSTEM_PORT=${PYODIDE_SYSTEM_PORT} instead of the provided port"
        else
          PYODIDE_SYSTEM_PORT=$2
        fi
        shift 2
      ;;
      --non-interactive)
        DOCKER_INTERACTIVE="--interactive=false"
        shift
      ;;
      --root)
        USER_FLAG=()
        shift
      ;;
      -*)
        >&2 echo "Unknown option $1"
        error
      ;;
      *)
        DOCKER_COMMAND="$*"
        break
      ;;
  esac
done

PYODIDE_DOCKER_PORT=${PYODIDE_DOCKER_PORT:-"8000"}
PYODIDE_SYSTEM_PORT=${PYODIDE_SYSTEM_PORT:-${DEFAULT_PYODIDE_SYSTEM_PORT}}
PYODIDE_DOCKER_IMAGE=${PYODIDE_DOCKER_IMAGE:-${DEFAULT_PYODIDE_DOCKER_IMAGE}}

# in case the port is not a number, do not bind the port
case $PYODIDE_SYSTEM_PORT in
  none)
  PORT_CONFIGURATION_LINE=()
  ;;
  ''|*[!0-9]*) # contains a non-digit character, therefore it is not a number
  echo "WARNING: Invalid port argument '$PYODIDE_SYSTEM_PORT'. Port binding disabled."
  PORT_CONFIGURATION_LINE=()
  ;;
  *)
  PORT_CONFIGURATION_LINE=("-p" "$PYODIDE_SYSTEM_PORT:$PYODIDE_DOCKER_PORT")
  ;;
esac

mkdir -p .docker_home

# Start a detached container as root, add the host uname and uid to /etc/passwd,
# then run forever
CONTAINER=$(\
  docker run \
    "${PORT_CONFIGURATION_LINE[@]}" \
    -d --rm \
    -v "$PWD":/src \
    --user root \
    --shm-size 2g \
    "${PYODIDE_DOCKER_IMAGE}" \
    /bin/bash -c " \
      echo '${USER_ACCOUNT_INFO}' >> /etc/passwd ; \
      tail -f /dev/null \
    " \
)

EXIT_STATUS=0
# Execute the provided command as the host user with HOME=/src
docker exec \
  "$DOCKER_INTERACTIVE" --tty \
  "${USER_FLAG[@]}" \
  "$CONTAINER" \
  /bin/bash -c "${DOCKER_COMMAND}" || EXIT_STATUS=$?

docker kill "$CONTAINER" > /dev/null
exit $EXIT_STATUS

#!/usr/bin/env bash

PYODIDE_IMAGE_REPO="pyodide"
PYODIDE_IMAGE_TAG="20220213-chrome97-firefox96"
PYODIDE_PREBUILT_IMAGE_TAG="0.19.0"
DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}"
DEFAULT_PYODIDE_SYSTEM_PORT="none"
DOCKER_COMMAND="/bin/bash"
DOCKER_INTERACTIVE="--interactive"


USER_HOME="/src/.docker_home"
USER_NAME="$(id -u -n)"
USER_PASS="x"
USER_ID="$(id -u)"
USER_GID="$(id -g)"
USER_COMMENT_FIELD="${USER_NAME} pyodide user alias"
USER_INTERPRETER="/sbin/nologin"
USER_ACCOUNT_INFO="${USER_NAME}:${USER_PASS}:${USER_ID}:${USER_GID}:${USER_COMMENT_FIELD}:${USER_HOME}:${USER_INTERPRETER}"
USER_FLAG=("--user" "$USER_ID:$USER_GID")

set -eo pipefail


function usage() {
  cat > /dev/stdout <<EOF
Usage: run_docker [OPTIONS] [COMMAND] [ARG...]

Runs COMMAND in a new Pyodide docker container. If no COMMAND is provided, starts a bash
shell in the container.

Options:
  -h, --help                  Show this information and exit.
  --pre-built                 Use the prebuilt Pyodide image.
                              This is ignored if the env var PYODIDE_DOCKER_IMAGE is set.
  -p, --port <port>           System port to which to forward.
                              This is ignored if the env var PYODIDE_SYSTEM_PORT is set.
                              If set to 'none', docker instance will not bind to any port.
  --non-interactive           Run docker without the --interactive flag.
                              Useful for running in headless mode on CI server.
  --root                      Run as root user inside the container

Prerequisites:
  Docker has to be set up on your system.
EOF
}

function error() {
  usage
  exit 255
}

while [[ $# -gt 0 ]]
do
  key="$1"
  case $key in
      -h|--help)
        usage
        exit 0
      ;;
      --pre-built)
        if [[ -n ${PYODIDE_DOCKER_IMAGE} ]]; then
          echo "WARNING: will use the env var PYODIDE_DOCKER_IMAGE=${PYODIDE_DOCKER_IMAGE},
          the flag --pre-built has no effect"
        fi
        DEFAULT_PYODIDE_DOCKER_IMAGE="pyodide/pyodide:${PYODIDE_PREBUILT_IMAGE_TAG}"
        shift
      ;;
      -p|--port)
        if [ "$#" -lt 2 ]; then
          >&2 echo "port cannot be empty"
          error
        fi
        if [[ -n ${PYODIDE_SYSTEM_PORT} ]]; then
          echo "WARNING: will use the env var PYODIDE_SYSTEM_PORT=${PYODIDE_SYSTEM_PORT} instead of the provided port"
        else
          PYODIDE_SYSTEM_PORT=$2
        fi
        shift 2
      ;;
      --non-interactive)
        DOCKER_INTERACTIVE="--interactive=false"
        shift
      ;;
      --root)
        USER_FLAG=()
        shift
      ;;
      -*)
        >&2 echo "Unknown option $1"
        error
      ;;
      *)
        DOCKER_COMMAND="$*"
        break
      ;;
  esac
done

PYODIDE_DOCKER_PORT=${PYODIDE_DOCKER_PORT:-"8000"}
PYODIDE_SYSTEM_PORT=${PYODIDE_SYSTEM_PORT:-${DEFAULT_PYODIDE_SYSTEM_PORT}}
PYODIDE_DOCKER_IMAGE=${PYODIDE_DOCKER_IMAGE:-${DEFAULT_PYODIDE_DOCKER_IMAGE}}

# in case the port is not a number, do not bind the port
case $PYODIDE_SYSTEM_PORT in
  none)
  PORT_CONFIGURATION_LINE=()
  ;;
  ''|*[!0-9]*) # contains a non-digit character, therefore it is not a number
  echo "WARNING: Invalid port argument '$PYODIDE_SYSTEM_PORT'. Port binding disabled."
  PORT_CONFIGURATION_LINE=()
  ;;
  *)
  PORT_CONFIGURATION_LINE=("-p" "$PYODIDE_SYSTEM_PORT:$PYODIDE_DOCKER_PORT")
  ;;
esac

mkdir -p .docker_home

# Start a detached container as root, add the host uname and uid to /etc/passwd,
# then run forever
CONTAINER=$(\
  docker run \
    "${PORT_CONFIGURATION_LINE[@]}" \
    -d --rm \
    -v "$PWD":/src \
    --user root \
    --shm-size 2g \
    "${PYODIDE_DOCKER_IMAGE}" \
    /bin/bash -c " \
      echo '${USER_ACCOUNT_INFO}' >> /etc/passwd ; \
      tail -f /dev/null \
    " \
)

EXIT_STATUS=0
# Execute the provided command as the host user with HOME=/src
docker exec \
  "$DOCKER_INTERACTIVE" --tty \
  "${USER_FLAG[@]}" \
  "$CONTAINER" \
  /bin/bash -c "${DOCKER_COMMAND}" || EXIT_STATUS=$?

docker kill "$CONTAINER" > /dev/null
exit $EXIT_STATUS
