Detect one version violations while building android_binary targets

PiperOrigin-RevId: 161910195
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index b9b9d78..6fb4a3c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -36,6 +36,7 @@
 import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
@@ -63,11 +64,13 @@
 import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaOptimizationMode;
+import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel;
 import com.google.devtools.build.lib.rules.java.JavaHelper;
 import com.google.devtools.build.lib.rules.java.JavaSemantics;
 import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider;
 import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
 import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
+import com.google.devtools.build.lib.rules.java.OneVersionCheckActionBuilder;
 import com.google.devtools.build.lib.rules.java.ProguardHelper;
 import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput;
 import com.google.devtools.build.lib.syntax.Type;
@@ -370,6 +373,29 @@
     Artifact deployJar = createDeployJar(ruleContext, javaSemantics, androidCommon, resourceClasses,
         derivedJarFunction);
 
+    OneVersionEnforcementLevel oneVersionEnforcementLevel =
+        ruleContext.getFragment(JavaConfiguration.class).oneVersionEnforcementLevel();
+    Artifact oneVersionOutputArtifact = null;
+    if (oneVersionEnforcementLevel != OneVersionEnforcementLevel.OFF) {
+      NestedSet<Artifact> transitiveDependencies =
+          NestedSetBuilder.<Artifact>stableOrder()
+              .addAll(
+                  Iterables.transform(resourceClasses.getRuntimeClassPath(), derivedJarFunction))
+              .addAll(
+                  Iterables.transform(
+                      androidCommon.getJarsProducedForRuntime(), derivedJarFunction))
+              .build();
+
+      oneVersionOutputArtifact =
+          OneVersionCheckActionBuilder.newBuilder()
+              .withEnforcementLevel(oneVersionEnforcementLevel)
+              .outputArtifact(
+                  ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_ONE_VERSION_ARTIFACT))
+              .useToolchain(JavaToolchainProvider.fromRuleContext(ruleContext))
+              .checkJars(transitiveDependencies)
+              .build(ruleContext);
+    }
+
     Artifact proguardMapping = ruleContext.getPrerequisiteArtifact(
         "proguard_apply_mapping", Mode.TARGET);
 
@@ -393,7 +419,8 @@
         resourceClasses,
         ImmutableList.<Artifact>of(),
         ImmutableList.<Artifact>of(),
-        proguardMapping);
+        proguardMapping,
+        oneVersionOutputArtifact);
   }
 
   public static RuleConfiguredTargetBuilder createAndroidBinary(
@@ -416,7 +443,8 @@
       JavaTargetAttributes resourceClasses,
       ImmutableList<Artifact> apksUnderTest,
       ImmutableList<Artifact> additionalMergedManifests,
-      Artifact proguardMapping)
+      Artifact proguardMapping,
+      @Nullable Artifact oneVersionEnforcementArtifact)
       throws InterruptedException, RuleErrorException {
 
     ImmutableList<Artifact> proguardSpecs = ProguardHelper.collectTransitiveProguardSpecs(
@@ -786,6 +814,10 @@
           ProguardMappingProvider.create(finalProguardMap));
     }
 
+    if (oneVersionEnforcementArtifact != null) {
+      builder.addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, oneVersionEnforcementArtifact);
+    }
+
     return builder
         .setFilesToBuild(filesToBuild)
         .addProvider(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
index b562eac..a77e250 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
@@ -15,7 +15,6 @@
 
 import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
 import static com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression.COMPRESSED;
-import static com.google.devtools.build.lib.vfs.FileSystemUtils.replaceExtension;
 import static java.util.stream.Collectors.toCollection;
 
 import com.google.common.collect.ImmutableList;
@@ -25,6 +24,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
@@ -55,6 +55,7 @@
 import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider;
 import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
 import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
+import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
 import com.google.devtools.build.lib.rules.java.OneVersionCheckActionBuilder;
 import com.google.devtools.build.lib.rules.java.SingleJarActionBuilder;
 import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
@@ -191,26 +192,21 @@
     Artifact deployJar =
         ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_DEPLOY_JAR);
 
