Change `--extra_toolchains` precedence to last-wins, instead of first-wins.

This only changes the precedence within all uses of `--extra_toolchains`, it does not change the precedence for `register_toolchains` calls.

After this, the priority order for toolchains is:
1. Consider toolchains registered via `--extra_toolchains`
  1. Within this set, the last mentioned toolchain has highest priority
2. Consider toolchains registered via `register_toolchains`
  1. Within this set, the first mentioned toolchain has highest priority

In all cases, pseudo-targets like `:all` and `/...` are ordered by Bazel's package loading mechanism, which is currently using a lexicographic ordering.

Fixes #19945.

Closes #19942. Closes #20031.

PiperOrigin-RevId: 579845262
Change-Id: Ibd9c32e5479a6a27717734852b875f9b3f31b510
diff --git a/site/en/extending/toolchains.md b/site/en/extending/toolchains.md
index d89e66d..c193683 100644
--- a/site/en/extending/toolchains.md
+++ b/site/en/extending/toolchains.md
@@ -508,6 +508,18 @@
 Available platforms and toolchains are tracked as ordered lists for determinism,
 with preference given to earlier items in the list.
 
+The set of available toolchains, in priority order, is created from
+`--extra_toolchains` and `register_toolchains`:
+
+1. Toolchains registered using `--extra_toolchains` are added first.
+  1. Within these, the **last** toolchain has highest priority.
+2. Toolchains registered using `register_toolchains`
+  1. Within these, the **first** mentioned toolchain has highest priority.
+
+**NOTE:** [Pseudo-targets like `:all`, `:*`, and
+`/...`](/run/build#specifying-build-targets) are ordered by Bazel's package
+loading mechanism, which uses a lexicographic ordering.
+
 The resolution steps are as follows.
 
 1. A `target_compatible_with` or `exec_compatible_with` clause *matches* a
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunction.java
index 2b40720..cec918f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunction.java
@@ -83,12 +83,13 @@
     ImmutableList.Builder<SignedTargetPattern> targetPatternBuilder = new ImmutableList.Builder<>();
 
     // Get the toolchains from the configuration.
+    // Reverse the list so the last one defined takes precedences.
     PlatformConfiguration platformConfiguration =
         configuration.getFragment(PlatformConfiguration.class);
     try {
       targetPatternBuilder.addAll(
           TargetPatternUtil.parseAllSigned(
-              platformConfiguration.getExtraToolchains(), mainRepoParser));
+              platformConfiguration.getExtraToolchains().reverse(), mainRepoParser));
     } catch (InvalidTargetPatternException e) {
       throw new RegisteredToolchainsFunctionException(
           new InvalidToolchainLabelException(e), Transience.PERSISTENT);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunctionTest.java
index cb3e364..0892a30 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/toolchains/RegisteredToolchainsFunctionTest.java
@@ -153,8 +153,8 @@
     // Verify that the target registered with the extra_toolchains flag is first in the list.
     assertToolchainLabels(result.get(toolchainsKey))
         .containsAtLeast(
-            Label.parseCanonicalUnchecked("//extra:extra_toolchain_impl_1"),
             Label.parseCanonicalUnchecked("//extra:extra_toolchain_impl_2"),
+            Label.parseCanonicalUnchecked("//extra:extra_toolchain_impl_1"),
             Label.parseCanonicalUnchecked("//toolchain:toolchain_1_impl"))
         .inOrder();
   }
diff --git a/src/test/shell/integration/java_integration_test.sh b/src/test/shell/integration/java_integration_test.sh
index 59d134d..758ce8a 100755
--- a/src/test/shell/integration/java_integration_test.sh
+++ b/src/test/shell/integration/java_integration_test.sh
@@ -267,7 +267,8 @@
   # Set javabase to an absolute path.
   bazel build //$pkg/java/hello:hello //$pkg/java/hello:hello_deploy.jar \
       "$stamp_arg" \
-      --extra_toolchains="//$pkg/jvm:all,//tools/jdk:all" \
+      --extra_toolchains="//tools/jdk:all" \
+      --extra_toolchains="//$pkg/jvm:all" \
       --platforms="//$pkg/jvm:platform" \
       "$embed_label" >&"$TEST_log" \
       || fail "Build failed"
diff --git a/src/test/shell/integration/toolchain_test.sh b/src/test/shell/integration/toolchain_test.sh
index d81aa14..fa15c4a 100755
--- a/src/test/shell/integration/toolchain_test.sh
+++ b/src/test/shell/integration/toolchain_test.sh
@@ -2781,6 +2781,87 @@
   expect_log "Selected execution platform //${pkg}/platforms:platform2"
 }
 
+# Regression test for https://github.com/bazelbuild/bazel/issues/19945.
+function test_extra_toolchain_precedence {
+  local -r pkg="${FUNCNAME[0]}"
+
+  write_test_toolchain "${pkg}"
+  write_test_rule "${pkg}"
+
+  cat > WORKSPACE <<EOF
+register_toolchains('//${pkg}:toolchain_1')
+EOF
+
+  cat > "${pkg}/BUILD" <<EOF
+load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')
+
+package(default_visibility = ["//visibility:public"])
+
+# Define and declare four identical toolchains.
+[
+  [
+    test_toolchain(
+      name = 'toolchain_impl_' + str(i),
+      extra_str = 'foo from toolchain_' + str(i),
+    ),
+    toolchain(
+      name = 'toolchain_' + str(i),
+      toolchain_type = '//${pkg}/toolchain:test_toolchain',
+      toolchain = ':toolchain_impl_' + str(i)
+    ),
+  ]
+  for i in range(1, 5)
+]
+EOF
+
+  mkdir -p "${pkg}/demo"
+  cat > "${pkg}/demo/BUILD" <<EOF
+load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')
+package(default_visibility = ["//visibility:public"])
+
+# Use the toolchain.
+use_toolchain(
+    name = 'use',
+    message = 'this is the rule')
+EOF
+
+  bazel query "//${pkg}:*"
+
+  bazel \
+    build \
+    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
+  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from toolchain_1"'
+
+  # Test that bazelrc options take precedence over registered toolchains
+  cat > "${pkg}/toolchain_rc" <<EOF
+import ${bazelrc}
+build --extra_toolchains=//${pkg}:toolchain_2
+EOF
+
+  bazel \
+    --${PRODUCT_NAME}rc="${pkg}/toolchain_rc" \
+    build \
+    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
+  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from toolchain_2"'
+
+  # Test that command-line options take precedence over other toolchains
+  bazel \
+    --${PRODUCT_NAME}rc="${pkg}/toolchain_rc" \
+    build \
+    --extra_toolchains=//${pkg}:toolchain_3 \
+    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
+  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from toolchain_3"'
+
+  # Test that the last --extra_toolchains takes precedence
+  bazel \
+    --${PRODUCT_NAME}rc="${pkg}/toolchain_rc" \
+    build \
+    --extra_toolchains=//${pkg}:toolchain_3 \
+    --extra_toolchains=//${pkg}:toolchain_4 \
+    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
+  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from toolchain_4"'
+}
+
 # TODO(katre): Test using toolchain-provided make variables from a genrule.
 
 run_suite "toolchain tests"