blob: 9e6494c10debb9cb4fd71d406193ba6493bbc412 [file] [log] [blame]
// Copyright 2014 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 com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Preprocess javac -Xlint options. We also need to make the different versions of javac treat
* -Xlint options uniformly.
*
* <p>Some versions of javac now process the -Xlint options without allowing later options to
* override earlier ones on the command line. For example, {@code -Xlint:All -Xlint:None} results in
* all warnings being enabled.
*
* <p>This class preprocesses the -Xlint options within the javac options to achieve a command line
* that is sensitive to ordering. That is, with this preprocessing step, {@code -Xlint:all
* -Xlint:none} results in no warnings being enabled.
*/
public final class JavacOptions {
/** Returns an immutable list containing all the non-Bazel specific Javac flags. */
public static ImmutableList<String> removeBazelSpecificFlags(String[] javacopts) {
return removeBazelSpecificFlags(Arrays.asList(javacopts));
}
/** Returns an immutable list containing all the non-Bazel specific Javac flags. */
public static ImmutableList<String> removeBazelSpecificFlags(Iterable<String> javacopts) {
return filterJavacopts(javacopts).standardJavacopts();
}
/** A collection of javac flags, divided into Bazel-specific and standard options. */
@AutoValue
public abstract static class FilteredJavacopts {
/** Bazel-specific javac flags, e.g. Error Prone's -Xep: flags. */
public abstract ImmutableList<String> bazelJavacopts();
/** Standard javac flags. */
public abstract ImmutableList<String> standardJavacopts();
/** Creates a {@link FilteredJavacopts}. */
public static FilteredJavacopts create(
ImmutableList<String> bazelJavacopts, ImmutableList<String> standardJavacopts) {
return new AutoValue_JavacOptions_FilteredJavacopts(bazelJavacopts, standardJavacopts);
}
}
/** Filters a list of javac flags into Bazel-specific and standard flags. */
public static FilteredJavacopts filterJavacopts(Iterable<String> javacopts) {
ImmutableList.Builder<String> bazelJavacopts = ImmutableList.builder();
ImmutableList.Builder<String> standardJavacopts = ImmutableList.builder();
for (String opt : javacopts) {
if (isBazelSpecificFlag(opt)) {
bazelJavacopts.add(opt);
} else {
standardJavacopts.add(opt);
}
}
return FilteredJavacopts.create(bazelJavacopts.build(), standardJavacopts.build());
}
private static boolean isBazelSpecificFlag(String opt) {
return opt.startsWith("-Werror:")
|| opt.startsWith("-Xep")
// TODO(b/36228287): delete this once the migration to -XepDisableAllChecks is complete
|| opt.equals("-extra_checks")
|| opt.startsWith("-extra_checks:");
}
/**
* 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.
*/
public static 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
* non-nullary options can also process the options' arguments.
*/
boolean processOption(String option, Iterator<String> remaining);
/**
* Add the normalized versions of the options handled by {@link #processOption(String)} to the
* {@code normalized} list
*/
void normalize(List<String> normalized);
}
/**
* Parse an option that starts with {@code -Xlint:} into a bunch of xlintopts. We silently drop
* xlintopts that would disable any warnings that we turn into errors by default (treating them
* like invalid xlintopts). It also parse -nowarn option as -Xlint:none.
*/
public static final class XlintOptionNormalizer implements JavacOptionNormalizer {
private static final Joiner COMMA_MINUS_JOINER = Joiner.on(",-");
private static final Joiner COMMA_JOINER = Joiner.on(",");
/**
* This type models a starting selection from which lint options can be added or removed. E.g.,
* {@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 {
/** {@code -Xlint:none} */
None,
/** {@code -Xlint:all} */
All,
/** {@code -Xlint} */
Recommended,
/** Nothing specified; default} */
Empty
}
private final ImmutableList<String> enforcedXlints;
private final Set<String> xlintPlus;
private final Set<String> xlintMinus = new LinkedHashSet<>();
private BasisXlintSelection xlintBasis = BasisXlintSelection.Empty;
public XlintOptionNormalizer() {
this(ImmutableList.<String>of());
}
public XlintOptionNormalizer(ImmutableList<String> enforcedXlints) {
this.enforcedXlints = enforcedXlints;
xlintPlus = new LinkedHashSet<>(enforcedXlints);
resetBasisTo(BasisXlintSelection.Empty);
}
@Override
public boolean processOption(String option, Iterator<String> remaining) {
if (option.equals("-nowarn")) {
// It is equivalent to -Xlint:none
resetBasisTo(BasisXlintSelection.None);
return true;
} else if (option.equals("-Xlint")) {
resetBasisTo(BasisXlintSelection.Recommended);
return true;
} else if (option.startsWith("-Xlint")) {
for (String arg : option.substring("-Xlint:".length()).split(",", -1)) {
if (arg.equals("all") || arg.isEmpty()) {
resetBasisTo(BasisXlintSelection.All);
} else if (arg.equals("none")) {
resetBasisTo(BasisXlintSelection.None);
} else if (arg.startsWith("-")) {
arg = arg.substring("-".length());
if (!enforcedXlints.contains(arg)) {
xlintPlus.remove(arg);
if (xlintBasis != BasisXlintSelection.None) {
xlintMinus.add(arg);
}
}
} else { // not a '-' prefix
xlintMinus.remove(arg);
if (xlintBasis != BasisXlintSelection.All) {
xlintPlus.add(arg);
}
}
}
return true;
}
return false;
}
@Override
public void normalize(List<String> normalized) {
switch (xlintBasis) {
case Recommended:
normalized.add("-Xlint");
break;
case All:
normalized.add("-Xlint:all");
break;
case None:
if (xlintPlus.isEmpty()) {
/*
* This should never happen with warnings as errors. The plus set should always contain
* at least the warnings in warningsAsErrors.
*/
normalized.add("-Xlint:none");
}
break;
default:
break;
}
if (xlintBasis != BasisXlintSelection.All && !xlintPlus.isEmpty()) {
normalized.add("-Xlint:" + COMMA_JOINER.join(xlintPlus));
}
if (xlintBasis != BasisXlintSelection.None && !xlintMinus.isEmpty()) {
normalized.add("-Xlint:-" + COMMA_MINUS_JOINER.join(xlintMinus));
}
}
private void resetBasisTo(BasisXlintSelection selection) {
xlintBasis = selection;
xlintPlus.clear();
xlintMinus.clear();
if (selection != BasisXlintSelection.All) {
xlintPlus.addAll(enforcedXlints);
}
}
}
/**
* Normalizer for {@code -source}, {@code -target}, and {@code --release} options. If both {@code
* -source} and {@code --release} are specified, {@code --release} wins.
*/
public static class ReleaseOptionNormalizer implements JavacOptionNormalizer {
private String source;
private String target;
private String release;
@Override
public boolean processOption(String option, Iterator<String> remaining) {
switch (option) {
case "-source":
if (remaining.hasNext()) {
source = remaining.next();
release = null;
}
return true;
case "-target":
if (remaining.hasNext()) {
target = remaining.next();
release = null;
}
return true;
case "--release":
if (remaining.hasNext()) {
release = remaining.next();
source = null;
target = null;
}
return true;
default: // fall out
}
if (option.startsWith("--release=")) {
release = option.substring("--release=".length());
source = null;
target = null;
return true;
}
return false;
}
@Override
public void normalize(List<String> normalized) {
if (release != null) {
normalized.add("--release");
normalized.add(release);
} else {
if (source != null) {
normalized.add("-source");
normalized.add(source);
}
if (target != null) {
normalized.add("-target");
normalized.add(target);
}
}
}
}
/**
* 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) {
List<String> normalized = new ArrayList<>();
Iterator<String> it = javacopts.iterator();
while (it.hasNext()) {
String opt = it.next();
boolean found = false;
for (JavacOptionNormalizer normalizer : normalizers) {
if (normalizer.processOption(opt, it)) {
found = true;
break;
}
}
if (!found) {
normalized.add(opt);
}
}
for (JavacOptionNormalizer normalizer : normalizers) {
normalizer.normalize(normalized);
}
return normalized;
}
/**
* A wrapper around {@ref #normalizeOptionsWithNormalizers(List, JavacOptionNormalizer...)} to use
* {@link XlintOptionNormalizer} as default normalizer.
*
* <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.
*/
public static List<String> normalizeOptions(List<String> javacopts) {
return normalizeOptionsWithNormalizers(
javacopts, new XlintOptionNormalizer(), new ReleaseOptionNormalizer());
}
}