Disable outputs param of rule function

This constitutes an incompatible change guarded by flag --incompatible_no_rule_outputs_param. See #7977 for further details.

Implementation for #7977.

RELNOTES: The `outputs` parameter of the `rule()` function is deprecated and attached to flag `--incompatible_no_rule_outputs_param`. Migrate rules to use `OutputGroupInfo` or `attr.output` instead. See https://github.com/bazelbuild/bazel/issues/7977 for more info.
PiperOrigin-RevId: 244032985
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index 3bb2b55..58208a4 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -417,6 +417,18 @@
   public boolean incompatibleNoOutputAttrDefault;
 
   @Option(
+      name = "incompatible_no_rule_outputs_param",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+      effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+      metadataTags = {
+        OptionMetadataTag.INCOMPATIBLE_CHANGE,
+        OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+      },
+      help = "If set to true, disables the `outputs` parameter of the `rule()` Starlark function.")
+  public boolean incompatibleNoRuleOutputsParam;
+
+  @Option(
       name = "incompatible_no_support_tools_in_action_inputs",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
@@ -587,6 +599,7 @@
         .incompatibleNoAttrLicense(incompatibleNoAttrLicense)
         .incompatibleNoKwargsInBuildFiles(incompatibleNoKwargsInBuildFiles)
         .incompatibleNoOutputAttrDefault(incompatibleNoOutputAttrDefault)
+        .incompatibleNoRuleOutputsParam(incompatibleNoRuleOutputsParam)
         .incompatibleNoSupportToolsInActionInputs(incompatibleNoSupportToolsInActionInputs)
         .incompatibleNoTargetOutputGroup(incompatibleNoTargetOutputGroup)
         .incompatibleNoTransitiveLoads(incompatibleNoTransitiveLoads)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
index c2b82d1..c66e708 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
@@ -153,9 +153,10 @@
             callbackEnabled = true,
             noneable = true,
             defaultValue = "None",
+            valueWhenDisabled = "None",
+            disableWithFlag = FlagIdentifier.INCOMPATIBLE_NO_RULE_OUTPUTS_PARAM,
             doc =
-                "<b>Experimental:</b> This API is in the process of being redesigned."
-                    + "<p>A schema for defining predeclared outputs. Unlike "
+                "A schema for defining predeclared outputs. Unlike "
                     + "<a href='attr.html#output'><code>output</code></a> and "
                     + "<a href='attr.html#output_list'><code>output_list</code></a> attributes, "
                     + "the user does not specify the labels for these files. "
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index aaddb98..5d78a88 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -48,6 +48,7 @@
     INCOMPATIBLE_DISABLE_OBJC_PROVIDER_RESOURCES(
         StarlarkSemantics::incompatibleDisableObjcProviderResources),
     INCOMPATIBLE_NO_OUTPUT_ATTR_DEFAULT(StarlarkSemantics::incompatibleNoOutputAttrDefault),
+    INCOMPATIBLE_NO_RULE_OUTPUTS_PARAM(StarlarkSemantics::incompatibleNoRuleOutputsParam),
     INCOMPATIBLE_NO_TARGET_OUTPUT_GROUP(StarlarkSemantics::incompatibleNoTargetOutputGroup),
     INCOMPATIBLE_NO_ATTR_LICENSE(StarlarkSemantics::incompatibleNoAttrLicense),
     INCOMPATIBLE_OBJC_FRAMEWORK_CLEANUP(StarlarkSemantics::incompatibleObjcFrameworkCleanup),
@@ -167,6 +168,8 @@
 
   public abstract boolean incompatibleNoOutputAttrDefault();
 
+  public abstract boolean incompatibleNoRuleOutputsParam();
+
   public abstract boolean incompatibleNoSupportToolsInActionInputs();
 
   public abstract boolean incompatibleNoTargetOutputGroup();
@@ -229,6 +232,7 @@
           .incompatibleNoAttrLicense(true)
           .incompatibleNoKwargsInBuildFiles(false)
           .incompatibleNoOutputAttrDefault(true)
+          .incompatibleNoRuleOutputsParam(false)
           .incompatibleNoSupportToolsInActionInputs(false)
           .incompatibleNoTargetOutputGroup(false)
           .incompatibleNoTransitiveLoads(true)
@@ -300,6 +304,8 @@
 
     public abstract Builder incompatibleNoOutputAttrDefault(boolean value);
 
+    public abstract Builder incompatibleNoRuleOutputsParam(boolean value);
+
     public abstract Builder incompatibleNoSupportToolsInActionInputs(boolean value);
 
     public abstract Builder incompatibleNoTargetOutputGroup(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 294d329..6b6e84b 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -151,6 +151,7 @@
         "--incompatible_no_attr_license=" + rand.nextBoolean(),
         "--incompatible_no_kwargs_in_build_files=" + rand.nextBoolean(),
         "--incompatible_no_output_attr_default=" + rand.nextBoolean(),
+        "--incompatible_no_rule_outputs_param=" + rand.nextBoolean(),
         "--incompatible_no_support_tools_in_action_inputs=" + rand.nextBoolean(),
         "--incompatible_no_target_output_group=" + rand.nextBoolean(),
         "--incompatible_no_transitive_loads=" + rand.nextBoolean(),
@@ -199,6 +200,7 @@
         .incompatibleNoAttrLicense(rand.nextBoolean())
         .incompatibleNoKwargsInBuildFiles(rand.nextBoolean())
         .incompatibleNoOutputAttrDefault(rand.nextBoolean())
+        .incompatibleNoRuleOutputsParam(rand.nextBoolean())
         .incompatibleNoSupportToolsInActionInputs(rand.nextBoolean())
         .incompatibleNoTargetOutputGroup(rand.nextBoolean())
         .incompatibleNoTransitiveLoads(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index 43b5c4b..cb7f3d8 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -2893,6 +2893,30 @@
             + "--incompatible_disallow_struct_provider_syntax=false");
   }
 
+  @Test
+  public void testNoRuleOutputsParam() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_no_rule_outputs_param=true");
+    scratch.file(
+        "test/skylark/test_rule.bzl",
+        "def _impl(ctx):",
+        "  output = ctx.outputs.out",
+        "  ctx.actions.write(output = output, content = 'hello')",
+        "",
+        "my_rule = rule(",
+        "  implementation = _impl,",
+        "  outputs = {\"out\": \"%{name}.txt\"})");
+    scratch.file(
+        "test/skylark/BUILD",
+        "load('//test/skylark:test_rule.bzl', 'my_rule')",
+        "my_rule(name = 'target')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test/skylark:target");
+    assertContainsEvent(
+        "parameter 'outputs' is deprecated and will be removed soon. It may be temporarily "
+            + "re-enabled by setting --incompatible_no_rule_outputs_param=false");
+  }
+
   /**
    * Skylark integration test that forces inlining.
    */