+    Artifact oneVersionOutputArtifact = null;
     OneVersionEnforcementLevel oneVersionEnforcementLevel =
         ruleContext.getFragment(JavaConfiguration.class).oneVersionEnforcementLevel();
     if (oneVersionEnforcementLevel != OneVersionEnforcementLevel.OFF) {
-      Artifact oneVersionOutput =
-          ruleContext
-              .getAnalysisEnvironment()
-              .getDerivedArtifact(
-                  replaceExtension(classJar.getRootRelativePath(), "-one-version.txt"),
-                  classJar.getRoot());
-      filesToBuildBuilder.add(oneVersionOutput);
-
-      NestedSet<Artifact> transitiveDependencies =
-          NestedSetBuilder.fromNestedSet(helper.getAttributes().getRuntimeClassPath())
-              .add(classJar)
-              .build();
-      OneVersionCheckActionBuilder.build(
-          ruleContext,
-          transitiveDependencies,
-          oneVersionOutput,
-          oneVersionEnforcementLevel);
+      oneVersionOutputArtifact =
+          OneVersionCheckActionBuilder.newBuilder()
+              .withEnforcementLevel(oneVersionEnforcementLevel)
+              .outputArtifact(
+                  ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_ONE_VERSION_ARTIFACT))
+              .useToolchain(JavaToolchainProvider.fromRuleContext(ruleContext))
+              .checkJars(
+                  NestedSetBuilder.fromNestedSet(helper.getAttributes().getRuntimeClassPath())
+                      .add(classJar)
+                      .build())
+              .build(ruleContext);
     }
 
     NestedSet<Artifact> filesToBuild = filesToBuildBuilder.build();
@@ -298,6 +294,10 @@
     // TODO(mstaib): remove when static configurations are removed.
     AndroidFeatureFlagSetProvider.getAndValidateFlagMapFromRuleContext(ruleContext);
 
+    if (oneVersionOutputArtifact != null) {
+      builder.addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, oneVersionOutputArtifact);
+    }
+
     return builder
         .setFilesToBuild(filesToBuild)
         .addSkylarkTransitiveInfo(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
index 5306249..bafa5e7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
@@ -15,7 +15,6 @@
 
 import static com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression.COMPRESSED;
 import static com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression.UNCOMPRESSED;
-import static com.google.devtools.build.lib.vfs.FileSystemUtils.replaceExtension;
 
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
@@ -25,6 +24,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
@@ -285,21 +285,18 @@
         ruleContext, deployJar, common.getBootClasspath(), mainClass, semantics, filesBuilder);
 
     if (javaConfig.oneVersionEnforcementLevel() != OneVersionEnforcementLevel.OFF) {
-      Artifact oneVersionOutput =
-          ruleContext
-              .getAnalysisEnvironment()
-              .getDerivedArtifact(
-                  replaceExtension(classJar.getRootRelativePath(), "-one-version.txt"),
-                  classJar.getRoot());
-      filesBuilder.add(oneVersionOutput);
-
-      NestedSet<Artifact> transitiveDependencies =
-          NestedSetBuilder.fromNestedSet(attributes.getRuntimeClassPath()).add(classJar).build();
-      OneVersionCheckActionBuilder.build(
-          ruleContext,
-          transitiveDependencies,
-          oneVersionOutput,
-          javaConfig.oneVersionEnforcementLevel());
+      builder.addOutputGroup(
+          OutputGroupProvider.HIDDEN_TOP_LEVEL,
+          OneVersionCheckActionBuilder.newBuilder()
+              .withEnforcementLevel(javaConfig.oneVersionEnforcementLevel())
+              .outputArtifact(
+                  ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_ONE_VERSION_ARTIFACT))
+              .useToolchain(JavaToolchainProvider.fromRuleContext(ruleContext))
+              .checkJars(
+                  NestedSetBuilder.fromNestedSet(attributes.getRuntimeClassPath())
+                      .add(classJar)
+                      .build())
+              .build(ruleContext));
     }
     NestedSet<Artifact> filesToBuild = filesBuilder.build();
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
index 571d517..b7b127d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
@@ -82,6 +82,8 @@
       fromTemplates("%{name}_proguard.usage");
   SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_CONFIG =
       fromTemplates("%{name}_proguard.config");
+  SafeImplicitOutputsFunction JAVA_ONE_VERSION_ARTIFACT =
+      fromTemplates("%{name}-one-version.txt");
 
   SafeImplicitOutputsFunction JAVA_BINARY_DEPLOY_SOURCE_JAR =
       fromTemplates("%{name}_deploy-src.jar");
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
index aab1f12..8a57708 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
@@ -22,22 +22,58 @@
 import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
