Enable GenClass to take Java source jars as inputs to extract their
corresponding class files from an input class jar.

No parsing of the Java files is done, so the classes to extract are just based
on the Java file's path in the source jar (i.e. the only compilation unit is
assumed to be a class that matches the name of the file, and that the Java
file's package matches its path in the jar.)

RELNOTES: None.
PiperOrigin-RevId: 293894803
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClass.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClass.java
index 16d4f10..177d9e7 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClass.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClass.java
@@ -30,6 +30,7 @@
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.function.Predicate;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -67,6 +68,12 @@
   public static void main(String[] args) throws IOException {
     GenClassOptions options = GenClassOptionsParser.parse(Arrays.asList(args));
     Manifest manifest = readManifest(options.manifest());
+
+    if (!options.getGeneratedSourceJars().isEmpty()) {
+      Manifest generatedJarsManifest = readGeneratedSourceJars(options.getGeneratedSourceJars());
+      manifest = Manifest.newBuilder(manifest).mergeFrom(generatedJarsManifest).build();
+    }
+
     deleteTree(options.tempDir());
     Files.createDirectories(options.tempDir());
     extractGeneratedClasses(options.classJar(), manifest, options.tempDir());
@@ -82,6 +89,49 @@
     return manifest;
   }
 
+  /** Reads the list of source jars and generates a Manifest based on the jars' entries. */
+  @VisibleForTesting
+  static Manifest readGeneratedSourceJars(List<Path> generatedSourceJarPaths) throws IOException {
+
+    Manifest.Builder manifestBuilder = Manifest.newBuilder();
+    for (Path generatedSourceJarPath : generatedSourceJarPaths) {
+
+      try (JarFile jar = new JarFile(generatedSourceJarPath.toFile())) {
+
+        Enumeration<JarEntry> entries = jar.entries();
+        while (entries.hasMoreElements()) {
+
+          JarEntry entry = entries.nextElement();
+          String path = entry.getName();
+          if (!path.endsWith(".java")) {
+            continue;
+          }
+
+          // This assumes that there is only 1 compilation unit in the Java file, and that the Java
+          // file's package matches its path in the jar.
+
+          int lastSlash = path.lastIndexOf("/");
+          String className = path.substring(lastSlash + 1, path.length() - ".java".length());
+          String pkg;
+          if (lastSlash == -1) {
+            pkg = "";
+          } else {
+            pkg = path.substring(0, lastSlash).replace('/', '.');
+          }
+          manifestBuilder.addCompilationUnit(
+              CompilationUnit.newBuilder()
+                  .setPath(path)
+                  .setPkg(pkg)
+                  .setGeneratedByAnnotationProcessor(true)
+                  .addTopLevel(className)
+                  .build());
+        }
+      }
+    }
+
+    return manifestBuilder.build();
+  }
+
   /**
    * For each top-level class in the compilation matching the given predicate, determine the path
    * prefix of classes corresponding to that compilation unit.
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptions.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptions.java
index 9e46d96..0269f18 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptions.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptions.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 
 /** The options for a {@GenClass} action. */
@@ -24,6 +25,7 @@
   /** A builder for {@link GenClassOptions}. */
   public static final class Builder {
     private Path manifest;
+    private final ImmutableList.Builder<Path> generatedSourceJars = ImmutableList.builder();
     private Path classJar;
     private Path outputJar;
     private Path tempDir;
@@ -34,6 +36,10 @@
       this.manifest = manifest;
     }
 
+    public void addGeneratedSourceJar(Path generatedSourceJar) {
+      this.generatedSourceJars.add(generatedSourceJar);
+    }
+
     public void setClassJar(Path classJar) {
       this.classJar = classJar;
     }
@@ -47,17 +53,25 @@
     }
 
     GenClassOptions build() {
-      return new GenClassOptions(manifest, classJar, outputJar, tempDir);
+      return new GenClassOptions(
+          manifest, generatedSourceJars.build(), classJar, outputJar, tempDir);
     }
   }
 
   private final Path manifest;
+  private final ImmutableList<Path> generatedSourceJars;
   private final Path classJar;
   private final Path outputJar;
   private final Path tempDir;
 
-  private GenClassOptions(Path manifest, Path classJar, Path outputJar, Path tempDir) {
+  private GenClassOptions(
+      Path manifest,
+      ImmutableList<Path> generatedSourceJars,
+      Path classJar,
+      Path outputJar,
+      Path tempDir) {
     this.manifest = checkNotNull(manifest);
+    this.generatedSourceJars = checkNotNull(generatedSourceJars);
     this.classJar = checkNotNull(classJar);
     this.outputJar = checkNotNull(outputJar);
     this.tempDir = checkNotNull(tempDir);
@@ -68,6 +82,16 @@
     return manifest;
   }
 
+  /**
+   * The list of paths to jars containing generated Java source files corresponding to classes in
+   * the input class jar which should be included in the gen jar. The packages of the Java source
+   * files must match their path in the jar, and there can be only 1 top-level compilation unit,
+   * because the list of compilation units in the Java file will be based on the file's name.
+   */
+  public ImmutableList<Path> getGeneratedSourceJars() {
+    return generatedSourceJars;
+  }
+
   /** The path to the compilation's class jar. */
   public Path classJar() {
     return classJar;
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptionsParser.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptionsParser.java
index 606185b..2f34b99 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptionsParser.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClassOptionsParser.java
@@ -30,6 +30,9 @@
         case "--manifest_proto":
           builder.setManifest(readPath(it));
           break;
+        case "--generated_source_jar":
+          builder.addGeneratedSourceJar(readPath(it));
+          break;
         case "--class_jar":
           builder.setClassJar(readPath(it));
           break;