port the worker files to kotlin and add bootstrap macros.
diff --git a/.bazelproject b/.bazelproject
index 2035d82..7828fba 100644
--- a/.bazelproject
+++ b/.bazelproject
@@ -17,8 +17,8 @@
   .
 
 targets:
-  //kotlin/workers/...
-  -//tests/smoke/...
+  //kotlin/workers:worker_for_ide
+  //kotlin/workers:unittests
 
 test_sources:
   */unittests/*
diff --git a/kotlin/workers/BUILD b/kotlin/workers/BUILD
index 342ddaf..622afeb 100644
--- a/kotlin/workers/BUILD
+++ b/kotlin/workers/BUILD
@@ -11,28 +11,37 @@
 # 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.
-java_library(
-    name = "lib",
-    srcs = glob(["src/**/*.java"]),
+load("//kotlin/workers:bootstrap.bzl", "kotlin_worker_lib")
+
+kotlin_worker_lib(
+    srcs = glob(["src/**/*.kt"]),
+    args = ["-jvm-target", "1.8"],
     deps = [
         "//kotlin/workers/proto",
-        "@com_github_jetbrains_kotlin//:preloader"
+        "@com_github_jetbrains_kotlin//:preloader",
+        "@io_bazel_rules_kotlin_protobuf_protobuf_java//jar",
     ],
-    exports = ["//kotlin/workers/proto"],
-    visibility = ["//visibility:private"]
+    exports = [
+        "//kotlin/workers/proto"
+    ],
+    runtime_deps = [
+        "@com_github_jetbrains_kotlin//:kotlin-stdlib",
+        "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7",
+        "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8"
+    ]
 )
 
 java_binary(
     name = "compiler_jvm",
     main_class = "io.bazel.ruleskotlin.workers.compilers.jvm.KotlinJvmBuilder",
     visibility = ["//visibility:public"],
-    runtime_deps = ["lib"]
+    runtime_deps = [":worker_lib"]
 )
 
 java_test(
     name = "unittests",
     test_class = "io.bazel.ruleskotlin.workers.compilers.jvm.utils.JdepsParserTest",
     srcs = glob(["unittests/**/*.java"]),
-    deps = ["lib"],
+    deps = [":worker_lib"],
     size = "small"
 )
\ No newline at end of file
diff --git a/kotlin/workers/bootstrap.bzl b/kotlin/workers/bootstrap.bzl
new file mode 100644
index 0000000..dbdc866
--- /dev/null
+++ b/kotlin/workers/bootstrap.bzl
@@ -0,0 +1,87 @@
+# 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.
+load("//kotlin:kotlin.bzl", _for_ide = "kotlin_library")
+
+_HEADER = """
+function join_by { local IFS="$$1"; shift; echo "$$*"; }
+
+CP=$$(join_by : $(locations :%s))
+"""
+
+# This is kept arround incase it's needed -- there shouldn't be a need for mixed compilation, but just in case.
+def _gen_mixed_cmd(name, args):
+    return _HEADER + ("""
+ARGS="-Xcompile-java -Xuse-javac %s"
+
+mkdir -p compile_classes
+
+JAVA_HOME=external/local_jdk  ./$(location @com_github_jetbrains_kotlin//:kotlinc) -cp $${CP} -d compile_classes $${ARGS} $(SRCS)
+
+jar cf  $(OUTS) -C compile_classes .
+
+rm -rf compile_classes
+""") % (name, args)
+
+def _gen_cmd(name, args):
+    return (_HEADER + """
+ARGS="%s"
+
+JAVA_HOME=external/local_jdk ./$(location @com_github_jetbrains_kotlin//:kotlinc) -cp $${CP} -d $(OUTS) $${ARGS} $(SRCS)
+""") % (name,args)
+
+
+
+
+def kotlin_worker_lib(srcs =[], args = [], deps=[], runtime_deps=[], exports=[]):
+    name = "worker"
+    dep_label = name + "_deps"
+    jar_file_label =  name + "_file"
+    jar_name = name+".jar"
+    libary_label =  name + "_lib"
+
+    # Dummy target loaded by `kotlin_library` so intellij can understand the sources.
+    _for_ide(
+        name = "for_ide",
+        srcs = srcs,
+        deps = deps,
+        runtime_deps = runtime_deps,
+        exports = exports,
+        visibility=["//visibility:private"],
+    )
+
+    native.filegroup(
+        name = dep_label,
+        srcs = deps,
+        visibility = ["//visibility:private"]
+    )
+    native.genrule(
+        name = jar_file_label,
+        tools = [
+            "@com_github_jetbrains_kotlin//:home",
+            "@com_github_jetbrains_kotlin//:kotlinc",
+            "@local_jdk//:jdk",
+            dep_label
+        ] + deps,
+        srcs = srcs,
+        outs = [jar_name],
+        cmd = _gen_cmd(dep_label, " ".join(args)),
+        visibility = ["//visibility:private"]
+    )
+    native.java_import(
+        name = libary_label,
+        jars = [jar_name],
+        exports = exports,
+        runtime_deps = (depset(runtime_deps) + exports + deps).to_list(),
+        visibility = ["//visibility:private"]
+    )
\ No newline at end of file
diff --git a/kotlin/workers/proto/BUILD b/kotlin/workers/proto/BUILD
index ebf55a8..e5770b9 100644
--- a/kotlin/workers/proto/BUILD
+++ b/kotlin/workers/proto/BUILD
@@ -19,4 +19,13 @@
     ],
     exports = ["@io_bazel_rules_kotlin_protobuf_protobuf_java//jar"],
     visibility = ["//kotlin/workers:__subpackages__"]
