Implement legacy_symbols

In the repositories that don't have autoloads we also expose native.legacy_symbols.
Those can be used to fallback to the native symbol, whenever it's still available in Bazel.

Fallback using a top-level symbol doesn't work, because BzlCompileFunction would throw an error when it's mentioned.

legacy_symbols aren't available when autoloads are not enabled.

The feature is intended to be used with bazel_features repository, which can correctly report native symbols on all versions of Bazel.

PiperOrigin-RevId: 673741927
Change-Id: I2334030d70cbb944b92784e32a184841ea238d51
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AutoloadSymbols.java b/src/main/java/com/google/devtools/build/lib/packages/AutoloadSymbols.java
index e9f08c4..03a9647 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AutoloadSymbols.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AutoloadSymbols.java
@@ -38,6 +38,7 @@
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
+import net.starlark.java.eval.GuardedValue;
 import net.starlark.java.eval.Starlark;
 import net.starlark.java.eval.StarlarkSemantics;
 
@@ -232,10 +233,14 @@
       ImmutableMap<String, Object> originalEnv,
       ImmutableMap<String, Object> newSymbols) {
     if (isWithAutoloads) {
-      return modifyBuildBzlEnv(originalEnv, /* add= */ newSymbols, /* remove= */ removedSymbols);
+      return modifyBuildBzlEnv(
+          originalEnv, /* add= */ newSymbols, /* remove= */ removedSymbols, isWithAutoloads);
     } else {
       return modifyBuildBzlEnv(
-          originalEnv, /* add= */ ImmutableMap.of(), /* remove= */ partiallyRemovedSymbols);
+          originalEnv,
+          /* add= */ ImmutableMap.of(),
+          /* remove= */ partiallyRemovedSymbols,
+          isWithAutoloads);
     }
   }
 
@@ -247,7 +252,8 @@
   private ImmutableMap<String, Object> modifyBuildBzlEnv(
       ImmutableMap<String, Object> originalEnv,
       ImmutableMap<String, Object> add,
-      ImmutableList<String> remove) {
+      ImmutableList<String> remove,
+      boolean isWithAutoloads) {
     Map<String, Object> envBuilder = new LinkedHashMap<>(originalEnv);
     Map<String, Object> nativeBindings =
         convertNativeStructToMap((StarlarkInfo) envBuilder.remove("native"));
@@ -266,6 +272,30 @@
         envBuilder.remove(symbol);
       }
     }
+
+    if (!isWithAutoloads) {
+      // In the repositories that don't have autoloads we also expose native.legacy_symbols.
+      // Those can be used to fallback to the native symbol, whenever it's still available in Bazel.
+      // Fallback using a top-level symbol doesn't work, because BzlCompileFunction would throw an
+      // error when it's mentioned.
+      // legacy_symbols aren't available when autoloads are not enabled. The feature is intended to
+      // be use with bazel_features repository, which can correctly report native symbols on all
+      // versions of Bazel.
+      ImmutableMap<String, Object> legacySymbols =
+          envBuilder.entrySet().stream()
+              .filter(entry -> AUTOLOAD_CONFIG.containsKey(entry.getKey()))
+              .collect(
+                  toImmutableMap(
+                      e -> e.getKey(),
+                      // Drop GuardedValue - it doesn't work on non-toplevel symbols
+                      e ->
+                          e.getValue() instanceof GuardedValue
+                              ? ((GuardedValue) e.getValue()).getObject()
+                              : e.getValue()));
+      nativeBindings.put(
+          "legacy_symbols", StructProvider.STRUCT.create(legacySymbols, "no native symbol '%s'"));
+    }
+
     envBuilder.put(
         "native", StructProvider.STRUCT.create(nativeBindings, "no native function or rule '%s'"));
     return ImmutableMap.copyOf(envBuilder);
@@ -485,7 +515,8 @@
           "rules_sh",
           "apple_common",
           "bazel_skylib",
-          "bazel_tools");
+          "bazel_tools",
+          "bazel_features");
 
   private static final ImmutableMap<String, SymbolRedirect> AUTOLOAD_CONFIG =
       ImmutableMap.<String, SymbolRedirect>builder()
diff --git a/src/test/shell/integration/load_removed_symbols_test.sh b/src/test/shell/integration/load_removed_symbols_test.sh
index ba2e070..355fb78 100755
--- a/src/test/shell/integration/load_removed_symbols_test.sh
+++ b/src/test/shell/integration/load_removed_symbols_test.sh
@@ -59,6 +59,26 @@
 EOF
 }
 
+
+function mock_rules_java() {
+  rules_java_workspace="${TEST_TMPDIR}/rules_java_workspace"
+  mkdir -p "${rules_java_workspace}/java"
+  touch "${rules_java_workspace}/java/BUILD"
+  touch "${rules_java_workspace}/WORKSPACE"
+  cat > "${rules_java_workspace}/MODULE.bazel" << EOF
+module(name = "rules_java")
+EOF
+  cat >> MODULE.bazel << EOF
+bazel_dep(
+    name = "rules_java",
+)
+local_path_override(
+    module_name = "rules_java",
+    path = "${rules_java_workspace}",
+)
+EOF
+}
+
 function disabled_test_removed_rule_loaded() {
   setup_module_dot_bazel
   mock_rules_android
@@ -413,5 +433,42 @@
   bazel query --incompatible_autoload_externally=+@rules_python ':py_library' --output=build >&$TEST_log 2>&1 || fail "build failed"
 }
 
+function test_legacy_symbols() {
+  setup_module_dot_bazel
+  mock_rules_java
+
+  rules_java_workspace="${TEST_TMPDIR}/rules_java_workspace"
+
+  mkdir -p "${rules_java_workspace}/java/common"
+  touch "${rules_java_workspace}/java/common/BUILD"
+  cat > "${rules_java_workspace}/java/common/proguard_spec_info.bzl" << EOF
+def _init(specs):
+  return {"specs": specs}
+
+def _proguard_spec_info():
+  if hasattr(native, "legacy_symbols"):
+    if hasattr(native.legacy_symbols, "ProguardSpecProvider"):
+      print("Native provider")
+      return native.legacy_symbols.ProguardSpecProvider
+  print("Starlark provider")
+  return provider(fields = ["specs"], init = _init)[0]
+
+ProguardSpecInfo = _proguard_spec_info()
+EOF
+
+  cat > BUILD << EOF
+load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
+EOF
+
+  bazel build --incompatible_autoload_externally=+ProguardSpecProvider :all >&$TEST_log 2>&1 || fail "build unexpectedly failed"
+  expect_log "Native provider"
+
+
+  bazel build --incompatible_autoload_externally=ProguardSpecProvider,-java_lite_proto_library,-java_import :all >&$TEST_log 2>&1 || fail "build unexpectedly failed"
+  expect_log "Starlark provider"
+}
+
+
+
 
 run_suite "load_removed_symbols"