| // 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.skydoc; |
| |
| import static com.google.common.collect.ImmutableMap.toImmutableMap; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.Label.PackageContext; |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; |
| import com.google.devtools.build.lib.cmdline.RepositoryMapping; |
| import com.google.devtools.build.lib.collect.nestedset.Depset; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeDeepStructure; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeProviderApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeStructApi; |
| import com.google.devtools.build.skydoc.rendering.AspectInfoWrapper; |
| import com.google.devtools.build.skydoc.rendering.DocstringParseException; |
| import com.google.devtools.build.skydoc.rendering.ProtoRenderer; |
| import com.google.devtools.build.skydoc.rendering.ProviderInfoWrapper; |
| import com.google.devtools.build.skydoc.rendering.RuleInfoWrapper; |
| import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AspectInfo; |
| import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo; |
| import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo; |
| import com.google.devtools.common.options.OptionsParser; |
| import java.io.BufferedOutputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.TreeMap; |
| import java.util.stream.Collectors; |
| import net.starlark.java.annot.Param; |
| import net.starlark.java.annot.StarlarkBuiltin; |
| import net.starlark.java.annot.StarlarkMethod; |
| import net.starlark.java.eval.Dict; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Module; |
| import net.starlark.java.eval.Mutability; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkCallable; |
| import net.starlark.java.eval.StarlarkFunction; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.eval.StarlarkValue; |
| import net.starlark.java.lib.json.Json; |
| import net.starlark.java.syntax.FileOptions; |
| import net.starlark.java.syntax.ParserInput; |
| import net.starlark.java.syntax.Program; |
| import net.starlark.java.syntax.Resolver; |
| import net.starlark.java.syntax.Resolver.Scope; |
| import net.starlark.java.syntax.StarlarkFile; |
| import net.starlark.java.syntax.SyntaxError; |
| |
| /** |
| * Main entry point for the Skydoc binary. |
| * |
| * <p>Skydoc generates human-readable documentation for relevant details of Starlark files by |
| * running a Starlark interpreter with a fake implementation of the build API. |
| * |
| * <p>Currently, Skydoc generates documentation for Starlark rule definitions (discovered by |
| * invocations of the build API function {@code rule()}. |
| * |
| * <p>Usage: |
| * |
| * <pre> |
| * skydoc {target_starlark_file_label} {output_file} [symbol_name]... |
| * </pre> |
| * |
| * <p>Generates documentation for all exported symbols of the target Starlark file that are |
| * specified in the list of symbol names. If no symbol names are supplied, outputs documentation for |
| * all exported symbols in the target Starlark file. |
| */ |
| public class SkydocMain { |
| |
| private final EventHandler eventHandler = new SystemOutEventHandler(); |
| private final LinkedHashSet<Path> pending = new LinkedHashSet<>(); |
| private final Map<Path, Module> loaded = new HashMap<>(); |
| private final StarlarkFileAccessor fileAccessor; |
| private final List<String> depRoots; |
| private final String workspaceName; |
| |
| public SkydocMain( |
| StarlarkFileAccessor fileAccessor, String workspaceName, List<String> depRoots) { |
| this.fileAccessor = fileAccessor; |
| this.workspaceName = workspaceName; |
| if (depRoots.isEmpty()) { |
| // For backwards compatibility, if no dep_roots are specified, use the current |
| // directory as the only root. |
| this.depRoots = ImmutableList.of("."); |
| } else { |
| this.depRoots = depRoots; |
| } |
| } |
| |
| public static void main(String[] args) |
| throws IOException, InterruptedException, LabelSyntaxException, DocstringParseException { |
| OptionsParser parser = |
| OptionsParser.builder() |
| .optionsClasses(BuildLanguageOptions.class, SkydocOptions.class) |
| .build(); |
| parser.parseAndExitUponError(args); |
| BuildLanguageOptions semanticsOptions = parser.getOptions(BuildLanguageOptions.class); |
| semanticsOptions.incompatibleNewActionsApi = false; |
| SkydocOptions skydocOptions = parser.getOptions(SkydocOptions.class); |
| |
| String targetFileLabelString; |
| String outputPath; |
| ImmutableList<String> depRoots; |
| |
| if (Strings.isNullOrEmpty(skydocOptions.targetFileLabel) |
| || Strings.isNullOrEmpty(skydocOptions.outputFilePath)) { |
| throw new IllegalArgumentException("Expected a target file label and an output file path."); |
| } |
| |
| targetFileLabelString = skydocOptions.targetFileLabel; |
| outputPath = skydocOptions.outputFilePath; |
| depRoots = ImmutableList.copyOf(skydocOptions.depRoots); |
| |
| Label targetFileLabel = Label.parseCanonical(targetFileLabelString); |
| |
| ImmutableMap.Builder<String, RuleInfo> ruleInfoMap = ImmutableMap.builder(); |
| ImmutableMap.Builder<String, ProviderInfo> providerInfoMap = ImmutableMap.builder(); |
| ImmutableMap.Builder<String, StarlarkFunction> userDefinedFunctions = ImmutableMap.builder(); |
| ImmutableMap.Builder<String, AspectInfo> aspectInfoMap = ImmutableMap.builder(); |
| |
| Module module = null; |
| try { |
| module = |
| new SkydocMain(new FilesystemFileAccessor(), skydocOptions.workspaceName, depRoots) |
| .eval( |
| semanticsOptions.toStarlarkSemantics(), |
| targetFileLabel, |
| ruleInfoMap, |
| providerInfoMap, |
| userDefinedFunctions, |
| aspectInfoMap); |
| } catch (StarlarkEvaluationException | EvalException exception) { |
| exception.printStackTrace(); |
| System.err.println("Stardoc documentation generation failed: " + exception.getMessage()); |
| System.exit(1); |
| } |
| |
| ProtoRenderer renderer = |
| render( |
| module, |
| ImmutableSet.copyOf(skydocOptions.symbolNames), |
| ruleInfoMap.buildOrThrow(), |
| providerInfoMap.buildOrThrow(), |
| userDefinedFunctions.buildOrThrow(), |
| aspectInfoMap.buildOrThrow()); |
| try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputPath))) { |
| renderer.writeModuleInfo(out); |
| } |
| } |
| |
| private static boolean validSymbolName(ImmutableSet<String> symbolNames, String symbolName) { |
| if (symbolNames.isEmpty()) { |
| // Symbols prefixed with an underscore are private, and thus, by default, documentation |
| // should not be generated for them. |
| return !symbolName.startsWith("_"); |
| } else if (symbolNames.contains(symbolName)) { |
| return true; |
| } else if (symbolName.contains(".")) { |
| return symbolNames.contains(symbolName.substring(0, symbolName.indexOf('.'))); |
| } |
| return false; |
| } |
| |
| /** |
| * Renders a Starlark module to proto form. |
| * |
| * @param symbolNames symbols to render; if empty, all non-private symbols (i.e. those whose names |
| * do not start with '_') will be rendered. |
| * @param ruleInfoMap a map of rule definition information for named rules. Keys are exported |
| * names of rules, and values are their {@link RuleInfo} rule descriptions. For example, |
| * 'my_rule = rule(...)' has key 'my_rule' |
| * @param providerInfoMap a map of provider definition information for named providers. Keys are |
| * exported names of providers, and values are their {@link ProviderInfo} descriptions. For |
| * example, 'my_provider = provider(...)' has key 'my_provider' |
| * @param userDefinedFunctions a map of user-defined functions. Keys are exported names of |
| * functions, and values are the {@link StarlarkFunction} objects. For example, 'def |
| * my_function(foo):' is a function with key 'my_function'. |
| * @param aspectInfoMap a map of aspect definition information for named aspects. Keys are |
| * exported names of aspects, and values are the {@link AspectInfo} asepct descriptions. For |
| * example, 'my_aspect = aspect(...)' has key 'my_aspect' |
| */ |
| @VisibleForTesting |
| public static ProtoRenderer render( |
| Module module, |
| ImmutableSet<String> symbolNames, |
| ImmutableMap<String, RuleInfo> ruleInfoMap, |
| ImmutableMap<String, ProviderInfo> providerInfoMap, |
| ImmutableMap<String, StarlarkFunction> userDefinedFunctions, |
| ImmutableMap<String, AspectInfo> aspectInfoMap) |
| throws DocstringParseException { |
| ImmutableMap<String, RuleInfo> filteredRuleInfos = |
| ruleInfoMap.entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(toImmutableMap(Entry::getKey, Entry::getValue)); |
| ImmutableMap<String, ProviderInfo> filteredProviderInfos = |
| providerInfoMap.entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(toImmutableMap(Entry::getKey, Entry::getValue)); |
| ImmutableMap<String, StarlarkFunction> filteredStarlarkFunctions = |
| userDefinedFunctions.entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(toImmutableMap(Entry::getKey, Entry::getValue)); |
| ImmutableMap<String, AspectInfo> filteredAspectInfos = |
| aspectInfoMap.entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(toImmutableMap(Entry::getKey, Entry::getValue)); |
| |
| String moduleDocstring = module.getDocumentation(); |
| if (moduleDocstring == null) { |
| moduleDocstring = ""; |
| } |
| return new ProtoRenderer() |
| .appendRuleInfos(filteredRuleInfos.values()) |
| .appendProviderInfos(filteredProviderInfos.values()) |
| .appendStarlarkFunctionInfos(filteredStarlarkFunctions) |
| .appendAspectInfos(filteredAspectInfos.values()) |
| .setModuleDocstring(moduleDocstring); |
| } |
| |
| /** |
| * Evaluates/interprets the Starlark file at a given path and its transitive Starlark dependencies |
| * using a fake build API and collects information about all rule definitions made in the root |
| * Starlark file. |
| * |
| * @param label the label of the Starlark file to evaluate |
| * @param ruleInfoMap a map builder to be populated with rule definition information for named |
| * rules. Keys are exported names of rules, and values are their {@link RuleInfo} rule |
| * descriptions. For example, 'my_rule = rule(...)' has key 'my_rule' |
| * @param providerInfoMap a map builder to be populated with provider definition information for |
| * named providers. Keys are exported names of providers, and values are their {@link |
| * ProviderInfo} descriptions. For example, 'my_provider = provider(...)' has key |
| * 'my_provider' |
| * @param userDefinedFunctionMap a map builder to be populated with user-defined functions. Keys |
| * are exported names of functions, and values are the {@link StarlarkFunction} objects. For |
| * example, 'def my_function(foo):' is a function with key 'my_function'. |
| * @param aspectInfoMap a map builder to be populated with aspect definition information for named |
| * aspects. Keys are exported names of aspects, and values are the {@link AspectInfo} asepct |
| * descriptions. For example, 'my_aspect = aspect(...)' has key 'my_aspect' |
| * @throws InterruptedException if evaluation is interrupted |
| */ |
| @VisibleForTesting |
| public Module eval( |
| StarlarkSemantics semantics, |
| Label label, |
| ImmutableMap.Builder<String, RuleInfo> ruleInfoMap, |
| ImmutableMap.Builder<String, ProviderInfo> providerInfoMap, |
| ImmutableMap.Builder<String, StarlarkFunction> userDefinedFunctionMap, |
| ImmutableMap.Builder<String, AspectInfo> aspectInfoMap) |
| throws InterruptedException, |
| IOException, |
| LabelSyntaxException, |
| EvalException, |
| StarlarkEvaluationException { |
| |
| List<RuleInfoWrapper> ruleInfoList = new ArrayList<>(); |
| |
| List<ProviderInfoWrapper> providerInfoList = new ArrayList<>(); |
| |
| List<AspectInfoWrapper> aspectInfoList = new ArrayList<>(); |
| |
| Module module = recursiveEval(semantics, label, ruleInfoList, providerInfoList, aspectInfoList); |
| |
| Map<StarlarkCallable, RuleInfoWrapper> ruleFunctions = |
| ruleInfoList.stream() |
| .collect( |
| Collectors.toMap(RuleInfoWrapper::getIdentifierFunction, Functions.identity())); |
| |
| Map<StarlarkCallable, ProviderInfoWrapper> providerInfos = |
| providerInfoList.stream() |
| .collect(Collectors.toMap(ProviderInfoWrapper::getIdentifier, Functions.identity())); |
| |
| Map<StarlarkCallable, AspectInfoWrapper> aspectFunctions = |
| aspectInfoList.stream() |
| .collect( |
| Collectors.toMap(AspectInfoWrapper::getIdentifierFunction, Functions.identity())); |
| |
| // Sort the globals bindings by name. |
| TreeMap<String, Object> sortedBindings = new TreeMap<>(module.getGlobals()); |
| |
| for (Entry<String, Object> envEntry : sortedBindings.entrySet()) { |
| if (ruleFunctions.containsKey(envEntry.getValue())) { |
| RuleInfo ruleInfo = ruleFunctions.get(envEntry.getValue()).getRuleInfo().build(); |
| // Use symbol name as the rule name only if not already set in the call to rule(). |
| if ("".equals(ruleInfo.getRuleName())) { |
| // We make a copy so that additional exports are not affected by setting the rule name on |
| // this builder |
| ruleInfo = ruleInfo.toBuilder().setRuleName(envEntry.getKey()).build(); |
| } |
| ruleInfoMap.put(ruleInfo.getRuleName(), ruleInfo); |
| } |
| if (providerInfos.containsKey(envEntry.getValue())) { |
| ProviderInfo.Builder providerInfoBuild = |
| providerInfos.get(envEntry.getValue()).getProviderInfo(); |
| ProviderInfo providerInfo = providerInfoBuild.setProviderName(envEntry.getKey()).build(); |
| providerInfoMap.put(envEntry.getKey(), providerInfo); |
| } |
| if (envEntry.getValue() instanceof StarlarkFunction) { |
| StarlarkFunction userDefinedFunction = (StarlarkFunction) envEntry.getValue(); |
| userDefinedFunctionMap.put(envEntry.getKey(), userDefinedFunction); |
| } |
| if (envEntry.getValue() instanceof FakeStructApi) { |
| String namespaceName = envEntry.getKey(); |
| FakeStructApi namespace = (FakeStructApi) envEntry.getValue(); |
| putStructFields( |
| namespaceName, namespace, ruleFunctions, ruleInfoMap, userDefinedFunctionMap); |
| } |
| if (aspectFunctions.containsKey(envEntry.getValue())) { |
| AspectInfo.Builder aspectInfoBuild = |
| aspectFunctions.get(envEntry.getValue()).getAspectInfo(); |
| AspectInfo aspectInfo = aspectInfoBuild.setAspectName(envEntry.getKey()).build(); |
| aspectInfoMap.put(envEntry.getKey(), aspectInfo); |
| } |
| } |
| |
| return module; |
| } |
| |
| /** |
| * Recursively adds functions defined in {@code namespace}, and in its nested namespaces, to |
| * {@code userDefinedFunctionMap}. |
| * |
| * <p>Each entry's key is the fully qualified function name, e.g. {@code |
| * "outernamespace.innernamespace.func"}. {@code namespaceName} is the fully qualified name of |
| * {@code namespace} itself. |
| */ |
| private static void putStructFields( |
| String namespaceName, |
| FakeStructApi namespace, |
| Map<StarlarkCallable, RuleInfoWrapper> ruleFunctions, |
| ImmutableMap.Builder<String, RuleInfo> ruleInfoMap, |
| ImmutableMap.Builder<String, StarlarkFunction> userDefinedFunctionMap) |
| throws EvalException { |
| for (String field : namespace.getFieldNames()) { |
| String qualifiedFieldName = namespaceName + "." + field; |
| if (ruleFunctions.containsKey(namespace.getValue(field))) { |
| ruleInfoMap.put( |
| qualifiedFieldName, ruleFunctions.get(namespace.getValue(field)).getRuleInfo().build()); |
| } else if (namespace.getValue(field) instanceof StarlarkFunction) { |
| StarlarkFunction userDefinedFunction = (StarlarkFunction) namespace.getValue(field); |
| userDefinedFunctionMap.put(qualifiedFieldName, userDefinedFunction); |
| } else if (namespace.getValue(field) instanceof FakeStructApi) { |
| FakeStructApi innerNamespace = (FakeStructApi) namespace.getValue(field); |
| putStructFields( |
| qualifiedFieldName, innerNamespace, ruleFunctions, ruleInfoMap, userDefinedFunctionMap); |
| } |
| } |
| } |
| |
| /** |
| * Recursively evaluates/interprets the Starlark file at a given path and its transitive Starlark |
| * dependencies using a fake build API and collects information about all rule definitions made in |
| * those files. |
| * |
| * @param label the label of the Starlark file to evaluate |
| * @param ruleInfoList a collection of all rule definitions made so far (using rule()); this |
| * method will add to this list as it evaluates additional files |
| * @throws InterruptedException if evaluation is interrupted |
| */ |
| private Module recursiveEval( |
| StarlarkSemantics semantics, |
| Label label, |
| List<RuleInfoWrapper> ruleInfoList, |
| List<ProviderInfoWrapper> providerInfoList, |
| List<AspectInfoWrapper> aspectInfoList) |
| throws InterruptedException, |
| IOException, |
| LabelSyntaxException, |
| StarlarkEvaluationException, |
| EvalException { |
| Path path = pathOfLabel(label, semantics); |
| |
| if (pending.contains(path)) { |
| throw new StarlarkEvaluationException("cycle with " + path); |
| } else if (loaded.containsKey(path)) { |
| return loaded.get(path); |
| } |
| pending.add(path); |
| |
| // Create an initial environment with a fake build API. Then use Starlark's name resolution |
| // step to further populate the environment with all additional symbols not in the fake build |
| // API but used by the program; these become FakeDeepStructures. |
| ImmutableMap.Builder<String, Object> initialEnvBuilder = ImmutableMap.builder(); |
| FakeApi.addPredeclared(initialEnvBuilder, ruleInfoList, providerInfoList, aspectInfoList); |
| addMorePredeclared(initialEnvBuilder); |
| |
| ImmutableMap<String, Object> initialEnv = initialEnvBuilder.build(); |
| |
| Map<String, Object> predeclaredSymbols = new HashMap<>(); |
| predeclaredSymbols.putAll(initialEnv); |
| |
| Resolver.Module predeclaredResolver = |
| (name) -> { |
| if (predeclaredSymbols.containsKey(name)) { |
| return Scope.PREDECLARED; |
| } |
| if (!Starlark.UNIVERSE.containsKey(name)) { |
| predeclaredSymbols.put(name, FakeDeepStructure.create(name)); |
| return Scope.PREDECLARED; |
| } |
| return Resolver.Scope.UNIVERSAL; |
| }; |
| |
| // parse & compile (and get doc) |
| ParserInput input = getInputSource(path.toString()); |
| Program prog; |
| try { |
| StarlarkFile file = StarlarkFile.parse(input, FileOptions.DEFAULT); |
| prog = Program.compileFile(file, predeclaredResolver); |
| } catch (SyntaxError.Exception ex) { |
| Event.replayEventsOn(eventHandler, ex.errors()); |
| throw new StarlarkEvaluationException(ex.getMessage()); |
| } |
| |
| // process loads |
| Map<String, Module> imports = new HashMap<>(); |
| for (String load : prog.getLoads()) { |
| Label relativeLabel = |
| Label.parseWithPackageContext( |
| load, |
| PackageContext.of(label.getPackageIdentifier(), RepositoryMapping.ALWAYS_FALLBACK)); |
| try { |
| Module loadedModule = |
| recursiveEval(semantics, relativeLabel, ruleInfoList, providerInfoList, aspectInfoList); |
| imports.put(load, loadedModule); |
| } catch (NoSuchFileException noSuchFileException) { |
| throw new StarlarkEvaluationException( |
| String.format( |
| "File %s imported '%s', yet %s was not found, even at roots %s.", |
| path, load, pathOfLabel(relativeLabel, semantics), depRoots), |
| noSuchFileException); |
| } |
| } |
| |
| // execute |
| Module module = Module.withPredeclared(semantics, predeclaredSymbols); |
| try (Mutability mu = Mutability.create("Skydoc")) { |
| StarlarkThread thread = new StarlarkThread(mu, semantics); |
| // We use the default print handler, which writes to stderr. |
| thread.setLoader(imports::get); |
| // Fake Bazel's "export" hack, by which provider symbols |
| // bound to global variables take on the name of the global variable. |
| thread.setPostAssignHook( |
| (name, value) -> { |
| if (value instanceof FakeProviderApi) { |
| ((FakeProviderApi) value).setName(name); |
| } |
| }); |
| |
| Starlark.execFileProgram(prog, module, thread); |
| } catch (EvalException ex) { |
| throw new StarlarkEvaluationException(ex.getMessageWithStack()); |
| } |
| |
| pending.remove(path); |
| loaded.put(path, module); |
| return module; |
| } |
| |
| private Path pathOfLabel(Label label, StarlarkSemantics semantics) throws EvalException { |
| String workspacePrefix = ""; |
| if (!label.getWorkspaceRootForStarlarkOnly(semantics).isEmpty() |
| && !label.getWorkspaceName().equals(workspaceName)) { |
| workspacePrefix = label.getWorkspaceRootForStarlarkOnly(semantics) + "/"; |
| } |
| |
| return Paths.get(workspacePrefix + label.toPathFragment()); |
| } |
| |
| private ParserInput getInputSource(String bzlWorkspacePath) throws IOException { |
| for (String rootPath : depRoots) { |
| if (fileAccessor.fileExists(rootPath + "/" + bzlWorkspacePath)) { |
| return fileAccessor.inputSource(rootPath + "/" + bzlWorkspacePath); |
| } |
| } |
| |
| // All depRoots attempted and no valid file was found. |
| throw new NoSuchFileException(bzlWorkspacePath); |
| } |
| |
| private static void addMorePredeclared(ImmutableMap.Builder<String, Object> env) { |
| // Add dummy declarations that would come from packages.StarlarkLibrary.COMMON |
| // were Skydoc allowed to depend on it. See hack for select below. |
| env.put("json", Json.INSTANCE); |
| env.put("proto", new ProtoModule()); |
| env.put( |
| "depset", |
| new StarlarkCallable() { |
| @Override |
| public Object fastcall(StarlarkThread thread, Object[] positional, Object[] named) { |
| // Accept any arguments, return empty Depset. |
| return Depset.of( |
| Depset.ElementType.EMPTY, NestedSetBuilder.emptySet(Order.STABLE_ORDER)); |
| } |
| |
| @Override |
| public String getName() { |
| return "depset"; |
| } |
| }); |
| |
| // Declare a fake implementation of select that just returns the first |
| // value in the dict. (This program is forbidden from depending on the real |
| // implementation of 'select' in lib.packages, and so the hacks multiply.) |
| env.put( |
| "select", |
| new StarlarkCallable() { |
| @Override |
| public Object fastcall(StarlarkThread thread, Object[] positional, Object[] named) |
| throws EvalException { |
| for (Map.Entry<?, ?> e : ((Dict<?, ?>) positional[0]).entrySet()) { |
| return e.getValue(); |
| } |
| throw Starlark.errorf("select: empty dict"); |
| } |
| |
| @Override |
| public String getName() { |
| return "select"; |
| } |
| }); |
| } |
| |
| @StarlarkBuiltin(name = "ProtoModule", doc = "") |
| private static final class ProtoModule implements StarlarkValue { |
| @StarlarkMethod( |
| name = "encode_text", |
| doc = ".", |
| parameters = {@Param(name = "x")}) |
| public String encodeText(Object x) { |
| return ""; |
| } |
| } |
| |
| /** Exception thrown when Starlark evaluation fails (due to malformed Starlark). */ |
| @VisibleForTesting |
| static class StarlarkEvaluationException extends Exception { |
| public StarlarkEvaluationException(String message) { |
| super(message); |
| } |
| |
| public StarlarkEvaluationException(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| } |