[bazel] Remove dependency on system python (#1676)

Thanks to @rickeylev for the help with figuring out the correct way to
ensure the Python interpreter with all its dependencies is correctly
added to the toolchain files.

Note: this does not touch Windows support, which may also have a similar
issue. I see windows bat files using `py -3`, which may also not be
hermetic, but I have no access to any Windows machines to test and fix
that, so I'll leave that untouched for now.

Fixes: #1675, #1642
diff --git a/.circleci/config.yml b/.circleci/config.yml
index a46d71a..b1d4346 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -374,8 +374,7 @@
               bazel_version: 
                 - "7.x"
                 - "8.x"
-              # https://github.com/emscripten-core/emsdk/issues/1642
-              # - "9.x"
+                - "9.x"
   test-bazel-windows:
     jobs:
       - test-bazel-windows:
diff --git a/bazel/MODULE.bazel b/bazel/MODULE.bazel
index 194bb33..886c28f 100644
--- a/bazel/MODULE.bazel
+++ b/bazel/MODULE.bazel
@@ -8,11 +8,11 @@
 bazel_dep(name = "aspect_rules_js", version = "2.9.2")
 bazel_dep(name = "rules_nodejs", version = "6.7.3")
 bazel_dep(name = "rules_cc", version = "0.2.16")
-bazel_dep(name = "rules_python", version = "1.8.3")
+bazel_dep(name = "rules_python", version = "1.8.4")
 
 python = use_extension("@rules_python//python/extensions:python.bzl", "python")
 python.toolchain(
-    python_version = "3.13",
+    python_version = "3.14",
 )
 
 node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
diff --git a/bazel/emscripten_toolchain/emar.sh b/bazel/emscripten_toolchain/emar.sh
index b4ead6e..7748cc7 100755
--- a/bazel/emscripten_toolchain/emar.sh
+++ b/bazel/emscripten_toolchain/emar.sh
@@ -2,4 +2,4 @@
 
 source $(dirname $0)/env.sh
 
-exec python3 $EMSCRIPTEN/emar.py "$@"
+exec $EMSDK_PYTHON $EMSCRIPTEN/emar.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc.sh b/bazel/emscripten_toolchain/emcc.sh
index 5fdaf9c..3fd72da 100755
--- a/bazel/emscripten_toolchain/emcc.sh
+++ b/bazel/emscripten_toolchain/emcc.sh
@@ -2,4 +2,4 @@
 
 source $(dirname $0)/env.sh
 
-exec python3 $EMSCRIPTEN/emcc.py "$@"
+exec $EMSDK_PYTHON $EMSCRIPTEN/emcc.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc_link.sh b/bazel/emscripten_toolchain/emcc_link.sh
index 44f3235..e538915 100755
--- a/bazel/emscripten_toolchain/emcc_link.sh
+++ b/bazel/emscripten_toolchain/emcc_link.sh
@@ -2,4 +2,4 @@
 
 source $(dirname $0)/env.sh
 
-exec python3 $(dirname $0)/link_wrapper.py "$@"
+exec $EMSDK_PYTHON $(dirname $0)/link_wrapper.py "$@"
diff --git a/bazel/emscripten_toolchain/toolchain.bzl b/bazel/emscripten_toolchain/toolchain.bzl
index 15cc001..80fd803 100644
--- a/bazel/emscripten_toolchain/toolchain.bzl
+++ b/bazel/emscripten_toolchain/toolchain.bzl
@@ -75,6 +75,11 @@
 
     nodejs_path = ctx.file.nodejs_bin.path
 
+    python_exec_runtime = (
+        ctx.toolchains["@rules_python//python:exec_tools_toolchain_type"].
+            exec_tools.exec_interpreter[platform_common.ToolchainInfo].py3_runtime
+        )
+
     builtin_sysroot = emscripten_dir + "/emscripten/cache/sysroot"
 
     emcc_script = "emcc.%s" % ctx.attr.script_extension
@@ -1078,6 +1083,10 @@
                     key = "NODE_JS_PATH",
                     value = nodejs_path,
                 ),
+                env_entry(
+                    key = "EMSDK_PYTHON",
+                    value = python_exec_runtime.interpreter.path,
+                ),
             ],
         ),
         # Use llvm backend.  Off by default, enabled via --features=llvm_backend
@@ -1156,4 +1165,23 @@
         "script_extension": attr.string(mandatory = True, values = ["sh", "bat"]),
     },
     provides = [CcToolchainConfigInfo],
+    toolchains = [
+        "@rules_python//python:exec_tools_toolchain_type",
+    ]
+)
+
+def _python_interpreter_files_impl(ctx):
+    python_exec_runtime = (
+        ctx.toolchains["@rules_python//python:exec_tools_toolchain_type"].
+            exec_tools.exec_interpreter[platform_common.ToolchainInfo].py3_runtime
+        )
+
+    return DefaultInfo(files = python_exec_runtime.files)
+
+
+emscripten_python_interpreter_files = rule(
+    implementation = _python_interpreter_files_impl,
+    toolchains = [
+        "@rules_python//python:exec_tools_toolchain_type",
+    ]
 )
diff --git a/bazel/remote_emscripten_repository.bzl b/bazel/remote_emscripten_repository.bzl
index ac0248d..ccd0842 100644
--- a/bazel/remote_emscripten_repository.bzl
+++ b/bazel/remote_emscripten_repository.bzl
@@ -1,6 +1,6 @@
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite")
-load("//emscripten_toolchain:toolchain.bzl", "emscripten_cc_toolchain_config_rule")
+load("//emscripten_toolchain:toolchain.bzl", "emscripten_cc_toolchain_config_rule", "emscripten_python_interpreter_files")
 load(":emscripten_build_file.bzl", "EMSCRIPTEN_BUILD_FILE_CONTENT_TEMPLATE")
 
 def remote_emscripten_repository(
@@ -45,6 +45,7 @@
     ar_files_name, ar_files_target = _get_name_and_target("ar_files_" + name)
     all_files_name, all_files_target = _get_name_and_target("all_files_" + name)
     cc_wasm_name, cc_wasm_target = _get_name_and_target("cc-compiler-wasm-" + name)
+    python_interpreter_name, python_interpreter_target = _get_name_and_target("python_interpreter-" + name)
 
     wasm_name = "wasm-" + name
 
@@ -54,6 +55,10 @@
     repo_linker_files_target = remote_repo + ":linker_files"
     repo_ar_files_target = remote_repo + ":ar_files"
 
+    emscripten_python_interpreter_files(
+        name = python_interpreter_name,
+    )
+
     native.filegroup(
         name = common_files_name,
         srcs = [
@@ -61,6 +66,7 @@
             "@emsdk//emscripten_toolchain:env.sh",
             "@emsdk//emscripten_toolchain:env.bat",
             "@nodejs//:node_files",
+            python_interpreter_target,
         ],
     )