mixed-mode rework, allign flags with javabuilder, don't use temp dir - declare class dir, reorganize source.
diff --git a/kotlin/rules/compile.bzl b/kotlin/rules/compile.bzl
index 80c591c..460de38 100644
--- a/kotlin/rules/compile.bzl
+++ b/kotlin/rules/compile.bzl
@@ -40,9 +40,12 @@
         by the caller -- kotlin-reflect could be optional.
       opts: struct containing Kotlin compilation options.
     """
+    compiler_output_base=ctx.actions.declare_directory(ctx.label.name + "." + "kotlinc")
+
     args = [
-        "--label", ctx.label,
-        "--output_classjar", output_jar.path,
+        "--target_label", ctx.label,
+        "--compiler_output_base", compiler_output_base.path,
+        "--output", output_jar.path,
         "--output_jdeps", ctx.outputs.jdeps.path,
         "--classpath", ":".join([f.path for f in compile_jars.to_list()]),
         "--sources", ":".join([f.path for f in ctx.files.srcs]),
@@ -79,7 +82,7 @@
     ctx.action(
         mnemonic = "KotlinCompile",
         inputs = compile_inputs,
-        outputs = [output_jar, ctx.outputs.jdeps],
+        outputs = [output_jar, ctx.outputs.jdeps, compiler_output_base],
         executable = ctx.executable._kotlinw,
         execution_requirements = {"supports-workers": "1"},
         arguments = ["@" + args_file.path],
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java
index 14b9db3..6ee7f1b 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BazelWorker.java
@@ -17,7 +17,7 @@
 
 
 import com.google.devtools.build.lib.worker.WorkerProtocol;
-import io.bazel.ruleskotlin.workers.compilers.jvm.utils.Utils;
+import io.bazel.ruleskotlin.workers.utils.IOUtils;
 
 import java.io.*;
 import java.nio.file.Files;
@@ -135,7 +135,7 @@
     }
 
     private boolean wasInterrupted(Throwable e) {
-        Throwable cause = Utils.getRootCause(e);
+        Throwable cause = IOUtils.getRootCause(e);
         if (cause instanceof InterruptedException
                 || cause instanceof InterruptedIOException) {
             output.println("Terminating worker due to interrupt signal");
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/BuildAction.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java
similarity index 85%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/BuildAction.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java
index 7039fb5..79dcd52 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/BuildAction.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/BuildAction.java
@@ -13,9 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers.compilers.jvm.actions;
+package io.bazel.ruleskotlin.workers;
 
-import io.bazel.ruleskotlin.workers.compilers.jvm.Context;
 
 import java.util.function.Function;
 
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java
new file mode 100644
index 0000000..49ea365
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/CompileResult.java
@@ -0,0 +1,120 @@
+/*
+ * 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/compilers/jvm/Context.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java
similarity index 66%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Context.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java
index 6ceb27d..5bb4fad 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Context.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Context.java
@@ -13,17 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers.compilers.jvm;
+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<Flag, String> args = new EnumMap<>(Flag.class);
+    private final EnumMap<Flags, String> args = new EnumMap<>(Flags.class);
     private final Map<Meta<?>, Object> meta = new HashMap<>();
 
-    private static final Map<String, Flag> ALL_FIELDS_MAP = Arrays.stream(Flag.values()).collect(Collectors.toMap(x -> x.name, x -> x));
-    private static final Flag[] MANDATORY_FIELDS = Arrays.stream(Flag.values()).filter(x -> x.mandatory).toArray(Flag[]::new);
+    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) {
@@ -33,27 +35,27 @@
         for (int i = 0; i < args.size() / 2; i++) {
             String flag = args.get(i * 2);
             String value = args.get((i * 2) + 1);
-            Flag field = ALL_FIELDS_MAP.get(flag);
+            Flags field = ALL_FIELDS_MAP.get(flag);
             if (field == null) {
                 throw new RuntimeException("unrecognised arg: " + flag);
             }
             this.args.put(field, value);
         }
 
-        for (Flag mandatoryField : MANDATORY_FIELDS) {
+        for (Flags mandatoryField : MANDATORY_FIELDS) {
             if (!this.args.containsKey(mandatoryField)) {
                 throw new RuntimeException("mandatory arg missing: " + mandatoryField.name);
             }
         }
     }
 
-    static Context from(List<String> args) {
+    public static Context from(List<String> args) {
         return new Context(args);
     }
 
-    public EnumMap<Flag, String> copyOfArgsContaining(Flag... fields) {
-        EnumMap<Flag, String> result = new EnumMap<>(Flag.class);
-        for (Flag field : fields) {
+    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);
@@ -62,7 +64,15 @@
         return result;
     }
 
-    String get(Flag field) {
+    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);
     }
 
@@ -70,6 +80,7 @@
     <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/compilers/jvm/Flag.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java
similarity index 67%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Flag.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java
index f827d1f..7aaa65a 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Flag.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Flags.java
@@ -13,14 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers.compilers.jvm;
+package io.bazel.ruleskotlin.workers;
 
-public enum Flag {
-    LABEL("--label", null, true),
-    OUTPUT_CLASSJAR("--output_classjar", "-d", true),
+
+public enum Flags {
+    // flags that line up with the java builder.
+    LABEL(JavaBuilderFlags.TARGET_LABEL.flag, null, true),
+    OUTPUT_CLASSJAR(JavaBuilderFlags.OUTPUT.flag, null, true),
+    SOURCES(JavaBuilderFlags.SOURCES.flag, null, true),
+    CLASSPATH(JavaBuilderFlags.CLASSPATH.flag, "-cp", true),
+
+    // flags that could be aligned with the java builder.
     OUTPUT_JDEPS("--output_jdeps", null, true),
-    CLASSPATH("--classpath", "-cp", true),
-    SOURCES("--sources", null, true),
+    COMPILER_OUTPUT_BASE("--compiler_output_base", null, true),
+
+    // flags for kotlin.
     KOTLIN_API_VERSION("--kotlin_api_version", "-api-version", false),
     KOTLIN_LANGUAGE_VERSION("--kotlin_language_version", "-language-version", false),
     KOTLIN_JVM_TARGET("--kotlin_jvm_target", "-jvm-target", false);
@@ -29,7 +36,7 @@
     public final String kotlinFlag;
     final boolean mandatory;
 
-    Flag(String name, String kotlinName, boolean mandatory) {
+    Flags(String name, String kotlinName, boolean mandatory) {
         this.name = name;
         this.kotlinFlag = kotlinName;
         this.mandatory = mandatory;
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java
new file mode 100644
index 0000000..dcecc40
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/JavaBuilderFlags.java
@@ -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;
+
+/**
+ * Flags used by the java builder.
+ */
+@SuppressWarnings("unused")
+public enum JavaBuilderFlags {
+    TARGET_LABEL("--target_label"),
+    CLASSPATH("--classpath"),
+    JAVAC_OPTS("--javacopts"),
+    DEPENDENCIES("--dependencies"),
+    DIRECT_DEPENDENCIES("--direct_dependencies"),
+    DIRECT_DEPENDENCY("--direct_dependency"),
+    INDIRECT_DEPENDENCY("--indirect_dependency"),
+    STRICT_JAVA_DEPS("--strict_java_deps"),
+    OUTPUT_DEPS_PROTO("--output_deps_proto"),
+    DEPS_ARTIFACTS("--deps_artifacts"),
+    REDUCE_CLASSPATH("--reduce_classpath"),
+    SOURCEGEN_DIR("--sourcegendir"),
+    GENERATED_SOURCES_OUTPUT("--generated_sources_output"),
+    OUTPUT_MANIFEST_PROTO("--output_manifest_proto"),
+    SOURCES("--sources"),
+    SOURCE_ROOTS("--source_roots"),
+    SOURCE_JARS("--source_jars"),
+    SOURCE_PATH("--sourcepath"),
+    BOOT_CLASSPATH("--bootclasspath"),
+    PROCESS_PATH("--processorpath"),
+    PROCESSORS("--processors"),
+    EXT_CLASSPATH("--extclasspath"),
+    EXT_DIR("--extdir"),
+    OUTPUT("--output"),
+    NATIVE_HEADER_OUTPUT("--native_header_output"),
+    CLASSDIR("--classdir"),
+    TEMPDIR("--tempdir"),
+    GENDIR("--gendir"),
+    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;
+    }
+}
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinPreloadedCompilerBuilder.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java
similarity index 68%
rename from kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinPreloadedCompilerBuilder.java
rename to kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java
index f08cf98..4f8e5c1 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinPreloadedCompilerBuilder.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/KotlinToolchain.java
@@ -13,42 +13,42 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.bazel.ruleskotlin.workers.compilers.jvm.utils;
+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.File;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.lang.reflect.Method;
 import java.nio.file.Paths;
-import java.util.List;
 import java.util.function.BiFunction;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
-public final class KotlinPreloadedCompilerBuilder {
+public final class KotlinToolchain {
     private static final Object[] NO_ARGS = new Object[]{};
+    private final ClassLoader classLoader;
 
-    private static final List<File> PRELOAD_JARS = Stream.concat(
-            Locations.KOTLIN_REPO.verifiedRelativeFiles(Paths.get("lib", "kotlin-compiler.jar")),
-            Locations.JAVA_HOME.verifiedRelativeFiles(Paths.get("lib", "tools.jar"))
-    ).collect(Collectors.toList());
+    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
+     * 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 static BiFunction<String[], PrintStream,Integer> build() {
+    public KotlinCompiler kotlinCompiler() {
         try {
-            ClassLoader classLoader = ClassPreloadingUtils.preloadClasses(
-                    PRELOAD_JARS,
-                    Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE,
-                    Thread.currentThread().getContextClassLoader(),
-                    null
-            );
-
             Class<?> compilerClass = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
             Class<?> exitCodeClass = classLoader.loadClass("org.jetbrains.kotlin.cli.common.ExitCode");
 
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java
new file mode 100644
index 0000000..94a0712
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/Meta.java
@@ -0,0 +1,67 @@
+/*
+ * 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/compilers/jvm/KotlinJvmBuilder.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java
index 71b881e..d6bdf85 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/KotlinJvmBuilder.java
@@ -16,24 +16,35 @@
 package io.bazel.ruleskotlin.workers.compilers.jvm;
 
 
-import io.bazel.ruleskotlin.workers.BazelWorker;
-import io.bazel.ruleskotlin.workers.CommandLineProgram;
-import io.bazel.ruleskotlin.workers.compilers.jvm.actions.BuildAction;
-import io.bazel.ruleskotlin.workers.compilers.jvm.actions.GenerateJdepsFile;
-import io.bazel.ruleskotlin.workers.compilers.jvm.actions.KotlinCreateClassJar;
-import io.bazel.ruleskotlin.workers.compilers.jvm.actions.KotlinMainCompile;
+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 static final BuildAction[] compileActions = new BuildAction[] {
-            KotlinMainCompile.INSTANCE,
-            KotlinCreateClassJar.INSTANCE,
-            GenerateJdepsFile.INSTANCE,
-    };
+    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) {
@@ -41,7 +52,7 @@
         Integer exitCode = 0;
         for (BuildAction action : compileActions) {
             exitCode = action.apply(context);
-            if(exitCode != 0)
+            if (exitCode != 0)
                 break;
         }
         return exitCode;
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
index f90e031..c877564 100644
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Locations.java
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Locations.java
@@ -19,6 +19,8 @@
 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 {
@@ -38,8 +40,8 @@
         /**
          * Return a stream of paths that are known to exists relative to this location.
          */
-        public final Stream<File> verifiedRelativeFiles(Path... paths) {
-            return Stream.of(paths).map(relative -> verified(path.resolve(relative)));
+        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) {
diff --git a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Meta.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Meta.java
deleted file mode 100644
index f6f0c9c..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Meta.java
+++ /dev/null
@@ -1,43 +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.util.Optional;
-
-/**
- * Meta is a key to some compilation state, it is stored in a {@link Context}. A meta is meant for setting up state for other actions.
- */
-public final class Meta<T> {
-    // if present contains the directory that classes were compiled to.
-    public static final Meta<File> COMPILE_TO_DIRECTORY = new Meta<>("compile_to_jar");
-
-    private final String id;
-
-    private Meta(String id) {
-        this.id = id;
-    }
-
-    public Optional<T> get(Context ctx) {
-        return Optional.ofNullable(ctx.get(this));
-    }
-
-    public void bind(Context ctx, T value) {
-        if (ctx.putIfAbsent(this, value) != null) {
-            throw new RuntimeException("attempting to change bound meta variable " + id);
-        }
-    }
-}
\ 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
new file mode 100644
index 0000000..eb8ff54
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/Metas.java
@@ -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;
+
+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/actions/CreateOutputJar.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.java
new file mode 100644
index 0000000..aba021e
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/CreateOutputJar.java
@@ -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.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/GenerateJdepsFile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/GenerateJdepsFile.java
index dfcc204..683cce5 100644
--- 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
@@ -16,10 +16,11 @@
 package io.bazel.ruleskotlin.workers.compilers.jvm.actions;
 
 import com.google.devtools.build.lib.view.proto.Deps;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Context;
+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.compilers.jvm.utils.Utils;
+import io.bazel.ruleskotlin.workers.utils.IOUtils;
 
 import java.io.FileOutputStream;
 import java.nio.file.Files;
@@ -28,7 +29,8 @@
 import java.util.List;
 import java.util.function.Predicate;
 
-import static io.bazel.ruleskotlin.workers.compilers.jvm.Flag.*;
+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();
@@ -52,7 +54,7 @@
                 output = OUTPUT_JDEPS.get(ctx);
         Deps.Dependencies jdepsContent;
         try {
-            List<String> jdepLines = Utils.waitForOutput(new String[]{JDEPS_PATH, "-cp", classPath, classJar}, System.err);
+            List<String> jdepLines = IOUtils.executeAndWaitOutput(10, JDEPS_PATH, "-cp", classPath, classJar);
             jdepsContent = JdepsParser.parse(
                     LABEL.get(ctx),
                     classJar,
@@ -61,7 +63,7 @@
                     IS_KOTLIN_IMPLICIT
             );
         } catch (Exception e) {
-            throw new RuntimeException("error reading or parsing jdeps file", Utils.getRootCause(e));
+            throw new RuntimeException("error reading or parsing jdeps file", IOUtils.getRootCause(e));
         }
 
         try {
@@ -71,7 +73,7 @@
                 jdepsContent.writeTo(fileOutputStream);
             }
         } catch (Exception e) {
-            throw new RuntimeException("error writing out jdeps file", Utils.getRootCause(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/Initialize.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.java
new file mode 100644
index 0000000..2e175bc
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/Initialize.java
@@ -0,0 +1,108 @@
+/*
+ * 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/JavaMainCompile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.java
new file mode 100644
index 0000000..da6da78
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/JavaMainCompile.java
@@ -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.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/KotlinCreateClassJar.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinCreateClassJar.java
deleted file mode 100644
index 4edc9e9..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinCreateClassJar.java
+++ /dev/null
@@ -1,46 +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.compilers.jvm.Context;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Flag;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Locations;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Meta;
-import io.bazel.ruleskotlin.workers.compilers.jvm.utils.Utils;
-
-/**
- * If classes for the main artifact were compiled to an intermediate temp directory turn them into a jar and clean up.
- */
-public final class KotlinCreateClassJar implements BuildAction {
-    public static final KotlinCreateClassJar INSTANCE = new KotlinCreateClassJar();
-    private static final String JAR_TOOL_PATH = Locations.JAVA_HOME.resolveVerified("bin", "jar").toString();
-
-    private KotlinCreateClassJar() {}
-
-    @Override
-    public Integer apply(Context ctx) {
-        Meta.COMPILE_TO_DIRECTORY.get(ctx).ifPresent((classDirectory) -> {
-            try {
-                String classJarPath = Flag.OUTPUT_CLASSJAR.get(ctx);
-                Utils.waitForSuccess(new String[]{JAR_TOOL_PATH, "cf", classJarPath, "-C", classDirectory.toString(), "."}, System.err);
-                Utils.deleteDirectory(classDirectory.toPath());
-            } 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/KotlinMainCompile.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinMainCompile.java
index 6e69f9e..ad467fe 100644
--- 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
@@ -15,54 +15,40 @@
  */
 package io.bazel.ruleskotlin.workers.compilers.jvm.actions;
 
-import io.bazel.ruleskotlin.workers.compilers.jvm.Context;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Flag;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Locations;
-import io.bazel.ruleskotlin.workers.compilers.jvm.Meta;
+import io.bazel.ruleskotlin.workers.*;
+import io.bazel.ruleskotlin.workers.compilers.jvm.Metas;
 import io.bazel.ruleskotlin.workers.compilers.jvm.utils.KotlinCompilerOutputProcessor;
-import io.bazel.ruleskotlin.workers.compilers.jvm.utils.KotlinPreloadedCompilerBuilder;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.Files;
+
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.List;
-import java.util.function.BiFunction;
 
 /**
  * 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 {
-    public static final KotlinMainCompile INSTANCE = new KotlinMainCompile(KotlinPreloadedCompilerBuilder.build());
+    private final KotlinToolchain.KotlinCompiler kotlinCompiler;
 
-    private static final String
-            JAVAC_PATH = Locations.JAVA_HOME.resolveVerified("bin", "javac").toString();
-
-    private static final String
-            X_COMPILE_JAVA_FLAG = "-Xcompile-java",
-            X_JAVAC_ARGUMENTS_FLAG = "-Xjavac-arguments",
-            X_USE_JAVAC_FLAG = "-Xuse-javac";
+    public KotlinMainCompile(KotlinToolchain toolchains) {
+        this.kotlinCompiler = toolchains.kotlinCompiler();
+    }
 
     /**
      * Default fields that are directly mappable to kotlin compiler args.
      */
-    private static final Flag[] COMPILE_MAPPED_FLAGS = new Flag[]{
-            Flag.OUTPUT_CLASSJAR,
-            Flag.CLASSPATH,
-            Flag.KOTLIN_API_VERSION,
-            Flag.KOTLIN_LANGUAGE_VERSION,
-            Flag.KOTLIN_JVM_TARGET
+    private static final Flags[] COMPILE_MAPPED_FLAGS = new Flags[]{
+            Flags.CLASSPATH,
+            Flags.KOTLIN_API_VERSION,
+            Flags.KOTLIN_LANGUAGE_VERSION,
+            Flags.KOTLIN_JVM_TARGET
     };
 
-    private final BiFunction<String[], PrintStream, Integer> compiler;
-
-    private KotlinMainCompile(BiFunction<String[], PrintStream, Integer> compiler) {
-        this.compiler = compiler;
-    }
-
     /**
      * Evaluate the compilation context and add Metadata to the ctx if needed.
      *
@@ -70,46 +56,32 @@
      */
     private static String[] setupCompileContext(Context ctx) {
         List<String> args = new ArrayList<>();
-        EnumMap<Flag, String> compileMappedFields = ctx.copyOfArgsContaining(COMPILE_MAPPED_FLAGS);
-        String[] sources = Flag.SOURCES.get(ctx).split(":");
-
-        for (String source : sources) {
-            if (source.endsWith(".java")) {
-                try {
-                    // Redirect the kotlin and java compilers to a temp directory.
-                    File temporaryClassOutputDirectory = Files.createTempDirectory("kotlinCompile").toFile();
-                    Meta.COMPILE_TO_DIRECTORY.bind(ctx, temporaryClassOutputDirectory);
-                    compileMappedFields.put(Flag.OUTPUT_CLASSJAR, temporaryClassOutputDirectory.toString());
-                    Collections.addAll(args,
-                            X_COMPILE_JAVA_FLAG,
-                            X_USE_JAVAC_FLAG + "=" + JAVAC_PATH,
-                            X_JAVAC_ARGUMENTS_FLAG + "=-d=" + temporaryClassOutputDirectory.toString());
-                    break;
-                } catch (IOException e) {
-                    throw new RuntimeException("could not create temp directory for kotlin compile operation", e);
-                }
-            }
-        }
-        compileMappedFields.forEach((field, arg) -> Collections.addAll(args, field.kotlinFlag, arg));
-        Collections.addAll(args, sources);
+        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 = KotlinCompilerOutputProcessor.delegatingTo(System.out);
-        try {
-            Integer exitCode = compiler.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
+        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;
-            } else {
-                throw new RuntimeException("KotlinMainCompile returned terminal error code: " + exitCode);
-            }
-        } finally {
+            }));
+            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/KotlinRenderClassCompileResult.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinRenderClassCompileResult.java
new file mode 100644
index 0000000..76c8674
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/actions/KotlinRenderClassCompileResult.java
@@ -0,0 +1,59 @@
+/*
+ * 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/utils/KotlinCompilerOutputProcessor.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/KotlinCompilerOutputProcessor.java
index a8c18ba..f40d071 100644
--- 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
@@ -17,6 +17,7 @@
 
 import java.io.*;
 import java.nio.file.Paths;
+import java.util.stream.Collectors;
 
 
 /**
@@ -25,29 +26,51 @@
  */
 // The kotlin compiler produces absolute file paths but the intellij plugin expects workspace root relative paths to
 // render errors.
-public class KotlinCompilerOutputProcessor {
-    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+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;
-    private final PrintStream delegate;
-
+    final PrintStream delegate;
 
     private KotlinCompilerOutputProcessor(PrintStream delegate) {
         this.delegate = delegate;
     }
 
-    public static KotlinCompilerOutputProcessor delegatingTo(PrintStream delegate) {
-        return new KotlinCompilerOutputProcessor(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() {
-        new BufferedReader(new InputStreamReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())))
-                .lines()
-                .forEach(line -> delegate.println(line.replace(executionRoot, "")));
+        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/Utils.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/Utils.java
deleted file mode 100644
index 3303d26..0000000
--- a/kotlin/workers/src/io/bazel/ruleskotlin/workers/compilers/jvm/utils/Utils.java
+++ /dev/null
@@ -1,85 +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.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public final class Utils {
-    public static List<String> waitForOutput(String[] command, PrintStream err) {
-        try {
-            ProcessBuilder builder = new ProcessBuilder(command);
-            Process process = builder.start();
-            try (BufferedReader processError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
-                 BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
-                while (true) {
-                    String line = processError.readLine();
-                    if (line == null)
-                        break;
-                    err.println(line);
-                }
-                if (process.waitFor() != 0) {
-                    throw new RuntimeException("non-zero return: " + process.exitValue());
-                }
-                return output.lines().collect(Collectors.toList());
-            }
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public static void waitForSuccess(String[] command, PrintStream err) {
-        try {
-            ProcessBuilder builder = new ProcessBuilder(command);
-            Process process = builder.start();
-            try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
-                while (true) {
-                    String line = in.readLine();
-                    if (line == null)
-                        break;
-                    err.println(line);
-                }
-                if (process.waitFor() != 0) {
-                    throw new RuntimeException("non-zero return: " + process.exitValue());
-                }
-            }
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-
-    public static void deleteDirectory(Path directory) throws IOException {
-        Files.walk(directory)
-                .map(Path::toFile)
-                .sorted((o1, o2) -> -o1.compareTo(o2))
-                .forEach(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.java b/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.java
new file mode 100644
index 0000000..d4e83eb
--- /dev/null
+++ b/kotlin/workers/src/io/bazel/ruleskotlin/workers/utils/IOUtils.java
@@ -0,0 +1,133 @@
+/*
+ * 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/tests/smoke/BUILD b/tests/smoke/BUILD
index 794b455..99a28d7 100644
--- a/tests/smoke/BUILD
+++ b/tests/smoke/BUILD
@@ -102,8 +102,13 @@
     data=glob(["data/*"]),
 )
 
-kotlin_binary(
+kotlin_library(
     name = "hellojava",
-    srcs = ["hellojava/HelloWorld.kt", "hellojava/MessageHolder.java"],
-    main_class = "hellojava.HelloWorldKt"
+    srcs = glob(["hellojava/*.kt", "hellojava/*.java"]),
+)
+
+kotlin_library(
+    name = "hellojava_withmerge",
+    resources = glob(["resourcejar/**"]),
+    srcs = glob(["hellojava/*.kt", "hellojava/*.java"]),
 )
\ No newline at end of file
diff --git a/tests/smoke/basic_tests.py b/tests/smoke/basic_tests.py
index 610a96c..3f97712 100644
--- a/tests/smoke/basic_tests.py
+++ b/tests/smoke/basic_tests.py
@@ -58,8 +58,25 @@
         self.buildLaunchExpectingSuccess("propagation_rt_via_runtime_deps_consumer")
 
     def test_mixed_mode_compilation(self):
-        self.buildLaunchExpectingSuccess("hellojava")
+        jar = self.buildJarGetZipFile("hellojava", "jar")
+        self.assertJarContains(
+            jar,
+            "hellojava/HelloWorldJava.class",
+            "hellojava/MessageHolderKotlin.class",
+            "hellojava/MessageHolder.class",
+            "hellojava/HelloWorldKt.class"
+        )
 
+    def test_mixed_mode_compilation_with_merge(self):
+        jar = self.buildJarGetZipFile("hellojava_withmerge", "jar")
+        self.assertJarContains(
+            jar,
+            "hellojava/HelloWorldJava.class",
+            "hellojava/MessageHolderKotlin.class",
+            "hellojava/MessageHolder.class",
+            "hellojava/HelloWorldKt.class",
+            "tests/smoke/resourcejar/pkg/file.txt"
+        )
         # re-enable this test, and ensure the srcjar includes java sources when mixed mode.
         # def test_srcjar(self):
         #     jar = self.buildJarGetZipFile("testresources", "srcjar")
diff --git a/tests/smoke/hellojava/Another.java b/tests/smoke/hellojava/HelloWorldJava.java
similarity index 81%
copy from tests/smoke/hellojava/Another.java
copy to tests/smoke/hellojava/HelloWorldJava.java
index a8c2eda..4aae5ae 100644
--- a/tests/smoke/hellojava/Another.java
+++ b/tests/smoke/hellojava/HelloWorldJava.java
@@ -16,5 +16,8 @@
 
 package hellojava;
 
-public class Another {
+public class HelloWorldJava {
+    public static void main(String[] args) {
+        System.out.println(MessageHolderKotlin.INSTANCE.hello());
+    }
 }
diff --git a/tests/smoke/hellojava/Another.java b/tests/smoke/hellojava/MessageHolderKotlin.kt
similarity index 87%
rename from tests/smoke/hellojava/Another.java
rename to tests/smoke/hellojava/MessageHolderKotlin.kt
index a8c2eda..d7e5112 100644
--- a/tests/smoke/hellojava/Another.java
+++ b/tests/smoke/hellojava/MessageHolderKotlin.kt
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package hellojava
 
-package hellojava;
-
-public class Another {
-}
+object MessageHolderKotlin {
+    fun hello() = "hello from kotlin"
+}
\ No newline at end of file
diff --git a/tests/smoke/propagation/CompileTimeDependent.java b/tests/smoke/propagation/CompileTimeDependent.java
index e505025..6d47706 100644
--- a/tests/smoke/propagation/CompileTimeDependent.java
+++ b/tests/smoke/propagation/CompileTimeDependent.java
@@ -17,7 +17,6 @@
 
 import org.junit.Test;
 
-
 public class CompileTimeDependent {
     @Test
     public void justSoIcanUseTheTestAnnotation() {