Initial jdeps output

The list of all symbols referenced in the API is already collected for
generating the InnerClasses attribute, so walk it and record any jars
that referenced symbols were loaded from.

There's no distinction between direct and indirect deps, because turbine
doesn't enforce strict deps.

MOE_MIGRATED_REVID=136876448
diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java
index d51c08e..ec5fc19 100644
--- a/java/com/google/turbine/binder/ClassPathBinder.java
+++ b/java/com/google/turbine/binder/ClassPathBinder.java
@@ -85,7 +85,9 @@
       }
       ClassSymbol sym = new ClassSymbol(name.substring(0, name.length() - ".class".length()));
       if (!env.containsKey(sym)) {
-        env.put(sym, new BytecodeBoundClass(sym, () -> toByteArrayOrDie(jf, je), benv));
+        env.put(
+            sym,
+            new BytecodeBoundClass(sym, () -> toByteArrayOrDie(jf, je), benv, path.toString()));
         tli.insert(sym);
       }
     }
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index 2401e70..897d49a 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -60,11 +60,16 @@
   private final ClassSymbol sym;
   private final Env<ClassSymbol, BytecodeBoundClass> env;
   private final Supplier<ClassFile> classFile;
+  private final String jarFile;
 
   public BytecodeBoundClass(
-      ClassSymbol sym, final Supplier<byte[]> bytes, Env<ClassSymbol, BytecodeBoundClass> env) {
+      ClassSymbol sym,
+      final Supplier<byte[]> bytes,
+      Env<ClassSymbol, BytecodeBoundClass> env,
+      String jarFile) {
     this.sym = sym;
     this.env = env;
+    this.jarFile = jarFile;
     this.classFile =
         Suppliers.memoize(
             new Supplier<ClassFile>() {
@@ -434,4 +439,9 @@
       }
     };
   }
+
+  /** The jar file the symbol was loaded from. */
+  public String jarFile() {
+    return jarFile;
+  }
 }
diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java
new file mode 100644
index 0000000..740ae2b
--- /dev/null
+++ b/java/com/google/turbine/deps/Dependencies.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Google Inc. 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.turbine.deps;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.lower.Lower.Lowered;
+import com.google.turbine.proto.DepsProto;
+
+/** Support for Bazel jdeps dependency output. */
+public class Dependencies {
+  /** Creates a jdeps proto for the current compilation. */
+  public static DepsProto.Dependencies collectDeps(
+      Optional<String> targetLabel,
+      ImmutableSet<String> bootClassPath,
+      BindingResult bound,
+      Lowered lowered) {
+    DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
+    for (ClassSymbol sym : lowered.symbols()) {
+      BytecodeBoundClass info = bound.classPathEnv().get(sym);
+      if (info == null) {
+        // the symbol wasn't loaded from the classpath
+        continue;
+      }
+      String jarFile = info.jarFile();
+      if (bootClassPath.contains(jarFile)) {
+        // bootclasspath deps are not tracked
+        continue;
+      }
+      deps.addDependency(
+          DepsProto.Dependency.newBuilder()
+              .setPath(jarFile)
+              .setKind(DepsProto.Dependency.Kind.EXPLICIT));
+    }
+    // we don't current write jdeps for failed compilations
+    deps.setSuccess(true);
+    if (targetLabel.isPresent()) {
+      deps.setRuleLabel(targetLabel.get());
+    }
+    return deps.build();
+  }
+}
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index 40fcee6..d157b57 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -19,6 +19,7 @@
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.turbine.binder.bound.SourceTypeBoundClass;
 import com.google.turbine.binder.bound.TypeBoundClass;
 import com.google.turbine.binder.bound.TypeBoundClass.AnnoInfo;