+)
+
+filegroup(
+    name = "files",
+    srcs = [
+        "jars/libdeps_proto-speed.jar",
+        "jars/libworker_protocol_proto-speed.jar"
+    ],
+    visibility = ["//kotlin/workers:__subpackages__"]
 )
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java
deleted file mode 100644
index 6ee7f1b..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-
-import com.google.devtools.build.lib.worker.WorkerProtocol;
-import io.bazel.ruleskotlin.workers.utils.IOUtils;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Bazel worker runner.
- * <p>
- * <p>This class adapts a traditional command line program so it can be spawned by Bazel as a
- * persistent worker process that handles multiple invocations per JVM. It will also be backwards
- * compatible with being run as a normal single-invocation command.
- *
- * @param <T> delegate program type
- */
-public final class BazelWorker<T extends CommandLineProgram> implements CommandLineProgram {
-    private final CommandLineProgram delegate;
-    private final String mnemonic;
-    private final PrintStream output;
-
-    public BazelWorker(T delegate, PrintStream output, String mnemonic) {
-        this.delegate = delegate;
-        this.output = output;
-        this.mnemonic = mnemonic;
-    }
-
-    @Override
-    public Integer apply(List<String> args) {
-        for (String arg : args) {
-            if (arg.equals("--persistent_worker")) {
-                return runAsPersistentWorker(args);
-            }
-        }
-        return delegate.apply(loadArguments(args, false));
-    }
-
-    @SuppressWarnings("unused")
-    private int runAsPersistentWorker(List<String> ignored) {
-        InputStream realStdIn = System.in;
-        PrintStream realStdOut = System.out;
-        PrintStream realStdErr = System.err;
-        try (InputStream emptyIn = new ByteArrayInputStream(new byte[0]);
-             ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-             PrintStream ps = new PrintStream(buffer)) {
-            System.setIn(emptyIn);
-            System.setOut(ps);
-            System.setErr(ps);
-            while (true) {
-                WorkerProtocol.WorkRequest request = WorkerProtocol.WorkRequest.parseDelimitedFrom(realStdIn);
-                if (request == null) {
-                    return 0;
-                }
-                int exitCode;
-
-                try {
-                    exitCode = delegate.apply(loadArguments(request.getArgumentsList(), true));
-                } catch (RuntimeException e) {
-                    if (wasInterrupted(e)) {
-                        return 0;
-                    }
-                    System.err.println(
-                            "ERROR: Worker threw uncaught exception with args: " +
-                                    request.getArgumentsList().stream().collect(Collectors.joining(" ")));
-                    e.printStackTrace(System.err);
-                    exitCode = 1;
-                }
-                WorkerProtocol.WorkResponse.newBuilder()
-                        .setOutput(buffer.toString())
-                        .setExitCode(exitCode)
-                        .build()
-                        .writeDelimitedTo(realStdOut);
-                realStdOut.flush();
-                buffer.reset();
-                System.gc();  // be a good little worker process and consume less memory when idle
-            }
-        } catch (IOException | RuntimeException e) {
-            if (wasInterrupted(e)) {
-                return 0;
-            }
-            throw new RuntimeException(e);
-        } finally {
-            System.setIn(realStdIn);
-            System.setOut(realStdOut);
-            System.setErr(realStdErr);
-        }
-    }
-
-    private List<String> loadArguments(List<String> args, boolean isWorker) {
-        if (args.size() > 0) {
-            String lastArg = args.get(args.size() - 1);
-
-            if (lastArg.startsWith("@")) {
-                String pathElement = lastArg.substring(1);
-                Path flagFile = Paths.get(pathElement);
-                if ((isWorker && lastArg.startsWith("@@")) || Files.exists(flagFile)) {
-                    if (!isWorker && !mnemonic.isEmpty()) {
-                        output.printf(
-                                "HINT: %s will compile faster if you run: "
-                                        + "echo \"build --strategy=%s=worker\" >>~/.bazelrc\n",
-                                mnemonic, mnemonic);
-                    }
-                    try {
-                        return Files.readAllLines(flagFile, UTF_8);
-                    } catch (IOException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-            }
-        }
-        return args;
-    }
-
-    private boolean wasInterrupted(Throwable e) {
-        Throwable cause = IOUtils.getRootCause(e);
-        if (cause instanceof InterruptedException
-                || cause instanceof InterruptedIOException) {
-            output.println("Terminating worker due to interrupt signal");
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.kt
new file mode 100644
index 0000000..0aa3f75
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.ruleskotlin.workers
+
+
+import com.google.devtools.build.lib.worker.WorkerProtocol
+import io.bazel.ruleskotlin.workers.utils.rootCause
+import java.io.*
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.util.stream.Collectors
+
+/**
+ * Bazel worker runner.
+ *
+ *
+ *
+ * This class adapts a traditional command line program so it can be spawned by Bazel as a
+ * persistent worker process that handles multiple invocations per JVM. It will also be backwards
+ * compatible with being run as a normal single-invocation command.
+ *
+ * @param <T> delegate program type
+</T> */
+class BazelWorker<T : CommandLineProgram>(private val delegate: T, private val output: PrintStream, private val mnemonic: String) : CommandLineProgram {
+    companion object {
+        private const val INTERUPTED_STATUS = 0
+        private const val ERROR_STATUS = 1
+    }
+
+    override fun apply(args: List<String>): Int {
+        return if (args.contains("--persistent_worker"))
+            runAsPersistentWorker(args)
+        else delegate.apply(loadArguments(args, false))
+    }
+
+    @Suppress("UNUSED_PARAMETER")
+    private fun runAsPersistentWorker(ignored: List<String>): Int {
+        val realStdIn = System.`in`
+        val realStdOut = System.out
+        val realStdErr = System.err
+        try {
+            ByteArrayInputStream(ByteArray(0)).use { emptyIn ->
+                ByteArrayOutputStream().use { buffer ->
+                    PrintStream(buffer).use { ps ->
+                        System.setIn(emptyIn)
+                        System.setOut(ps)
+                        System.setErr(ps)
+                        while (true) {
+                            val request = WorkerProtocol.WorkRequest.parseDelimitedFrom(realStdIn) ?: return 0
+                            var exitCode: Int
+
+                            exitCode = try {
+                                delegate.apply(loadArguments(request.argumentsList, true))
+                            } catch (e: RuntimeException) {
+                                if (wasInterrupted(e)) {
+                                    return INTERUPTED_STATUS
+                                }
+                                System.err.println(
+                                        "ERROR: Worker threw uncaught exception with args: " + request.argumentsList.stream().collect(Collectors.joining(" ")))
+                                e.printStackTrace(System.err)
+                                ERROR_STATUS
+                            }
+
+                            WorkerProtocol.WorkResponse.newBuilder()
+                                    .setOutput(buffer.toString())
+                                    .setExitCode(exitCode)
+                                    .build()
+                                    .writeDelimitedTo(realStdOut)
+                            realStdOut.flush()
+                            buffer.reset()
+                            System.gc()  // be a good little worker process and consume less memory when idle
+                        }
+                    }
+                }
+            }
+        } catch (e: IOException) {
+            if (wasInterrupted(e)) {
+                return INTERUPTED_STATUS
+            }
+            throw e
+        } catch (e: RuntimeException) {
+            if (wasInterrupted(e)) {
+                return INTERUPTED_STATUS
+            }
+            throw e
+        } finally {
+            System.setIn(realStdIn)
+            System.setOut(realStdOut)
+            System.setErr(realStdErr)
+        }
+        throw RuntimeException("drop through")
+    }
+
+    private fun loadArguments(args: List<String>, isWorker: Boolean): List<String> {
+        if (args.isNotEmpty()) {
+            val lastArg = args[args.size - 1]
+
+            if (lastArg.startsWith("@")) {
+                val pathElement = lastArg.substring(1)
+                val flagFile = Paths.get(pathElement)
+                if (isWorker && lastArg.startsWith("@@") || Files.exists(flagFile)) {
+                    if (!isWorker && !mnemonic.isEmpty()) {
+                        output.printf(
+                                "HINT: %s will compile faster if you run: " + "echo \"build --strategy=%s=worker\" >>~/.bazelrc\n",
+                                mnemonic, mnemonic)
+                    }
+                    try {
+                        return Files.readAllLines(flagFile, UTF_8)
+                    } catch (e: IOException) {
+                        throw RuntimeException(e)
+                    }
+
+                }
+            }
+        }
+        return args
+    }
+
+    private fun wasInterrupted(e: Throwable): Boolean {
+        val cause = e.rootCause
+        if (cause is InterruptedException || cause is InterruptedIOException) {
+            output.println("Terminating worker due to interrupt signal")
+            return true
+        }
+        return false
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.kt
similarity index 75%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.kt
index 79dcd52..7805229 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.kt
@@ -13,11 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers;
+package io.bazel.ruleskotlin.workers
 
 
-import java.util.function.Function;
-
-public interface BuildAction extends Function<Context, Integer> {
-
+abstract class BuildAction(@Suppress("unused") val id : String, protected val toolchain: KotlinToolchain) {
+    abstract operator fun invoke(ctx: Context): Int
 }
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.java
deleted file mode 100644
index 751241f..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * Interface for command line programs.
- * <p>
- * <p>This is the same thing as a main function, except not static.
- */
-public interface CommandLineProgram extends Function<List<String>, Integer> {
-
-    /**
-     * Runs blocking program start to finish.
-     * <p>
-     * <p>This function might be called multiple times throughout the life of this object. Output
-     * must be sent to {@link System#out} and {@link System#err}.
-     *
-     * @param args command line arguments
-     * @return program exit code, i.e. 0 for success, non-zero for failure
-     */
-    @Override
-    Integer apply(List<String> args);
-
-    default Integer apply(String[] args) {
-        return apply(Arrays.asList(args));
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.kt
new file mode 100644
index 0000000..5fb40fe
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CommandLineProgram.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.ruleskotlin.workers
+
+
+/**
+ * Interface for command line programs.
+ *
+ * This is the same thing as a main function, except not static.
+ */
+interface CommandLineProgram {
+
+    /**
+     * Runs blocking program start to finish.
+     *
+     *
+     *
+     * This function might be called multiple times throughout the life of this object. Output
+     * must be sent to [System.out] and [System.err].
+     *
+     * @param args command line arguments
+     * @return program exit code, i.e. 0 for success, non-zero for failure
+     */
+    fun apply(args: List<String>): Int
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java
deleted file mode 100644
index 49ea365..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-@FunctionalInterface
-public interface CompileResult {
-    /**
-     * The status of this operation.
-     */
-    default int status() {
-        return 0;
-    }
-
-    default Optional<Exception> error() {
-        return Optional.empty();
-    }
-
-    default void propogateError(String message) throws RuntimeException {
-        error().ifPresent(e -> {
-            throw new RuntimeException(message, e);
-        });
-    }
-
-    static CompileResult just(final int status) {
-        return new CompileResult() {
-            @Override
-            public int status() {
-                return status;
-            }
-
-            @Override
-            public Integer render(Context ctx) {
-                return status;
-            }
-        };
-    }
-
-    static CompileResult error(final Exception error) {
-        return new CompileResult() {
-            @Override
-            public int status() {
-                return -1;
-            }
-
-            @Override
-            public Optional<Exception> error() {
-                return Optional.of(error);
-            }
-
-            @Override
-            public Integer render(Context ctx) {
-                throw new RuntimeException(error);
-            }
-        };
-    }
-
-    static CompileResult deferred(final int status, Function<Context, Integer> renderer) {
-        return new CompileResult() {
-            @Override
-            public int status() {
-                return status;
-            }
-
-            @Override
-            public Integer render(Context ctx) {
-                return renderer.apply(ctx);
-            }
-        };
-    }
-
-    final class Meta extends io.bazel.ruleskotlin.workers.Meta<CompileResult> {
-        public Meta(String id) {
-            super(id);
-        }
-
-        public CompileResult run(final Context ctx, Function<Context, Integer> op) {
-            CompileResult result;
-            try {
-                result = CompileResult.just(op.apply(ctx));
-            } catch (Exception e) {
-                result = CompileResult.error(e);
-            }
-            return result;
-        }
-
-        public CompileResult runAndBind(final Context ctx, Function<Context, Integer> op) {
-            CompileResult res = run(ctx, op);
-            bind(ctx, res);
-            return res;
-        }
-
-        public CompileResult runAndBind(final Context ctx, Supplier<Integer> op) {
-            return runAndBind(ctx, (c) -> op.get());
-        }
-    }
-
-    /**
-     * Materialise the output of the compile result.
-     *
-     * @return the new status of the compile operation, this shouldn't make a failing status pass, but it could fail a compile operation.
-     */
-    Integer render(Context ctx);
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.kt
new file mode 100644
index 0000000..6ac5a57
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.ruleskotlin.workers
+
+import java.util.*
+
+interface CompileResult {
+    /**
+     * The status of this operation.
+     */
+    fun status(): Int {
+        return 0
+    }
+
+    fun error(): Optional<Exception> {
+        return Optional.empty()
+    }
+
+    @Throws(RuntimeException::class)
+    fun propogateError(message: String) {
+        error().ifPresent { e -> throw RuntimeException(message, e) }
+    }
+
+    class Meta(id: String) : io.bazel.ruleskotlin.workers.Meta<CompileResult>(id) {
+
+        fun run(ctx: Context, op: (Context) -> Int): CompileResult {
+            var result: CompileResult
+            try {
+                result = CompileResult.just(op(ctx))
+            } catch (e: Exception) {
+                result = CompileResult.error(e)
+            }
+
+            return result
+        }
+
+        fun runAndBind(ctx: Context, op: (Context) -> Int): CompileResult {
+            val res = run(ctx, op)
+            bind(ctx, res)
+            return res
+        }
+
+        //        public CompileResult runAndBind(final Context ctx, Supplier<Integer> op) {
+        //            return runAndBind(ctx, (c) -> op.get());
+        //        }
+    }
+
+    /**
+     * Materialise the output of the compile result.
+     *
+     * @return the new status of the compile operation, this shouldn't make a failing status pass, but it could fail a compile operation.
+     */
+    fun render(ctx: Context): Int
+
+    companion object {
+
+        fun just(status: Int): CompileResult {
+            return object : CompileResult {
+                override fun status(): Int {
+                    return status
+                }
+
+                override fun render(ctx: Context): Int {
+                    return status
+                }
+            }
+        }
+
+        fun error(error: Exception): CompileResult {
+            return object : CompileResult {
+                override fun status(): Int {
+                    return -1
+                }
+
+                override fun error(): Optional<Exception> {
+                    return Optional.of(error)
+                }
+
+                override fun render(ctx: Context): Int {
+                    throw RuntimeException(error)
+                }
+            }
+        }
+
+        fun deferred(status: Int, renderer: (Context) -> Int): CompileResult {
+            return object : CompileResult {
+                override fun status(): Int {
+                    return status
+                }
+
+                override fun render(ctx: Context): Int {
+                    return renderer(ctx)
+                }
+            }
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java
deleted file mode 100644
index 5bb4fad..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public class Context {
-    private final EnumMap<Flags, String> args = new EnumMap<>(Flags.class);
-    private final Map<Meta<?>, Object> meta = new HashMap<>();
-
-    private static final Map<String, Flags> ALL_FIELDS_MAP = Arrays.stream(Flags.values()).collect(Collectors.toMap(x -> x.name, x -> x));
-    private static final Flags[] MANDATORY_FIELDS = Arrays.stream(Flags.values()).filter(x -> x.mandatory).toArray(Flags[]::new);
-
-    private Context(List<String> args) {
-        if (args.size() % 2 != 0) {
-            throw new RuntimeException("args should be k,v pairs");
-        }
-
-        for (int i = 0; i < args.size() / 2; i++) {
-            String flag = args.get(i * 2);
-            String value = args.get((i * 2) + 1);
-            Flags field = ALL_FIELDS_MAP.get(flag);
-            if (field == null) {
-                throw new RuntimeException("unrecognised arg: " + flag);
-            }
-            this.args.put(field, value);
-        }
-
-        for (Flags mandatoryField : MANDATORY_FIELDS) {
-            if (!this.args.containsKey(mandatoryField)) {
-                throw new RuntimeException("mandatory arg missing: " + mandatoryField.name);
-            }
-        }
-    }
-
-    public static Context from(List<String> args) {
-        return new Context(args);
-    }
-
-    public EnumMap<Flags, String> of(Flags... fields) {
-        EnumMap<Flags, String> result = new EnumMap<>(Flags.class);
-        for (Flags field : fields) {
-            String value = args.get(field);
-            if (value != null) {
-                result.put(field, value);
-            }
-        }
-        return result;
-    }
-
-    public interface Action extends Consumer<Context> {
-    }
-
-    public void apply(Action... consumers) {
-        Stream.of(consumers).forEach(c -> c.accept(this));
-    }
-
-
-    String get(Flags field) {
-        return args.get(field);
-    }
-
-    @SuppressWarnings("unchecked")
-    <T> T get(Meta<T> key) {
-        return (T) meta.get(key);
-    }
-
-    @SuppressWarnings("unchecked")
-    <T> T putIfAbsent(Meta<T> key, T value) {
-        return (T) meta.putIfAbsent(key, value);
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.kt
new file mode 100644
index 0000000..1a42331
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+@file:Suppress("UNCHECKED_CAST")
+
+package io.bazel.ruleskotlin.workers
+
+import java.util.*
+import java.util.stream.Stream
+
+class Context private constructor(args: List<String>) {
+    private val args = EnumMap<Flags, String>(Flags::class.java)
+    private val meta = HashMap<Meta<*>, Any>()
+
+    init {
+        if (args.size % 2 != 0) {
+            throw RuntimeException("args should be k,v pairs")
+        }
+
+        for (i in 0 until args.size / 2) {
+            val flag = args[i * 2]
+            val value = args[i * 2 + 1]
+            val field = ALL_FIELDS_MAP[flag] ?: throw RuntimeException("unrecognised arg: " + flag)
+            this.args[field] = value
+        }
+
+        MANDATORY_FIELDS.asSequence()
+                .filterNot { this.args.containsKey(it) }
+                .forEach { throw RuntimeException("mandatory arg missing: " + it.globalFlag) }
+    }
+
+    fun of(vararg fields: Flags): EnumMap<Flags, String> {
+        val result = EnumMap<Flags, String>(Flags::class.java)
+        for (field in fields) {
+            val value = args[field]
+            if (value != null) {
+                result[field] = value
+            }
+        }
+        return result
+    }
+
+    fun apply(vararg consumers: (Context) -> Unit) {
+        Stream.of(*consumers).forEach { it(this) }
+    }
+
+    internal operator fun get(field: Flags): String? = args[field]
+    internal operator fun <T : Any> get(key: Meta<T>): T? = meta[key] as T?
+    internal fun <T : Any> putIfAbsent(key: Meta<T>, value: T): T? = meta.putIfAbsent(key, value as Any) as T?
+
+    companion object {
+        private val ALL_FIELDS_MAP = Flags.values().map { it.globalFlag to Flags.valueOf(it.name) }.toMap()
+        private val MANDATORY_FIELDS = Flags.values().filter { x -> x.mandatory }
+
+        fun from(args: List<String>): Context {
+            return Context(args)
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.kt
similarity index 76%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.kt
index 7aaa65a..fe20049 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.kt
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers;
+package io.bazel.ruleskotlin.workers
 
 
-public enum Flags {
+enum class Flags(val globalFlag: String, val kotlinFlag: String?, internal val mandatory: Boolean) {
     // flags that line up with the java builder.
     LABEL(JavaBuilderFlags.TARGET_LABEL.flag, null, true),
     OUTPUT_CLASSJAR(JavaBuilderFlags.OUTPUT.flag, null, true),
@@ -32,17 +32,5 @@
     KOTLIN_LANGUAGE_VERSION("--kotlin_language_version", "-language-version", false),
     KOTLIN_JVM_TARGET("--kotlin_jvm_target", "-jvm-target", false);
 
-    public final String name;
-    public final String kotlinFlag;
-    final boolean mandatory;
-
-    Flags(String name, String kotlinName, boolean mandatory) {
-        this.name = name;
-        this.kotlinFlag = kotlinName;
-        this.mandatory = mandatory;
-    }
-
-    public String get(Context context) {
-        return context.get(this);
-    }
+    operator fun get(context: Context): String? = context[this]
 }
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.kt
similarity index 88%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.kt
index dcecc40..3287a27 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.kt
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers;
+package io.bazel.ruleskotlin.workers
 
 /**
  * Flags used by the java builder.
  */
-@SuppressWarnings("unused")
-public enum JavaBuilderFlags {
+enum class JavaBuilderFlags(val flag: String) {
     TARGET_LABEL("--target_label"),
     CLASSPATH("--classpath"),
     JAVAC_OPTS("--javacopts"),
@@ -51,11 +50,5 @@
     POST_PROCESSOR("--post_processor"),
     COMPRESS_JAR("--compress_jar"),
     RULE_KIND("--rule_kind"),
-    TEST_ONLY("--testonly");
-            
-    public final String flag;
-
-    JavaBuilderFlags(String flag) {
-        this.flag = flag;
-    }
+    TEST_ONLY("--testonly")
 }
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java
deleted file mode 100644
index 4f8e5c1..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-import io.bazel.ruleskotlin.workers.compilers.jvm.Locations;
-import org.jetbrains.kotlin.preloading.ClassPreloadingUtils;
-import org.jetbrains.kotlin.preloading.Preloader;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.lang.reflect.Method;
-import java.nio.file.Paths;
-import java.util.function.BiFunction;
-
-public final class KotlinToolchain {
-    private static final Object[] NO_ARGS = new Object[]{};
-    private final ClassLoader classLoader;
-
-    public KotlinToolchain() throws IOException {
-        this.classLoader = ClassPreloadingUtils.preloadClasses(
-                Locations.KOTLIN_REPO.verifiedRelativeFiles(
-                        Paths.get("lib", "kotlin-compiler.jar")
-                ),
-                Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE,
-                Thread.currentThread().getContextClassLoader(),
-                null
-        );
-    }
-
-    public interface KotlinCompiler extends BiFunction<String[], PrintStream, Integer> {
-    }
-
-    /**
-     * Load the Kotlin compiler and the javac tools.jar into a Preloading classLoader. The Kotlin compiler is invoked reflectively to eventually allow
-     * toolchain replacement.
-     */
-    public KotlinCompiler kotlinCompiler() {
-        try {
-            Class<?> compilerClass = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
-            Class<?> exitCodeClass = classLoader.loadClass("org.jetbrains.kotlin.cli.common.ExitCode");
-
-            Object compiler = compilerClass.newInstance();
-            Method execMethod = compilerClass.getMethod("exec", PrintStream.class, String[].class);
-            Method getCodeMethod = exitCodeClass.getMethod("getCode");
-
-            return (args, stream) -> {
-                final Object exitCodeInstance;
-                try {
-                    exitCodeInstance = execMethod.invoke(compiler, stream, args);
-                    return (Integer) getCodeMethod.invoke(exitCodeInstance, NO_ARGS);
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            };
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.kt
new file mode 100644
index 0000000..6e7a2c8
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.ruleskotlin.workers
+
+import io.bazel.ruleskotlin.workers.utils.resolveVerified
+import io.bazel.ruleskotlin.workers.utils.verifiedRelativeFiles
+import org.jetbrains.kotlin.preloading.ClassPreloadingUtils
+import org.jetbrains.kotlin.preloading.Preloader
+import java.io.PrintStream
+import java.nio.file.Path
+import java.nio.file.Paths
+
+@Suppress("PropertyName")
+class KotlinToolchain {
+    companion object {
+        internal val JAVA_HOME = Paths.get("external", "local_jdk")
+        internal val KOTLIN_HOME = Paths.get("external", "com_github_jetbrains_kotlin")
+
+        internal val NO_ARGS = arrayOf<Any>()
+    }
+
+    val JAVAC_PATH = JAVA_HOME.resolveVerified("bin", "javac").toString()
+    val JAR_TOOL_PATH = JAVA_HOME.resolveVerified("bin", "jar").toString()
+    val JDEPS_PATH = JAVA_HOME.resolveVerified("bin", "jdeps").toString()
+    val KOTLIN_LIB_DIR: Path = KOTLIN_HOME.resolveVerified("lib").toPath()
+
+    private val kotlinPreloadJars = KOTLIN_LIB_DIR.verifiedRelativeFiles(
+            Paths.get("kotlin-compiler.jar")
+    )
+
+    val KOTLIN_STD_LIBS = arrayOf(
+            "kotlin-stdlib.jar",
+            "kotlin-stdlib-jdk7.jar",
+            "kotlin-stdlib-jdk8.jar"
+    )
+
+    private val classLoader: ClassLoader by lazy {
+        ClassPreloadingUtils.preloadClasses(
+                kotlinPreloadJars,
+                Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE,
+                Thread.currentThread().contextClassLoader,
+                null
+        )
+    }
+
+    interface KotlinCompiler {
+        fun compile(args: Array<String>, out: PrintStream): Int
+    }
+
+    /**
+     * Load the Kotlin compiler into a Preloading classLoader.
+     */
+    val kotlinCompiler: KotlinCompiler by lazy {
+        val compilerClass = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
+        val exitCodeClass = classLoader.loadClass("org.jetbrains.kotlin.cli.common.ExitCode")
+
+        val compiler = compilerClass.newInstance()
+        val execMethod = compilerClass.getMethod("exec", PrintStream::class.java, Array<String>::class.java)
+        val getCodeMethod = exitCodeClass.getMethod("getCode")
+
+        object : KotlinCompiler {
+            override fun compile(args: Array<String>, out: PrintStream): Int {
+                val exitCodeInstance: Any
+                try {
+                    exitCodeInstance = execMethod.invoke(compiler, out, args)
+                    return getCodeMethod.invoke(exitCodeInstance, *NO_ARGS) as Int
+                } catch (e: Exception) {
+                    throw RuntimeException(e)
+                }
+            }
+        }
+    }
+}
+
+
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java
deleted file mode 100644
index 94a0712..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.ruleskotlin.workers;
-
-import java.util.Optional;
-
-public class Meta<T> {
-    private final String id;
-
-    private final T defaultValue;
-
-    public Meta(String id) {
-        this.id = id;
-        this.defaultValue = null;
-    }
-
-    @SuppressWarnings("unused")
-    private Meta(String id, T defaultValue) {
-        this.id = id;
-        this.defaultValue = defaultValue;
-    }
-
-    /**
-     * Gets a mandatory value.
-     */
-    public T mustGet(Context ctx) {
-        T res = ctx.get(this);
-        if(res == null) {
-            assert defaultValue != null : "mandatory meta parameter missing in context and does not have a default value";
-            return defaultValue;
-        }
-        return res;
-    }
-
-    /**
-     * Gets an optional value, if it has not been bound the default value is used.
-     */
-    public Optional<T> get(Context ctx) {
-        T res = ctx.get(this);
-        if( res != null) {
-            return Optional.of(res);
-        } else if(defaultValue != null) {
-            return Optional.of(defaultValue);
-        } else {
-            return Optional.empty();
-        }
-    }
-
-    public void bind(Context ctx, T value) {
-        if (ctx.putIfAbsent(this, value) != null) {
-            throw new RuntimeException("attempting to change bound meta variable " + id);
-        }
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.kt
new file mode 100644
index 0000000..a0aca11
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.ruleskotlin.workers
+
+open class Meta<T: Any>(
+        private val id: String,
+        private val defaultValue: T? = null
+) {
+    constructor(id: String) : this(id, null)
+
+    /**
+     * Gets a mandatory value.
+     */
+    fun mustGet(ctx: Context): T =
+        ctx[this] ?: checkNotNull(defaultValue) { "mandatory meta parameter missing in context and does not have a default value" }
+
+    /**
+     * Gets an optional value, if it has not been bound the default value is used.
+     */
+    operator fun get(ctx: Context): T? {
+        val res = ctx[this]
+        return when {
+            res != null -> res
+            defaultValue != null -> defaultValue
+            else -> null
+        }
+    }
+
+    fun bind(ctx: Context, value: T) { check(ctx.putIfAbsent(this, value) == null) { "attempting to change bound meta variable: $id " } }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java
deleted file mode 100644
index d6bdf85..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm;
-
-
-import io.bazel.ruleskotlin.workers.*;
-import io.bazel.ruleskotlin.workers.compilers.jvm.actions.*;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Bazel Kotlin Compiler worker.
- */
-public final class KotlinJvmBuilder implements CommandLineProgram {
-    private final BuildAction[] compileActions;
-
-    private KotlinJvmBuilder() {
-        KotlinToolchain kotlinToolchain;
-        try {
-            kotlinToolchain = new KotlinToolchain();
-        } catch (IOException e) {
-            throw new RuntimeException("could not initialize toolchain", e);
-        }
-
-        compileActions = new BuildAction[]{
-                Initialize.INSTANCE,
-                new KotlinMainCompile(kotlinToolchain),
-                new JavaMainCompile(),
-                KotlinRenderClassCompileResult.INSTANCE,
-                CreateOutputJar.INSTANCE,
-                GenerateJdepsFile.INSTANCE,
-        };
-    }
-
-    @Override
-    public Integer apply(List<String> args) {
-        Context context = Context.from(args);
-        Integer exitCode = 0;
-        for (BuildAction action : compileActions) {
-            exitCode = action.apply(context);
-            if (exitCode != 0)
-                break;
-        }
-        return exitCode;
-    }
-
-    public static void main(String[] args) {
-        KotlinJvmBuilder kotlinBuilder = new KotlinJvmBuilder();
-        BazelWorker<KotlinJvmBuilder> kotlinCompilerBazelWorker = new BazelWorker<>(
-                kotlinBuilder,
-                System.err,
-                "KotlinCompile"
-        );
-        System.exit(kotlinCompilerBazelWorker.apply(args));
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.kt
new file mode 100644
index 0000000..571bdd1
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm
+
+
+import io.bazel.ruleskotlin.workers.*
+import io.bazel.ruleskotlin.workers.compilers.jvm.actions.*
+
+import java.io.IOException
+
+/**
+ * Bazel Kotlin Compiler worker.
+ */
+class KotlinJvmBuilder private constructor() : CommandLineProgram {
+    private val compileActions: Array<BuildAction>
+
+    init {
+        val toolchain: KotlinToolchain
+        try {
+            toolchain = KotlinToolchain()
+        } catch (e: IOException) {
+            throw RuntimeException("could not initialize toolchain", e)
+        }
+
+        compileActions = arrayOf(
+                Initialize(toolchain),
+                KotlinMainCompile(toolchain),
+                JavaMainCompile(toolchain),
+                ProcessCompileResult(toolchain),
+                CreateOutputJar(toolchain),
+                GenerateJdepsFile(toolchain)
+        )
+    }
+
+    override fun apply(args: List<String>): Int {
+        val ctx = Context.from(args)
+        var exitCode = 0
+        for (action in compileActions) {
+            exitCode = action(ctx)
+            if (exitCode != 0)
+                break
+        }
+        return exitCode
+    }
+
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+            val kotlinBuilder = KotlinJvmBuilder()
+            val kotlinCompilerBazelWorker = BazelWorker(
+                    kotlinBuilder,
+                    System.err,
+                    "KotlinCompile"
+            )
+            System.exit(kotlinCompilerBazelWorker.apply(args.toList()))
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Locations.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Locations.java
deleted file mode 100644
index c877564..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Locations.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public enum Locations {
-        KOTLIN_REPO(Paths.get("external", "com_github_jetbrains_kotlin")),
-        JAVA_HOME(Paths.get("external", "local_jdk"));
-
-        private final Path path;
-
-        Locations(Path path) {
-            this.path = path;
-        }
-
-        public final File resolveVerified(String... parts) {
-            return verified(path.resolve(Paths.get(parts[0], Arrays.copyOfRange(parts, 1, parts.length))));
-        }
-
-        /**
-         * Return a stream of paths that are known to exists relative to this location.
-         */
-        public final List<File> verifiedRelativeFiles(Path... paths) {
-            return Stream.of(paths).map(relative -> verified(path.resolve(relative))).collect(Collectors.toList());
-        }
-
-        private File verified(Path target) {
-            File asFile = target.toFile();
-            if (!asFile.exists()) {
-                throw new RuntimeException("location " + this.name() + " did not have relative path file " + target);
-            }
-            return asFile;
-        }
-    }
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.java
deleted file mode 100644
index eb8ff54..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm;
-
-import io.bazel.ruleskotlin.workers.CompileResult;
-import io.bazel.ruleskotlin.workers.Meta;
-
-import java.nio.file.Path;
-import java.util.List;
-
-/**
- * Meta is a key to some compilation state,.
- */
-public class Metas {
-    // mandatory: the package part of the label.
-    public static final Meta<String> PKG = new Meta<>("package");
-    // mandatory: The target part of the label.
-    public static final Meta<String> TARGET = new Meta<>("target");
-    // mandatory: the class staging directory.
-    public static final Meta<Path> CLASSES_DIRECTORY = new Meta<>("class_directory");
-    // mandatory: If this is non empty then it is a mixed mode operation.
-    public static final Meta<List<String>> JAVA_SOURCES = new Meta<>("java_sources");
-    // mandatory:
-    public static final Meta<List<String>> ALL_SOURCES = new Meta<>("all_sources");
-    // mandatory:
-    public static final CompileResult.Meta KOTLINC_RESULT = new CompileResult.Meta("kotlin_compile_result");
-    // optional: when not a mixed mode operation.
-    public static final CompileResult.Meta JAVAC_RESULT = new CompileResult.Meta("javac_compile_result");
-}
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.kt
new file mode 100644
index 0000000..6b56242
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm
+
+import io.bazel.ruleskotlin.workers.CompileResult
+import io.bazel.ruleskotlin.workers.Meta
+
+import java.nio.file.Path
+
+/**
+ * Meta is a key to some compilation state,.
+ */
+object Metas {
+    // mandatory: the package part of the label.
+    val PKG = Meta<String>("package")
+    // mandatory: The target part of the label.
+    val TARGET = Meta<String>("target")
+    // mandatory: the class staging directory.
+    val CLASSES_DIRECTORY = Meta<Path>("class_directory")
+    // mandatory: If this is non empty then it is a mixed mode operation.
+    val JAVA_SOURCES = Meta<List<String>>("java_sources")
+    // mandatory:
+    val ALL_SOURCES = Meta<List<String>>("all_sources")
+    // mandatory:
+    val KOTLINC_RESULT = CompileResult.Meta("kotlin_compile_result")
+    // optional: when not a mixed mode operation.
+    val JAVAC_RESULT = CompileResult.Meta("javac_compile_result")
+}
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.java
deleted file mode 100644
index aba021e..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-
-import io.bazel.ruleskotlin.workers.BuildAction;
-import io.bazel.ruleskotlin.workers.Context;
-import io.bazel.ruleskotlin.workers.Flags;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Locations;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Metas;
-import io.bazel.ruleskotlin.workers.utils.IOUtils;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Create a jar from the classes.
- */
-public final class CreateOutputJar implements BuildAction {
-    public static final CreateOutputJar INSTANCE = new CreateOutputJar();
-    private static final String JAR_TOOL_PATH = Locations.JAVA_HOME.resolveVerified("bin", "jar").toString();
-
-    private CreateOutputJar() {
-    }
-
-    @Override
-    public Integer apply(Context ctx) {
-        try {
-            List<String> command = Arrays.asList(JAR_TOOL_PATH,
-                    "cf", Flags.OUTPUT_CLASSJAR.get(ctx),
-                    "-C", Metas.CLASSES_DIRECTORY.mustGet(ctx).toString(),
-                    ".");
-            IOUtils.executeAndAwaitSuccess(10, command);
-        } catch (Exception e) {
-            throw new RuntimeException("unable to create class jar", e);
-        }
-        return 0;
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.kt
new file mode 100644
index 0000000..38545fa
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+
+import io.bazel.ruleskotlin.workers.BuildAction
+import io.bazel.ruleskotlin.workers.Context
+import io.bazel.ruleskotlin.workers.Flags
+import io.bazel.ruleskotlin.workers.KotlinToolchain
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas
+import io.bazel.ruleskotlin.workers.utils.executeAndAwaitSuccess
+
+/**
+ * Create a jar from the classes.
+ */
+class CreateOutputJar(toolchain: KotlinToolchain) : BuildAction("create output jar", toolchain) {
+    override fun invoke(ctx: Context): Int {
+        try {
+            executeAndAwaitSuccess(10,
+                    toolchain.JAR_TOOL_PATH,
+                    "cf", checkNotNull(Flags.OUTPUT_CLASSJAR[ctx]),
+                    "-C", Metas.CLASSES_DIRECTORY.mustGet(ctx).toString(),
+                    "."
+            )
+        } catch (e: Exception) {
+            throw RuntimeException("unable to create class jar", e)
+        }
+        return 0
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.java
deleted file mode 100644
index 683cce5..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-import com.google.devtools.build.lib.view.proto.Deps;
-import io.bazel.ruleskotlin.workers.BuildAction;
-import io.bazel.ruleskotlin.workers.Context;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Locations;
-import io.bazel.ruleskotlin.workers.compilers.jvm.utils.JdepsParser;
-import io.bazel.ruleskotlin.workers.utils.IOUtils;
-
-import java.io.FileOutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.function.Predicate;
-
-import static io.bazel.ruleskotlin.workers.Flags.*;
-
-
-public final class GenerateJdepsFile implements BuildAction {
-    private static final String JDEPS_PATH = Locations.JAVA_HOME.resolveVerified("bin", "jdeps").toString();
-
-    private static final Predicate<String> IS_KOTLIN_IMPLICIT = JdepsParser.pathSuffixMatchingPredicate(
-            Paths.get("external", "com_github_jetbrains_kotlin", "lib"),
-            "kotlin-stdlib.jar",
-            "kotlin-stdlib-jdk7.jar",
-            "kotlin-stdlib-jdk8.jar");
-
-    public static final GenerateJdepsFile INSTANCE = new GenerateJdepsFile();
-
-    private GenerateJdepsFile() {
-    }
-
-    @Override
-    public Integer apply(Context ctx) {
-        final String
-                classJar = OUTPUT_CLASSJAR.get(ctx),
-                classPath = CLASSPATH.get(ctx),
-                output = OUTPUT_JDEPS.get(ctx);
-        Deps.Dependencies jdepsContent;
-        try {
-            List<String> jdepLines = IOUtils.executeAndWaitOutput(10, JDEPS_PATH, "-cp", classPath, classJar);
-            jdepsContent = JdepsParser.parse(
-                    LABEL.get(ctx),
-                    classJar,
-                    classPath,
-                    jdepLines.stream(),
-                    IS_KOTLIN_IMPLICIT
-            );
-        } catch (Exception e) {
-            throw new RuntimeException("error reading or parsing jdeps file", IOUtils.getRootCause(e));
-        }
-
-        try {
-            Path outputPath = Paths.get(output);
-            Files.deleteIfExists(outputPath);
-            try (FileOutputStream fileOutputStream = new FileOutputStream(Files.createFile(outputPath).toFile())) {
-                jdepsContent.writeTo(fileOutputStream);
-            }
-        } catch (Exception e) {
-            throw new RuntimeException("error writing out jdeps file", IOUtils.getRootCause(e));
-        }
-
-        return 0;
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.kt
new file mode 100644
index 0000000..120e38d
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+import com.google.devtools.build.lib.view.proto.Deps
+import io.bazel.ruleskotlin.workers.BuildAction
+import io.bazel.ruleskotlin.workers.Context
+import io.bazel.ruleskotlin.workers.Flags.*
+import io.bazel.ruleskotlin.workers.KotlinToolchain
+import io.bazel.ruleskotlin.workers.compilers.jvm.utils.JdepsParser
+import io.bazel.ruleskotlin.workers.utils.executeAndWaitOutput
+import io.bazel.ruleskotlin.workers.utils.rootCause
+import java.io.FileOutputStream
+import java.nio.file.Files
+import java.nio.file.Paths
+
+
+class GenerateJdepsFile(toolchain: KotlinToolchain) : BuildAction("generate jdeps", toolchain) {
+    private val isKotlinImplicit = JdepsParser.pathSuffixMatchingPredicate(toolchain.KOTLIN_LIB_DIR, *toolchain.KOTLIN_STD_LIBS)
+
+    override fun invoke(ctx: Context): Int {
+        val classJar = checkNotNull(OUTPUT_CLASSJAR[ctx])
+        val classPath = checkNotNull(CLASSPATH[ctx])
+        val output = checkNotNull(OUTPUT_JDEPS[ctx])
+        val label = checkNotNull(LABEL[ctx])
+        val jdepsContent: Deps.Dependencies
+
+        try {
+            jdepsContent = executeAndWaitOutput(10, toolchain.JDEPS_PATH, "-cp", classPath, classJar).let {
+                JdepsParser.parse(label, classJar, classPath, it.stream(), isKotlinImplicit)
+            }
+        } catch (e: Exception) {
+            throw RuntimeException("error reading or parsing jdeps file", e.rootCause)
+        }
+
+        try {
+            Paths.get(output).also {
+                Files.deleteIfExists(it)
+                FileOutputStream(Files.createFile(it).toFile()).use {
+                    jdepsContent.writeTo(it)
+                }
+            }
+        } catch (e: Exception) {
+            throw RuntimeException("error writing out jdeps file", e.rootCause)
+        }
+        return 0
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.java
deleted file mode 100644
index 2e175bc..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-
-import io.bazel.ruleskotlin.workers.BuildAction;
-import io.bazel.ruleskotlin.workers.Context;
-import io.bazel.ruleskotlin.workers.Flags;
-import io.bazel.ruleskotlin.workers.Meta;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Metas;
-import io.bazel.ruleskotlin.workers.utils.IOUtils;
-
-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.Collections;
-import java.util.List;
-
-/**
- * Should be the first step, does mandatory pre-processing.
- */
-public final class Initialize implements BuildAction {
-    public static final Initialize INSTANCE = new Initialize();
-
-    private Initialize() {
-    }
-
-    @Override
-    public Integer apply(Context ctx) {
-        ctx.apply(
-                Initialize::initializeAndBindBindDirectories,
-                Initialize::bindLabelComponents,
-                Initialize::bindSources
-        );
-        return 0;
-    }
-
-    private static void bindSources(Context ctx) {
-        List<String> javaSources = new ArrayList<>();
-        List<String> allSources = new ArrayList<>();
-        for (String src : Flags.SOURCES.get(ctx).split(":")) {
-            if (src.endsWith(".java")) {
-                javaSources.add(src);
-                allSources.add(src);
-            } else if (src.endsWith(".kt")) {
-                allSources.add(src);
-            } else {
-                throw new RuntimeException("unrecognised file type: " + src);
-            }
-        }
-        Metas.JAVA_SOURCES.bind(ctx, Collections.unmodifiableList(javaSources));
-        Metas.ALL_SOURCES.bind(ctx, Collections.unmodifiableList(allSources));
-    }
-
-    private static void initializeAndBindBindDirectories(Context ctx) {
-        Path outputBase;
-
-        try {
-            outputBase = Files.createDirectories(Paths.get(Flags.COMPILER_OUTPUT_BASE.get(ctx)));
-        } catch (IOException e) {
-            throw new RuntimeException("could not create compiler output base", e);
-        }
-
-        try {
-            IOUtils.purgeDirectory(outputBase);
-        } catch (IOException e) {
-            throw new RuntimeException("could not purge output directory", e);
-        }
-
-        createAndBindComponentDirectory(ctx, outputBase, Metas.CLASSES_DIRECTORY, "_classes");
-    }
-
-    private static void createAndBindComponentDirectory(Context ctx, Path outputBase, Meta<Path> key, String component) {
-        try {
-            key.bind(ctx, Files.createDirectories(outputBase.resolve(component)));
-        } catch (IOException e) {
-            throw new RuntimeException("could not create subdirectory for component " + component, e);
-        }
-    }
-
-    /**
-     * parses the label, sets up the meta elements and returns the target part.
-     */
-    private static void bindLabelComponents(Context ctx) {
-        String label = Flags.LABEL.get(ctx);
-        String[] parts = label.split(":");
-        if (parts.length != 2) {
-            throw new RuntimeException("the label " + label + " is invalid");
-        }
-        Metas.PKG.bind(ctx, parts[0]);
-        Metas.TARGET.bind(ctx, parts[1]);
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.kt
new file mode 100644
index 0000000..6a3f3b9
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+
+import io.bazel.ruleskotlin.workers.*
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas
+
+import io.bazel.ruleskotlin.workers.utils.purgeDirectory
+
+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.Collections
+
+/**
+ * Should be the first step, does mandatory pre-processing.
+ */
+class Initialize(toolchain: KotlinToolchain) : BuildAction("initialize KotlinBuilder", toolchain) {
+    override fun invoke(ctx: Context): Int {
+        ctx.apply(
+                ::initializeAndBindBindDirectories,
+                ::bindLabelComponents,
+                ::bindSources
+        )
+        return 0
+    }
+
+    private fun bindSources(ctx: Context) {
+        val javaSources = ArrayList<String>()
+        val allSources = ArrayList<String>()
+        for (src in requireNotNull(Flags.SOURCES[ctx]).split(":")) {
+            when {
+                src.endsWith(".java") -> {
+                    javaSources.add(src)
+                    allSources.add(src)
+                }
+                src.endsWith(".kt") -> allSources.add(src)
+                else -> throw RuntimeException("unrecognised file type: $src")
+            }
+        }
+        Metas.JAVA_SOURCES.bind(ctx, Collections.unmodifiableList(javaSources))
+        Metas.ALL_SOURCES.bind(ctx, Collections.unmodifiableList(allSources))
+    }
+
+    private fun initializeAndBindBindDirectories(ctx: Context) {
+        val outputBase: Path
+
+        try {
+            outputBase = Files.createDirectories(Paths.get(checkNotNull(Flags.COMPILER_OUTPUT_BASE[ctx])))
+        } catch (e: IOException) {
+            throw RuntimeException("could not create compiler output base", e)
+        }
+
+        try {
+            outputBase.purgeDirectory()
+        } catch (e: IOException) {
+            throw RuntimeException("could not purge output directory", e)
+        }
+
+        createAndBindComponentDirectory(ctx, outputBase, Metas.CLASSES_DIRECTORY, "_classes")
+    }
+
+    private fun createAndBindComponentDirectory(ctx: Context, outputBase: Path, key: Meta<Path>, component: String) {
+        try {
+            key.bind(ctx, Files.createDirectories(outputBase.resolve(component)))
+        } catch (e: IOException) {
+            throw RuntimeException("could not create subdirectory for component " + component, e)
+        }
+
+    }
+
+    /**
+     * parses the label, sets up the meta elements and returns the target part.
+     */
+    private fun bindLabelComponents(ctx: Context) {
+        val label = requireNotNull(Flags.LABEL[ctx])
+        val parts = label.split(":")
+        require(parts.size == 2) { "the label $label is invalid" }
+        Metas.PKG.bind(ctx, parts[0])
+        Metas.TARGET.bind(ctx, parts[1])
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.java
deleted file mode 100644
index da6da78..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-import io.bazel.ruleskotlin.workers.BuildAction;
-import io.bazel.ruleskotlin.workers.Context;
-import io.bazel.ruleskotlin.workers.Flags;
-import io.bazel.ruleskotlin.workers.compilers.jvm.*;
-import io.bazel.ruleskotlin.workers.utils.IOUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Simple java compile action that invokes javac directly and simply.
- */
-public final class JavaMainCompile implements BuildAction {
-    private static final String JAVAC_PATH = Locations.JAVA_HOME.resolveVerified("bin", "javac").toString();
-
-    public JavaMainCompile() {}
-
-    @Override
-    public Integer apply(Context ctx) {
-        List<String> javaSources = Metas.JAVA_SOURCES.mustGet(ctx);
-        if (!javaSources.isEmpty()) {
-            List<String> args = new ArrayList<>();
-            String classesDirectory = Metas.CLASSES_DIRECTORY.mustGet(ctx).toString();
-            Collections.addAll(args,
-                    JAVAC_PATH, "-cp", classesDirectory + "/:" + Flags.CLASSPATH.get(ctx),
-                    "-d", classesDirectory
-            );
-            args.addAll(javaSources);
-            Metas.JAVAC_RESULT.runAndBind(ctx, () -> IOUtils.executeAndAwait(30, args));
-        }
-        return 0;
-    }
-}
-
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.kt
new file mode 100644
index 0000000..3ae306b
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+import io.bazel.ruleskotlin.workers.BuildAction
+import io.bazel.ruleskotlin.workers.Context
+import io.bazel.ruleskotlin.workers.Flags
+import io.bazel.ruleskotlin.workers.KotlinToolchain
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas
+import io.bazel.ruleskotlin.workers.utils.executeAndAwait
+
+/**
+ * Simple java compile action that invokes javac directly and simply.
+ */
+class JavaMainCompile(toolchain: KotlinToolchain) : BuildAction("compile java classes", toolchain) {
+    override fun invoke(ctx: Context): Int {
+        val javaSources = Metas.JAVA_SOURCES.mustGet(ctx)
+        val classpath = checkNotNull(Flags.CLASSPATH[ctx])
+
+        if (!javaSources.isEmpty()) {
+            val classesDirectory = Metas.CLASSES_DIRECTORY.mustGet(ctx).toString()
+
+            val args = mutableListOf(toolchain.JAVAC_PATH, "-cp", "$classesDirectory/:$classpath", "-d", classesDirectory).also { it.addAll(javaSources) }
+            Metas.JAVAC_RESULT.runAndBind(ctx) { executeAndAwait(30, args) }
+        }
+        return 0
+    }
+}
+
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.java
deleted file mode 100644
index ad467fe..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-import io.bazel.ruleskotlin.workers.*;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Metas;
-import io.bazel.ruleskotlin.workers.compilers.jvm.utils.KotlinCompilerOutputProcessor;
-
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Either compiles to a jar directly or when performing mixed-mode-compilation compiles to a temp directory first.
- * <p>
- * Mixed-Mode:
- * <p>
- * The Kotlin compiler is not suited for javac compilation as of 1.2.21. The errors are not conveyed directly and would need to be preprocessed, also javac
- * invocations Configured via Kotlin use eager analysis in some corner cases this can result in classpath exceptions from the Java Compiler..
- */
-public final class KotlinMainCompile implements BuildAction {
-    private final KotlinToolchain.KotlinCompiler kotlinCompiler;
-
-    public KotlinMainCompile(KotlinToolchain toolchains) {
-        this.kotlinCompiler = toolchains.kotlinCompiler();
-    }
-
-    /**
-     * Default fields that are directly mappable to kotlin compiler args.
-     */
-    private static final Flags[] COMPILE_MAPPED_FLAGS = new Flags[]{
-            Flags.CLASSPATH,
-            Flags.KOTLIN_API_VERSION,
-            Flags.KOTLIN_LANGUAGE_VERSION,
-            Flags.KOTLIN_JVM_TARGET
-    };
-
-    /**
-     * Evaluate the compilation context and add Metadata to the ctx if needed.
-     *
-     * @return The args to pass to the kotlin compile class.
-     */
-    private static String[] setupCompileContext(Context ctx) {
-        List<String> args = new ArrayList<>();
-        Collections.addAll(args, "-d", Metas.CLASSES_DIRECTORY.mustGet(ctx).toString());
-        ctx.of(COMPILE_MAPPED_FLAGS).forEach((field, arg) -> Collections.addAll(args, field.kotlinFlag, arg));
-        args.addAll(Metas.ALL_SOURCES.mustGet(ctx));
-        return args.toArray(new String[args.size()]);
-    }
-
-    @Override
-    public Integer apply(Context ctx) {
-        KotlinCompilerOutputProcessor outputProcessor;
-        outputProcessor = new KotlinCompilerOutputProcessor.ForKotlinC(System.out);
-
-        final Integer exitCode = kotlinCompiler.apply(setupCompileContext(ctx), outputProcessor.getCollector());
-        if (exitCode < 2) {
-            // 1 is a standard compilation error
-            // 2 is an internal error
-            // 3 is the script execution error
-
-            // give javac a chance to process the java sources.
-            Metas.KOTLINC_RESULT.bind(ctx, CompileResult.deferred(exitCode, (c) -> {
-                outputProcessor.process();
-                return exitCode;
-            }));
-            return 0;
-        } else {
-            outputProcessor.process();
-            throw new RuntimeException("KotlinMainCompile returned terminal error code: " + exitCode);
-        }
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.kt
new file mode 100644
index 0000000..bf5b1ab
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+import io.bazel.ruleskotlin.workers.*
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas
+import io.bazel.ruleskotlin.workers.compilers.jvm.utils.KotlinCompilerOutputProcessor
+
+
+import java.util.ArrayList
+import java.util.Collections
+
+/**
+ * Either compiles to a jar directly or when performing mixed-mode-compilation compiles to a temp directory first.
+ *
+ *
+ * Mixed-Mode:
+ *
+ *
+ * The Kotlin compiler is not suited for javac compilation as of 1.2.21. The errors are not conveyed directly and would need to be preprocessed, also javac
+ * invocations Configured via Kotlin use eager analysis in some corner cases this can result in classpath exceptions from the Java Compiler..
+ */
+class KotlinMainCompile(toolchain: KotlinToolchain) : BuildAction("compile kotlin classes", toolchain) {
+    companion object {
+        /**
+         * Default fields that are directly mappable to kotlin compiler args.
+         */
+        private val COMPILE_MAPPED_FLAGS = arrayOf(
+                Flags.CLASSPATH,
+                Flags.KOTLIN_API_VERSION,
+                Flags.KOTLIN_LANGUAGE_VERSION,
+                Flags.KOTLIN_JVM_TARGET)
+
+        /**
+         * Evaluate the compilation context and add Metadata to the ctx if needed.
+         *
+         * @return The args to pass to the kotlin compile class.
+         */
+        private fun setupCompileContext(ctx: Context): Array<String> {
+            val args = ArrayList<String>()
+            Collections.addAll(args, "-d", Metas.CLASSES_DIRECTORY.mustGet(ctx).toString())
+            ctx.of(*COMPILE_MAPPED_FLAGS).forEach { field, arg ->
+                args.add(field.kotlinFlag!!); args.add(arg)
+
+            }
+            args.addAll(Metas.ALL_SOURCES.mustGet(ctx))
+            return args.toTypedArray()
+        }
+    }
+
+    override fun invoke(ctx: Context): Int {
+        val outputProcessor = KotlinCompilerOutputProcessor.ForKotlinC(System.out)
+
+        val exitCode = try {
+            toolchain.kotlinCompiler.compile(setupCompileContext(ctx), outputProcessor.collector)
+        } catch (ex: Exception) {
+            outputProcessor.process()
+            throw ex
+        }
+
+        if (exitCode < 2) {
+            // 1 is a standard compilation error
+            // 2 is an internal error
+            // 3 is the script execution error
+
+            // give javac a chance to process the java sources.
+            Metas.KOTLINC_RESULT.bind(ctx, CompileResult.deferred(exitCode) { _ ->
+                outputProcessor.process()
+                exitCode
+            })
+            return 0
+        } else {
+            outputProcessor.process()
+            throw RuntimeException("KotlinMainCompile returned terminal error code: $exitCode")
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinRenderClassCompileResult.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinRenderClassCompileResult.java
deleted file mode 100644
index 76c8674..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinRenderClassCompileResult.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.actions;
-
-
-import io.bazel.ruleskotlin.workers.BuildAction;
-import io.bazel.ruleskotlin.workers.CompileResult;
-import io.bazel.ruleskotlin.workers.Context;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Metas;
-
-import java.util.Optional;
-
-
-/**
- * Render the result of class compilation. This is a separate step at the moment for mixed mode compilation scenarios. If there is an error in Java sources in
- * a large mixed mode package the Kotlin errors don't make any sense and overwhelm the console and intellij. The {@link KotlinMainCompile} step binds a deferred
- * renderer and proceeds to lets javac compile the java sources. The step below merges the result of the two actions.
- */
-public final class KotlinRenderClassCompileResult implements BuildAction {
-    public static final KotlinRenderClassCompileResult INSTANCE = new KotlinRenderClassCompileResult();
-
-    private KotlinRenderClassCompileResult() {
-    }
-
-    @Override
-    public Integer apply(Context ctx) {
-        CompileResult kotlincResult = Metas.KOTLINC_RESULT.mustGet(ctx);
-        Optional<CompileResult> javacResult = Metas.JAVAC_RESULT.get(ctx);
-        if (!javacResult.isPresent()) {
-            return kotlincResult.render(ctx);
-        } else {
-            try {
-                javacResult.get().propogateError("javac failed");
-                if (kotlincResult.status() != 0) {
-                    return kotlincResult.status();
-                } else if (javacResult.get().status() != 0) {
-                    // treat all javac statuses as non terminal compile errors.
-                    return 1;
-                }
-                return 0;
-            } finally {
-                kotlincResult.render(ctx);
-            }
-        }
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/ProcessCompileResult.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/ProcessCompileResult.kt
new file mode 100644
index 0000000..01fbf3b
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/ProcessCompileResult.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.actions
+
+
+import io.bazel.ruleskotlin.workers.BuildAction
+import io.bazel.ruleskotlin.workers.Context
+import io.bazel.ruleskotlin.workers.KotlinToolchain
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas
+
+
+/**
+ * Render the result of class compilation. This is a separate step at the moment for mixed mode compilation scenarios. If there is an error in Java sources in
+ * a large mixed mode package the Kotlin errors don't make any sense and overwhelm the console and intellij. The [KotlinMainCompile] step binds a deferred
+ * renderer and proceeds to lets javac compile the java sources. The step below merges the result of the two actions.
+ */
+class ProcessCompileResult(toolchain: KotlinToolchain) : BuildAction("render class compile output", toolchain) {
+    override fun invoke(ctx: Context): Int {
+        val kotlincResult = Metas.KOTLINC_RESULT.mustGet(ctx)
+        val javacResult = Metas.JAVAC_RESULT[ctx]
+
+        return if (javacResult == null) {
+            kotlincResult.render(ctx)
+        } else {
+            try {
+                javacResult.propogateError("javac failed")
+                if (kotlincResult.status() != 0) {
+                    return kotlincResult.status()
+                } else if (javacResult.status() != 0) {
+                    // treat all javac statuses as non terminal compile errors.
+                    return 1
+                }
+                0
+            } finally {
+                kotlincResult.render(ctx)
+            }
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.java
deleted file mode 100644
index 9d705b8..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.utils;
-
-import com.google.devtools.build.lib.view.proto.Deps;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-public class JdepsParser {
-    private final String filename;
-    private final String packageSuffix;
-    private final Predicate<String> isImplicit;
-
-    private final Map<String, Deps.Dependency.Builder> depMap = new HashMap<>();
-    private final Set<String> packages = new HashSet<>();
-
-    private JdepsParser(String filename, Predicate<String> isImplicit) {
-        this.filename = filename;
-        this.packageSuffix = " (" + filename + ")";
-        this.isImplicit = isImplicit;
-    }
-
-    private void consumeJarLine(String classJarPath, Deps.Dependency.Kind kind) {
-        Path path = Paths.get(classJarPath);
-
-        // ignore absolute files, -- jdk jar paths etc.
-        // only process jar files
-        if (!(path.isAbsolute() || !classJarPath.endsWith(".jar"))) {
-            Deps.Dependency.Builder entry = depMap.computeIfAbsent(classJarPath, (key) -> {
-                Deps.Dependency.Builder depBuilder = Deps.Dependency.newBuilder();
-                depBuilder.setPath(classJarPath);
-                depBuilder.setKind(kind);
-
-                if (isImplicit.test(classJarPath)) {
-                    depBuilder.setKind(Deps.Dependency.Kind.IMPLICIT);
-                }
-                return depBuilder;
-            });
-
-            // don't flip an implicit dep.
-            if (entry.getKind() != Deps.Dependency.Kind.IMPLICIT) {
-                entry.setKind(kind);
-            }
-        }
-    }
-
-    private enum Mode {
-        COLLECT_DEPS,
-        DETERMINE_JDK,
-        COLLECT_PACKAGES_JDK8,
-        COLLECT_PACKAGES_JDK9
-    }
-
-    private Mode mode = Mode.COLLECT_DEPS;
-
-    // maybe simplify this by tokenizing on whitespace and arrows.
-    private void processLine(String line) {
-        String trimmedLine = line.trim();
-        switch (mode) {
-            case COLLECT_DEPS:
-                if (!line.startsWith(" ")) {
-                    String[] parts = line.split(" -> ");
-                    if (parts.length == 2) {
-                        if (!parts[0].equals(filename)) {
-                            throw new RuntimeException("should only get dependencies for dep: " + filename);
-                        }
-                        consumeJarLine(parts[1], Deps.Dependency.Kind.EXPLICIT);
-                    }
-                } else {
-                    mode = Mode.DETERMINE_JDK;
-                    processLine(line);
-                }
-                break;
-            case DETERMINE_JDK:
-                mode = Mode.COLLECT_PACKAGES_JDK8;
-                if (!line.endsWith(packageSuffix)) {
-                    mode = Mode.COLLECT_PACKAGES_JDK9;
-                }
-                processLine(line);
-                break;
-            case COLLECT_PACKAGES_JDK8:
-                if (trimmedLine.endsWith(packageSuffix)) {
-                    packages.add(trimmedLine.substring(0, trimmedLine.length() - packageSuffix.length()));
-                } else if (trimmedLine.startsWith("-> ")) {
-                    // ignore package detail lines, in the jdk8 format these start with arrows.
-                } else throw new RuntimeException("unexpected line while collecting packages: " + line);
-                break;
-            case COLLECT_PACKAGES_JDK9:
-                String[] pkg = trimmedLine.split("\\s+");
-                packages.add(pkg[0]);
-                break;
-        }
-    }
-
-
-    public static Predicate<String> pathSuffixMatchingPredicate(Path directory, String... jars) {
-        String[] suffixes = Stream.of(jars).map(lib -> directory.resolve(lib).toString()).toArray(String[]::new);
-        return (jar) -> {
-            for (String implicitJarsEnding : suffixes) {
-                if (jar.endsWith(implicitJarsEnding)) {
-                    return true;
-                }
-            }
-            return false;
-        };
-    }
-
-    public static Deps.Dependencies parse(String label, String classJar, String classPath, Stream<String> jdepLines, Predicate<String> isImplicit) {
-        String filename = Paths.get(classJar).getFileName().toString();
-        JdepsParser jdepsParser = new JdepsParser(filename, isImplicit);
-        Stream.of(classPath.split(":")).forEach(x -> jdepsParser.consumeJarLine(x, Deps.Dependency.Kind.UNUSED));
-        jdepLines.forEach(jdepsParser::processLine);
-
-        Deps.Dependencies.Builder rootBuilder = Deps.Dependencies.newBuilder();
-        rootBuilder.setSuccess(false);
-        rootBuilder.setRuleLabel(label);
-
-        rootBuilder.addAllContainedPackage(jdepsParser.packages);
-        jdepsParser.depMap.values().forEach(b -> rootBuilder.addDependency(b.build()));
-
-        rootBuilder.setSuccess(true);
-        return rootBuilder.build();
-    }
-}
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.kt
new file mode 100644
index 0000000..dc835dd
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParser.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.utils
+
+import com.google.devtools.build.lib.view.proto.Deps
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.*
+import java.util.function.Predicate
+import java.util.stream.Stream
+
+class JdepsParser private constructor(private val filename: String, private val isImplicit: Predicate<String>) {
+    private val packageSuffix: String = " ($filename)"
+
+    private val depMap = HashMap<String, Deps.Dependency.Builder>()
+    private val packages = HashSet<String>()
+
+    private var mode = Mode.COLLECT_DEPS
+
+    private fun consumeJarLine(classJarPath: String, kind: Deps.Dependency.Kind) {
+        val path = Paths.get(classJarPath)
+
+        // ignore absolute files, -- jdk jar paths etc.
+        // only process jar files
+        if (!(path.isAbsolute || !classJarPath.endsWith(".jar"))) {
+            val entry = depMap.computeIfAbsent(classJarPath) {
+                val depBuilder = Deps.Dependency.newBuilder()
+                depBuilder.path = classJarPath
+                depBuilder.kind = kind
+
+                if (isImplicit.test(classJarPath)) {
+                    depBuilder.kind = Deps.Dependency.Kind.IMPLICIT
+                }
+                depBuilder
+            }
+
+            // don't flip an implicit dep.
+            if (entry.kind != Deps.Dependency.Kind.IMPLICIT) {
+                entry.kind = kind
+            }
+        }
+    }
+
+    private enum class Mode {
+        COLLECT_DEPS,
+        DETERMINE_JDK,
+        COLLECT_PACKAGES_JDK8,
+        COLLECT_PACKAGES_JDK9
+    }
+
+    // maybe simplify this by tokenizing on whitespace and arrows.
+    private fun processLine(line: String) {
+        val trimmedLine = line.trim { it <= ' ' }
+        when (mode) {
+            JdepsParser.Mode.COLLECT_DEPS -> if (!line.startsWith(" ")) {
+                val parts = line.split(" -> ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                if (parts.size == 2) {
+                    if (parts[0] != filename) {
+                        throw RuntimeException("should only get dependencies for dep: " + filename)
+                    }
+                    consumeJarLine(parts[1], Deps.Dependency.Kind.EXPLICIT)
+                }
+            } else {
+                mode = Mode.DETERMINE_JDK
+                processLine(line)
+            }
+            JdepsParser.Mode.DETERMINE_JDK -> {
+                mode = Mode.COLLECT_PACKAGES_JDK8
+                if (!line.endsWith(packageSuffix)) {
+                    mode = Mode.COLLECT_PACKAGES_JDK9
+                }
+                processLine(line)
+            }
+            JdepsParser.Mode.COLLECT_PACKAGES_JDK8 -> when {
+                trimmedLine.endsWith(packageSuffix) -> packages.add(trimmedLine.substring(0, trimmedLine.length - packageSuffix.length))
+                trimmedLine.startsWith("-> ") -> {
+                    // ignore package detail lines, in the jdk8 format these start with arrows.
+                }
+                else -> throw RuntimeException("unexpected line while collecting packages: " + line)
+            }
+            JdepsParser.Mode.COLLECT_PACKAGES_JDK9 -> {
+                val pkg = trimmedLine.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                packages.add(pkg[0])
+            }
+        }
+    }
+
+    companion object {
+        fun pathSuffixMatchingPredicate(directory: Path, vararg jars: String): Predicate<String> {
+            val suffixes = jars.map { directory.resolve(it).toString() }
+            return Predicate { jar ->
+                for (implicitJarsEnding in suffixes) {
+                    if (jar.endsWith(implicitJarsEnding)) {
+                        return@Predicate true
+                    }
+                }
+                false
+            }
+        }
+
+        fun parse(label: String, classJar: String, classPath: String, jdepLines: Stream<String>, isImplicit: Predicate<String>): Deps.Dependencies {
+            val filename = Paths.get(classJar).fileName.toString()
+            val jdepsParser = JdepsParser(filename, isImplicit)
+            Stream.of(*classPath.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()).forEach { x -> jdepsParser.consumeJarLine(x, Deps.Dependency.Kind.UNUSED) }
+            jdepLines.forEach { jdepsParser.processLine(it) }
+
+            val rootBuilder = Deps.Dependencies.newBuilder()
+            rootBuilder.success = false
+            rootBuilder.ruleLabel = label
+
+            rootBuilder.addAllContainedPackage(jdepsParser.packages)
+            jdepsParser.depMap.values.forEach { b -> rootBuilder.addDependency(b.build()) }
+
+            rootBuilder.success = true
+            return rootBuilder.build()
+        }
+    }
+}
\ No newline at end of file
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.java
deleted file mode 100644
index f40d071..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.ruleskotlin.workers.compilers.jvm.utils;
-
-import java.io.*;
-import java.nio.file.Paths;
-import java.util.stream.Collectors;
-
-
-/**
- * Utility class to perform common pre-processing on the compiler output before it is passed onto a delegate
- * PrintStream.
- */
-// The kotlin compiler produces absolute file paths but the intellij plugin expects workspace root relative paths to
-// render errors.
-public abstract class KotlinCompilerOutputProcessor {
-    private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-    // Get the absolute path to ensure the sandbox root is resolved.
-    private final String executionRoot = Paths.get("").toAbsolutePath().toString() + File.separator;
-    final PrintStream delegate;
-
-    private KotlinCompilerOutputProcessor(PrintStream delegate) {
-        this.delegate = delegate;
-    }
-
-    public PrintStream getCollector() {
-        return new PrintStream(byteArrayOutputStream);
-    }
-
-    public static class ForKotlinC extends KotlinCompilerOutputProcessor {
-        public ForKotlinC(PrintStream delegate) {
-            super(delegate);
-        }
-
-        @Override
-        protected boolean processLine(String line) {
-            delegate.println(trimExecutionRootPrefix(line));
-            return true;
-        }
-    }
-
-
-    final String trimExecutionRootPrefix(String toPrint) {
-        // trim off the workspace component
-        if (toPrint.startsWith(executionRoot)) {
-            return toPrint.replaceFirst(executionRoot, "");
-        }
-        return toPrint;
-    }
-
-    protected abstract boolean processLine(String line);
-
-    public void process() {
-        for (String s : new BufferedReader(new InputStreamReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())))
-                .lines().collect(Collectors.toList())) {
-            boolean shouldContinue = processLine(s);
-            if(!shouldContinue) {
-                break;
-            }
-        }
-        delegate.flush();
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.kt
new file mode 100644
index 0000000..899fbc8
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.ruleskotlin.workers.compilers.jvm.utils
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.PrintStream
+import java.nio.file.Paths
+
+
+/**
+ * Utility class to perform common pre-processing on the compiler output before it is passed onto a delegate
+ * PrintStream.
+ */
+// The kotlin compiler produces absolute file paths but the intellij plugin expects workspace root relative paths to
+// render errors.
+abstract class KotlinCompilerOutputProcessor private constructor(internal val delegate: PrintStream) {
+    private val byteArrayOutputStream = ByteArrayOutputStream()
+    // Get the absolute path to ensure the sandbox root is resolved.
+    private val executionRoot = Paths.get("").toAbsolutePath().toString() + File.separator
+
+    val collector: PrintStream = PrintStream(byteArrayOutputStream)
+
+    class ForKotlinC(delegate: PrintStream) : KotlinCompilerOutputProcessor(delegate) {
+        override fun processLine(line: String): Boolean {
+            delegate.println(trimExecutionRootPrefix(line))
+            return true
+        }
+    }
+
+    internal fun trimExecutionRootPrefix(toPrint: String): String {
+        // trim off the workspace component
+        return if (toPrint.startsWith(executionRoot)) {
+            toPrint.replaceFirst(executionRoot.toRegex(), "")
+        } else toPrint
+    }
+
+    protected abstract fun processLine(line: String): Boolean
+
+    fun process() {
+        try {
+            for (s in ByteArrayInputStream(byteArrayOutputStream.toByteArray()).bufferedReader().lineSequence()) {
+                val shouldContinue = processLine(s)
+                if (!shouldContinue) {
+                    break
+                }
+            }
+        } finally {
+            delegate.flush()
+        }
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.java
deleted file mode 100644
index d4e83eb..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.ruleskotlin.workers.utils;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-public final class IOUtils {
-    // sort this one out
-    public static List<String> executeAndWaitOutput(int timeoutSeconds, String... command) {
-        try {
-            ProcessBuilder builder = new ProcessBuilder(command).redirectError(ProcessBuilder.Redirect.INHERIT);
-            Process process = builder.start();
-            ArrayList<String> al = new ArrayList<>();
-            CompletableFuture<Void> streamReader = null;
-            try (BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
-                streamReader = CompletableFuture.runAsync(() -> {
-                    while (true) {
-                        try {
-                            String line = output.readLine();
-                            if (line == null)
-                                break;
-                            al.add(line);
-                        } catch (IOException e) {
-                            throw new UncheckedIOException(e);
-                        }
-                    }
-                });
-                executeAwait(timeoutSeconds, process);
-                return al;
-            } finally {
-                if (streamReader != null && !streamReader.isDone()) {
-                    streamReader.cancel(true);
-                }
-            }
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static int executeAwait(int timeoutSeconds, Process process) throws TimeoutException {
-        try {
-            if (!process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) {
-                throw new TimeoutException();
-            }
-            return process.exitValue();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        } finally {
-            if (process.isAlive()) {
-                process.destroy();
-            }
-        }
-    }
-
-    public static int executeAndAwait(int timeoutSeconds, List<String> args) {
-        BufferedReader is = null;
-        BufferedReader es = null;
-        try {
-            ProcessBuilder builder = new ProcessBuilder(args.toArray(new String[args.size()]));
-            builder.redirectInput(ProcessBuilder.Redirect.PIPE);
-            builder.redirectError(ProcessBuilder.Redirect.PIPE);
-            Process process = builder.start();
-            is = new BufferedReader(new InputStreamReader(process.getInputStream()));
-            es = new BufferedReader(new InputStreamReader(process.getErrorStream()));
-            return executeAwait(timeoutSeconds, process);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            drainStreamTo(System.out, is);
-            drainStreamTo(System.err, es);
-        }
-    }
-
-    private static void drainStreamTo(PrintStream writer, BufferedReader reader) {
-        if (reader != null) {
-            reader.lines().forEach(writer::println);
-            try {
-                reader.close();
-            } catch (IOException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-    }
-
-    public static void executeAndAwaitSuccess(int timeoutSeconds, List<String> command) {
-        int status = executeAndAwait(timeoutSeconds, command);
-        if (status != 0) {
-            throw new RuntimeException("process failed with status: " + status);
-        }
-    }
-
-
-    public static void purgeDirectory(Path directory) throws IOException {
-        File directoryAsFile = directory.toFile();
-        Files.walk(directory)
-                .map(Path::toFile)
-                .sorted((o1, o2) -> -o1.compareTo(o2))
-                .filter(file -> !directoryAsFile.equals(file)) // nasty
-                .forEach(file -> {
-                    assert !file.delete();
-                });
-    }
-
-    public static Throwable getRootCause(Throwable e) {
-        Throwable cause;
-        Throwable result = e;
-
-        while (null != (cause = result.getCause()) && (result != cause)) {
-            result = cause;
-        }
-        return result;
-    }
-}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.kt b/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.kt
new file mode 100644
index 0000000..f743403
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+@file:JvmName("IOUtils")
+
+package io.bazel.ruleskotlin.workers.utils
+
+import java.io.*
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.*
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun executeAndWaitOutput(timeoutSeconds: Int, vararg command: String): List<String> {
+    try {
+        val builder = ProcessBuilder(*command).redirectError(ProcessBuilder.Redirect.INHERIT)
+        val process = builder.start()
+        val al = ArrayList<String>()
+        var streamReader: CompletableFuture<Void>? = null
+        try {
+            BufferedReader(InputStreamReader(process.inputStream)).use { output ->
+                streamReader = CompletableFuture.runAsync {
+                    while (true) {
+                        try {
+                            val line = output.readLine() ?: break
+                            al.add(line)
+                        } catch (e: IOException) {
+                            throw UncheckedIOException(e)
+                        }
+
+                    }
+                }
+                executeAwait(timeoutSeconds, process)
+                return al
+            }
+        } finally {
+            if (streamReader != null && !streamReader!!.isDone) {
+                streamReader!!.cancel(true)
+            }
+        }
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+private fun executeAwait(timeoutSeconds: Int, process: Process): Int {
+    try {
+        if (!process.waitFor(timeoutSeconds.toLong(), TimeUnit.SECONDS)) {
+            throw TimeoutException()
+        }
+        return process.exitValue()
+    } finally {
+        if (process.isAlive) {
+            process.destroy()
+        }
+    }
+}
+
+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()
+    }
+
+    var isr: BufferedReader? = null
+    var esr: BufferedReader? = null
+
+    try {
+        isr = process.inputStream.bufferedReader()
+        esr = process.errorStream.bufferedReader()
+        return executeAwait(timeoutSeconds, process)
+    } finally {
+        isr?.drainTo(System.out)
+        esr?.drainTo(System.err)
+    }
+}
+
+fun executeAndAwaitSuccess(timeoutSeconds: Int, vararg command: String) {
+    val status = executeAndAwait(timeoutSeconds, command.toList())
+    check(status == 0) {
+        "process failed with status: $status"
+    }
+}
+
+private fun BufferedReader.drainTo(pw: PrintStream) {
+    lines().forEach(pw::println); close()
+}
+
+fun Path.purgeDirectory() {
+    toFile().listFiles().forEach { check(it.deleteRecursively()) { "$it could not be deleted" } }
+}
+
+fun Path.resolveVerified(vararg parts: String): File = resolve(Paths.get(parts[0], *Arrays.copyOfRange(parts, 1, parts.size))).verified()
+
+/**
+ * Return a stream of paths that are known to exists relative to this location.
+ */
+fun Path.verifiedRelativeFiles(vararg paths: Path): List<File> = paths.map { relative -> resolve(relative).verified() }
+
+private fun Path.verified(): File = this.toFile().also { check(it.exists()) { "file did not exist: $this" } }
+
+
+val Throwable.rootCause: Throwable
+    get() {
+        var result = this
+        do {
+            val cause = result.cause
+            if (cause != null) result = cause
+        } while (cause != null && result != cause)
+        return result
+    }
diff --git a/kotlin/workers/unittests/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParserTest.java b/kotlin/workers/unittests/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParserTest.java
index b61377c..ec49d86 100644
--- a/kotlin/workers/unittests/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParserTest.java
+++ b/kotlin/workers/unittests/io/bazel/ruleskotlin/workers/compilers/jvm/utils/JdepsParserTest.java
@@ -89,7 +89,7 @@
     private static final String LABEL = "//cloud/qa/integrationtests/pkg/alt";
     private static final String CLASS_JAR = "bazel-bin/something/alt.jar";
 
-    private static final Predicate<String> IS_KOTLIN_IMPLICIT = JdepsParser.pathSuffixMatchingPredicate(
+    private static final Predicate<String> IS_KOTLIN_IMPLICIT = JdepsParser.Companion.pathSuffixMatchingPredicate(
             Paths.get("external", "com_github_jetbrains_kotlin", "lib"),
             "kotlin-stdlib.jar",
             "kotlin-stdlib-jdk7.jar",
@@ -106,7 +106,7 @@
     }
 
     private void testWithFixture(String fixture) throws IOException {
-        Deps.Dependencies result = JdepsParser.parse(LABEL, CLASS_JAR, CLASSPATH.stream().collect(Collectors.joining(":")), Stream.of(fixture.split("\n")), IS_KOTLIN_IMPLICIT);
+        Deps.Dependencies result = JdepsParser.Companion.parse(LABEL, CLASS_JAR, CLASSPATH.stream().collect(Collectors.joining(":")), Stream.of(fixture.split("\n")), IS_KOTLIN_IMPLICIT);
         Assert.assertEquals(LABEL, result.getRuleLabel());
 
         Assert.assertEquals(7, result.getDependencyCount());