blob: 2fbfa7ce80ceeefc9d14e566e12a860c8789a841 [file] [log] [blame]
/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.cli;
import com.facebook.buck.rules.Label;
import com.facebook.infer.annotation.SuppressFieldNotInitialized;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Options for including and excluding rules by label.
* <p>
* If we were starting from scratch, we'd just have a single command line option: --labels !foo bar
* <p>
* However, we have to support the historical dual-option form: --exclude foo --include bar
* <p>
* Order is significant as the first matching include/exclude rule wins. To support the legacy
* --include and --exclude options and yet still respect order, we use a special option handler
* {@link com.facebook.buck.cli.TestLabelOptions.LabelsOptionHandler} which keeps track of the
* argument position of each label-rule in the respective maps populated by those options.
*/
class TestLabelOptions {
public static final String LABEL_SEPERATOR = "+";
@Option(
name = "--exclude",
usage = "Labels to ignore when running tests, --exclude L1 L2 ... LN.",
handler = LabelsOptionHandler.class)
@SuppressFieldNotInitialized
private Map<Integer, LabelSelector> excludedLabelSets;
@Option(
name = "--labels",
aliases = {"--include"},
usage =
"Labels to include (or exclude, when prefixed with '!') when running test rules. " +
"The first matching statement is used to decide whether to " +
"include or exclude a test rule.",
handler = LabelsOptionHandler.class)
@SuppressFieldNotInitialized
private Map<Integer, LabelSelector> includedLabelSets;
@Option(
name = "--always-exclude",
aliases = {"--always_exclude"},
usage =
"If set, an exclude filter will win over a target on the command line, so tests " +
"that were both specified to run on the command line and are excluded through either " +
"the '--exclude' option or in the .buckconfig will not run.")
private boolean alwaysExclude;
private Supplier<ImmutableList<LabelSelector>> supplier =
Suppliers.memoize(new Supplier<ImmutableList<LabelSelector>>() {
@Override
public ImmutableList<LabelSelector> get() {
TreeMap<Integer, LabelSelector> all = Maps.newTreeMap();
all.putAll(includedLabelSets);
// Invert the sense of anything given to --exclude.
// This means we could --exclude !includeMe ...lolololol
for (Integer ordinal : excludedLabelSets.keySet()) {
LabelSelector original = Preconditions.checkNotNull(excludedLabelSets.get(ordinal));
all.put(ordinal, original.invert());
}
return ImmutableList.copyOf(all.values());
}
});
public boolean shouldExcludeWin() {
return alwaysExclude;
}
public boolean isMatchedByLabelOptions(BuckConfig buckConfig, Set<Label> rawLabels) {
ImmutableList<LabelSelector> labelSelectors = supplier.get();
for (LabelSelector labelSelector : labelSelectors) {
if (labelSelector.matches(rawLabels)) {
return labelSelector.isInclusive();
}
}
List<String> defaultRawExcludedLabelSelectors =
buckConfig.getDefaultRawExcludedLabelSelectors();
for (String raw : defaultRawExcludedLabelSelectors) {
LabelSelector labelSelector = LabelSelector.fromString(raw).invert();
if (labelSelector.matches(rawLabels)) {
return labelSelector.isInclusive();
}
}
boolean defaultResult = true;
for (LabelSelector labelSelector : labelSelectors) {
if (labelSelector.isInclusive()) {
defaultResult = false;
}
}
return defaultResult;
}
public static class LabelsOptionHandler extends OptionHandler<Map<Integer, LabelSelector>> {
/**
* Shared across all instances of this handler, to keep track of the order of label rules given
* on the command line.
*/
private static final AtomicInteger ordinal = new AtomicInteger();
private final Map<Integer, LabelSelector> labels = Maps.newHashMap();
public LabelsOptionHandler(
CmdLineParser parser,
OptionDef option,
Setter<Map<Integer, LabelSelector>> setter) throws CmdLineException {
super(parser, option, setter);
setter.addValue(labels);
}
@Override
public int parseArguments(Parameters parameters) throws CmdLineException {
int index;
for (index = 0; index < parameters.size(); index++) {
String parameter = parameters.getParameter(index);
if (parameter.charAt(0) == '-') {
break;
}
labels.put(ordinal.getAndIncrement(), LabelSelector.fromString(parameter));
}
return index;
}
@Override
public String getDefaultMetaVariable() {
return "LIST<LABELS>";
}
}
}