blob: b04952f35b0dc4fb63c8bf086248c45445b840f2 [file] [log] [blame]
// Copyright 2018 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.docgen;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.ApiContext;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.Builtins;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.Callable;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.Param;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.Type;
import com.google.devtools.build.docgen.builtin.BuiltinProtos.Value;
import com.google.devtools.build.docgen.skylark.SkylarkConstructorMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc;
import com.google.devtools.build.docgen.skylark.SkylarkParamDoc;
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.BuiltinCallable;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.MethodDescriptor;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.common.options.OptionsParser;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/** The main class for the Skylark documentation generator. */
public class ApiExporter {
private static void appendTypes(
Builtins.Builder builtins,
Map<String, SkylarkModuleDoc> types,
List<RuleDocumentation> nativeRules)
throws BuildEncyclopediaDocException {
for (Entry<String, SkylarkModuleDoc> modEntry : types.entrySet()) {
SkylarkModuleDoc mod = modEntry.getValue();
Type.Builder type = Type.newBuilder();
type.setName(mod.getName());
type.setDoc(mod.getDocumentation());
for (SkylarkMethodDoc meth : mod.getJavaMethods()) {
// Constructors are exported as global symbols.
if (!(meth instanceof SkylarkConstructorMethodDoc)) {
Value.Builder value = collectMethodInfo(meth);
// Methods from the native package are available as top level functions in BUILD files.
if (mod.getName().equals("native")) {
value.setApiContext(ApiContext.BUILD);
builtins.addGlobal(value);
value.setApiContext(ApiContext.BZL);
type.addField(value);
} else {
value.setApiContext(ApiContext.ALL);
type.addField(value);
}
}
}
// Native rules are available in BZL file as methods of the native package.
if (mod.getName().equals("native")) {
for (RuleDocumentation rule : nativeRules) {
Value.Builder field = collectRuleInfo(rule);
field.setApiContext(ApiContext.BZL);
type.addField(field);
}
}
builtins.addType(type);
}
}
// Globals are available for both BUILD and BZL files.
private static void appendGlobals(Builtins.Builder builtins, Map<String, Object> globalMethods) {
for (Entry<String, Object> entry : globalMethods.entrySet()) {
Object obj = entry.getValue();
Value.Builder value = Value.newBuilder();
if (obj instanceof BaseFunction) {
value = collectFunctionInfo((BaseFunction) obj);
} else if (obj instanceof BuiltinCallable) {
BuiltinCallable builtinCallable = (BuiltinCallable) obj;
MethodDescriptor descriptor =
builtinCallable.getMethodDescriptor(StarlarkSemantics.DEFAULT_SEMANTICS);
value =
collectFunctionInfo(
descriptor.getName(),
SkylarkSignatureProcessor.getSignatureForCallable(
descriptor.getName(), descriptor, null, null));
} else {
value.setName(entry.getKey());
}
value.setApiContext(ApiContext.ALL);
builtins.addGlobal(value);
}
}
private static void appendBzlGlobals(
Builtins.Builder builtins, Map<String, Object> starlarkGlobals) {
for (Entry<String, Object> entry : starlarkGlobals.entrySet()) {
Object obj = entry.getValue();
Value.Builder value = Value.newBuilder();
if (obj instanceof BaseFunction) {
value = collectFunctionInfo((BaseFunction) obj);
} else {
SkylarkModule typeModule = SkylarkInterfaceUtils.getSkylarkModule(obj.getClass());
if (typeModule != null) {
if (FuncallExpression.hasSelfCallMethod(
StarlarkSemantics.DEFAULT_SEMANTICS, obj.getClass())) {
MethodDescriptor descriptor =
FuncallExpression.getSelfCallMethodDescriptor(
StarlarkSemantics.DEFAULT_SEMANTICS, obj);
value = collectFunctionInfo(
descriptor.getName(),
SkylarkSignatureProcessor.getSignatureForCallable(
descriptor.getName(), descriptor, null, null));
} else {
value.setName(entry.getKey());
value.setType(entry.getKey());
value.setDoc(typeModule.doc());
}
}
}
value.setApiContext(ApiContext.BZL);
builtins.addGlobal(value);
}
}
// Native rules are available as top level functions in BUILD files.
private static void appendNativeRules(
Builtins.Builder builtins, List<RuleDocumentation> nativeRules)
throws BuildEncyclopediaDocException {
for (RuleDocumentation rule : nativeRules) {
Value.Builder global = collectRuleInfo(rule);
global.setApiContext(ApiContext.BUILD);
builtins.addGlobal(global);
}
}
private static Value.Builder collectFunctionInfo(BaseFunction func) {
return collectFunctionInfo(func.getName(), func.getSignature());
}
private static Value.Builder collectFunctionInfo(
String funcName, FunctionSignature.WithValues<Object, SkylarkType> funcSignature) {
Value.Builder value = Value.newBuilder();
value.setName(funcName);
Callable.Builder callable = Callable.newBuilder();
ImmutableList<String> paramNames = funcSignature.getSignature().getNames();
List<Object> defaultValues = funcSignature.getDefaultValues();
int positionals = funcSignature.getSignature().getShape().getMandatoryPositionals();
int optionals = funcSignature.getSignature().getShape().getOptionals();
int nameIndex = 0;
for (int i = 0; i < positionals; i++) {
Param.Builder param = Param.newBuilder();
param.setName(paramNames.get(nameIndex));
param.setIsMandatory(true);
callable.addParam(param);
nameIndex++;
}
for (int i = 0; i < optionals; i++) {
Param.Builder param = Param.newBuilder();
param.setName(paramNames.get(nameIndex));
param.setIsMandatory(false);
param.setDefaultValue(defaultValues.get(i).toString());
callable.addParam(param);
nameIndex++;
}
if (funcSignature.getSignature().getShape().hasStarArg()) {
Param.Builder param = Param.newBuilder();
param.setName("*" + paramNames.get(nameIndex));
param.setIsMandatory(false);
param.setIsStarArg(true);
nameIndex++;
callable.addParam(param);
}
if (funcSignature.getSignature().getShape().hasKwArg()) {
Param.Builder param = Param.newBuilder();
param.setIsMandatory(false);
param.setIsStarStarArg(true);
param.setName("**" + paramNames.get(nameIndex));
callable.addParam(param);
}
value.setCallable(callable);
return value;
}
private static Value.Builder collectMethodInfo(SkylarkMethodDoc meth) {
Value.Builder field = Value.newBuilder();
field.setName(meth.getShortName());
field.setDoc(meth.getDocumentation());
if (meth.isCallable()) {
Callable.Builder callable = Callable.newBuilder();
for (SkylarkParamDoc par : meth.getParams()) {
Param.Builder param = newParam(par.getName(), par.getDefaultValue().isEmpty());
param.setType(par.getType());
param.setDoc(par.getDocumentation());
param.setDefaultValue(par.getDefaultValue());
callable.addParam(param);
}
callable.setReturnType(meth.getReturnType());
field.setCallable(callable);
} else {
field.setType(meth.getReturnType());
}
return field;
}
private static Param.Builder newParam(String name, Boolean isMandatory) {
Param.Builder param = Param.newBuilder();
param.setName(name);
param.setIsMandatory(isMandatory);
return param;
}
private static Value.Builder collectRuleInfo(RuleDocumentation rule)
throws BuildEncyclopediaDocException {
Value.Builder value = Value.newBuilder();
value.setName(rule.getRuleName());
value.setDoc(rule.getHtmlDocumentation());
Callable.Builder callable = Callable.newBuilder();
// All native rules have attribute "name". It is not included in the attributes list and needs
// to be added separately.
callable.addParam(newParam("name", true));
for (RuleDocumentationAttribute attr : rule.getAttributes()) {
callable.addParam(newParam(attr.getAttributeName(), attr.isMandatory()));
}
value.setCallable(callable);
return value;
}
private static void writeBuiltins(String filename, Builtins.Builder builtins) throws IOException {
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filename))) {
Builtins build = builtins.build();
build.writeTo(out);
}
}
private static void printUsage(OptionsParser parser) {
System.err.println(
"Usage: api_exporter_bin -n product_name -p rule_class_provider (-i input_dir)+\n"
+ " -f outputFile [-b blacklist] [-h]\n\n"
+ "Exports all Starlark builtins to a file including the embedded native rules.\n"
+ "The product name (-n), rule class provider (-p), output file (-f) and at least \n"
+ " one input_dir (-i) must be specified.\n");
System.err.println(
parser.describeOptionsWithDeprecatedCategories(
Collections.<String, String>emptyMap(), OptionsParser.HelpVerbosity.LONG));
}
public static void main(String[] args) {
OptionsParser parser = OptionsParser.newOptionsParser(BuildEncyclopediaOptions.class);
parser.parseAndExitUponError(args);
BuildEncyclopediaOptions options = parser.getOptions(BuildEncyclopediaOptions.class);
if (options.help) {
printUsage(parser);
Runtime.getRuntime().exit(0);
}
if (options.productName.isEmpty()
|| options.inputDirs.isEmpty()
|| options.provider.isEmpty()
|| options.outputFile.isEmpty()) {
printUsage(parser);
Runtime.getRuntime().exit(1);
}
try {
SymbolFamilies symbols =
new SymbolFamilies(
options.productName, options.provider, options.inputDirs, options.blacklist);
Builtins.Builder builtins = Builtins.newBuilder();
appendTypes(builtins, symbols.getTypes(), symbols.getNativeRules());
appendGlobals(builtins, symbols.getGlobals());
appendBzlGlobals(builtins, symbols.getBzlGlobals());
appendNativeRules(builtins, symbols.getNativeRules());
writeBuiltins(options.outputFile, builtins);
} catch (Throwable e) {
System.err.println("ERROR: " + e.getMessage());
e.printStackTrace();
}
}
}