@@ -60,24 +61,49 @@
 /** Lowering from bound classes to bytecode. */
 public class Lower {
 
+  /** The lowered compilation output. */
+  public static class Lowered {
+    private final ImmutableMap<String, byte[]> bytes;
+    private final ImmutableSet<ClassSymbol> symbols;
+
+    public Lowered(ImmutableMap<String, byte[]> bytes, ImmutableSet<ClassSymbol> symbols) {
+      this.bytes = bytes;
+      this.symbols = symbols;
+    }
+
+    /** Returns the bytecode for classes in the compilation. */
+    public ImmutableMap<String, byte[]> bytes() {
+      return bytes;
+    }
+
+    /** Returns the set of all referenced symbols in the compilation. */
+    public ImmutableSet<ClassSymbol> symbols() {
+      return symbols;
+    }
+  }
+
   /** Lowers all given classes to bytecode. */
-  public static Map<String, byte[]> lowerAll(
+  public static Lowered lowerAll(
       ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
       CompoundEnv<ClassSymbol, BytecodeBoundClass> classpath) {
     CompoundEnv<ClassSymbol, TypeBoundClass> env =
         CompoundEnv.<ClassSymbol, TypeBoundClass>of(classpath).append(new SimpleEnv<>(units));
     ImmutableMap.Builder<String, byte[]> result = ImmutableMap.builder();
+    Set<ClassSymbol> symbols = new LinkedHashSet<>();
     for (ClassSymbol sym : units.keySet()) {
-      result.put(sym.binaryName(), new Lower().lower(units.get(sym), env, sym));
+      result.put(sym.binaryName(), new Lower().lower(units.get(sym), env, sym, symbols));
     }
-    return result.build();
+    return new Lowered(result.build(), ImmutableSet.copyOf(symbols));
   }
 
   private final LowerSignature sig = new LowerSignature();
 
   /** Lowers a class to bytecode. */
   public byte[] lower(
-      SourceTypeBoundClass info, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
+      SourceTypeBoundClass info,
+      Env<ClassSymbol, TypeBoundClass> env,
+      ClassSymbol sym,
+      Set<ClassSymbol> symbols) {
 
     int access = classAccess(info);
     String name = sig.descriptor(sym);
@@ -123,6 +149,8 @@
             annotations,
             inners);
 
+    symbols.addAll(sig.classes);
+
     return ClassWriter.writeClass(classfile);
   }
 
diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java
index 30855ce..2e31adc 100644
--- a/java/com/google/turbine/main/Main.java
+++ b/java/com/google/turbine/main/Main.java
@@ -25,10 +25,14 @@
 import com.google.common.io.ByteStreams;
 import com.google.turbine.binder.Binder;
 import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.deps.Dependencies;
+import com.google.turbine.diag.SourceFile;
 import com.google.turbine.lower.Lower;
+import com.google.turbine.lower.Lower.Lowered;
 import com.google.turbine.options.TurbineOptions;
 import com.google.turbine.options.TurbineOptionsParser;
 import com.google.turbine.parse.Parser;
+import com.google.turbine.proto.DepsProto;
 import com.google.turbine.tree.Tree.CompUnit;
 import java.io.BufferedOutputStream;
 import java.io.IOException;
@@ -50,11 +54,14 @@
   private static final int BUFFER_SIZE = 65536;
 
   public static void main(String[] args) throws IOException {
-
     TurbineOptions options = TurbineOptionsParser.parse(Arrays.asList(args));
+    compile(options);
+  }
 
+  public static void compile(TurbineOptions options) throws IOException {
     ImmutableList<CompUnit> units = parseAll(options);
 
+    // TODO(cushon): reduce classpath
     BindingResult bound =
         Binder.bind(
             units,
@@ -62,15 +69,18 @@
             Iterables.transform(options.bootClassPath(), TO_PATH));
 
     // TODO(cushon): parallelize
-    Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+    Lowered lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
 
-    writeOutput(Paths.get(options.outputFile()), lowered);
-
-    // TODO(cushon): jdeps
-    // for now just touch the output file, since Bazel expects it to be created
     if (options.outputDeps().isPresent()) {
-      Files.write(Paths.get(options.outputDeps().get()), new byte[0]);
+      DepsProto.Dependencies deps =
+          Dependencies.collectDeps(options.targetLabel(), options.bootClassPath(), bound, lowered);
+      try (OutputStream os =
+          new BufferedOutputStream(Files.newOutputStream(Paths.get(options.outputDeps().get())))) {
+        deps.writeTo(os);
+      }
     }
+
+    writeOutput(Paths.get(options.outputFile()), lowered.bytes());
   }
 
   /** Parse all source files and source jars. */
@@ -78,7 +88,9 @@
   private static ImmutableList<CompUnit> parseAll(TurbineOptions options) throws IOException {
     ImmutableList.Builder<CompUnit> units = ImmutableList.builder();
     for (String source : options.sources()) {
-      units.add(Parser.parse(new String(Files.readAllBytes(Paths.get(source)), UTF_8)));
+      units.add(
+          Parser.parse(
+              new SourceFile(source, new String(Files.readAllBytes(Paths.get(source)), UTF_8))));
     }
     for (String sourceJar : options.sourceJars()) {
       try (JarFile jf = new JarFile(sourceJar)) {
@@ -93,7 +105,10 @@
             continue;
           }
           units.add(
-              Parser.parse(new String(ByteStreams.toByteArray(jf.getInputStream(je)), UTF_8)));
+              Parser.parse(
+                  new SourceFile(
+                      sourceJar + "!" + je.getName(),
+                      new String(ByteStreams.toByteArray(jf.getInputStream(je)), UTF_8))));
         }
       }
     }
diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java
index dc18a80..866318f 100644
--- a/java/com/google/turbine/options/TurbineOptions.java
+++ b/java/com/google/turbine/options/TurbineOptions.java
@@ -29,7 +29,7 @@
 
   private final String output;
   private final ImmutableList<String> classPath;
-  private final ImmutableList<String> bootClassPath;
+  private final ImmutableSet<String> bootClassPath;
   private final ImmutableList<String> sources;
   private final ImmutableList<String> processorPath;
   private final ImmutableSet<String> processors;
@@ -47,7 +47,7 @@
   private TurbineOptions(
       String output,
       ImmutableList<String> classPath,
-      ImmutableList<String> bootClassPath,
+      ImmutableSet<String> bootClassPath,
       ImmutableList<String> sources,
       ImmutableList<String> processorPath,
       ImmutableSet<String> processors,
@@ -93,7 +93,7 @@
   }
 
   /** Paths to compilation bootclasspath artifacts. */
-  public ImmutableList<String> bootClassPath() {
+  public ImmutableSet<String> bootClassPath() {
     return bootClassPath;
   }
 
@@ -180,7 +180,7 @@
     private final ImmutableSet.Builder<String> blacklistedProcessors = ImmutableSet.builder();
     private String tempDir;
     private final ImmutableList.Builder<String> sourceJars = ImmutableList.builder();
-    private final ImmutableList.Builder<String> bootClassPath = ImmutableList.builder();
+    private final ImmutableSet.Builder<String> bootClassPath = ImmutableSet.builder();
     private String outputDeps;
     private final ImmutableMap.Builder<String, String> directJarsToTargets = ImmutableMap.builder();
     private final ImmutableMap.Builder<String, String> indirectJarsToTargets =
diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
index eb83171..7922afb 100644
--- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java
+++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
@@ -108,6 +108,7 @@
                 throw new IOError(e);
               }
             },
