Implement imports (via load()) in Skydoc.
Skydoc will generate documentation for all rule definitions in the transitive dependencies of the given input file.
RELNOTES: None.
PiperOrigin-RevId: 202553088
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
index 6a56283..8466f20 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -29,6 +29,7 @@
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkImport;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skydoc.fakebuildapi.FakeActionsInfoProvider;
import com.google.devtools.build.skydoc.fakebuildapi.FakeBuildApiGlobals;
@@ -43,10 +44,11 @@
import com.google.devtools.build.skydoc.rendering.RuleInfo;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
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;
@@ -69,6 +71,13 @@
public class SkydocMain {
private final EventHandler eventHandler = new SystemOutEventHandler();
+ private final LinkedHashSet<Path> pending = new LinkedHashSet<>();
+ private final Map<Path, Environment> loaded = new HashMap<>();
+ private final SkylarkFileAccessor fileAccessor;
+
+ public SkydocMain(SkylarkFileAccessor fileAccessor) {
+ this.fileAccessor = fileAccessor;
+ }
public static void main(String[] args) throws IOException, InterruptedException {
if (args.length != 2) {
@@ -80,15 +89,11 @@
String outputPath = args[1];
Path path = Paths.get(bzlPath);
- byte[] content = Files.readAllBytes(path);
-
- ParserInputSource parserInputSource =
- ParserInputSource.create(content, PathFragment.create(path.toString()));
ImmutableMap.Builder<String, RuleInfo> ruleInfoMap = ImmutableMap.builder();
ImmutableList.Builder<RuleInfo> unexportedRuleInfos = ImmutableList.builder();
- new SkydocMain().eval(parserInputSource, ruleInfoMap, unexportedRuleInfos);
+ new SkydocMain(new FilesystemFileAccessor()).eval(path, ruleInfoMap, unexportedRuleInfos);
try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
printRuleInfos(printWriter, ruleInfoMap.build(), unexportedRuleInfos.build());
@@ -116,10 +121,10 @@
}
/**
- * Evaluates/interprets the skylark file at the given input source using a fake build API and
- * collects information about all rule definitions made in that file.
+ * 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 parserInputSource the input source representing the input skylark file
+ * @param path the path 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'
@@ -128,19 +133,58 @@
* @throws InterruptedException if evaluation is interrupted
*/
// TODO(cparsons): Evaluate load statements recursively.
- public void eval(ParserInputSource parserInputSource,
+ public Environment eval(
+ Path path,
ImmutableMap.Builder<String, RuleInfo> ruleInfoMap,
ImmutableList.Builder<RuleInfo> unexportedRuleInfos)
- throws InterruptedException {
- List<RuleInfo> ruleInfoList = new ArrayList<>();
+ throws InterruptedException, IOException {
+ if (pending.contains(path)) {
+ throw new IllegalStateException("cycle with " + path);
+ } else if (loaded.containsKey(path)) {
+ return loaded.get(path);
+ }
+ pending.add(path);
- BuildFileAST buildFileAST = BuildFileAST.parseSkylarkFile(
- parserInputSource, eventHandler);
+ ParserInputSource parserInputSource = fileAccessor.inputSource(path.toString());
+ BuildFileAST buildFileAST = BuildFileAST.parseSkylarkFile(parserInputSource, eventHandler);
+
+ Map<String, Extension> imports = new HashMap<>();
+ for (SkylarkImport anImport : buildFileAST.getImports()) {
+ Path importPath = fromPathFragment(path, anImport.asPathFragment());
+
+ Environment importEnv = eval(importPath, ruleInfoMap, unexportedRuleInfos);
+
+ imports.put(anImport.getImportString(), new Extension(importEnv));
+ }
+
+ Environment env = evalSkylarkBody(buildFileAST, imports, ruleInfoMap, unexportedRuleInfos);
+
+ pending.remove(path);
+ env.mutability().freeze();
+ loaded.put(path, env);
+ return env;
+ }
+
+ private static Path fromPathFragment(Path fromPath, PathFragment pathFragment) {
+ return pathFragment.isAbsolute()
+ ? Paths.get(pathFragment.getPathString())
+ : fromPath.resolveSibling(pathFragment.getPathString());
+ }
+
+ /**
+ * Evaluates the AST from a single skylark file, given the already-resolved imports.
+ */
+ private Environment evalSkylarkBody(
+ BuildFileAST buildFileAST,
+ Map<String, Extension> imports,
+ ImmutableMap.Builder<String, RuleInfo> ruleInfoMap,
+ ImmutableList.Builder<RuleInfo> unexportedRuleInfos) throws InterruptedException {
+ List<RuleInfo> ruleInfoList = new ArrayList<>();
Environment env = createEnvironment(
eventHandler,
globalFrame(ruleInfoList),
- /* imports= */ ImmutableMap.of());
+ imports);
if (!buildFileAST.exec(env, eventHandler)) {
throw new RuntimeException("Error loading file");
@@ -159,8 +203,9 @@
ruleFunctions.remove(envEntry.getValue());
}
}
-
unexportedRuleInfos.addAll(ruleFunctions.values());
+
+ return env;
}
/**