fix(toolchains): use posix-compatible exec -a alternative (#3010)

The `exec -a` command doesn't work in dash, the default shell for
Ubuntu/debian.

To work around, use `sh -c`, which is posix and dash compatible. This
allows changing
the argv0 while invoking a different command. Also adds a test to verify
the the
runtime_env toolchain works with bootstrap script.

Fixes https://github.com/bazel-contrib/rules_python/issues/3009

(cherry picked from commit c4543cd193752d0248226dcd07cc027e63ed7b8b)
diff --git a/python/private/runtime_env_toolchain_interpreter.sh b/python/private/runtime_env_toolchain_interpreter.sh
index 7b3ec59..dd4d648 100755
--- a/python/private/runtime_env_toolchain_interpreter.sh
+++ b/python/private/runtime_env_toolchain_interpreter.sh
@@ -71,14 +71,15 @@
   if [ ! -e "$PYTHON_BIN" ]; then
     die "ERROR: Python interpreter does not exist: $PYTHON_BIN"
   fi
-  # PYTHONEXECUTABLE is also used because `exec -a` doesn't fully trick the
-  # pyenv wrappers.
+  # PYTHONEXECUTABLE is also used because switching argv0 doesn't fully trick
+  # the pyenv wrappers.
   # NOTE: The PYTHONEXECUTABLE envvar only works for non-Mac starting in Python 3.11
   export PYTHONEXECUTABLE="$venv_bin"
-  # Python looks at argv[0] to determine sys.executable, so use exec -a
-  # to make it think it's the venv's binary, not the actual one invoked.
-  # NOTE: exec -a isn't strictly posix-compatible, but very widespread
-  exec -a "$venv_bin" "$PYTHON_BIN" "$@"
+  # Python looks at argv[0] to determine sys.executable, so set that to the venv
+  # binary, not the actual one invoked.
+  # NOTE: exec -a would be simpler, but isn't posix-compatible, and dash shell
+  # (Ubuntu/debian default) doesn't support it; see #3009.
+  exec sh -c "$PYTHON_BIN \$@" "$venv_bin" "$@"
 else
   exec "$PYTHON_BIN" "$@"
 fi
diff --git a/tests/runtime_env_toolchain/BUILD.bazel b/tests/runtime_env_toolchain/BUILD.bazel
index 2f82d20..f1bda25 100644
--- a/tests/runtime_env_toolchain/BUILD.bazel
+++ b/tests/runtime_env_toolchain/BUILD.bazel
@@ -40,3 +40,26 @@
     tags = ["no-remote-exec"],
     deps = ["//python/runfiles"],
 )
+
+py_reconfig_test(
+    name = "bootstrap_script_test",
+    srcs = ["toolchain_runs_test.py"],
+    bootstrap_impl = "script",
+    data = [
+        "//tests/support:current_build_settings",
+    ],
+    extra_toolchains = [
+        "//python/runtime_env_toolchains:all",
+        # Necessary for RBE CI
+        CC_TOOLCHAIN,
+    ],
+    main = "toolchain_runs_test.py",
+    # With bootstrap=script, the build version must match the runtime version
+    # because the venv has the version in the lib/site-packages dir name.
+    python_version = PYTHON_VERSION,
+    # Our RBE has Python 3.6, which is too old for the language features
+    # we use now. Using the runtime-env toolchain on RBE is pretty
+    # questionable anyways.
+    tags = ["no-remote-exec"],
+    deps = ["//python/runfiles"],
+)
diff --git a/tests/runtime_env_toolchain/toolchain_runs_test.py b/tests/runtime_env_toolchain/toolchain_runs_test.py
index 7be2472..c66b0bb 100644
--- a/tests/runtime_env_toolchain/toolchain_runs_test.py
+++ b/tests/runtime_env_toolchain/toolchain_runs_test.py
@@ -1,6 +1,7 @@
 import json
 import pathlib
 import platform
+import sys
 import unittest
 
 from python.runfiles import runfiles
@@ -23,6 +24,14 @@
                 settings["interpreter"]["short_path"],
             )
 
+        if settings["bootstrap_impl"] == "script":
+            # Verify we're running in a venv
+            self.assertNotEqual(sys.prefix, sys.base_prefix)
+            # .venv/ occurs for a build-time venv.
+            # For a runtime created venv, it goes into a temp dir, so
+            # look for the /bin/ dir as an indicator.
+            self.assertRegex(sys.executable, r"[.]venv/|/bin/")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/support/sh_py_run_test.bzl b/tests/support/sh_py_run_test.bzl
index 69141fe..49445ed 100644
--- a/tests/support/sh_py_run_test.bzl
+++ b/tests/support/sh_py_run_test.bzl
@@ -135,6 +135,7 @@
     ctx.actions.write(
         output = info,
         content = json.encode({
+            "bootstrap_impl": ctx.attr._bootstrap_impl_flag[config_common.FeatureFlagInfo].value,
             "interpreter": {
                 "short_path": runtime.interpreter.short_path if runtime.interpreter else None,
             },
@@ -153,6 +154,11 @@
 This is so tests can verify information about the build config used for them.
 """,
     implementation = _current_build_settings_impl,
+    attrs = {
+        "_bootstrap_impl_flag": attr.label(
+            default = "//python/config_settings:bootstrap_impl",
+        ),
+    },
     toolchains = [
         TARGET_TOOLCHAIN_TYPE,
     ],