add srcjar support to enable code gen plugins (#54)

diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt b/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
index 606fdbc..1bcece6 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
@@ -30,6 +30,7 @@
     }
 
     private val compileActions: List<BuildAction> = listOf(
+            UnpackSourceJars(toolchain),
             Initialize(toolchain),
             KotlinMainCompile(toolchain),
             JavaMainCompile(toolchain),
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
index 845bf92..383a521 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
@@ -52,7 +52,21 @@
     private fun bindSources(ctx: Context) {
         val javaSources = mutableListOf<String>()
         val allSources = mutableListOf<String>()
-        for (src in ctx.flags.source) {
+
+        val sourcePool = with(mutableListOf<String>()) {
+            ctx.flags.source?.also {
+                check(it.isNotEmpty())
+                addAll(it)
+            }
+            addAll(Metas.UNPACKED_SOURCES[ctx] ?: emptyList())
+            toList()
+        }
+
+        if(sourcePool.isEmpty()) {
+            throw RuntimeException("no compilable sources found")
+        }
+
+        for (src in sourcePool) {
             when {
                 src.endsWith(".java") -> {
                     javaSources.add(src)
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
index bed49a6..2b0f5bc 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
@@ -46,7 +46,7 @@
                 it.addAll(javaSources)
                 it.addAll(additionalJavaSources)
             }
-            Result.runAndBind(ctx) { executeAndAwait(30, args) }
+            Result.runAndBind(ctx) { executeAndAwait(30, null, args) }
         }
         return 0
     }
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/UnpackSourceJars.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/UnpackSourceJars.kt
new file mode 100644
index 0000000..46b305f
--- /dev/null
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/UnpackSourceJars.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 The Bazel Authors. 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 io.bazel.kotlin.builder.mode.jvm.actions
+
+import io.bazel.kotlin.builder.BuildAction
+import io.bazel.kotlin.builder.Context
+import io.bazel.kotlin.builder.KotlinToolchain
+import io.bazel.kotlin.builder.model.Metas
+import io.bazel.kotlin.builder.utils.executeAndAwaitSuccess
+import java.nio.file.Paths
+
+/**
+ * Unpack files with the srcjar extension into a temp directory.
+ */
+class UnpackSourceJars(toolchain: KotlinToolchain) : BuildAction("unpack srcjars", toolchain) {
+    override fun invoke(ctx: Context): Int {
+        if (ctx.flags.sourceJars != null) {
+            check(ctx.flags.sourceJars.isNotEmpty())
+
+            val unpackDir = ctx.flags.tempDirPath.value.resolve("_srcjars").toFile()
+                    .also {
+                        try {
+                            it.mkdirs()
+                        } catch(ex: Exception) {
+                            throw RuntimeException("could not create unpack directory at $it", ex)
+                        }
+                    }
+            ctx.flags.sourceJars.map { Paths.get(it) }.forEach { srcjar ->
+                try {
+                    mutableListOf(
+                            Paths.get(toolchain.JAR_TOOL_PATH).toAbsolutePath().toString(),
+                            "xf", srcjar.toAbsolutePath().toString()
+                    ).also { executeAndAwaitSuccess(10, unpackDir, it) }
+                } catch (e: Exception) {
+                    throw RuntimeException("unable to unpack source jar: $srcjar", e)
+                }
+            }
+            unpackDir.walk()
+                    .filter { it.name.endsWith(".kt") || it.name.endsWith(".java") }
+                    .map { it.toString() }
+                    .toList()
+                    // bind the sources even if the list is empty. throw an appropriate error if needed in Initialize.
+                    .also { Metas.UNPACKED_SOURCES[ctx] = it }
+            return 0
+        } else {
+            return 0
+        }
+    }
+}
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt b/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
index f41f875..f3e4e84 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
@@ -31,7 +31,9 @@
     // val strictJavaDeps = argMap.mandatorySingle(JavaBuilderFlags.STRICT_JAVA_DEPS.flag)
     val outputClassJar = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT.flag)
 
-    val source = argMap.mandatory(JavaBuilderFlags.SOURCES.flag)
+    val source = argMap.optional(JavaBuilderFlags.SOURCES.flag)
+    val sourceJars = argMap.optional(JavaBuilderFlags.SOURCE_JARS.flag)
+
     val classpath = argMap.mandatory(JavaBuilderFlags.CLASSPATH.flag)
     val plugins = argMap.optionalFromJson<PluginDescriptors>("--kt-plugins")
     val outputJdeps = argMap.mandatorySingle("--output_jdeps")
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/model/Metas.kt b/kotlin/builder/src/io/bazel/kotlin/builder/model/Metas.kt
index 48ec630..32e845e 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/model/Metas.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/model/Metas.kt
@@ -16,6 +16,7 @@
 package io.bazel.kotlin.builder.model
 
 import io.bazel.kotlin.builder.MandatoryMeta
+import io.bazel.kotlin.builder.Meta
 
 /**
  * Listin of Meta keys that don't make sense as companion objects.
@@ -26,9 +27,11 @@
     // The target part of the label.
     val TARGET = MandatoryMeta<String>("target")
 
-    //If this is non empty then it is a mixed mode operation.
+    // If this is non empty then it is a mixed mode operation.
     val JAVA_SOURCES = MandatoryMeta<List<String>>("java_sources")
 
+    // .kt and .java source files unpacked from .srcjar files
+    val UNPACKED_SOURCES = Meta<List<String>>("unpacked_sources")
 
     val ALL_SOURCES = MandatoryMeta<List<String>>("all_sources")
 
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/utils/IOUtils.kt b/kotlin/builder/src/io/bazel/kotlin/builder/utils/IOUtils.kt
index fd52c42..4da723c 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/utils/IOUtils.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/utils/IOUtils.kt
@@ -70,11 +70,12 @@
     }
 }
 
-fun executeAndAwait(timeoutSeconds: Int, args: List<String>): Int {
-    val process = ProcessBuilder(*args.toTypedArray()).let {
-        it.redirectError(ProcessBuilder.Redirect.PIPE)
-        it.redirectOutput(ProcessBuilder.Redirect.PIPE)
-        it.start()
+fun executeAndAwait(timeoutSeconds: Int, directory: File? = null, args: List<String>): Int {
+    val process = ProcessBuilder(*args.toTypedArray()).let { pb ->
+        pb.redirectError(ProcessBuilder.Redirect.PIPE)
+        pb.redirectOutput(ProcessBuilder.Redirect.PIPE)
+        directory?.also { pb.directory(it) }
+        pb.start()
     }
 
     var isr: BufferedReader? = null
@@ -91,7 +92,11 @@
 }
 
 fun executeAndAwaitSuccess(timeoutSeconds: Int, vararg command: String) {
-    val status = executeAndAwait(timeoutSeconds, command.toList())
+    executeAndAwaitSuccess(timeoutSeconds, null, command = command.toList())
+}
+
+fun executeAndAwaitSuccess(timeoutSeconds: Int, directory: File?, command: List<String>) {
+    val status = executeAndAwait(timeoutSeconds, directory, command)
     check(status == 0) {
         "process failed with status: $status"
     }
diff --git a/kotlin/internal/compile.bzl b/kotlin/internal/compile.bzl
index eefee9e..bce6126 100644
--- a/kotlin/internal/compile.bzl
+++ b/kotlin/internal/compile.bzl
@@ -15,6 +15,9 @@
 load("//kotlin/internal:plugins.bzl", "plugins")
 load("//kotlin/internal:utils.bzl", "utils")
 
+_src_file_types = FileType([".java", ".kt"])
+_srcjar_file_type = FileType([".srcjar"])
+
 def _kotlin_do_compile_action(ctx, rule_kind, output_jar, compile_jars):
     """Internal macro that sets up a Kotlin compile action.
 
@@ -44,13 +47,25 @@
         "--output", output_jar.path,
         "--output_jdeps", ctx.outputs.jdeps.path,
         "--classpath", "\n".join([f.path for f in compile_jars.to_list()]),
-        "--sources", "\n".join([f.path for f in ctx.files.srcs]),
         "--kotlin_jvm_target", tc.jvm_target,
         "--kotlin_api_version", tc.api_version,
         "--kotlin_language_version", tc.language_version,
         "--kotlin_module_name", getattr(ctx.attr, "module_name", ""),
         "--kotlin_passthrough_flags", "-Xcoroutines=%s" % tc.coroutines
     ]
+
+    srcs=[f.path for f in _src_file_types.filter(ctx.files.srcs)]
+    src_jars = [f.path for f in _srcjar_file_type.filter(ctx.files.srcs)]
+
+    if len(srcs) == 0 and len(src_jars) == 0:
+        fail("srcs did not contain kotlin/java files or any srcjars")
+
+    if len(srcs) > 0:
+        args += ["--sources", "\n".join(srcs)]
+
+    if len(src_jars) > 0:
+        args += ["--source_jars", "\n".join(src_jars)]
+
     # Collect and prepare plugin descriptor for the worker.
     plugin_info=plugins.merge_plugin_infos(ctx.attr.plugins + ctx.attr.deps)
     if len(plugin_info.processors) > 0:
diff --git a/kotlin/kotlin.bzl b/kotlin/kotlin.bzl
index e1f564a..54049de 100644
--- a/kotlin/kotlin.bzl
+++ b/kotlin/kotlin.bzl
@@ -118,8 +118,10 @@
     _kotlin_compiler_repository = "kotlin_compiler_repository",
 )
 
-# The files types that may be passed to the core Kotlin compile rule.
 _kt_compile_filetypes = FileType([
+    # source jars these will be unpacked by the compiler.
+    ".srcjar",
+    # The files types that may be passed to the core Kotlin compile rule.
     ".kt",
     ".java",
 ])