blob: 1d9043f8b707e4b38e4a3098413a140d986d38c6 [file] [log] [blame]
cparsons5d85e752018-06-26 13:47:28 -07001// Copyright 2018 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.skydoc;
16
cparsonsd790ce42018-06-27 15:29:02 -070017import com.google.common.base.Functions;
18import com.google.common.collect.ImmutableList;
cparsons5d85e752018-06-26 13:47:28 -070019import com.google.common.collect.ImmutableMap;
20import com.google.devtools.build.lib.events.EventHandler;
cparsons5d85e752018-06-26 13:47:28 -070021import com.google.devtools.build.lib.skylarkbuildapi.TopLevelBootstrap;
cparsonsd790ce42018-06-27 15:29:02 -070022import com.google.devtools.build.lib.syntax.BaseFunction;
cparsons5d85e752018-06-26 13:47:28 -070023import com.google.devtools.build.lib.syntax.BuildFileAST;
24import com.google.devtools.build.lib.syntax.Environment;
25import com.google.devtools.build.lib.syntax.Environment.Extension;
26import com.google.devtools.build.lib.syntax.Environment.GlobalFrame;
27import com.google.devtools.build.lib.syntax.MethodLibrary;
28import com.google.devtools.build.lib.syntax.Mutability;
29import com.google.devtools.build.lib.syntax.ParserInputSource;
30import com.google.devtools.build.lib.syntax.Runtime;
31import com.google.devtools.build.lib.vfs.PathFragment;
32import com.google.devtools.build.skydoc.fakebuildapi.FakeActionsInfoProvider;
33import com.google.devtools.build.skydoc.fakebuildapi.FakeBuildApiGlobals;
34import com.google.devtools.build.skydoc.fakebuildapi.FakeDefaultInfoProvider;
35import com.google.devtools.build.skydoc.fakebuildapi.FakeOutputGroupInfo.FakeOutputGroupInfoProvider;
36import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkAttrApi;
37import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkCommandLineApi;
38import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkNativeModuleApi;
39import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkRuleFunctionsApi;
40import com.google.devtools.build.skydoc.fakebuildapi.FakeStructApi.FakeStructProviderApi;
41import com.google.devtools.build.skydoc.rendering.RuleInfo;
42import java.io.IOException;
43import java.io.PrintWriter;
44import java.nio.file.Files;
45import java.nio.file.Path;
46import java.nio.file.Paths;
47import java.util.ArrayList;
48import java.util.List;
49import java.util.Map;
cparsonsd790ce42018-06-27 15:29:02 -070050import java.util.Map.Entry;
51import java.util.stream.Collectors;
cparsons5d85e752018-06-26 13:47:28 -070052
53/**
54 * Main entry point for the Skydoc binary.
55 *
56 * <p>Skydoc generates human-readable documentation for relevant details of skylark files by
57 * running a skylark interpreter with a fake implementation of the build API.</p>
58 *
59 * <p>Currently, Skydoc generates documentation for skylark rule definitions (discovered by
60 * invocations of the build API function {@code rule()}.</p>
61 *
62 * <p>Usage:</p>
63 * <pre>
64 * skydoc {target_skylark_file} {output_file}
65 * </pre>
66 */
67public class SkydocMain {
68
cparsons5d85e752018-06-26 13:47:28 -070069 private final EventHandler eventHandler = new SystemOutEventHandler();
70
71 public static void main(String[] args) throws IOException, InterruptedException {
72 if (args.length != 2) {
73 throw new IllegalArgumentException("Expected two arguments. Usage:\n"
74 + "{skydoc_bin} {target_skylark_file} {output_file}");
75 }
76
77 String bzlPath = args[0];
78 String outputPath = args[1];
79
80 Path path = Paths.get(bzlPath);
81 byte[] content = Files.readAllBytes(path);
82
83 ParserInputSource parserInputSource =
84 ParserInputSource.create(content, PathFragment.create(path.toString()));
85
cparsonsd790ce42018-06-27 15:29:02 -070086 ImmutableMap.Builder<String, RuleInfo> ruleInfoMap = ImmutableMap.builder();
87 ImmutableList.Builder<RuleInfo> unexportedRuleInfos = ImmutableList.builder();
88
89 new SkydocMain().eval(parserInputSource, ruleInfoMap, unexportedRuleInfos);
cparsons5d85e752018-06-26 13:47:28 -070090
91 try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
cparsonsd790ce42018-06-27 15:29:02 -070092 printRuleInfos(printWriter, ruleInfoMap.build(), unexportedRuleInfos.build());
cparsons5d85e752018-06-26 13:47:28 -070093 }
94 }
95
96 // TODO(cparsons): Improve output (markdown or HTML).
97 private static void printRuleInfos(
cparsonsd790ce42018-06-27 15:29:02 -070098 PrintWriter printWriter,
99 Map<String, RuleInfo> ruleInfos,
100 List<RuleInfo> unexportedRuleInfos) throws IOException {
101 for (Entry<String, RuleInfo> ruleInfoEntry : ruleInfos.entrySet()) {
102 printRuleInfo(printWriter, ruleInfoEntry.getKey(), ruleInfoEntry.getValue());
cparsons5d85e752018-06-26 13:47:28 -0700103 }
cparsonsd790ce42018-06-27 15:29:02 -0700104 for (RuleInfo unexportedRuleInfo : unexportedRuleInfos) {
105 printRuleInfo(printWriter, "<unknown name>", unexportedRuleInfo);
106 }
107 }
108
109 private static void printRuleInfo(
110 PrintWriter printWriter, String exportedName, RuleInfo ruleInfo) {
111 printWriter.println(exportedName);
112 printWriter.println(ruleInfo.getDescription());
113 printWriter.println();
cparsons5d85e752018-06-26 13:47:28 -0700114 }
115
116 /**
117 * Evaluates/interprets the skylark file at the given input source using a fake build API and
118 * collects information about all rule definitions made in that file.
119 *
120 * @param parserInputSource the input source representing the input skylark file
cparsonsd790ce42018-06-27 15:29:02 -0700121 * @param ruleInfoMap a map builder to be populated with rule definition information for
122 * named rules. Keys are exported names of rules, and values are their {@link RuleInfo}
123 * rule descriptions. For example, 'my_rule = rule(...)' has key 'my_rule'
124 * @param unexportedRuleInfos a list builder to be populated with rule definition information
125 * for rules which were not exported as top level symbols
cparsons5d85e752018-06-26 13:47:28 -0700126 * @throws InterruptedException if evaluation is interrupted
127 */
128 // TODO(cparsons): Evaluate load statements recursively.
cparsonsd790ce42018-06-27 15:29:02 -0700129 public void eval(ParserInputSource parserInputSource,
130 ImmutableMap.Builder<String, RuleInfo> ruleInfoMap,
131 ImmutableList.Builder<RuleInfo> unexportedRuleInfos)
cparsons5d85e752018-06-26 13:47:28 -0700132 throws InterruptedException {
133 List<RuleInfo> ruleInfoList = new ArrayList<>();
134
135 BuildFileAST buildFileAST = BuildFileAST.parseSkylarkFile(
136 parserInputSource, eventHandler);
137
138 Environment env = createEnvironment(
139 eventHandler,
140 globalFrame(ruleInfoList),
141 /* imports= */ ImmutableMap.of());
142
143 if (!buildFileAST.exec(env, eventHandler)) {
144 throw new RuntimeException("Error loading file");
145 }
146
147 env.mutability().freeze();
148
cparsonsd790ce42018-06-27 15:29:02 -0700149 Map<BaseFunction, RuleInfo> ruleFunctions = ruleInfoList.stream()
150 .collect(Collectors.toMap(
151 RuleInfo::getIdentifierFunction,
152 Functions.identity()));
153
154 for (Entry<String, Object> envEntry : env.getGlobals().getBindings().entrySet()) {
155 if (ruleFunctions.containsKey(envEntry.getValue())) {
156 ruleInfoMap.put(envEntry.getKey(), ruleFunctions.get(envEntry.getValue()));
157 ruleFunctions.remove(envEntry.getValue());
158 }
159 }
160
161 unexportedRuleInfos.addAll(ruleFunctions.values());
cparsons5d85e752018-06-26 13:47:28 -0700162 }
163
164 /**
165 * Initialize and return a global frame containing the fake build API.
166 *
167 * @param ruleInfoList the list of {@link RuleInfo} objects, to which rule() invocation
168 * information will be added
169 */
170 private static GlobalFrame globalFrame(List<RuleInfo> ruleInfoList) {
171 // TODO(cparsons): Complete the Fake Build API stubs. For example, implement provider(),
172 // and include the other bootstraps.
173 TopLevelBootstrap topLevelBootstrap =
174 new TopLevelBootstrap(new FakeBuildApiGlobals(),
175 new FakeSkylarkAttrApi(),
176 new FakeSkylarkCommandLineApi(),
177 new FakeSkylarkNativeModuleApi(),
178 new FakeSkylarkRuleFunctionsApi(ruleInfoList),
179 new FakeStructProviderApi(),
180 new FakeOutputGroupInfoProvider(),
181 new FakeActionsInfoProvider(),
182 new FakeDefaultInfoProvider());
183
184 ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder();
185
186 Runtime.addConstantsToBuilder(envBuilder);
187 MethodLibrary.addBindingsToBuilder(envBuilder);
188 topLevelBootstrap.addBindingsToBuilder(envBuilder);
189
190 return GlobalFrame.createForBuiltins(envBuilder.build());
191 }
192
193 private static Environment createEnvironment(EventHandler eventHandler, GlobalFrame globals,
194 Map<String, Extension> imports) {
195 return Environment.builder(Mutability.create("Skydoc"))
196 .useDefaultSemantics()
197 .setGlobals(globals)
198 .setImportedExtensions(imports)
199 .setEventHandler(eventHandler)
200 .build();
201 }
202}