/*
* Copyright 2013-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.event.ConsoleEvent;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.model.Flavored;
import com.facebook.buck.model.UserFlavor;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.util.DirtyPrintStreamDecorator;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.MoreExceptions;
import com.facebook.buck.util.ObjectMappers;
import com.facebook.buck.util.RichStream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
/** List flavor domains for build targets. */
public class AuditFlavorsCommand extends AbstractCommand {
@Option(name = "--json", usage = "Output in JSON format")
private boolean generateJsonOutput;
public boolean shouldGenerateJsonOutput() {
return generateJsonOutput;
}
@Override
public String getShortDescription() {
return "List flavor domains for build targets.";
}
@Argument private List<String> arguments = new ArrayList<>();
public List<String> getArguments() {
return arguments;
}
private ImmutableList<String> getArgumentsFormattedAsBuildTargets(BuckConfig buckConfig) {
return getCommandLineBuildTargetNormalizer(buckConfig).normalizeAll(getArguments());
}
@Override
public int runWithoutHelp(CommandRunnerParams params) throws IOException, InterruptedException {
ImmutableSet<BuildTarget> targets =
getArgumentsFormattedAsBuildTargets(params.getBuckConfig())
.stream()
.map(
input ->
BuildTargetParser.INSTANCE.parse(
input,
BuildTargetPatternParser.fullyQualified(),
params.getCell().getCellPathResolver()))
.collect(MoreCollectors.toImmutableSet());
if (targets.isEmpty()) {
params
.getBuckEventBus()
.post(ConsoleEvent.severe("Please specify at least one build target."));
return 1;
}
ImmutableList.Builder<TargetNode<?, ?>> builder = ImmutableList.builder();
try (CommandThreadManager pool =
new CommandThreadManager("Audit", getConcurrencyLimit(params.getBuckConfig()))) {
for (BuildTarget target : targets) {
TargetNode<?, ?> targetNode =
params
.getParser()
.getTargetNode(
params.getBuckEventBus(),
params.getCell(),
getEnableParserProfiling(),
pool.getExecutor(),
target);
builder.add(targetNode);
}
} catch (BuildFileParseException | BuildTargetException e) {
params
.getBuckEventBus()
.post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e)));
return 1;
}
ImmutableList<TargetNode<?, ?>> targetNodes = builder.build();
if (shouldGenerateJsonOutput()) {
printJsonFlavors(targetNodes, params);
} else {
printFlavors(targetNodes, params);
}
return 0;
}
@Override
public boolean isReadOnly() {
return true;
}
private void printFlavors(
ImmutableList<TargetNode<?, ?>> targetNodes, CommandRunnerParams params) {
DirtyPrintStreamDecorator stdout = params.getConsole().getStdOut();
for (TargetNode<?, ?> node : targetNodes) {
Description<?> description = node.getDescription();
stdout.println(node.getBuildTarget().getFullyQualifiedName());
if (description instanceof Flavored) {
Optional<ImmutableSet<FlavorDomain<?>>> flavorDomains =
((Flavored) description).flavorDomains();
if (flavorDomains.isPresent()) {
for (FlavorDomain<?> domain : flavorDomains.get()) {
ImmutableSet<UserFlavor> userFlavors =
RichStream.from(domain.getFlavors().stream())
.filter(UserFlavor.class)
.collect(MoreCollectors.toImmutableSet());
if (userFlavors.isEmpty()) {
continue;
}
stdout.printf(" %s\n", domain.getName());
for (UserFlavor flavor : userFlavors) {
String flavorLine = String.format(" %s", flavor.getName());
String flavorDescription = flavor.getDescription();
if (flavorDescription.length() > 0) {
flavorLine += String.format(" -> %s", flavorDescription);
}
flavorLine += "\n";
stdout.printf(flavorLine);
}
}
} else {
stdout.println(" unknown");
}
} else {
stdout.println(" no flavors");
}
}
}
private void printJsonFlavors(
ImmutableList<TargetNode<?, ?>> targetNodes, CommandRunnerParams params) throws IOException {
DirtyPrintStreamDecorator stdout = params.getConsole().getStdOut();
SortedMap<String, SortedMap<String, SortedMap<String, String>>> targetsJson = new TreeMap<>();
for (TargetNode<?, ?> node : targetNodes) {
Description<?> description = node.getDescription();
SortedMap<String, SortedMap<String, String>> flavorDomainsJson = new TreeMap<>();
if (description instanceof Flavored) {
Optional<ImmutableSet<FlavorDomain<?>>> flavorDomains =
((Flavored) description).flavorDomains();
if (flavorDomains.isPresent()) {
for (FlavorDomain<?> domain : flavorDomains.get()) {
ImmutableSet<UserFlavor> userFlavors =
RichStream.from(domain.getFlavors().stream())
.filter(UserFlavor.class)
.collect(MoreCollectors.toImmutableSet());
if (userFlavors.isEmpty()) {
continue;
}
SortedMap<String, String> flavorsJson =
userFlavors
.stream()
.collect(
MoreCollectors.toImmutableSortedMap(
UserFlavor::getName, UserFlavor::getDescription));
flavorDomainsJson.put(domain.getName(), flavorsJson);
}
} else {
flavorDomainsJson.put("unknown", new TreeMap<>());
}
}
String targetName = node.getBuildTarget().getFullyQualifiedName();
targetsJson.put(targetName, flavorDomainsJson);
}
ObjectMappers.WRITER.writeValue(stdout, targetsJson);
}
}