-import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.Builder;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.CustomMultiArgv;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel;
+import com.google.devtools.build.lib.util.Preconditions;
 
 /** Utility for generating a call to the one-version binary. */
-public class OneVersionCheckActionBuilder {
+public final class OneVersionCheckActionBuilder {
 
-  public static void build(
-      RuleContext ruleContext,
-      NestedSet<Artifact> jarsToCheck,
-      Artifact oneVersionOutput,
+  private OneVersionCheckActionBuilder() {}
+
+  private OneVersionEnforcementLevel enforcementLevel;
+  private Artifact outputArtifact;
+  private JavaToolchainProvider javaToolchain;
+  private NestedSet<Artifact> jarsToCheck;
+
+  public static OneVersionCheckActionBuilder newBuilder() {
+    return new OneVersionCheckActionBuilder();
+  }
+
+  public OneVersionCheckActionBuilder useToolchain(JavaToolchainProvider toolchain) {
+    javaToolchain = toolchain;
+    return this;
+  }
+
+  public OneVersionCheckActionBuilder checkJars(NestedSet<Artifact> jarsToCheck) {
+    this.jarsToCheck = jarsToCheck;
+    return this;
+  }
+
+  public OneVersionCheckActionBuilder outputArtifact(Artifact outputArtifact) {
+    this.outputArtifact = outputArtifact;
+    return this;
+  }
+
+  public OneVersionCheckActionBuilder withEnforcementLevel(
       OneVersionEnforcementLevel enforcementLevel) {
-    JavaToolchainProvider javaToolchain = JavaToolchainProvider.fromRuleContext(ruleContext);
+    Preconditions.checkArgument(
+        enforcementLevel != OneVersionEnforcementLevel.OFF,
+        "one version enforcement actions shouldn't be built if the enforcement "
+            + "level is set to off");
+    this.enforcementLevel = enforcementLevel;
+    return this;
+  }
+
+  public Artifact build(RuleContext ruleContext) {
+    Preconditions.checkNotNull(enforcementLevel);
+    Preconditions.checkNotNull(outputArtifact);
+    Preconditions.checkNotNull(javaToolchain);
+    Preconditions.checkNotNull(jarsToCheck);
+
     Artifact oneVersionTool = javaToolchain.getOneVersionBinary();
     Artifact oneVersionWhitelist = javaToolchain.getOneVersionWhitelist();
     if (oneVersionTool == null || oneVersionWhitelist == null) {
@@ -45,14 +81,15 @@
           String.format(
               "one version enforcement was requested but it is not supported by the current "
                   + "Java toolchain '%s'; see the "
-                  + "java_toolchain.oneversion and java_toolchain.oneversion_whitelist attributes",
+                  + "java_toolchain.oneversion and java_toolchain.oneversion_whitelist "
+                  + "attributes",
               javaToolchain.getToolchainLabel()));
-      return;
+      return outputArtifact;
     }
 
-    Builder oneVersionArgsBuilder =
+    CustomCommandLine.Builder oneVersionArgsBuilder =
         CustomCommandLine.builder()
-            .addExecPath("--output", oneVersionOutput)
+            .addExecPath("--output", outputArtifact)
             .addExecPath("--whitelist", oneVersionWhitelist);
     if (enforcementLevel == OneVersionEnforcementLevel.WARNING) {
       oneVersionArgsBuilder.add("--succeed_on_found_violations");
@@ -61,7 +98,7 @@
     CustomCommandLine oneVersionArgs = oneVersionArgsBuilder.build();
     ruleContext.registerAction(
         new SpawnAction.Builder()
-            .addOutput(oneVersionOutput)
+            .addOutput(outputArtifact)
             .addInput(oneVersionWhitelist)
             .addTransitiveInputs(jarsToCheck)
             .setExecutable(oneVersionTool)
@@ -70,6 +107,7 @@
             .setMnemonic("JavaOneVersion")
             .setProgressMessage("Checking for one-version violations in " + ruleContext.getLabel())
             .build(ruleContext));
+    return outputArtifact;
   }
 
   private static class OneVersionJarMapArgv extends CustomMultiArgv {