Implement -Werror: in JavaBuilder

PiperOrigin-RevId: 311233509
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD
index 1796e26..1ed2516 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD
@@ -30,21 +30,26 @@
         "//src/test/java/com/google/devtools/build/lib:__subpackages__",
     ],
     deps = [
+        ":javac_options",
         "//third_party:guava",
         "//third_party:jsr305",
     ],
 )
 
+JAVAC_OPTIONS_SRCS = [
+    "javac/JavacOptions.java",
+    "javac/WerrorCustomOption.java",
+]
+
 java_library(
     name = "javac",
     srcs = glob(
         ["javac/*.java"],
-        exclude = [
-            "javac/JavacOptions.java",
-        ],
+        exclude = JAVAC_OPTIONS_SRCS,
     ),
     deps = [
         ":invalid_command_line_exception",
+        ":javac_options",
         "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins",
         "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics",
         "//third_party:auto_value",
@@ -103,9 +108,7 @@
 
 java_library(
     name = "javac_options",
-    srcs = [
-        "javac/JavacOptions.java",
-    ],
+    srcs = JAVAC_OPTIONS_SRCS,
     deps = [
         "//third_party:auto_value",
         "//third_party:guava",
@@ -193,6 +196,7 @@
         "OptionsParser.java",
         "VanillaJavaBuilder.java",
         "javac/JavacOptions.java",
+        "javac/WerrorCustomOption.java",
     ],
     main_class = "com.google.devtools.build.buildjar.VanillaJavaBuilder",
     tags = ["manual"],
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java
index ef22809..a71d163 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java
@@ -133,11 +133,11 @@
   @VisibleForTesting
   public static JavaLibraryBuildRequest parse(List<String> args)
       throws IOException, InvalidCommandLineException {
-    OptionsParser optionsParser = new OptionsParser(args);
+    OptionsParser optionsParser =
+        new OptionsParser(args, JavacOptions.createWithWarningsAsErrorsDefault(ImmutableList.of()));
     ImmutableList<BlazeJavaCompilerPlugin> plugins = ImmutableList.of(new ErrorPronePlugin());
     JavaLibraryBuildRequest build =
         new JavaLibraryBuildRequest(optionsParser, plugins, new DependencyModule.Builder());
-    build.setJavacOpts(JavacOptions.normalizeOptions(build.getJavacOpts()));
     return build;
   }
 }
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java
index e124dd2..6dd1255 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java
@@ -23,6 +23,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.buildjar.instrumentation.JacocoInstrumentationProcessor;
 import com.google.devtools.build.buildjar.javac.BlazeJavacArguments;
+import com.google.devtools.build.buildjar.javac.JavacOptions;
+import com.google.devtools.build.buildjar.javac.JavacOptions.FilteredJavacopts;
 import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin;
 import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule;
 import com.google.devtools.build.buildjar.javac.plugins.processing.AnnotationProcessingModule;
@@ -197,10 +199,6 @@
     return javacOpts;
   }
 
-  public void setJavacOpts(List<String> javacOpts) {
-    this.javacOpts = ImmutableList.copyOf(javacOpts);
-  }
-
   public Path getSourceGenDir() {
     return sourceGenDir;
   }
@@ -317,7 +315,6 @@
             .classOutput(getClassDir())
             .bootClassPath(getBootClassPath())
             .system(getSystem())
-            .javacOptions(makeJavacArguments())
             .sourceFiles(ImmutableList.copyOf(getSourceFiles()))
             .processors(null)
             .builtinProcessors(builtinProcessorNames)
@@ -325,6 +322,7 @@
             .sourceOutput(getSourceGenDir())
             .processorPath(getProcessorPath())
             .plugins(getPlugins());
+    addJavacArguments(builder);
     // Performance optimization: when reduced classpaths are enabled, stop the compilation after
     // the first diagnostic that would result in fallback to the transitive classpath. The user
     // only sees diagnostics from the fallback compilation, so collecting additional diagnostics
@@ -342,11 +340,17 @@
   }
 
   /** Constructs a command line that can be used for a javac invocation. */
