Support dual execution modes in rbe_config.bzl with automatic host mode (#2747)

This change introduces automatic support for --exec_mode=host in
rbe_config.bzl when generating RBE toolchain configurations:
- By default (when RBE_CONFIG_CONTAINER is unset), rbe_config continues
to use docker mode (--exec_mode=docker), compiling rbe_configs_gen via a
golang:1.21 container (DooD) and running detection inside Docker.
- When RBE_CONFIG_CONTAINER is exported in the environment (e.g. CI
workers), rbe_config automatically switches to host mode
(--exec_mode=host), compiling rbe_configs_gen natively on the host using
go build and running detection against the host filesystem without
Docker.
- Enforces Host Container Authority in host mode: the target container
image string in @rbe_ubuntu//config:platform is authoritatively
determined by RBE_CONFIG_CONTAINER to prevent toolchain and ABI
discrepancies.
- Updates rules/README.md to document execution modes and environment
variables.
diff --git a/rules/README.md b/rules/README.md
index d001840..35ef4af 100644
--- a/rules/README.md
+++ b/rules/README.md
@@ -8,8 +8,8 @@
 
 ## 1. Design & Architectural Highlights
 
-- **Self-Compiling Go (DooD)**: The repository rule downloads the upstream `bazel-toolchains` source repository and dynamically compiles `rbe_configs_gen` inside a sibling `golang:1.21` Docker container via the host's Docker socket (Docker-out-of-Docker). This eliminates any local Go compiler installation requirements.
-- **On-Demand Auto-Detection**: The compiled Go binary launches your target toolchain container, mounts your running host Bazel binary, auto-detects the compiler and JDK runtimes inside the container sandbox, and extracts the generated C++ and Java toolchain configurations directly into Bazel's `output_base`.
+- **Dual Execution Modes (`docker` vs `host`)**: Supports both standard Docker-out-of-Docker generation (default when `RBE_CONFIG_CONTAINER` is unset) and direct host environment generation (automatically enabled when `RBE_CONFIG_CONTAINER` is set).
+- **Direct Host Toolchain Detection**: When `RBE_CONFIG_CONTAINER` is exported, `rbe_configs_gen` compiles natively on the host using `go build` and runs with `--exec_mode=host`, executing C++ and Java auto-detection (`@@rules_cc...`, `java -version`) against the host filesystem without Docker. The target container image is authoritative from the `RBE_CONFIG_CONTAINER` environment variable.
 - **Decoupled Presets Rollout**: Standard environment configurations are stored in a public JSON file (`rules/rbe_presets.json`) on the `master` branch of this repository. At runtime, the repository rule dynamically downloads this file. If the CI maintainers update a container image tag or environment parameter on `master`, **all projects immediately receive the update without modifying their pinned ruleset commit hashes!**
 
 ---
@@ -131,7 +131,25 @@
 
 ---
 
-## 5. Advanced: Bazel Version & Binary Resolution Strategy
+## 5. Execution Modes (`docker` vs `host`)
+
+`rbe_config` supports two execution modes for toolchain auto-detection, automatically selected based on your environment:
+
+### 5.1 Docker Mode (`--exec_mode=docker`, Default)
+When `RBE_CONFIG_CONTAINER` is **unset** (e.g., on developer workstations running macOS or Linux):
+- **Go Compilation**: `rbe_configs_gen` is compiled inside a sibling `golang:1.21` Docker container using Docker-out-of-Docker (`/var/run/docker.sock`). No local Go compiler is required.
+- **Toolchain Detection**: `rbe_configs_gen` pulls and runs the target toolchain container image (from `preset` or `spec["container"]`), mounts host Bazel, and runs C++ and Java auto-detection inside the sandboxed container.
+
+### 5.2 Host Mode (`--exec_mode=host`, Automatic in Container CI)
+When the **`RBE_CONFIG_CONTAINER`** environment variable is exported (e.g., in containerized CI workers or Buildkite pipelines):
+- **Automatic Activation**: `rbe_config` automatically switches to `host` mode.
+- **Go Compilation**: `rbe_configs_gen` is compiled natively on the host using `go build` (requires Go installed in `PATH`; no Docker required).
+- **Direct Host Detection**: `rbe_configs_gen` runs with `--exec_mode=host`, discovering C++ compilers (`gcc`/`clang`) and JDK runtimes directly from the host filesystem without invoking Docker.
+- **Host Container Authority**: In `host` mode, because detection happens against the host filesystem, the RBE execution platform image in `@rbe_ubuntu//config:platform` is **authoritatively determined by `RBE_CONFIG_CONTAINER`** (overriding any requested preset container image to prevent ABI or toolchain discrepancies).
+
+---
+
+## 6. Advanced: Bazel Version & Binary Resolution Strategy
 
 To generate toolchain configurations, `rbe_config` must determine which Bazel version to target and potentially mount a host Bazel binary inside the compiler container. It resolves this using the following **four-tier precedence lookup strategy**:
 
diff --git a/rules/rbe_config.bzl b/rules/rbe_config.bzl
index 0489ea4..24935ad 100644
--- a/rules/rbe_config.bzl
+++ b/rules/rbe_config.bzl
@@ -38,29 +38,36 @@
     return repository_ctx.path("bazel-toolchains-src")
 
 
-def _compile_generator(repository_ctx, src_dir):
-    """Compiles rbe_configs_gen inside a sibling Go container.
+def _compile_generator(repository_ctx, src_dir, exec_mode):
+    """Compiles rbe_configs_gen using Docker Go container (docker mode) or host go compiler (host mode).
 
     Returns:
         Path to the compiled generator executable.
     """
     rbe_gen_path = repository_ctx.path("bazel-toolchains-src/rbe_configs_gen")
-    print("rbe_config: Compiling rbe_configs_gen via Go container...")
-    compile_res = repository_ctx.execute([
-        "docker", "run", "--rm",
-        "-v", "{}:/srcdir".format(src_dir),
-        "-w", "/srcdir",
-        "golang:1.21",
-        "go", "build", "-o", "/srcdir/rbe_configs_gen", "./cmd/rbe_configs_gen"
-    ])
+    if exec_mode == "docker":
+        print("rbe_config: Compiling rbe_configs_gen via Go container (docker mode)...")
+        compile_res = repository_ctx.execute([
+            "docker", "run", "--rm",
+            "-v", "{}:/srcdir".format(src_dir),
+            "-w", "/srcdir",
+            "golang:1.21",
+            "go", "build", "-o", "/srcdir/rbe_configs_gen", "./cmd/rbe_configs_gen"
+        ])
+    else:
+        print("rbe_config: Compiling rbe_configs_gen on host environment (host mode)...")
+        compile_res = repository_ctx.execute(
+            ["go", "build", "-o", str(rbe_gen_path), "./cmd/rbe_configs_gen"],
+            working_directory = str(src_dir),
+        )
 
     if compile_res.return_code != 0:
-        fail("rbe_config: compilation of rbe_configs_gen failed:\nStdout: {}\nStderr: {}".format(compile_res.stdout, compile_res.stderr))
+        fail("rbe_config: compilation of rbe_configs_gen failed (exec_mode={}):\nStdout: {}\nStderr: {}".format(exec_mode, compile_res.stdout, compile_res.stderr))
     return rbe_gen_path
 
 
-def _generate_toolchains(repository_ctx, rbe_gen_path, bazel_version, bazel_path, container_image, cpp_env):
-    """Runs the generator inside the sandboxed container and extracts RBE files."""
+def _generate_toolchains(repository_ctx, rbe_gen_path, bazel_version, bazel_path, container_image, cpp_env, exec_mode):
+    """Runs the generator in docker or host mode and extracts RBE files."""
     # Serialize C++ environment dict to JSON inside sandbox
     cpp_env_json = "cpp_env.json"
     repository_ctx.file(cpp_env_json, json.encode(cpp_env))
@@ -70,6 +77,7 @@
 
     args = [
         rbe_gen_path,
+        "--exec_mode=" + exec_mode,
         "--toolchain_container=" + container_image,
         "--cpp_env_json=" + str(repository_ctx.path(cpp_env_json)),
         "--output_tarball=" + str(repository_ctx.path(output_tarball)),
@@ -85,11 +93,11 @@
     else:
         fail("rbe_config: Neither bazel_version nor bazel_path is available.")
 
-    print("rbe_config: Executing generator to detect toolchains inside {}...".format(container_image))
-    exec_res = repository_ctx.execute(args)
+    print("rbe_config: Executing generator (exec_mode={}) to detect toolchains (target container: {})...".format(exec_mode, container_image))
+    exec_res = repository_ctx.execute(args, environment = cpp_env if cpp_env else {})
 
     if exec_res.return_code != 0:
-        fail("rbe_config: Dynamic generation failed:\nStdout: {}\nStderr: {}".format(exec_res.stdout, exec_res.stderr))
+        fail("rbe_config: Dynamic generation failed (exec_mode={}):\nStdout: {}\nStderr: {}".format(exec_mode, exec_res.stdout, exec_res.stderr))
 
     # Extract the generated configs directly into the repository's directory
     repository_ctx.extract(archive = output_tarball)
@@ -98,20 +106,33 @@
 # --- Private Repository Rule Entrypoint ---
 def _rbe_config_impl(repository_ctx):
     # 1. Resolve presets/custom image container and environment
-    container_image, cpp_env = _resolve_preset(
+    preset_container, cpp_env = _resolve_preset(
         repository_ctx,
         repository_ctx.attr.preset_name,
         repository_ctx.attr.container,
         repository_ctx.attr.cpp_env
     )
 
-    # 2. Download bazel-toolchains source code
+    # 2. Determine execution mode ("docker" vs "host") and target container image
+    host_container = repository_ctx.os.environ.get("RBE_CONFIG_CONTAINER")
+    if host_container:
+        exec_mode = "host"
+        container_image = host_container
+        if preset_container and preset_container != host_container:
+            print("rbe_config: RBE_CONFIG_CONTAINER ('{}') overrides requested preset container ('{}') in host mode.".format(host_container, preset_container))
+    else:
+        exec_mode = "docker"
+        container_image = preset_container
+
+    print("rbe_config: Using exec_mode='{}' for toolchain generation.".format(exec_mode))
+
+    # 3. Download bazel-toolchains source code
     src_dir = _download_bazel_toolchains(repository_ctx)
 
-    # 3. Compile rbe_configs_gen
-    rbe_gen_path = _compile_generator(repository_ctx, src_dir)
+    # 4. Compile rbe_configs_gen in docker or host mode
+    rbe_gen_path = _compile_generator(repository_ctx, src_dir, exec_mode)
 
-    # 4. Resolve Bazel version or locate host Bazel
+    # 5. Resolve Bazel version or locate host Bazel
     bazel_version = None
     bazel_path = repository_ctx.os.environ.get("RBE_CONFIG_BAZEL_PATH")
 
@@ -133,8 +154,8 @@
                 else:
                     fail("rbe_config: Bazel executable not found in PATH. If you are using a custom-named binary or non-standard layout, please export RBE_CONFIG_BAZEL_PATH=/path/to/your/binary or use a tools/bazel wrapper script.")
 
-    # 5. Run the generator and extract configurations
-    _generate_toolchains(repository_ctx, rbe_gen_path, bazel_version, bazel_path, container_image, cpp_env)
+    # 6. Run the generator and extract configurations
+    _generate_toolchains(repository_ctx, rbe_gen_path, bazel_version, bazel_path, container_image, cpp_env, exec_mode)
 
 
 # Private repository rule
@@ -149,7 +170,8 @@
     environ = [
         "RBE_CONFIG_BAZEL_PATH",
         "BAZEL_REAL",
-    ], # Propagate custom Bazel path environment variables
+        "RBE_CONFIG_CONTAINER",
+    ], # Propagate custom Bazel path and container environment variables
 )
 
 # --- Public WORKSPACE wrapper macro ---