(Skydoc) Handle cross-repository dependencies

RELNOTES: None.
PiperOrigin-RevId: 209029649
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 2a0bf3d..53a87d5 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -230,7 +230,8 @@
   private Environment recursiveEval(
       Label label, List<RuleInfo> ruleInfoList)
       throws InterruptedException, IOException, LabelSyntaxException {
-    Path path = Paths.get(label.toPathFragment().toString());
+    Path path = pathOfLabel(label);
+
     if (pending.contains(path)) {
       throw new IllegalStateException("cycle with " + path);
     } else if (loaded.containsKey(path)) {
@@ -244,7 +245,6 @@
     Map<String, Extension> imports = new HashMap<>();
     for (SkylarkImport anImport : buildFileAST.getImports()) {
       Label relativeLabel = label.getRelative(anImport.getImportString());
-      Path importPath = Paths.get(relativeLabel.toPathFragment().toString());
 
       try {
         Environment importEnv = recursiveEval(relativeLabel, ruleInfoList);
@@ -252,7 +252,7 @@
       } catch (NoSuchFileException noSuchFileException) {
         throw new IllegalStateException(
             String.format("File %s imported '%s', yet %s was not found.",
-                path, anImport.getImportString(), importPath));
+                path, anImport.getImportString(), pathOfLabel(relativeLabel)));
       }
     }
 
@@ -264,6 +264,14 @@
     return env;
   }
 
+  private Path pathOfLabel(Label label) {
+    String workspacePrefix = label.getWorkspaceRoot().isEmpty()
+        ? ""
+        : label.getWorkspaceRoot() + "/";
+
+    return Paths.get(workspacePrefix + label.toPathFragment());
+  }
+
   /**
    * Evaluates the AST from a single skylark file, given the already-resolved imports.
    */
diff --git a/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java b/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
index f9f4d62..91faae8 100644
--- a/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
+++ b/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
@@ -205,6 +205,58 @@
   }
 
   @Test
+  public void testRulesAcrossRepository() throws Exception {
+    scratch.file(
+        "/external/dep_repo/lib/rule_impl.bzl",
+        "def rule_impl(ctx):",
+        "  return struct()");
+
+    scratch.file(
+        "/deps/foo/docstring.bzl",
+        "doc_string = 'Dep rule'");
+
+    scratch.file(
+        "/deps/foo/dep_rule.bzl",
+        "load('@dep_repo//lib:rule_impl.bzl', 'rule_impl')",
+        "load(':docstring.bzl', 'doc_string')",
+        "",
+        "_hidden_rule = rule(",
+        "    doc = doc_string,",
+        "    implementation = rule_impl,",
+        ")",
+        "",
+        "dep_rule = rule(",
+        "    doc = doc_string,",
+        "    implementation = rule_impl,",
+        ")");
+
+    scratch.file(
+        "/test/main.bzl",
+        "load('@dep_repo//lib:rule_impl.bzl', 'rule_impl')",
+        "load('//deps/foo:dep_rule.bzl', 'dep_rule')",
+        "",
+        "main_rule = rule(",
+        "    doc = 'Main rule',",
+        "    implementation = rule_impl,",
+        ")");
+
+    ImmutableMap.Builder<String, RuleInfo> ruleInfoMapBuilder = ImmutableMap.builder();
+
+    skydocMain.eval(
+        Label.parseAbsoluteUnchecked("//test:main.bzl"),
+        ruleInfoMapBuilder,
+        ImmutableList.builder());
+
+    Map<String, RuleInfo> ruleInfoMap = ruleInfoMapBuilder.build();
+
+    // dep_rule is available here, even though it was not defined in main.bzl, because it is
+    // imported in main.bzl. Thus, it's a top-level symbol in main.bzl.
+    assertThat(ruleInfoMap.keySet()).containsExactly("main_rule", "dep_rule");
+    assertThat(ruleInfoMap.get("main_rule").getDocString()).isEqualTo("Main rule");
+    assertThat(ruleInfoMap.get("dep_rule").getDocString()).isEqualTo("Dep rule");
+  }
+
+  @Test
   public void testSkydocCrashesOnCycle() throws Exception {
     scratch.file(
         "/dep/dep.bzl",