-  ImmutableList<String> makeJavacArguments() {
+  void addJavacArguments(BlazeJavacArguments.Builder builder) {
+    FilteredJavacopts filtered = JavacOptions.filterJavacopts(getJavacOpts());
+    builder.blazeJavacOptions(filtered.bazelJavacopts());
+
+    ImmutableList<String> javacOpts = filtered.standardJavacopts();
+
     ImmutableList.Builder<String> javacArguments = ImmutableList.builder();
+
     // default to -implicit:none, but allow the user to override with -implicit:class.
     javacArguments.add("-implicit:none");
-    javacArguments.addAll(getJavacOpts());
+    javacArguments.addAll(javacOpts);
 
     if (!getProcessors().isEmpty() && !getSourceFiles().isEmpty()) {
       // ImmutableSet.copyOf maintains order
@@ -360,7 +364,7 @@
       javacArguments.add("-proc:none");
     }
 
-    for (String option : getJavacOpts()) {
+    for (String option : javacOpts) {
       if (option.startsWith("-J")) { // ignore the VM options.
         continue;
       }
@@ -373,6 +377,6 @@
       }
     }
 
-    return javacArguments.build();
+    builder.javacOptions(javacArguments.build());
   }
 }
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/OptionsParser.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/OptionsParser.java
index b88221c..802fa6e 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/OptionsParser.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/OptionsParser.java
@@ -18,6 +18,7 @@
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.buildjar.javac.JavacOptions;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -100,6 +101,8 @@
 
   @Nullable private String profile;
 
+  @Nullable private final JavacOptions normalizer;
+
   /**
    * Constructs an {@code OptionsParser} from a list of command args. Sets the same JavacRunner for
    * both compilation and annotation processing.
@@ -108,6 +111,19 @@
    * @throws InvalidCommandLineException on any command line error.
    */
   public OptionsParser(List<String> args) throws InvalidCommandLineException, IOException {
+    this(args, null);
+  }
+
+  /**
+   * Constructs an {@code OptionsParser} from a list of command args. Sets the same JavacRunner for
+   * both compilation and annotation processing.
+   *
+   * @param args the list of command line args.
+   * @throws InvalidCommandLineException on any command line error.
+   */
+  public OptionsParser(List<String> args, @Nullable JavacOptions normalizer)
+      throws InvalidCommandLineException, IOException {
+    this.normalizer = normalizer;
     processCommandlineArgs(expandArguments(args));
   }
 
@@ -357,7 +373,7 @@
   }
 
   public List<String> getJavacOpts() {
-    return javacOpts;
+    return normalizer != null ? normalizer.normalize(javacOpts) : javacOpts;
   }
 
   public Set<String> directJars() {
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacArguments.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacArguments.java
index be40f45..b983ec9 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacArguments.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacArguments.java
@@ -37,6 +37,9 @@
   /** Javac options, not including location settings. */
   public abstract ImmutableList<String> javacOptions();
 
+  /** Blaze-specific Javac options. */
+  public abstract ImmutableList<String> blazeJavacOptions();
+
   /** The compilation classpath. */
   public abstract ImmutableList<Path> classPath();
 
@@ -59,7 +62,7 @@
 
   /**
    * Annotation processor classes. In production builds, processors are specified by string class
-   * name in {@link javacOptions}; this is used for tests that instantate processors directly.
+   * name in {@link #javacOptions}; this is used for tests that instantate processors directly.
    */
   @Nullable
   public abstract ImmutableList<Processor> processors();
@@ -83,6 +86,7 @@
         .classPath(ImmutableList.of())
         .bootClassPath(ImmutableList.of())
         .javacOptions(ImmutableList.of())
+        .blazeJavacOptions(ImmutableList.of())
         .sourceFiles(ImmutableList.of())
         .sourcePath(ImmutableList.of())
         .processors(null)
@@ -108,6 +112,8 @@
 
     Builder javacOptions(ImmutableList<String> javacOptions);
 
+    Builder blazeJavacOptions(ImmutableList<String> javacOptions);
+
     Builder sourcePath(ImmutableList<Path> sourcePath);
 
     Builder sourceFiles(ImmutableList<Path> sourceFiles);
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java
index 151a5d8..da5abfc 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.MoreCollectors.toOptional;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Comparator.comparing;
 
@@ -44,6 +45,7 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 import javax.tools.Diagnostic;
 import javax.tools.StandardLocation;
 
@@ -74,7 +76,8 @@
 
     List<String> javacArguments = arguments.javacOptions();
     try {
-      javacArguments = processPluginArgs(arguments.plugins(), javacArguments);
+      processPluginArgs(
+          arguments.plugins(), arguments.javacOptions(), arguments.blazeJavacOptions());
     } catch (InvalidCommandLineException e) {
       return BlazeJavacResult.error(e.getMessage());
     }
@@ -90,7 +93,7 @@
     // TODO(cushon): where is this used when a diagnostic listener is registered? Consider removing
     // it and handling exceptions directly in callers.
     PrintWriter errWriter = new PrintWriter(errOutput);
-    Listener diagnostics = new Listener(arguments.failFast(), context);
+    Listener diagnosticsBuilder = new Listener(arguments.failFast(), context);
     BlazeJavaCompiler compiler;
 
     try (JavacFileManager fileManager =
@@ -100,7 +103,7 @@
               .getTask(
                   errWriter,
                   fileManager,
-                  diagnostics,
+                  diagnosticsBuilder,
                   javacArguments,
                   /* classes= */ ImmutableList.of(),
                   fileManager.getJavaFileObjectsFromPaths(arguments.sourceFiles()),
@@ -133,12 +136,25 @@
       }
     }
     errWriter.flush();
+    ImmutableList<FormattedDiagnostic> diagnostics = diagnosticsBuilder.build();
+
+    if (status.equals(Status.OK)) {
+      Optional<WerrorCustomOption> maybeWerrorCustom =
+          arguments.blazeJavacOptions().stream()
+              .filter(arg -> arg.startsWith("-Werror:"))
+              .collect(toOptional())
+              .map(WerrorCustomOption::create);
+      if (maybeWerrorCustom.isPresent()) {
+        WerrorCustomOption werrorCustom = maybeWerrorCustom.get();
+        if (diagnostics.stream().anyMatch(d -> werrorCustom.isEnabled(d.getLintCategory()))) {
+          errOutput.append("error: warnings found and -Werror specified\n");
+          status = Status.ERROR;
+        }
+      }
+    }
+
     return BlazeJavacResult.createFullResult(
-        status,
-        filterDiagnostics(diagnostics.build()),
-        errOutput.toString(),
-        compiler,
-        builder.build());
+        status, filterDiagnostics(diagnostics), errOutput.toString(), compiler, builder.build());
   }
 
   private static final ImmutableSet<String> IGNORED_DIAGNOSTIC_CODES =