diff --git a/tools/build_defs/pkg/pkg.bzl b/tools/build_defs/pkg/pkg.bzl
index 8ef8c1a..1e1391c 100644
--- a/tools/build_defs/pkg/pkg.bzl
+++ b/tools/build_defs/pkg/pkg.bzl
@@ -212,6 +212,12 @@
         inputs = [ctx.outputs.deb],
         outputs = [ctx.outputs.out],
     )
+    output_groups = {"out": [ctx.outputs.out]}
+    if hasattr(ctx.outputs, "out_deb"):
+        output_groups["deb"] = ctx.outputs.deb
+    if hasattr(ctx.outputs, "out_changes"):
+        output_groups["changes"] = ctx.outputs.changes
+    return OutputGroupInfo(**output_groups)
 
 # A rule for creating a tar file, see README.md
 _real_pkg_tar = rule(
@@ -226,6 +232,7 @@
         "modes": attr.string_dict(),
         "mtime": attr.int(default = -1),
         "portable_mtime": attr.bool(default = True),
+        "out": attr.output(),
         "owner": attr.string(default = "0.0"),
         "ownername": attr.string(default = "."),
         "owners": attr.string_dict(),
@@ -244,9 +251,6 @@
             allow_files = True,
         ),
     },
-    outputs = {
-        "out": "%{name}.%{extension}",
-    },
 )
 
 def pkg_tar(**kwargs):