+            null,
             null);
     try {
       c.owner();
diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java
new file mode 100644
index 0000000..9932d0e
--- /dev/null
+++ b/javatests/com/google/turbine/deps/DependenciesTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016 Google Inc. 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.turbine.deps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.turbine.binder.Binder;
+import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.diag.SourceFile;
+import com.google.turbine.lower.IntegrationTestSupport;
+import com.google.turbine.lower.Lower;
+import com.google.turbine.lower.Lower.Lowered;
+import com.google.turbine.parse.Parser;
+import com.google.turbine.proto.DepsProto;
+import com.google.turbine.tree.Tree.CompUnit;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DependenciesTest {
+
+  static final ImmutableSet<Path> BOOTCLASSPATH =
+      ImmutableSet.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
+
+  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  class LibraryBuilder {
+    final Map<String, String> sources = new LinkedHashMap<>();
+    private ImmutableList<Path> classpath;
+
+    LibraryBuilder addSourceLines(String path, String... lines) {
+      sources.put(path, Joiner.on('\n').join(lines));
+      return this;
+    }
+
+    LibraryBuilder setClasspath(Path... classpath) {
+      this.classpath = ImmutableList.copyOf(classpath);
+      return this;
+    }
+
+    Path compileToJar(String path) throws Exception {
+      Path lib = temporaryFolder.newFile(path).toPath();
+      Map<String, byte[]> classes =
+          IntegrationTestSupport.runJavac(sources, classpath, BOOTCLASSPATH);
+      try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
+        for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
+          jos.putNextEntry(new JarEntry(entry.getKey() + ".class"));
+          jos.write(entry.getValue());
+        }
+      }
+      return lib;
+    }
+  }
+
+  static class DepsBuilder {
+    List<Path> classpath;
+    List<CompUnit> units = new ArrayList<>();
+
+    DepsBuilder setClasspath(Path... classpath) {
+      this.classpath = ImmutableList.copyOf(classpath);
+      return this;
+    }
+
+    DepsBuilder addSourceLines(String path, String... lines) {
+      units.add(Parser.parse(new SourceFile(path, Joiner.on('\n').join(lines))));
+      return this;
+    }
+
+    DepsProto.Dependencies run() throws IOException {
+      BindingResult bound = Binder.bind(units, classpath, BOOTCLASSPATH);
+
+      Lowered lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+
+      return Dependencies.collectDeps(
+          Optional.of("//test"),
+          ImmutableSet.copyOf(Iterables.transform(BOOTCLASSPATH, Path::toString)),
+          bound,
+          lowered);
+    }
+  }
+
+  private Map<Path, DepsProto.Dependency.Kind> depsMap(DepsProto.Dependencies deps) {
+    return StreamSupport.stream(deps.getDependencyList().spliterator(), false)
+        .collect(Collectors.toMap(d -> Paths.get(d.getPath()), DepsProto.Dependency::getKind));
+  }
+
+  @Test
+  public void simple() throws Exception {
+    Path liba =
+        new LibraryBuilder().addSourceLines("A.java", "class A {}").compileToJar("liba.jar");
+    DepsProto.Dependencies deps =
+        new DepsBuilder()
+            .setClasspath(liba)
+            .addSourceLines("Test.java", "class Test extends A {}")
+            .run();
+
+    assertThat(depsMap(deps)).isEqualTo(ImmutableMap.of(liba, DepsProto.Dependency.Kind.EXPLICIT));
+  }
+
+  @Test
+  public void excluded() throws Exception {
+    Path liba =
+        new LibraryBuilder()
+            .addSourceLines(
+                "A.java", //
+                "class A {}")
+            .compileToJar("liba.jar");
+    Path libb =
+        new LibraryBuilder()
+            .setClasspath(liba)
+            .addSourceLines("B.java", "class B extends A {}")
+            .compileToJar("libb.jar");
+    DepsProto.Dependencies deps =
+        new DepsBuilder()
+            .setClasspath(liba, libb)
+            .addSourceLines("Test.java", "class Test extends B {}")
+            .run();
+
+    assertThat(depsMap(deps)).isEqualTo(ImmutableMap.of(libb, DepsProto.Dependency.Kind.EXPLICIT));
+  }
+
+  @Test
+  public void transitive() throws Exception {
+    Path liba =
+        new LibraryBuilder()
+            .addSourceLines(
+                "A.java", //
+                "class A {",
+                "  public static final class Y {}",
+                "}")
+            .compileToJar("liba.jar");
+    Path libb =
+        new LibraryBuilder()
+            .setClasspath(liba)
+            .addSourceLines("B.java", "class B extends A {}")
+            .compileToJar("libb.jar");
+    DepsProto.Dependencies deps =
+        new DepsBuilder()
+            .setClasspath(liba, libb)
+            .addSourceLines(
+                "Test.java", //
+                "class Test extends B {",
+                "  public static class X extends Y {}",
+                "}")
+            .run();
+    assertThat(depsMap(deps))
+        .isEqualTo(
+            ImmutableMap.of(
+                libb, DepsProto.Dependency.Kind.EXPLICIT,
+                liba, DepsProto.Dependency.Kind.EXPLICIT));
+  }
+}
diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
index 701fce8..4e99284 100644
--- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java
+++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
@@ -277,10 +277,10 @@
             .collect(toList());
 
     Binder.BindingResult bound = Binder.bind(units, classpath, bootclasspath);
-    return Lower.lowerAll(bound.units(), bound.classPathEnv());
+    return Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
   }
 
-  static Map<String, byte[]> runJavac(
+  public static Map<String, byte[]> runJavac(
       Map<String, String> sources, Iterable<Path> classpath, Iterable<? extends Path> bootclasspath)
       throws Exception {
 
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index 744128c..2d0bb7a 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -190,8 +190,10 @@
 
     Map<String, byte[]> bytes =
         Lower.lowerAll(
-            ImmutableMap.of(new ClassSymbol("test/Test"), c, new ClassSymbol("test/Test$Inner"), i),
-            classpath);
+                ImmutableMap.of(
+                    new ClassSymbol("test/Test"), c, new ClassSymbol("test/Test$Inner"), i),
+                classpath)
+            .bytes();
 
     assertThat(AsmUtils.textify(bytes.get("test/Test")))
         .isEqualTo(
@@ -222,7 +224,7 @@
                             "}"))),
             ImmutableList.of(),
             BOOTCLASSPATH);
-    Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+    Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
     List<String> attributes = new ArrayList<>();
     new org.objectweb.asm.ClassReader(lowered.get("Test$Inner$InnerMost"))
         .accept(