blob: 2fb5f8acfa559394534fb8914352bfb9500ac7d2 [file] [log] [blame]
/*
* 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.json.BuildFileParseException;
import com.facebook.buck.json.ProjectBuildFileParser;
import com.facebook.buck.util.Escaper;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreStrings;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import javax.annotation.Nullable;
/**
* Evaluates a build file and prints out an equivalent build file with all includes/macros
* expanded. When complex macros are in play, this helps clarify what the resulting build rule
* definitions are.
*/
public class AuditRulesCommand extends AbstractCommandRunner<AuditRulesOptions> {
/** Indent to use in generated build file. */
private static String INDENT = " ";
/**
* Properties from the JSON produced by {@code buck.py} that start with this prefix do not
* correspond to build rule arguments specified by the user. Instead, they contain internal-only
* metadata, so they should not be printed when the build rule is reproduced.
*/
private static final String INTERNAL_PROPERTY_NAME_PREFIX = "buck.";
/**
* The name of the property in the JSON produced by {@code buck.py} that identifies the type of
* the build rule being defined.
* <p>
* TODO(mbolin): Change this property name to "buck.type" so that all internal properties start
* with "buck.".
*/
private static final String TYPE_PROPERTY_NAME = "type";
/** Properties that should be listed last in the declaration of a build rule. */
private static final ImmutableSet<String> LAST_PROPERTIES = ImmutableSet.of("deps", "visibility");
protected AuditRulesCommand(CommandRunnerParams params) {
super(params);
}
@Override
AuditRulesOptions createOptions(BuckConfig buckConfig) {
return new AuditRulesOptions(buckConfig);
}
@Override
String getUsageIntro() {
return "List build rule definitions resulting from expanding macros.";
}
/** Prints the expanded build rules from the specified build files to the console. */
@Override
int runCommandWithOptionsInternal(AuditRulesOptions options) throws IOException {
ProjectFilesystem projectFilesystem = getProjectFilesystem();
ProjectBuildFileParser parser = new ProjectBuildFileParser(projectFilesystem,
options.getBuckConfig().getDefaultIncludes(),
options.getBuckConfig().getPythonInterpreter());
PrintStream out = console.getStdOut();
for (String pathToBuildFile : options.getArguments()) {
// Print a comment with the path to the build file.
out.printf("# %s\n\n", pathToBuildFile);
// Resolve the path specified by the user.
Path path = Paths.get(pathToBuildFile);
if (!path.isAbsolute()) {
Path root = projectFilesystem.getRootPath();
path = root.resolve(path);
}
// Parse the rules from the build file.
List<Map<String, Object>> rawRules;
try {
rawRules = parser.getAllRules(path.toString());
} catch (BuildFileParseException e) {
throw new HumanReadableException(e);
}
// Format and print the rules from the raw data, filtered by type.
final ImmutableSet<String> types = options.getTypes();
Predicate<String> includeType = new Predicate<String>() {
@Override
public boolean apply(String type) {
return types.isEmpty() || types.contains(type);
}};
printRulesToStdout(rawRules, includeType);
}
return 0;
}
private void printRulesToStdout(List<Map<String, Object>> rawRules,
Predicate<String> includeType) {
PrintStream out = console.getStdOut();
for (Map<String, Object> rawRule : rawRules) {
String type = (String)rawRule.get(TYPE_PROPERTY_NAME);
if (!includeType.apply(type)) {
continue;
}
out.printf("%s(\n", type);
// The properties in the order they should be displayed for this rule.
LinkedHashSet<String> properties = Sets.newLinkedHashSet();
// Always display the "name" property first.
properties.add("name");
// Add the properties specific to the rule.
SortedSet<String> customProperties = Sets.newTreeSet();
for (String key : rawRule.keySet()) {
// Ignore keys that start with "buck.".
if (!(key.equals(TYPE_PROPERTY_NAME) ||
key.startsWith(INTERNAL_PROPERTY_NAME_PREFIX) ||
LAST_PROPERTIES.contains(key))) {
customProperties.add(key);
}
}
properties.addAll(customProperties);
// Add common properties that should be displayed last.
properties.addAll(LAST_PROPERTIES);
// Write out the properties and their corresponding values.
for (String property : properties) {
String displayValue = createDisplayString(rawRule.get(property));
out.printf("%s%s = %s,\n", INDENT, property, displayValue);
}
// Close the rule definition.
out.printf(")\n\n");
}
}
/**
* @param value in a Map returned by {@link ProjectBuildFileParser#getAllRules(String)}.
* @return a string that represents the Python equivalent of the value.
*/
@VisibleForTesting
static String createDisplayString(@Nullable Object value) {
if (value == null) {
return "None";
} else if (value instanceof Boolean) {
return MoreStrings.capitalize(value.toString());
} else if (value instanceof String) {
return Escaper.escapeAsPythonString(value.toString());
} else if (value instanceof Number) {
return value.toString();
} else if (value instanceof List) {
StringBuilder out = new StringBuilder("[\n");
String indent = Strings.repeat(INDENT, 2);
for (Object item : (List<?>)value) {
out.append(indent).append(createDisplayString(item)).append(",\n");
}
out.append(INDENT).append("]");
return out.toString();
} else {
throw new IllegalStateException();
}
}
}