@@ -260,10 +264,17 @@
                       "This attribute was renamed to `srcs`. " +
                       "Consider renaming it in your BUILD file.")
                 kwargs["srcs"] = kwargs.pop("files")
-    _real_pkg_tar(**kwargs)
+    if "extension" in kwargs and kwargs["extension"]:
+        extension = kwargs["extension"]
+    else:
+        extension = "tar"
+    _real_pkg_tar(
+        out = kwargs["name"] + "." + extension,
+        **kwargs
+    )
 
 # A rule for creating a deb file, see README.md
-pkg_deb = rule(
+_pkg_deb = rule(
     implementation = _pkg_deb_impl,
     attrs = {
         "data": attr.label(mandatory = True, allow_single_file = tar_filetype),
@@ -300,10 +311,27 @@
             executable = True,
             allow_files = True,
         ),
-    },
-    outputs = {
-        "out": "%{name}.deb",
-        "deb": "%{package}_%{version}_%{architecture}.deb",
-        "changes": "%{package}_%{version}_%{architecture}.changes",
+        # Outputs.
+        "out": attr.output(mandatory = True),
+        "deb": attr.output(),
+        "changes": attr.output(),
     },
 )
+
+def pkg_deb(name, package, **kwargs):
+    """Creates a deb file. See README.md."""
+    version = kwargs.get("version", None)
+    architecture = kwargs.get("architecture", "all")
+    out_deb = None
+    out_changes = None
+    if version and architecture:
+        out_deb = "%s_%s_%s.deb" % (package, version, architecture)
+        out_changes = "%s_%s_%s.changes" % (package, version, architecture)
+    _pkg_deb(
+        name = name,
+        package = package,
+        out = name + ".deb",
+        deb = out_deb,
+        changes = out_changes,
+        **kwargs
+    )
diff --git a/tools/jdk/default_java_toolchain.bzl b/tools/jdk/default_java_toolchain.bzl
index 3fdcfc1..1824199 100644
--- a/tools/jdk/default_java_toolchain.bzl
+++ b/tools/jdk/default_java_toolchain.bzl
@@ -108,7 +108,7 @@
             outs = [src],
         )
 
-def _bootclasspath(ctx):
+def _bootclasspath_impl(ctx):
     host_javabase = ctx.attr.host_javabase[java_common.JavaRuntimeInfo]
 
     # explicitly list output files instead of using TreeArtifact to work around
@@ -141,7 +141,7 @@
         arguments = [args],
     )
 
-    bootclasspath = ctx.outputs.jar
+    bootclasspath = ctx.outputs.output_jar
 
     inputs = class_outputs + ctx.files.host_javabase
 
@@ -166,9 +166,13 @@
         outputs = [bootclasspath],
         arguments = [args],
     )
+    return [
+        DefaultInfo(files = depset([bootclasspath])),
+        OutputGroupInfo(jar = [bootclasspath]),
+    ]
 
-bootclasspath = rule(
-    implementation = _bootclasspath,
+_bootclasspath = rule(
+    implementation = _bootclasspath_impl,
     attrs = {
         "host_javabase": attr.label(
             cfg = "host",
@@ -181,6 +185,13 @@
         "target_javabase": attr.label(
             providers = [java_common.JavaRuntimeInfo],
         ),
+        "output_jar": attr.output(mandatory = True),
     },
-    outputs = {"jar": "%{name}.jar"},
 )
+
+def bootclasspath(name, **kwargs):
+    _bootclasspath(
+        name = name,
+        output_jar = name + ".jar",
+        **kwargs
+    )