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() {