Skylark IDE info aspect: sources and dependencies.

1. Refactored IntelliJSkylarkAspectTest. Eventually that test will be
merged with AndroidStudioInfoAspect test to validate implementation
equivalence.

2. Exposed ``root`` and ``is_source`` on Artifacts to Skylark.

3. Skylark aspect implementation outputs sources and dependencies
information.

--
MOS_MIGRATED_REVID=112473407
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index 5a98552..d4de3db 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -271,6 +271,9 @@
    * package-path entries (for source Artifacts), or one of the bin, genfiles or includes dirs
    * (for derived Artifacts). It will always be an ancestor of getPath().
    */
+  @SkylarkCallable(name = "root", structField = true,
+      doc = "The root beneath which this file resides."
+  )
   public final Root getRoot() {
     return root;
   }
@@ -289,6 +292,8 @@
    * root relationships. Note that this will report all Artifacts in the output
    * tree, including in the include symlink tree, as non-source.
    */
+  @SkylarkCallable(name = "is_source", structField =  true,
+      doc = "Returns true if this is a source file, i.e. it is not generated")
   public final boolean isSourceArtifact() {
     return execPath == rootRelativePath;
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index 81fabbb..cd97821 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -337,6 +337,27 @@
     assertThat(constructed).isEqualTo("aaa/bbb/ccc/ddd");
   }
 
+  @Test
+  public void testIsSourceArtifact() throws Exception {
+    assertThat(
+        new Artifact(scratch.file("/src/foo.cc"), Root.asSourceRoot(scratch.dir("/")),
+            new PathFragment("src/foo.cc"))
+            .isSourceArtifact())
+        .isTrue();
+    assertThat(
+        new Artifact(scratch.file("/genfiles/aaa/bar.out"),
+            Root.asDerivedRoot(scratch.dir("/genfiles"), scratch.dir("/genfiles/aaa")))
+            .isSourceArtifact())
+        .isFalse();
+
+  }
+
+  @Test
+  public void testGetRoot() throws Exception {
+    Root root = Root.asDerivedRoot(scratch.dir("/newRoot"));
+    assertThat(new Artifact(scratch.file("/newRoot/foo"), root).getRoot()).isEqualTo(root);
+  }
+
   private Artifact createDirNameArtifact() throws Exception {
     return new Artifact(scratch.file("/aaa/bbb/ccc/ddd"), Root.asDerivedRoot(scratch.dir("/")));
   }
diff --git a/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTestBase.java b/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTestBase.java
index 6c32134..f91eec0 100644
--- a/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTestBase.java
+++ b/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTestBase.java
@@ -81,8 +81,7 @@
         }
       };
 
-  private AnalysisResult analysisResult;
-  private ConfiguredAspect configuredAspect;
+  protected ConfiguredAspect configuredAspect;
 
   /**
    * Constructs a string that matches OutputJar#toString for comparison testing.
@@ -114,7 +113,7 @@
   }
 
   protected void buildTarget(String target) throws Exception {
-    this.analysisResult =
+    AnalysisResult analysisResult =
         update(
             ImmutableList.of(target),
             ImmutableList.of(AndroidStudioInfoAspect.NAME),
diff --git a/src/test/java/com/google/devtools/build/lib/ideinfo/IntelliJSkylarkAspectTest.java b/src/test/java/com/google/devtools/build/lib/ideinfo/IntelliJSkylarkAspectTest.java
index 7efbb63..fc965e0 100644
--- a/src/test/java/com/google/devtools/build/lib/ideinfo/IntelliJSkylarkAspectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/ideinfo/IntelliJSkylarkAspectTest.java
@@ -23,7 +23,6 @@
 import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
 import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
-import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo.RuleIdeInfo;
 import com.google.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo.RuleIdeInfo.Builder;
@@ -42,12 +41,14 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Tests for Skylark implementation of Android Studio info aspect
  */
 @RunWith(JUnit4.class)