@@ -191,14 +207,14 @@
 
   /** Processes Plugin-specific arguments and removes them from the args array. */
   @VisibleForTesting
-  static List<String> processPluginArgs(
-      ImmutableList<BlazeJavaCompilerPlugin> plugins, List<String> args)
+  static void processPluginArgs(
+      ImmutableList<BlazeJavaCompilerPlugin> plugins,
+      ImmutableList<String> standardJavacopts,
+      ImmutableList<String> blazeJavacopts)
       throws InvalidCommandLineException {
-    List<String> processedArgs = args;
     for (BlazeJavaCompilerPlugin plugin : plugins) {
-      processedArgs = plugin.processArgs(processedArgs);
+      plugin.processArgs(standardJavacopts, blazeJavacopts);
     }
-    return processedArgs;
   }
 
   private static void setLocations(JavacFileManager fileManager, BlazeJavacArguments arguments) {
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/FormattedDiagnostic.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/FormattedDiagnostic.java
index b5251b6..7224de3 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/FormattedDiagnostic.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/FormattedDiagnostic.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableList;
 import com.sun.tools.javac.api.ClientCodeWrapper.Trusted;
 import com.sun.tools.javac.api.DiagnosticFormatter;
+import com.sun.tools.javac.code.Lint.LintCategory;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.JCDiagnostic;
 import com.sun.tools.javac.util.JavacMessages;
@@ -34,10 +35,13 @@
 
   public final Diagnostic<? extends JavaFileObject> diagnostic;
   public final String formatted;
+  public final String lintCategory;
 
-  public FormattedDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic, String formatted) {
+  public FormattedDiagnostic(
+      Diagnostic<? extends JavaFileObject> diagnostic, String formatted, String lintCategory) {
     this.diagnostic = diagnostic;
     this.formatted = formatted;
+    this.lintCategory = lintCategory;
   }
 
   /** The formatted diagnostic message produced by javac's diagnostic formatter. */
@@ -45,6 +49,10 @@
     return formatted;
   }
 
