| // 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 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.LabelSyntaxException; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions; |
| import com.google.devtools.build.lib.skylarkbuildapi.TopLevelBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidAssetsInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidBinaryDataInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidCcLinkParamsProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidDex2OatInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidFeatureFlagSetProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidIdeInfoProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidIdlProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidLibraryAarInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidLibraryResourceClassJarProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidManifestInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidPreDexJarProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidProguardInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidSdkProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.ProguardMappingProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.android.UsesDataBindingProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.apple.AppleBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.config.ConfigBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.cpp.CcBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.java.GeneratedExtensionRegistryProviderApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.java.JavaBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.javascript.JsModuleInfoApi; |
| import com.google.devtools.build.lib.skylarkbuildapi.platform.PlatformBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.proto.ProtoBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.python.PyBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap; |
| import com.google.devtools.build.lib.skylarkbuildapi.stubs.ProviderStub; |
| import com.google.devtools.build.lib.skylarkbuildapi.stubs.SkylarkAspectStub; |
| import com.google.devtools.build.lib.skylarkbuildapi.test.TestingBootstrap; |
| import com.google.devtools.build.lib.syntax.BaseFunction; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.EvalUtils; |
| import com.google.devtools.build.lib.syntax.LoadStatement; |
| import com.google.devtools.build.lib.syntax.Module; |
| import com.google.devtools.build.lib.syntax.Mutability; |
| import com.google.devtools.build.lib.syntax.ParserInput; |
| import com.google.devtools.build.lib.syntax.Starlark; |
| import com.google.devtools.build.lib.syntax.StarlarkFile; |
| import com.google.devtools.build.lib.syntax.StarlarkFunction; |
| import com.google.devtools.build.lib.syntax.StarlarkSemantics; |
| import com.google.devtools.build.lib.syntax.StarlarkThread; |
| import com.google.devtools.build.lib.syntax.StarlarkThread.Extension; |
| import com.google.devtools.build.lib.syntax.Statement; |
| import com.google.devtools.build.lib.syntax.StringLiteral; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeActionsInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeBuildApiGlobals; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeConfigApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeDefaultInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeOutputGroupInfo.FakeOutputGroupInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkAttrApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkCommandLineApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkNativeModuleApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkRuleFunctionsApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeStructApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.FakeStructApi.FakeStructProviderApi; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidApplicationResourceInfo.FakeAndroidApplicationResourceInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidDeviceBrokerInfo.FakeAndroidDeviceBrokerInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidInstrumentationInfo.FakeAndroidInstrumentationInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidNativeLibsInfo.FakeAndroidNativeLibsInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidResourcesInfo.FakeAndroidResourcesInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeAndroidSkylarkCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.android.FakeApkInfo.FakeApkInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.apple.FakeAppleCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.config.FakeConfigGlobalLibrary; |
| import com.google.devtools.build.skydoc.fakebuildapi.config.FakeConfigSkylarkCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakeCcInfo; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakeCcModule; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakeCcToolchainConfigInfo; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakeGoWrapCcHelper; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakePyCcLinkParamsProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakePyWrapCcHelper; |
| import com.google.devtools.build.skydoc.fakebuildapi.cpp.FakePyWrapCcInfo; |
| import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaCcLinkParamsProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaInfo.FakeJavaInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaProtoCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.platform.FakePlatformCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.proto.FakeProtoInfoApiProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.proto.FakeProtoModule; |
| import com.google.devtools.build.skydoc.fakebuildapi.python.FakePyInfo.FakePyInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.python.FakePyRuntimeInfo.FakePyRuntimeInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.python.FakePyStarlarkTransitions; |
| import com.google.devtools.build.skydoc.fakebuildapi.repository.FakeRepositoryModule; |
| import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisFailureInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisTestResultInfoProvider; |
| import com.google.devtools.build.skydoc.fakebuildapi.test.FakeCoverageCommon; |
| import com.google.devtools.build.skydoc.fakebuildapi.test.FakeTestingModule; |
| 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 com.google.devtools.skylark.common.DocstringUtils; |
| 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; |
| |
| /** |
| * Main entry point for the Skydoc binary. |
| * |
| * <p>Skydoc generates human-readable documentation for relevant details of skylark files by running |
| * a skylark interpreter with a fake implementation of the build API. |
| * |
| * <p>Currently, Skydoc generates documentation for skylark rule definitions (discovered by |
| * invocations of the build API function {@code rule()}. |
| * |
| * <p>Usage: |
| * |
| * <pre> |
| * skydoc {target_skylark_file_label} {output_file} [symbol_name]... |
| * </pre> |
| * |
| * <p>Generates documentation for all exported symbols of the target skylark 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 skylark file. |
| */ |
| public class SkydocMain { |
| |
| private final EventHandler eventHandler = new SystemOutEventHandler(); |
| private final LinkedHashSet<Path> pending = new LinkedHashSet<>(); |
| private final Map<Path, StarlarkThread> loaded = new HashMap<>(); |
| private final SkylarkFileAccessor fileAccessor; |
| private final List<String> depRoots; |
| private final String workspaceName; |
| |
| public SkydocMain(SkylarkFileAccessor 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, EvalException, |
| DocstringParseException { |
| OptionsParser parser = |
| OptionsParser.builder() |
| .optionsClasses(StarlarkSemanticsOptions.class, SkydocOptions.class) |
| .build(); |
| parser.parseAndExitUponError(args); |
| StarlarkSemanticsOptions semanticsOptions = parser.getOptions(StarlarkSemanticsOptions.class); |
| semanticsOptions.incompatibleDepsetUnion = false; |
| semanticsOptions.incompatibleDisableDeprecatedAttrParams = false; |
| semanticsOptions.incompatibleNewActionsApi = false; |
| SkydocOptions skydocOptions = parser.getOptions(SkydocOptions.class); |
| |
| String targetFileLabelString; |
| String outputPath; |
| ImmutableSet<String> symbolNames; |
| 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; |
| symbolNames = ImmutableSet.copyOf(skydocOptions.symbolNames); |
| depRoots = ImmutableList.copyOf(skydocOptions.depRoots); |
| |
| Label targetFileLabel = Label.parseAbsolute(targetFileLabelString, ImmutableMap.of()); |
| |
| 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(); |
| ImmutableMap.Builder<Label, String> moduleDocMap = ImmutableMap.builder(); |
| |
| try { |
| new SkydocMain(new FilesystemFileAccessor(), skydocOptions.workspaceName, depRoots) |
| .eval( |
| semanticsOptions.toSkylarkSemantics(), |
| targetFileLabel, |
| ruleInfoMap, |
| providerInfoMap, |
| userDefinedFunctions, |
| aspectInfoMap, |
| moduleDocMap); |
| } catch (StarlarkEvaluationException exception) { |
| System.err.println("Stardoc documentation generation failed: " + exception.getMessage()); |
| System.exit(1); |
| } |
| |
| Map<String, RuleInfo> filteredRuleInfos = |
| ruleInfoMap.build().entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue)); |
| Map<String, ProviderInfo> filteredProviderInfos = |
| providerInfoMap.build().entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue)); |
| Map<String, StarlarkFunction> filteredStarlarkFunctions = |
| userDefinedFunctions.build().entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue)); |
| Map<String, AspectInfo> filteredAspectInfos = |
| aspectInfoMap.build().entrySet().stream() |
| .filter(entry -> validSymbolName(symbolNames, entry.getKey())) |
| .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue)); |
| |
| try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputPath))) { |
| new ProtoRenderer() |
| .appendRuleInfos(filteredRuleInfos.values()) |
| .appendProviderInfos(filteredProviderInfos.values()) |
| .appendStarlarkFunctionInfos(filteredStarlarkFunctions) |
| .appendAspectInfos(filteredAspectInfos.values()) |
| .setModuleDocstring(moduleDocMap.build().get(targetFileLabel)) |
| .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; |
| } |
| |
| /** |
| * Evaluates/interprets the skylark file at a given path and its transitive skylark dependencies |
| * using a fake build API and collects information about all rule definitions made in the root |
| * skylark file. |
| * |
| * @param label the label of the skylark 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' |
| * @param moduleDocMap a map builder to be populated with module docstrings for skylark file. Keys |
| * are labels of skylark files and values are their module docstrings. If the module has no |
| * docstring, an empty string will be printed. |
| * @throws InterruptedException if evaluation is interrupted |
| */ |
| public StarlarkThread 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, |
| ImmutableMap.Builder<Label, String> moduleDocMap) |
| throws InterruptedException, IOException, LabelSyntaxException, EvalException, |
| StarlarkEvaluationException { |
| |
| List<RuleInfoWrapper> ruleInfoList = new ArrayList<>(); |
| |
| List<ProviderInfoWrapper> providerInfoList = new ArrayList<>(); |
| |
| List<AspectInfoWrapper> aspectInfoList = new ArrayList<>(); |
| |
| StarlarkThread thread = |
| recursiveEval( |
| semantics, label, ruleInfoList, providerInfoList, aspectInfoList, moduleDocMap); |
| |
| Map<BaseFunction, RuleInfoWrapper> ruleFunctions = |
| ruleInfoList.stream() |
| .collect( |
| Collectors.toMap(RuleInfoWrapper::getIdentifierFunction, Functions.identity())); |
| |
| Map<BaseFunction, ProviderInfoWrapper> providerInfos = |
| providerInfoList.stream() |
| .collect(Collectors.toMap(ProviderInfoWrapper::getIdentifier, Functions.identity())); |
| |
| Map<BaseFunction, AspectInfoWrapper> aspectFunctions = |
| aspectInfoList.stream() |
| .collect( |
| Collectors.toMap(AspectInfoWrapper::getIdentifierFunction, Functions.identity())); |
| |
| // Sort the bindings so their ordering is deterministic. |
| TreeMap<String, Object> sortedBindings = |
| new TreeMap<>(thread.getGlobals().getExportedBindings()); |
| |
| for (Entry<String, Object> envEntry : sortedBindings.entrySet()) { |
| if (ruleFunctions.containsKey(envEntry.getValue())) { |
| RuleInfo.Builder ruleInfoBuild = ruleFunctions.get(envEntry.getValue()).getRuleInfo(); |
| RuleInfo ruleInfo = ruleInfoBuild.setRuleName(envEntry.getKey()).build(); |
| ruleInfoMap.put(envEntry.getKey(), 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, 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 thread; |
| } |
| |
| /** |
| * 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, |
| ImmutableMap.Builder<String, StarlarkFunction> userDefinedFunctionMap) |
| throws EvalException { |
| for (String field : namespace.getFieldNames()) { |
| String qualifiedFieldName = namespaceName + "." + field; |
| 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, userDefinedFunctionMap); |
| } |
| } |
| } |
| |
| private static String getModuleDoc(StarlarkFile buildFileAST) { |
| ImmutableList<Statement> fileStatements = buildFileAST.getStatements(); |
| if (!fileStatements.isEmpty()) { |
| Statement moduleComment = fileStatements.get(0); |
| StringLiteral moduleDocLiteral = DocstringUtils.getStringLiteral(moduleComment); |
| if (moduleDocLiteral != null) { |
| return moduleDocLiteral.getValue(); |
| } |
| } |
| return ""; |
| } |
| |
| /** |
| * Recursively evaluates/interprets the skylark file at a given path and its transitive skylark |
| * dependencies using a fake build API and collects information about all rule definitions made in |
| * those files. |
| * |
| * @param label the label of the skylark 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 StarlarkThread recursiveEval( |
| StarlarkSemantics semantics, |
| Label label, |
| List<RuleInfoWrapper> ruleInfoList, |
| List<ProviderInfoWrapper> providerInfoList, |
| List<AspectInfoWrapper> aspectInfoList, |
| ImmutableMap.Builder<Label, String> moduleDocMap) |
| throws InterruptedException, IOException, LabelSyntaxException, StarlarkEvaluationException { |
| Path path = pathOfLabel(label); |
| |
| if (pending.contains(path)) { |
| throw new StarlarkEvaluationException("cycle with " + path); |
| } else if (loaded.containsKey(path)) { |
| return loaded.get(path); |
| } |
| pending.add(path); |
| |
| ParserInput parserInputSource = getInputSource(path.toString()); |
| StarlarkFile file = StarlarkFile.parse(parserInputSource); |
| Event.replayEventsOn(eventHandler, file.errors()); |
| |
| moduleDocMap.put(label, getModuleDoc(file)); |
| |
| Map<String, Extension> imports = new HashMap<>(); |
| for (Statement stmt : file.getStatements()) { |
| if (stmt instanceof LoadStatement) { |
| LoadStatement load = (LoadStatement) stmt; |
| String module = load.getImport().getValue(); |
| Label relativeLabel = label.getRelativeWithRemapping(module, ImmutableMap.of()); |
| try { |
| StarlarkThread importThread = |
| recursiveEval( |
| semantics, |
| relativeLabel, |
| ruleInfoList, |
| providerInfoList, |
| aspectInfoList, |
| moduleDocMap); |
| imports.put(module, new Extension(importThread)); |
| } catch (NoSuchFileException noSuchFileException) { |
| throw new StarlarkEvaluationException( |
| String.format( |
| "File %s imported '%s', yet %s was not found, even at roots %s.", |
| path, module, pathOfLabel(relativeLabel), depRoots)); |
| } |
| } |
| } |
| |
| StarlarkThread thread = |
| evalSkylarkBody(semantics, file, imports, ruleInfoList, providerInfoList, aspectInfoList); |
| |
| pending.remove(path); |
| thread.mutability().freeze(); |
| loaded.put(path, thread); |
| return thread; |
| } |
| |
| private Path pathOfLabel(Label label) { |
| String workspacePrefix = ""; |
| if (!label.getWorkspaceRoot().isEmpty() && !label.getWorkspaceName().equals(workspaceName)) { |
| workspacePrefix = label.getWorkspaceRoot() + "/"; |
| } |
| |
| 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); |
| } |
| |
| /** Evaluates the AST from a single skylark file, given the already-resolved imports. */ |
| private StarlarkThread evalSkylarkBody( |
| StarlarkSemantics semantics, |
| StarlarkFile file, |
| Map<String, Extension> imports, |
| List<RuleInfoWrapper> ruleInfoList, |
| List<ProviderInfoWrapper> providerInfoList, |
| List<AspectInfoWrapper> aspectInfoList) |
| throws InterruptedException, StarlarkEvaluationException { |
| |
| StarlarkThread thread = |
| createStarlarkThread( |
| semantics, |
| eventHandler, |
| globalFrame(ruleInfoList, providerInfoList, aspectInfoList), |
| imports); |
| |
| try { |
| EvalUtils.exec(file, thread); |
| } catch (EvalException | InterruptedException ex) { |
| // This exception class seems a bit unnecessary. Replace with EvalException? |
| throw new StarlarkEvaluationException("Starlark evaluation error"); |
| } |
| |
| thread.mutability().freeze(); |
| |
| return thread; |
| } |
| |
| /** |
| * Initialize and return a global frame containing the fake build API. |
| * |
| * @param ruleInfoList the list of {@link RuleInfo} objects, to which rule() invocation |
| * information will be added |
| * @param providerInfoList the list of {@link ProviderInfo} objects, to which provider() |
| * invocation information will be added |
| */ |
| private static Module globalFrame( |
| List<RuleInfoWrapper> ruleInfoList, |
| List<ProviderInfoWrapper> providerInfoList, |
| List<AspectInfoWrapper> aspectInfoList) { |
| TopLevelBootstrap topLevelBootstrap = |
| new TopLevelBootstrap( |
| new FakeBuildApiGlobals(), |
| new FakeSkylarkAttrApi(), |
| new FakeSkylarkCommandLineApi(), |
| new FakeSkylarkNativeModuleApi(), |
| new FakeSkylarkRuleFunctionsApi(ruleInfoList, providerInfoList, aspectInfoList), |
| new FakeStructProviderApi(), |
| new FakeOutputGroupInfoProvider(), |
| new FakeActionsInfoProvider(), |
| new FakeDefaultInfoProvider()); |
| AndroidBootstrap androidBootstrap = |
| new AndroidBootstrap( |
| new FakeAndroidSkylarkCommon(), |
| new FakeApkInfoProvider(), |
| new FakeAndroidInstrumentationInfoProvider(), |
| new FakeAndroidDeviceBrokerInfoProvider(), |
| new FakeAndroidResourcesInfoProvider(), |
| new FakeAndroidNativeLibsInfoProvider(), |
| new FakeAndroidApplicationResourceInfoProvider()); |
| AppleBootstrap appleBootstrap = new AppleBootstrap(new FakeAppleCommon()); |
| ConfigBootstrap configBootstrap = |
| new ConfigBootstrap( |
| new FakeConfigSkylarkCommon(), new FakeConfigApi(), new FakeConfigGlobalLibrary()); |
| CcBootstrap ccBootstrap = |
| new CcBootstrap( |
| new FakeCcModule(), |
| new FakeCcInfo.Provider(), |
| new FakeCcToolchainConfigInfo.Provider(), |
| new FakePyWrapCcHelper(), |
| new FakeGoWrapCcHelper(), |
| new FakePyWrapCcInfo.Provider(), |
| new FakePyCcLinkParamsProvider.Provider()); |
| JavaBootstrap javaBootstrap = |
| new JavaBootstrap( |
| new FakeJavaCommon(), |
| new FakeJavaInfoProvider(), |
| new FakeJavaProtoCommon(), |
| new FakeJavaCcLinkParamsProvider.Provider()); |
| PlatformBootstrap platformBootstrap = new PlatformBootstrap(new FakePlatformCommon()); |
| ProtoBootstrap protoBootstrap = |
| new ProtoBootstrap( |
| new FakeProtoInfoApiProvider(), |
| new FakeProtoModule(), |
| new SkylarkAspectStub(), |
| new ProviderStub()); |
| PyBootstrap pyBootstrap = |
| new PyBootstrap( |
| new FakePyInfoProvider(), |
| new FakePyRuntimeInfoProvider(), |
| new FakePyStarlarkTransitions()); |
| RepositoryBootstrap repositoryBootstrap = |
| new RepositoryBootstrap(new FakeRepositoryModule(ruleInfoList)); |
| TestingBootstrap testingBootstrap = |
| new TestingBootstrap( |
| new FakeTestingModule(), |
| new FakeCoverageCommon(), |
| new FakeAnalysisFailureInfoProvider(), |
| new FakeAnalysisTestResultInfoProvider()); |
| |
| ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder(); |
| |
| envBuilder.putAll(Starlark.UNIVERSE); |
| topLevelBootstrap.addBindingsToBuilder(envBuilder); |
| androidBootstrap.addBindingsToBuilder(envBuilder); |
| appleBootstrap.addBindingsToBuilder(envBuilder); |
| ccBootstrap.addBindingsToBuilder(envBuilder); |
| configBootstrap.addBindingsToBuilder(envBuilder); |
| javaBootstrap.addBindingsToBuilder(envBuilder); |
| platformBootstrap.addBindingsToBuilder(envBuilder); |
| protoBootstrap.addBindingsToBuilder(envBuilder); |
| pyBootstrap.addBindingsToBuilder(envBuilder); |
| repositoryBootstrap.addBindingsToBuilder(envBuilder); |
| testingBootstrap.addBindingsToBuilder(envBuilder); |
| addNonBootstrapGlobals(envBuilder); |
| |
| return Module.createForBuiltins(envBuilder.build()); |
| } |
| |
| // TODO(cparsons): Remove this constant by migrating the contained symbols to bootstraps. |
| private static final String[] nonBootstrapGlobals = { |
| "android_data", |
| AndroidDex2OatInfoApi.NAME, |
| AndroidManifestInfoApi.NAME, |
| AndroidAssetsInfoApi.NAME, |
| AndroidLibraryAarInfoApi.NAME, |
| AndroidProguardInfoApi.NAME, |
| AndroidIdlProviderApi.NAME, |
| AndroidIdeInfoProviderApi.NAME, |
| AndroidPreDexJarProviderApi.NAME, |
| UsesDataBindingProviderApi.NAME, |
| AndroidCcLinkParamsProviderApi.NAME, |
| AndroidLibraryResourceClassJarProviderApi.NAME, |
| AndroidSdkProviderApi.NAME, |
| AndroidFeatureFlagSetProviderApi.NAME, |
| ProguardMappingProviderApi.NAME, |
| GeneratedExtensionRegistryProviderApi.NAME, |
| AndroidBinaryDataInfoApi.NAME, |
| JsModuleInfoApi.NAME, |
| "JsInfo", |
| "PintoModuleProvider" |
| }; |
| |
| /** |
| * A hack to add a number of global symbols which are part of the build API but are otherwise |
| * added by Bazel. |
| */ |
| // TODO(cparsons): Remove this method by migrating the contained symbols to bootstraps. |
| private static void addNonBootstrapGlobals(ImmutableMap.Builder<String, Object> envBuilder) { |
| for (String global : nonBootstrapGlobals) { |
| envBuilder.put(global, global); |
| } |
| } |
| |
| private static StarlarkThread createStarlarkThread( |
| StarlarkSemantics semantics, |
| EventHandler eventHandler, |
| Module globals, |
| Map<String, Extension> imports) { |
| return StarlarkThread.builder(Mutability.create("Skydoc")) |
| .setSemantics(semantics) |
| .setGlobals(globals) |
| .setImportedExtensions(imports) |
| .setEventHandler(eventHandler) |
| .build(); |
| } |
| |
| /** Exception thrown when Starlark evaluation fails (due to malformed Starlark). */ |
| @VisibleForTesting |
| static class StarlarkEvaluationException extends Exception { |
| public StarlarkEvaluationException(String message) { |
| super(message); |
| } |
| } |
| } |