-public class IntelliJSkylarkAspectTest extends BuildViewTestCase {
+public class IntelliJSkylarkAspectTest extends AndroidStudioInfoAspectTestBase {
   @Before
   public void setupBzl() throws Exception {
     InputStream stream = IntelliJSkylarkAspectTest.class
@@ -73,8 +74,53 @@
         "    name = 'simple',",
         "    srcs = ['simple/Simple.java']",
         ")");
+    String target = "//com/google/example:simple";
+    Map<String, RuleIdeInfo> ruleIdeInfos = buildRuleIdeInfo(target);
+
+    RuleIdeInfo ruleIdeInfo =
+        getRuleInfoAndVerifyLabel("//com/google/example:simple", ruleIdeInfos);
+    assertThat(ruleIdeInfo.getKind()).isEqualTo(Kind.JAVA_LIBRARY);
+    assertThat(ruleIdeInfo.getDependenciesCount()).isEqualTo(0);
+    assertThat(relativePathsForSourcesOf(ruleIdeInfo))
+        .containsExactly("com/google/example/simple/Simple.java");
+  }
+
+  @Test
+  public void testJavaLibraryWithTransitiveDependencies() throws Exception {
+    scratch.file(
+        "com/google/example/BUILD",
+        "java_library(",
+        "    name = 'simple',",
+        "    srcs = ['simple/Simple.java']",
+        ")",
+        "java_library(",
+        "    name = 'complex',",
+        "    srcs = ['complex/Complex.java'],",
+        "    deps = [':simple']",
+        ")",
+        "java_library(",
+        "    name = 'extracomplex',",
+        "    srcs = ['extracomplex/ExtraComplex.java'],",
+        "    deps = [':complex']",
+        ")");
+    Map<String, RuleIdeInfo> ruleIdeInfos = buildRuleIdeInfo("//com/google/example:extracomplex");
+    assertThat(ruleIdeInfos.size()).isEqualTo(3);
+
+    getRuleInfoAndVerifyLabel("//com/google/example:simple", ruleIdeInfos);
+    getRuleInfoAndVerifyLabel("//com/google/example:complex", ruleIdeInfos);
+
+    RuleIdeInfo extraComplexRuleIdeInfo = getRuleInfoAndVerifyLabel(
+        "//com/google/example:extracomplex", ruleIdeInfos);
+
+    assertThat(relativePathsForSourcesOf(extraComplexRuleIdeInfo))
+        .containsExactly("com/google/example/extracomplex/ExtraComplex.java");
+    assertThat(extraComplexRuleIdeInfo.getDependenciesList())
+        .containsExactly("//com/google/example:complex");
+  }
+
+  protected Map<String, RuleIdeInfo> buildRuleIdeInfo(String target) throws Exception {
     AnalysisResult analysisResult = update(
-        ImmutableList.of("//com/google/example:simple"),
+        ImmutableList.of(target),
         ImmutableList.of("intellij_tools/intellij_info.bzl%intellij_info_aspect"),
         false,
         LOADING_PHASE_THREADS,
@@ -84,20 +130,19 @@
     Collection<AspectValue> aspects = analysisResult.getAspects();
     assertThat(aspects).hasSize(1);
     AspectValue aspectValue = aspects.iterator().next();
-    OutputGroupProvider provider = aspectValue.getConfiguredAspect()
-        .getProvider(OutputGroupProvider.class);
+    this.configuredAspect = aspectValue.getConfiguredAspect();
+    OutputGroupProvider provider = configuredAspect.getProvider(OutputGroupProvider.class);
     NestedSet<Artifact> outputGroup = provider.getOutputGroup("ide-info-text");
-    assertThat(outputGroup.toList()).hasSize(1);
+    Map<String, RuleIdeInfo> ruleIdeInfos = new HashMap<>();
     for (Artifact artifact : outputGroup) {
       Action generatingAction = getGeneratingAction(artifact);
       assertThat(generatingAction).isInstanceOf(FileWriteAction.class);
       String fileContents = ((FileWriteAction) generatingAction).getFileContents();
       Builder builder = RuleIdeInfo.newBuilder();
       TextFormat.getParser().merge(fileContents, builder);
-      RuleIdeInfo build = builder.build();
-      assertThat(build.getLabel()).isEqualTo("//com/google/example:simple");
-      assertThat(build.getKind()).isEqualTo(Kind.JAVA_LIBRARY);
+      RuleIdeInfo ruleIdeInfo = builder.build();
+      ruleIdeInfos.put(ruleIdeInfo.getLabel(), ruleIdeInfo);
     }
-
+    return ruleIdeInfos;
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/ideinfo/intellij_info.bzl b/src/test/java/com/google/devtools/build/lib/ideinfo/intellij_info.bzl
index 251a0a2..feb37fe 100644
--- a/src/test/java/com/google/devtools/build/lib/ideinfo/intellij_info.bzl
+++ b/src/test/java/com/google/devtools/build/lib/ideinfo/intellij_info.bzl
@@ -28,23 +28,72 @@
 
 _unrecognized_rule = -1;
 
+DEPENDENCY_ATTRIBUTES = [
+  "deps",
+  "exports",
+  "_robolectric", # From android_robolectric_test
+  "_junit", # From android_robolectric_test
+  "binary_under_test", #  From android_test
+  "java_lib",# From proto_library
+  "_proto1_java_lib", # From proto_library
+]
+
 def get_kind(target, ctx):
   return _kind_to_kind_id.get(ctx.rule.kind, _unrecognized_rule)
 
+def is_java_rule(target, ctx):
+  return ctx.rule.kind != "android_sdk";
+
+def artifact_location(file):
+  return struct(
+      root_path = file.root.path, # todo(dslomov): this gives path relative to execution root
+      relative_path = file.short_path,
+      is_source = file.is_source,
+  )
+
+def java_rule_ide_info(target, ctx):
+   if hasattr(ctx.rule.attr, "srcs"):
+      sources = [artifact_location(file)
+                 for src in ctx.rule.attr.srcs
+                 for file in src.files]
+   else:
+      sources = []
+   return struct(sources = sources) # todo(dslomov): more fields
+
+
 def _aspect_impl(target, ctx):
-  ide_info_text = set()
   kind = get_kind(target, ctx)
+  rule_attrs = ctx.rule.attr
+
+  ide_info_text = set()
+  all_deps = []
+
+  for attr_name in DEPENDENCY_ATTRIBUTES:
+    if hasattr(rule_attrs, attr_name):
+      deps = getattr(rule_attrs, attr_name)
+      for dep in deps:
+        ide_info_text += dep.android_studio_info_files
+      all_deps += [str(dep.label) for dep in deps]
+
   if kind != _unrecognized_rule:
+    if is_java_rule(target, ctx):
       info = struct(
           label = str(target.label),
           kind = kind,
+          dependencies = all_deps,
+          # build_file = ???
+          java_rule_ide_info = java_rule_ide_info(target, ctx)
+      )
+    else:
+      info = struct(
+          label = str(target.label),
+          kind = kind,
+          dependencies = all_deps,
           # build_file = ???
       )
-      output = ctx.new_file(target.label.name + ".aswb-build.txt")
-      ctx.file_action(output, info.to_proto())
-      ide_info_text += set([output])
-  for dep in ctx.rule.attr.deps:
-    ide_info_text += dep.android_studio_info_files
+  output = ctx.new_file(target.label.name + ".aswb-build.txt")
+  ctx.file_action(output, info.to_proto())
+  ide_info_text += set([output])
 
   return struct(
       output_groups = {
@@ -54,13 +103,5 @@
     )
 
 intellij_info_aspect = aspect(implementation = _aspect_impl,
-    attr_aspects = [
-          "deps",
-          "exports",
-          "_robolectric", # From android_robolectric_test
-          "_junit", # From android_robolectric_test
-          "binary_under_test", #  From android_test
-          "java_lib",# From proto_library
-          "_proto1_java_lib", # From proto_library
-    ]
+    attr_aspects = DEPENDENCY_ATTRIBUTES
 )
\ No newline at end of file