+  public String getLintCategory() {
+    return lintCategory;
+  }
+
   @Override
   public String toString() {
     return formatted;
@@ -114,7 +122,10 @@
       DiagnosticFormatter<JCDiagnostic> formatter = Log.instance(context).getDiagnosticFormatter();
       Locale locale = JavacMessages.instance(context).getCurrentLocale();
       String formatted = formatter.format((JCDiagnostic) diagnostic, locale);
-      FormattedDiagnostic formattedDiagnostic = new FormattedDiagnostic(diagnostic, formatted);
+      LintCategory lintCategory = ((JCDiagnostic) diagnostic).getLintCategory();
+      FormattedDiagnostic formattedDiagnostic =
+          new FormattedDiagnostic(
+              diagnostic, formatted, lintCategory != null ? lintCategory.option : null);
       diagnostics.add(formattedDiagnostic);
       if (failFast && diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
         throw new FailFastException(formatted);
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/JavacOptions.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/JavacOptions.java
index 9e6494c..9ac1ccb 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/JavacOptions.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/JavacOptions.java
@@ -90,14 +90,12 @@
    * Interface to define an option normalizer. For instance, to group all -Xlint: option into one
    * place.
    *
-   * <p>All normalizers used by the JavacOptions class will be started by calling the {@link
-   * #start()} method when starting the parsing of a list of option. For each option, the first
-   * option normalized whose {@link #processOption(String)} method returns true stops its parsing
-   * and the option is supposed to be added at the end to the normalized list of option with the
-   * {@link #normalize(List)} method. Options not handled by a normalizer will be returned as such
-   * in the normalized option list.
+   * <p>For each option, the first option normalized whose {@link #processOption} method returns
+   * true stops its parsing and the option is supposed to be added at the end to the normalized list
+   * of option with the {@link #normalize(List)} method. Options not handled by a normalizer will be
+   * returned as such in the normalized option list.
    */
-  public static interface JavacOptionNormalizer {
+  public interface JavacOptionNormalizer {
     /**
      * Process an option and return true if the option was handled by this normalizer. {@code
      * remaining} provides an iterator to any remaining options so normalizers that process
@@ -106,8 +104,8 @@
     boolean processOption(String option, Iterator<String> remaining);
 
     /**
-     * Add the normalized versions of the options handled by {@link #processOption(String)} to the
-     * {@code normalized} list
+     * Add the normalized versions of the options handled by {@link #processOption} to the {@code
+     * normalized} list
      */
     void normalize(List<String> normalized);
   }
@@ -127,7 +125,7 @@
      * {@code -Xlint} indicates we start with the set of recommended checks enabled, and {@code
      * -Xlint:none} means we start without any checks enabled.
      */
-    private static enum BasisXlintSelection {
+    private enum BasisXlintSelection {
       /** {@code -Xlint:none} */
       None,
       /** {@code -Xlint:all} */
@@ -289,14 +287,49 @@
   }
 
   /**
+   * Parse an option that starts with {@code -Werror:} into a bunch of werroropts. We silently drop
+   * werroropts that would disable any warnings that we turn into errors by default (treating them
+   * like invalid werroropts).
+   */
+  private static final class WErrorOptionNormalizer implements JavacOptionNormalizer {
+
+    private final WerrorCustomOption.Builder builder;
+
+    WErrorOptionNormalizer(ImmutableList<String> warningsAsErrorsDefault) {
+      builder = new WerrorCustomOption.Builder(warningsAsErrorsDefault);
+    }
+
+    @Override
+    public boolean processOption(String option, Iterator<String> remaining) {
+      if (option.startsWith("-Werror:")) {
+        builder.process(option);
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public void normalize(List<String> normalized) {
+      String flag = builder.build().toString();
+      if (!flag.isEmpty()) {
+        normalized.add(flag);
+      }
+    }
+  }
+
+  private final ImmutableList<JavacOptionNormalizer> normalizers;
+
+  JavacOptions(ImmutableList<JavacOptionNormalizer> normalizers) {
+    this.normalizers = normalizers;
+  }
+
+  /**
    * Outputs a reasonably normalized javac option list.
    *
    * @param javacopts the raw javac option list to cleanup
-   * @param normalizers the list of normalizers to apply
    * @return a new cleaned up javac option list
    */
-  public static List<String> normalizeOptionsWithNormalizers(
-      List<String> javacopts, JavacOptionNormalizer... normalizers) {
+  public List<String> normalize(List<String> javacopts) {
     List<String> normalized = new ArrayList<>();
 
     Iterator<String> it = javacopts.iterator();
@@ -321,14 +354,27 @@
   }
 
   /**
-   * A wrapper around {@ref #normalizeOptionsWithNormalizers(List, JavacOptionNormalizer...)} to use
-   * {@link XlintOptionNormalizer} as default normalizer.
+   * Outputs a reasonably normalized javac option list.
    *
-   * <p>The -Xlint option list has up to one each of a -Xlint* basis flag followed by a
-   * -Xlint:xxx,yyy,zzz add flag followed by a -Xlint:-xxx,-yyy,-zzz minus flag.
+   * @param javacopts the raw javac option list to cleanup
+   * @param normalizers the list of normalizers to apply
+   * @return a new cleaned up javac option list
    */
-  public static List<String> normalizeOptions(List<String> javacopts) {
-    return normalizeOptionsWithNormalizers(
-        javacopts, new XlintOptionNormalizer(), new ReleaseOptionNormalizer());
+  public static List<String> normalizeOptionsWithNormalizers(
+      List<String> javacopts, JavacOptionNormalizer... normalizers) {
+    return new JavacOptions(ImmutableList.copyOf(normalizers)).normalize(javacopts);
+  }
+
+  /**
+   * Creates a {@link JavacOptions} normalizer that will ensure the given set of lint categories are
+   * enabled as errors, overriding any user-provided configuration for those options.
+   */
+  public static JavacOptions createWithWarningsAsErrorsDefault(
+      ImmutableList<String> warningsAsErrorsDefault) {
+    return new JavacOptions(
+        ImmutableList.of(
+            new XlintOptionNormalizer(warningsAsErrorsDefault),
+            new WErrorOptionNormalizer(warningsAsErrorsDefault),
+            new ReleaseOptionNormalizer()));
   }
 }
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/WerrorCustomOption.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/WerrorCustomOption.java
new file mode 100644
index 0000000..db4cc06
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/WerrorCustomOption.java
@@ -0,0 +1,121 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.buildjar.javac;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Logic for handling non-standard javac flag {@code -Werror:}, which allows failing the compilation
+ * for individual xlint warnings.
+ */
+public class WerrorCustomOption {
+
+  private static final String WERROR = "-Werror:";
+
+  private final ImmutableMap<String, Boolean> werrors;
+
+  public WerrorCustomOption(ImmutableMap<String, Boolean> werrors) {
+    this.werrors = werrors;
+  }
+
+  /** Returns true if the given lint category should be promoted to an error. */
+  public boolean isEnabled(String lintCategory) {
+    if (lintCategory == null) {
+      return false;
+    }
+    boolean all = werrors.containsKey("all");
+    return werrors.getOrDefault(lintCategory, all);
+  }
+
+  static WerrorCustomOption create(String arg) {
+    return new WerrorCustomOption.Builder(/* warningsAsErrorsDefault= */ ImmutableList.of())
+        .process(arg)
+        .build();
+  }
+
+  /** A builder for {@link WerrorCustomOption}s. */
+  static class Builder {
+
+    private final ImmutableList<String> warningsAsErrorsDefault;
+
+    private final Map<String, Boolean> werrors = new LinkedHashMap<>();
+
+    Builder(ImmutableList<String> warningsAsErrorsDefault) {
+      this.warningsAsErrorsDefault = warningsAsErrorsDefault;
+      // initialize list of werrors with the ones we want on by default
+      for (String errorWarning : warningsAsErrorsDefault) {
+        werrors.put(errorWarning, true);
+      }
+    }
+
+    Builder process(String flag) {
+      checkArgument(flag.startsWith(WERROR), flag);
+      for (String arg : Splitter.on(',').split(flag.substring(WERROR.length()))) {
+        // Warnings with a '+' or '-' have an implicit '+'.
+        if (arg.equals("+all") || arg.equals("all")) {
+          werrors.clear();
+          werrors.put("all", true);
+        } else if (arg.equals("-all") || arg.equals("none")) {
+          werrors.clear();
+          werrors.put("none", true);
+          for (String errorWarning : warningsAsErrorsDefault) {
+            werrors.put(errorWarning, true);
+          }
+        } else if (arg.startsWith("-")) {
+          String warning = arg.substring(1);
+          if (!warningsAsErrorsDefault.contains(warning)) {
+            werrors.put(warning, false);
+          }
+        } else {
+          // '+' or raw warning category (implicit '+')
+          String warning = arg.startsWith("+") ? arg.substring(1) : arg;
+          werrors.put(warning, true);
+        }
+      }
+      return this;
+    }
+
+    WerrorCustomOption build() {
+      return new WerrorCustomOption(ImmutableMap.copyOf(werrors));
+    }
+  }
+
+  /** Returns a normalized {@code -Werror:} flag. */
+  @Override
+  public String toString() {
+    if (this.werrors.isEmpty()) {
+      return "";
+    }
+    Map<String, Boolean> werrors = new LinkedHashMap<>(this.werrors);
+    StringBuilder sb = new StringBuilder("-Werror:");
+    if (werrors.containsKey("all")) {
+      boolean b = werrors.remove("all");
+      sb.append(b ? "" : "-").append("all,");
+    }
+    for (String warning : werrors.keySet()) {
+      boolean b = werrors.get(warning);
+      sb.append(b ? "" : "-").append(warning).append(",");
+    }
+    // delete trailing ","
+    sb.deleteCharAt(sb.length() - 1);
+    return sb.toString();
+  }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BUILD b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BUILD
index 2860ef9..59131bf 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BUILD
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BUILD
@@ -1,4 +1,5 @@
 load("@rules_java//java:defs.bzl", "java_library")
+load("//tools/build_rules:java_rules_skylark.bzl", "bootstrap_java_library")
 
 # Description:
 #   Plugins for the Java library builders, which are used by Bazel to
@@ -11,6 +12,7 @@
     deps = [
         "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar:invalid_command_line_exception",
         "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics",
+        "//third_party:guava",
         "//third_party/java/jdk/langtools:javac",
     ],
 )
@@ -66,8 +68,6 @@
 ## Bootstrapping using Starlark rules
 #
 
-load("//tools/build_rules:java_rules_skylark.bzl", "bootstrap_java_library")
-
 bootstrap_java_library(
     name = "bootstrap_plugins",
     srcs = glob([
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BlazeJavaCompilerPlugin.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BlazeJavaCompilerPlugin.java
index 5ecd212..0799eea 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BlazeJavaCompilerPlugin.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/BlazeJavaCompilerPlugin.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.buildjar.javac.plugins;
 
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.buildjar.InvalidCommandLineException;
 import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics;
 import com.sun.tools.javac.comp.AttrContext;
@@ -20,7 +21,6 @@
 import com.sun.tools.javac.main.JavaCompiler;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.Log;
-import java.util.List;
 
 /**
  * An interface for additional static analyses that need access to the javac compiler's AST at
@@ -42,13 +42,14 @@
    * #init(Context, Log, JavaCompiler, BlazeJavacStatistics.Builder)} and {@link
    * #initializeContext(Context)}.
    *
-   * @param args The command-line flags that javac was invoked with.
+   * @param standardJavacopts The standard javac command-line flags.
+   * @param blazeJavacopts Blaze-specific command-line flags.
    * @throws InvalidCommandLineException if the arguments are invalid
    * @return The flags that do not belong to this plugin.
    */
-  public List<String> processArgs(List<String> args) throws InvalidCommandLineException {
-    return args;
-  }
+  public void processArgs(
+      ImmutableList<String> standardJavacopts, ImmutableList<String> blazeJavacopts)
+      throws InvalidCommandLineException {}
 
   /**
    * Called after all plugins have processed arguments and can be used to customize the Java
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
index 75977eb..d3677be 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
@@ -37,7 +37,6 @@
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.Log;
 import java.time.Duration;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -119,20 +118,21 @@
   }
 
   @Override
-  public List<String> processArgs(List<String> args) throws InvalidCommandLineException {
-    ImmutableList.Builder<String> epArgs = ImmutableList.<String>builder().addAll(args);
+  public void processArgs(
+      ImmutableList<String> standardJavacopts, ImmutableList<String> blazeJavacopts)
+      throws InvalidCommandLineException {
+    ImmutableList.Builder<String> epArgs = ImmutableList.<String>builder().addAll(blazeJavacopts);
     // allow javacopts that reference unknown error-prone checks
     epArgs.add("-XepIgnoreUnknownCheckNames");
-    return processEpOptions(epArgs.build());
+    processEpOptions(epArgs.build());
   }
 
-  private List<String> processEpOptions(List<String> args) throws InvalidCommandLineException {
+  private void processEpOptions(List<String> args) throws InvalidCommandLineException {
     try {
       epOptions = ErrorProneOptions.processArgs(args);
     } catch (InvalidCommandLineOptionException e) {
       throw new InvalidCommandLineException(e.getMessage());
     }
-    return Arrays.asList(epOptions.getRemainingArgs());
   }
 
   @Override