diff --git a/kotlin/internal/jvm/compile.bzl b/kotlin/internal/jvm/compile.bzl index b1344bd6b..ded6c9317 100644 --- a/kotlin/internal/jvm/compile.bzl +++ b/kotlin/internal/jvm/compile.bzl @@ -28,7 +28,7 @@ load( # INTERNAL ACTIONS ##################################################################################################### def _fold_jars_action(ctx, rule_kind, output_jar, input_jars): - """Set up an action to Fold the input jars into a normalized ouput jar.""" + """Set up an action to Fold the input jars into a normalized output jar.""" args = ctx.actions.args() args.add_all([ "--normalize", @@ -114,12 +114,8 @@ def _build_resourcejar_action(ctx): ) return resources_jar_output -# MAIN ACTIONS ######################################################################################################### -def _output_dir_path(ctx, aspect, dir_name): - return "_kotlinc/%s_%s/%s_%s" % (ctx.label.name, aspect, ctx.label.name, dir_name) - -def _partition_srcs(srcs): - """Partition sources for the jvm aspect.""" +def _partitioned_srcs(srcs): + """Creates a struct of srcs sorted by extension. Fails if there are no sources.""" kt_srcs = [] java_srcs = [] src_jars = [] @@ -132,72 +128,156 @@ def _partition_srcs(srcs): elif f.path.endswith(".srcjar"): src_jars.append(f) + if not kt_srcs and not java_srcs and not src_jars: + fail("no sources provided") + return struct( - kt = depset(kt_srcs), - java = depset(java_srcs), - all_srcs = depset(kt_srcs + java_srcs), - src_jars = depset(src_jars), + kt = kt_srcs, + java = java_srcs, + all_srcs = kt_srcs + java_srcs, + src_jars = src_jars, ) -def kt_jvm_compile_action(ctx, rule_kind, output_jar): - """This macro sets up a compile action for a Kotlin jar. +def _output_dir_path(ctx, aspect, dir_name): + return "_kotlinc/%s_%s/%s_%s" % (ctx.label.name, aspect, ctx.label.name, dir_name) - Args: - rule_kind: The rule kind --e.g., `kt_jvm_library`. - output_jar: The jar file that this macro will use as the output. - Returns: - A struct containing the providers JavaInfo (`java`) and `kt` (KtJvmInfo). This struct is not intended to be - used as a legacy provider -- rather the caller should transform the result. - """ - toolchain = ctx.toolchains[_TOOLCHAIN_TYPE] +def _compiler_directories(ctx): + """Creates a dict of the necessary compiler directories for generating compile actions""" + return dict( + classdir = _output_dir_path(ctx, "jvm", "classes"), + kotlin_generated_classdir = _output_dir_path(ctx, "jvm", "generated_classes"), + sourcegendir = _output_dir_path(ctx, "jvm", "sourcegenfiles"), + tempdir = _output_dir_path(ctx, "jvm", "temp"), + ) - srcs = _partition_srcs(ctx.files.srcs) - if not srcs.kt and not srcs.java and not srcs.src_jars: - fail("no sources provided") +def _compiler_toolchains(ctx): + """Creates a struct of the relevant compilation toolchains""" + return struct( + kt = ctx.toolchains[_TOOLCHAIN_TYPE], + ) - # TODO extract and move this into common. Need to make it generic first. - friends = getattr(ctx.attr, "friends", []) - deps = [d[JavaInfo] for d in friends + ctx.attr.deps] + [toolchain.jvm_stdlibs] - compile_jars = java_common.merge(deps).transitive_compile_time_jars +def _compiler_friends(ctx, friends): + """Creates a struct of friends meta data""" + # TODO extract and move this into common. Need to make it generic first. if len(friends) == 0: - module_name = _utils.derive_module_name(ctx) - friend_paths = depset() + return struct( + targets = [], + module_name = _utils.derive_module_name(ctx), + paths = [], + ) elif len(friends) == 1: if friends[0][_KtJvmInfo] == None: fail("only kotlin dependencies can be friends") elif ctx.attr.module_name: fail("if friends has been set then module_name cannot be provided") else: - friend_paths = depset([j.path for j in friends[0][JavaInfo].compile_jars.to_list()]) - module_name = friends[0][_KtJvmInfo].module_name + return struct( + targets = friends, + paths = friends[0][JavaInfo].compile_jars, + module_name = friends[0][_KtJvmInfo].module_name, + ) else: fail("only one friend is possible") - classes_directory = _output_dir_path(ctx, "jvm", "classes") - generated_classes_directory = _output_dir_path(ctx, "jvm", "generated_classes") - sourcegen_directory = _output_dir_path(ctx, "jvm", "sourcegenfiles") - temp_directory = _output_dir_path(ctx, "jvm", "temp") +def _compiler_deps(toolchains, friend, deps): + """Encapsulates compiler dependency metadata.""" + dep_infos = [d[JavaInfo] for d in friend.targets + deps] + [toolchains.kt.jvm_stdlibs] + return struct( + deps = dep_infos, + compile_jars = depset( + transitive = [ + d.compile_jars + for d in dep_infos + ] + [ + d.transitive_compile_time_jars + for d in dep_infos + ], + ), + ) - args = _utils.init_args(ctx, rule_kind, module_name) +def _java_info_to_compile_jars(target): + i = target[JavaInfo] + if i == None: + return None + return i.compile_jars - args.add("--classdir", classes_directory) - args.add("--sourcegendir", sourcegen_directory) - args.add("--tempdir", temp_directory) - args.add("--kotlin_generated_classdir", generated_classes_directory) +# MAIN ACTIONS ######################################################################################################### - args.add("--output", output_jar) - args.add("--kotlin_output_jdeps", ctx.outputs.jdeps) - args.add("--kotlin_output_srcjar", ctx.outputs.srcjar) +def kt_jvm_compile_action(ctx, rule_kind, output_jar): + """This macro sets up a compile action for a Kotlin jar. + + Args: + rule_kind: The rule kind --e.g., `kt_jvm_library`. + output_jar: The jar file that this macro will use as the output. + Returns: + A struct containing the providers JavaInfo (`java`) and `kt` (KtJvmInfo). This struct is not intended to be + used as a legacy provider -- rather the caller should transform the result. + """ + toolchains = _compiler_toolchains(ctx) + dirs = _compiler_directories(ctx) + srcs = _partitioned_srcs(ctx.files.srcs) + friend = _compiler_friends(ctx, friends = getattr(ctx.attr, "friends", [])) + compile_deps = _compiler_deps(toolchains, friend, deps = ctx.attr.deps) + plugins = _plugin_mappers.targets_to_kt_plugins(ctx.attr.plugins + ctx.attr.deps) + _run_kt_builder_action( + ctx = ctx, + rule_kind = rule_kind, + toolchains = toolchains, + dirs = dirs, + srcs = srcs, + friend = friend, + compile_deps = compile_deps, + plugins = plugins, + outputs = { + "output": output_jar, + "kotlin_output_jdeps": ctx.outputs.jdeps, + "kotlin_output_srcjar": ctx.outputs.srcjar, + } + ) + + return struct( + java = JavaInfo( + output_jar = ctx.outputs.jar, + compile_jar = ctx.outputs.jar, + source_jar = ctx.outputs.srcjar, + # jdeps = ctx.outputs.jdeps, + deps = compile_deps.deps, + runtime_deps = [d[JavaInfo] for d in ctx.attr.runtime_deps], + exports = [d[JavaInfo] for d in getattr(ctx.attr, "exports", [])], + neverlink = getattr(ctx.attr, "neverlink", False), + ), + kt = _KtJvmInfo( + srcs = ctx.files.srcs, + module_name = friend.module_name, + language_version = toolchains.kt.api_version, + # intellij aspect needs this. + outputs = struct( + jdeps = ctx.outputs.jdeps, + jars = [struct( + class_jar = ctx.outputs.jar, + ijar = None, + source_jars = [ctx.outputs.srcjar], + )], + ), + ), + ) - args.add("--kotlin_friend_paths", "\n".join(friend_paths.to_list())) - args.add_all("--classpath", compile_jars) +def _run_kt_builder_action(ctx, rule_kind, toolchains, dirs, srcs, friend, compile_deps, plugins, outputs): + """Creates a KotlinBuilder action invocation.""" + args = _utils.init_args(ctx, rule_kind, friend.module_name) + + for f, path in dirs.items() + outputs.items(): + args.add("--" + f, path) + + args.add_all("--classpath", compile_deps.compile_jars) args.add_all("--sources", srcs.all_srcs, omit_if_empty = True) args.add_all("--source_jars", srcs.src_jars, omit_if_empty = True) + args.add_joined("--kotlin_friend_paths", friend.paths, join_with = "\n") + # Collect and prepare plugin descriptor for the worker. - plugins = _plugin_mappers.targets_to_kt_plugins(ctx.attr.plugins + ctx.attr.deps) args.add_all( "--processors", plugins, @@ -213,57 +293,34 @@ def kt_jvm_compile_action(ctx, rule_kind, output_jar): progress_message = "Compiling Kotlin to JVM %s { kt: %d, java: %d, srcjars: %d }" % ( ctx.label, - len(srcs.kt.to_list()), - len(srcs.java.to_list()), - len(srcs.src_jars.to_list()), + len(srcs.kt), + len(srcs.java), + len(srcs.src_jars), + ) + + tools, input_manifests = ctx.resolve_tools( + tools = [ + toolchains.kt.kotlinbuilder, + toolchains.kt.kotlin_home, + ], ) - tools, _, input_manifests = ctx.resolve_command(tools = [toolchain.kotlinbuilder, toolchain.kotlin_home]) ctx.actions.run( mnemonic = "KotlinCompile", - inputs = depset(ctx.files.srcs, transitive = [compile_jars]), + inputs = depset(ctx.files.srcs, transitive = [compile_deps.compile_jars]), tools = tools, - outputs = [ - output_jar, - ctx.outputs.jdeps, - ctx.outputs.srcjar, - ], - executable = toolchain.kotlinbuilder.files_to_run.executable, + input_manifests = input_manifests, + outputs = [f for f in outputs.values()], + executable = toolchains.kt.kotlinbuilder.files_to_run.executable, execution_requirements = {"supports-workers": "1"}, arguments = [args], progress_message = progress_message, - input_manifests = input_manifests, env = { "LC_CTYPE": "en_US.UTF-8", # For Java source files }, ) - return struct( - java = JavaInfo( - output_jar = ctx.outputs.jar, - compile_jar = ctx.outputs.jar, - source_jar = ctx.outputs.srcjar, - # jdeps = ctx.outputs.jdeps, - deps = deps, - runtime_deps = [d[JavaInfo] for d in ctx.attr.runtime_deps], - exports = [d[JavaInfo] for d in getattr(ctx.attr, "exports", [])], - neverlink = getattr(ctx.attr, "neverlink", False), - ), - kt = _KtJvmInfo( - srcs = ctx.files.srcs, - module_name = module_name, - language_version = toolchain.api_version, - # intelij aspect needs this. - outputs = struct( - jdeps = ctx.outputs.jdeps, - jars = [struct( - class_jar = ctx.outputs.jar, - ijar = None, - source_jars = [ctx.outputs.srcjar], - )], - ), - ), - ) + def kt_jvm_produce_jar_actions(ctx, rule_kind): """Setup The actions to compile a jar and if any resources or resource_jars were provided to merge these in with the diff --git a/kotlin/internal/jvm/impl.bzl b/kotlin/internal/jvm/impl.bzl index 6b84e2a69..c188812c4 100644 --- a/kotlin/internal/jvm/impl.bzl +++ b/kotlin/internal/jvm/impl.bzl @@ -33,7 +33,11 @@ def _make_providers(ctx, providers, transitive_files = depset(order = "default") DefaultInfo( files = depset([ctx.outputs.jar]), runfiles = ctx.runfiles( + # explicitly include data files, otherwise they appear to be missing + files = ctx.files.data, transitive_files = transitive_files, + # continue to use collect_default until proper transitive data collecting is + # implmented. collect_default = True, ), ), diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt index 3cc5c1637..6263ab0dd 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt +++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt @@ -19,8 +19,15 @@ import io.bazel.kotlin.builder.tasks.js.Kotlin2JsTaskExecutor import io.bazel.kotlin.builder.tasks.jvm.KotlinJvmTaskExecutor import io.bazel.kotlin.builder.toolchain.CompilationStatusException import io.bazel.kotlin.builder.toolchain.CompilationTaskContext -import io.bazel.kotlin.builder.utils.* -import io.bazel.kotlin.model.* +import io.bazel.kotlin.builder.utils.ArgMap +import io.bazel.kotlin.builder.utils.ArgMaps +import io.bazel.kotlin.builder.utils.Flag +import io.bazel.kotlin.builder.utils.partitionJvmSources +import io.bazel.kotlin.model.CompilationTaskInfo +import io.bazel.kotlin.model.JsCompilationTask +import io.bazel.kotlin.model.JvmCompilationTask +import io.bazel.kotlin.model.Platform +import io.bazel.kotlin.model.RuleKind import java.io.PrintStream import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -33,88 +40,90 @@ import javax.inject.Singleton @Singleton @Suppress("MemberVisibilityCanBePrivate") class KotlinBuilder @Inject internal constructor( - private val outputProvider: Provider, - private val jvmTaskExecutor: KotlinJvmTaskExecutor, - private val jsTaskExecutor: Kotlin2JsTaskExecutor + private val outputProvider: Provider, + private val jvmTaskExecutor: KotlinJvmTaskExecutor, + private val jsTaskExecutor: Kotlin2JsTaskExecutor ) : CommandLineProgram { - companion object { - @JvmStatic - private val FLAGFILE_RE = Pattern.compile("""^--flagfile=((.*)-(\d+).params)$""").toRegex() - } - - override fun apply(args: List): Int { - val (argMap, context) = buildContext(args) - var success = false - var status = 0 - try { - @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") - when (context.info.platform) { - Platform.JVM -> executeJvmTask(context, argMap) - Platform.JS -> executeJsTask(context, argMap) - Platform.UNRECOGNIZED -> throw IllegalStateException("unrecognized platform: ${context.info}") - } - success = true - } catch (ex: CompilationStatusException) { - System.err.println("Compilation failure: ${ex.message}") - status = ex.status - } catch (throwable: Throwable) { - context.reportUnhandledException(throwable) - throw throwable - } finally { - context.finalize(success) - } - return status - } - - private fun buildContext(args: List): Pair { - check(args.isNotEmpty()) { "expected at least a single arg got: ${args.joinToString(" ")}" } - val (flagFileName, primaryOutputPath, _) = - checkNotNull(FLAGFILE_RE.matchEntire(args[0])) { "invalid flagfile ${args[0]}" }.destructured - val argMap = Files.readAllLines(Paths.get(flagFileName), StandardCharsets.UTF_8).let(ArgMaps::from) - val info = buildTaskInfo(argMap).also { - it.primaryOutputPath = primaryOutputPath - }.build() - val context = CompilationTaskContext(info, outputProvider.get()) - return Pair(argMap, context) - } - - /** - * Declares the flags used by the java builder. - */ - private enum class JavaBuilderFlags(override val flag: String) : Flag { - 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"), - PROCESSOR_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"); + companion object { + @JvmStatic + private val FLAGFILE_RE = Pattern.compile("""^--flagfile=((.*)-(\d+).params)$""").toRegex() + } + + override fun apply(args: List): Int { + val (argMap, context) = buildContext(args) + var success = false + var status = 0 + try { + @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") + when (context.info.platform) { + Platform.JVM -> executeJvmTask(context, argMap) + Platform.JS -> executeJsTask(context, argMap) + Platform.UNRECOGNIZED -> throw IllegalStateException( + "unrecognized platform: ${context.info}") + } + success = true + } catch (ex: CompilationStatusException) { + System.err.println("Compilation failure: ${ex.message}") + status = ex.status + } catch (throwable: Throwable) { + context.reportUnhandledException(throwable) + } finally { + context.finalize(success) } + return status + } + + private fun buildContext(args: List): Pair { + check(args.isNotEmpty()) { "expected at least a single arg got: ${args.joinToString(" ")}" } + val (flagFileName, primaryOutputPath, _) = + checkNotNull( + FLAGFILE_RE.matchEntire(args[0])) { "invalid flagfile ${args[0]}" }.destructured + val argMap = + Files.readAllLines(Paths.get(flagFileName), StandardCharsets.UTF_8).let(ArgMaps::from) + val info = buildTaskInfo(argMap).also { + it.primaryOutputPath = primaryOutputPath + }.build() + val context = CompilationTaskContext(info, outputProvider.get()) + return Pair(argMap, context) + } + + /** + * Declares the flags used by the java builder. + */ + private enum class JavaBuilderFlags(override val flag: String) : Flag { + 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"), + PROCESSOR_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"); + } private enum class KotlinBuilderFlags(override val flag: String) : Flag { MODULE_NAME("--kotlin_module_name"), @@ -133,97 +142,104 @@ class KotlinBuilder @Inject internal constructor( TASK_ID("--kotlin_task_id"); } - private fun buildTaskInfo(argMap: ArgMap): CompilationTaskInfo.Builder = - with(CompilationTaskInfo.newBuilder()) { - addAllDebug(argMap.mandatory(KotlinBuilderFlags.DEBUG)) - - label = argMap.mandatorySingle(JavaBuilderFlags.TARGET_LABEL) - argMap.mandatorySingle(JavaBuilderFlags.RULE_KIND).split("_").also { - check(it.size == 3 && it[0] == "kt") { "invalid rule kind $it" } - platform = checkNotNull(Platform.valueOf(it[1].toUpperCase())) { - "unrecognized platform ${it[1]}" - } - ruleKind = checkNotNull(RuleKind.valueOf(it[2].toUpperCase())) { - "unrecognized rule kind ${it[2]}" - } - } - moduleName = argMap.mandatorySingle(KotlinBuilderFlags.MODULE_NAME).also { - check(it.isNotBlank()) { "--kotlin_module_name should not be blank" } - } - passthroughFlags = argMap.optionalSingle(KotlinBuilderFlags.PASSTHROUGH_FLAGS) - argMap.optional(KotlinBuilderFlags.FRIEND_PATHS)?.let(::addAllFriendPaths) - toolchainInfoBuilder.commonBuilder.apiVersion = argMap.mandatorySingle(KotlinBuilderFlags.API_VERSION) - toolchainInfoBuilder.commonBuilder.languageVersion = argMap.mandatorySingle(KotlinBuilderFlags.LANGUAGE_VERSION) - this + private fun buildTaskInfo(argMap: ArgMap): CompilationTaskInfo.Builder = + with(CompilationTaskInfo.newBuilder()) { + addAllDebug(argMap.mandatory(KotlinBuilderFlags.DEBUG)) + + label = argMap.mandatorySingle(JavaBuilderFlags.TARGET_LABEL) + argMap.mandatorySingle(JavaBuilderFlags.RULE_KIND).split("_").also { + check(it.size == 3 && it[0] == "kt") { "invalid rule kind $it" } + platform = checkNotNull(Platform.valueOf(it[1].toUpperCase())) { + "unrecognized platform ${it[1]}" + } + ruleKind = checkNotNull(RuleKind.valueOf(it[2].toUpperCase())) { + "unrecognized rule kind ${it[2]}" + } } + moduleName = argMap.mandatorySingle(KotlinBuilderFlags.MODULE_NAME).also { + check(it.isNotBlank()) { "--kotlin_module_name should not be blank" } + } + passthroughFlags = argMap.optionalSingle(KotlinBuilderFlags.PASSTHROUGH_FLAGS) + argMap.optional(KotlinBuilderFlags.FRIEND_PATHS)?.let(::addAllFriendPaths) + toolchainInfoBuilder.commonBuilder.apiVersion = + argMap.mandatorySingle(KotlinBuilderFlags.API_VERSION) + toolchainInfoBuilder.commonBuilder.languageVersion = + argMap.mandatorySingle(KotlinBuilderFlags.LANGUAGE_VERSION) + this + } + + private fun executeJsTask(context: CompilationTaskContext, argMap: ArgMap) = + buildJsTask(context.info, argMap).let { jsTask -> + context.whenTracing { printProto("js task input", jsTask) } + jsTaskExecutor.execute(context, jsTask) + } + + private fun buildJsTask(info: CompilationTaskInfo, argMap: ArgMap): JsCompilationTask = + with(JsCompilationTask.newBuilder()) { + this.info = info + with(inputsBuilder) { + addAllLibraries(argMap.mandatory(KotlinBuilderFlags.JS_LIBRARIES)) + addAllKotlinSources(argMap.mandatory(JavaBuilderFlags.SOURCES)) + } + with(outputsBuilder) { + js = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT) + jar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_JS_JAR) + srcjar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_SRCJAR) + } + addAllPassThroughFlags(argMap.mandatory(KotlinBuilderFlags.JS_PASSTHROUGH_FLAGS)) + build() + } + + private fun executeJvmTask(context: CompilationTaskContext, argMap: ArgMap) { + val task = buildJvmTask(context.info, argMap) + context.whenTracing { + printProto("jvm task message", task) + } + jvmTaskExecutor.execute(context, task) + } - private fun executeJsTask(context: CompilationTaskContext, argMap: ArgMap) = - buildJsTask(context.info, argMap).let { jsTask -> - context.whenTracing { printProto("js task input", jsTask) } - jsTaskExecutor.execute(context, jsTask) + private fun buildJvmTask(info: CompilationTaskInfo, argMap: ArgMap): JvmCompilationTask = + JvmCompilationTask.newBuilder().let { root -> + root.info = info + + with(root.outputsBuilder) { + jar = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT) + srcjar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_SRCJAR) + + argMap.optionalSingle(KotlinBuilderFlags.OUTPUT_JDEPS)?.apply { jdeps = this } } - private fun buildJsTask(info: CompilationTaskInfo, argMap: ArgMap): JsCompilationTask = - with(JsCompilationTask.newBuilder()) { - this.info = info - with(inputsBuilder) { - addAllLibraries(argMap.mandatory(KotlinBuilderFlags.JS_LIBRARIES)) - addAllKotlinSources(argMap.mandatory(JavaBuilderFlags.SOURCES)) - } - with(outputsBuilder) { - js = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT) - jar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_JS_JAR) - srcjar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_SRCJAR) - } - addAllPassThroughFlags(argMap.mandatory(KotlinBuilderFlags.JS_PASSTHROUGH_FLAGS)) - build() + with(root.directoriesBuilder) + { + classes = argMap.mandatorySingle(JavaBuilderFlags.CLASSDIR) + generatedClasses = argMap.mandatorySingle(KotlinBuilderFlags.GENERATED_CLASSDIR) + temp = argMap.mandatorySingle(JavaBuilderFlags.TEMPDIR) + generatedSources = argMap.mandatorySingle(JavaBuilderFlags.SOURCEGEN_DIR) } - private fun executeJvmTask(context: CompilationTaskContext, argMap: ArgMap) { - val task = buildJvmTask(context.info, argMap) - context.whenTracing { - printProto("jvm task message", task) + with(root.inputsBuilder) + { + addAllClasspath(argMap.mandatory(JavaBuilderFlags.CLASSPATH)) + putAllIndirectDependencies(argMap.labelDepMap(JavaBuilderFlags.DIRECT_DEPENDENCY)) + putAllIndirectDependencies(argMap.labelDepMap(JavaBuilderFlags.INDIRECT_DEPENDENCY)) + + addAllProcessors(argMap.optional(JavaBuilderFlags.PROCESSORS) ?: emptyList()) + addAllProcessorpaths(argMap.optional(JavaBuilderFlags.PROCESSOR_PATH) ?: emptyList()) + + argMap.optional(JavaBuilderFlags.SOURCES)?.iterator()?.partitionJvmSources( + { addKotlinSources(it) }, + { addJavaSources(it) } + ) + argMap.optional(JavaBuilderFlags.SOURCE_JARS)?.also { + addAllSourceJars(it) + } } - jvmTaskExecutor.execute(context, task) - } - private fun buildJvmTask(info: CompilationTaskInfo, argMap: ArgMap): JvmCompilationTask = - JvmCompilationTask.newBuilder().let { root -> - root.info = info - - with(root.outputsBuilder) { - jar = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT) - jdeps = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_JDEPS) - srcjar = argMap.mandatorySingle(KotlinBuilderFlags.OUTPUT_SRCJAR) - } - - with(root.directoriesBuilder) { - classes = argMap.mandatorySingle(JavaBuilderFlags.CLASSDIR) - generatedClasses = argMap.mandatorySingle(KotlinBuilderFlags.GENERATED_CLASSDIR) - temp = argMap.mandatorySingle(JavaBuilderFlags.TEMPDIR) - generatedSources = argMap.mandatorySingle(JavaBuilderFlags.SOURCEGEN_DIR) - } - - with(root.inputsBuilder) { - addAllClasspath(argMap.mandatory(JavaBuilderFlags.CLASSPATH)) - putAllIndirectDependencies(argMap.labelDepMap(JavaBuilderFlags.DIRECT_DEPENDENCY)) - putAllIndirectDependencies(argMap.labelDepMap(JavaBuilderFlags.INDIRECT_DEPENDENCY)) - - addAllProcessors(argMap.optional(JavaBuilderFlags.PROCESSORS) ?: emptyList()) - addAllProcessorpaths(argMap.optional(JavaBuilderFlags.PROCESSOR_PATH) ?: emptyList()) - - argMap.optional(JavaBuilderFlags.SOURCES)?.iterator()?.partitionJvmSources( - { addKotlinSources(it) }, - { addJavaSources(it) } - ) - argMap.optional(JavaBuilderFlags.SOURCE_JARS)?.also { - addAllSourceJars(it) - } - } - - with(root.infoBuilder) { - toolchainInfoBuilder.jvmBuilder.jvmTarget = argMap.mandatorySingle(KotlinBuilderFlags.JVM_TARGET) - } - root.build() + with(root.infoBuilder) + { + toolchainInfoBuilder.jvmBuilder.jvmTarget = + argMap.mandatorySingle(KotlinBuilderFlags.JVM_TARGET) } + root.build() + } } diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/CompilationArgs.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/CompilationArgs.kt new file mode 100644 index 000000000..7f9d5062c --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/CompilationArgs.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.bazel.kotlin.builder.tasks.jvm + +import java.nio.file.FileSystem +import java.nio.file.FileSystems +import java.nio.file.Path + +/** + * CompilationArgs collects the arguments for executing the Kotlin compiler. + */ +internal class CompilationArgs( + val args: MutableList = mutableListOf(), + private val dfs: FileSystem = FileSystems.getDefault() +) { + + fun givenNotEmpty(value: String, map: (String) -> Collection): CompilationArgs { + if (value.isNotEmpty()) { + return values(map(value)) + } + return this + } + + fun absolutePaths( + paths: Collection, + toArgs: (Sequence) -> String + ): CompilationArgs { + return value(toArgs(paths.asSequence().map { dfs.getPath(it) }.map(Path::toAbsolutePath))) + } + + fun value(value: String): CompilationArgs { + args.add(value) + return this + } + + fun flag(value: String): CompilationArgs { + args.add(value) + return this + } + + fun flag(flag: String, value: String): CompilationArgs { + args.add(flag) + args.add(value) + return this + } + + fun values(values: Collection): CompilationArgs { + args.addAll(values) + return this + } + + fun list(): List = args.toList() +} diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt index 293520725..06f654423 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt +++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt @@ -15,6 +15,7 @@ */ package io.bazel.kotlin.builder.tasks.jvm +import io.bazel.kotlin.builder.toolchain.CompilationStatusException import io.bazel.kotlin.builder.toolchain.CompilationTaskContext import io.bazel.kotlin.builder.toolchain.KotlinCompilerPluginArgsEncoder import io.bazel.kotlin.builder.toolchain.KotlinToolchain @@ -36,13 +37,38 @@ class KotlinJvmTaskExecutor @Inject internal constructor( private val jDepsGenerator: JDepsGenerator ) { fun execute(context: CompilationTaskContext, task: JvmCompilationTask) { - val preprocessedTask = task.preProcessingSteps(context, pluginArgsEncoder, compiler) + val preprocessedTask = task + .preProcessingSteps(context) + .runAnnotationProcessors(context, pluginArgsEncoder, compiler) + context.execute("compile classes") { - preprocessedTask.compileAll(context, compiler, javaCompiler) + preprocessedTask.apply { + var kotlinError: CompilationStatusException? = null + var result: List = listOf() + // skip compilation if there are no kt files. + if (inputs.kotlinSourcesCount > 0) { + context.execute("kotlinc") { + result = try { + compileKotlin(context, compiler, printOnFail = false) + } catch (ex: CompilationStatusException) { + kotlinError = ex + ex.lines + } + } + } + try { + context.execute("javac") { javaCompiler.compile(context, this) } + } finally { + result.apply(context::printCompilerOutput) + kotlinError?.also { throw it } + } + } } context.execute("create jar") { preprocessedTask.createOutputJar() } context.execute("produce src jar") { preprocessedTask.produceSourceJar() } - context.execute("generate jdeps") { jDepsGenerator.generateJDeps(preprocessedTask) } + if (preprocessedTask.outputs.jdeps.isNotEmpty()) { + context.execute("generate jdeps") { jDepsGenerator.generateJDeps(preprocessedTask) } + } } } diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/compilation_task.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/compilation_task.kt index d9dcbfb2f..72cb65e3a 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/compilation_task.kt +++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/compilation_task.kt @@ -17,63 +17,49 @@ // Provides extensions for the JvmCompilationTask protocol buffer. package io.bazel.kotlin.builder.tasks.jvm -import io.bazel.kotlin.builder.toolchain.CompilationStatusException import io.bazel.kotlin.builder.toolchain.CompilationTaskContext import io.bazel.kotlin.builder.toolchain.KotlinCompilerPluginArgsEncoder import io.bazel.kotlin.builder.toolchain.KotlinToolchain import io.bazel.kotlin.builder.utils.IS_JVM_SOURCE_FILE -import io.bazel.kotlin.builder.utils.addAll import io.bazel.kotlin.builder.utils.bazelRuleKind import io.bazel.kotlin.builder.utils.ensureDirectories import io.bazel.kotlin.builder.utils.jars.JarCreator import io.bazel.kotlin.builder.utils.jars.SourceJarCreator import io.bazel.kotlin.builder.utils.jars.SourceJarExtractor -import io.bazel.kotlin.builder.utils.joinedClasspath import io.bazel.kotlin.builder.utils.partitionJvmSources import io.bazel.kotlin.model.JvmCompilationTask import java.io.File import java.nio.file.Files +import java.nio.file.Path import java.nio.file.Paths /** * Return a list with the common arguments. */ -internal fun JvmCompilationTask.getCommonArgs(): MutableList { - val args = mutableListOf() - val friendPaths = info.friendPathsList.map { Paths.get(it).toAbsolutePath() } - val cp = inputs.joinedClasspath - .split(File.pathSeparator) - .map { Paths.get(it).toAbsolutePath() } - .joinToString(File.pathSeparator) - args.addAll( - "-cp", cp, - "-api-version", info.toolchainInfo.common.apiVersion, - "-language-version", info.toolchainInfo.common.languageVersion, - "-jvm-target", info.toolchainInfo.jvm.jvmTarget, - "-Xfriend-paths=${friendPaths.joinToString(X_FRIENDS_PATH_SEPARATOR)}" - ) - args - .addAll("-module-name", info.moduleName) - .addAll("-d", directories.classes) +internal fun JvmCompilationTask.commonArgs(): CompilationArgs = baseArgs() + .absolutePaths(info.friendPathsList) { + "-Xfriend-paths=${it.joinToString(X_FRIENDS_PATH_SEPARATOR)}" + } + .flag("-d", directories.classes) + .givenNotEmpty(info.passthroughFlags) { it.split(" ") } - info.passthroughFlags?.takeIf { it.isNotBlank() }?.also { args.addAll(it.split(" ")) } - return args -} +internal fun JvmCompilationTask.baseArgs(): CompilationArgs = CompilationArgs() + .flag("-cp").absolutePaths(inputs.classpathList) { + it.map(Path::toString).joinToString(File.pathSeparator) + } + .flag("-api-version", info.toolchainInfo.common.apiVersion) + .flag("-language-version", info.toolchainInfo.common.languageVersion) + .flag("-jvm-target", info.toolchainInfo.jvm.jvmTarget) + .flag("-module-name", info.moduleName) -internal fun JvmCompilationTask.preProcessingSteps( - context: CompilationTaskContext, - pluginArgsEncoder: KotlinCompilerPluginArgsEncoder, - compiler: KotlinToolchain.KotlincInvoker -): JvmCompilationTask { +internal fun JvmCompilationTask.preProcessingSteps(context: CompilationTaskContext): JvmCompilationTask { ensureDirectories( directories.temp, directories.generatedSources, - directories.generatedClasses + directories.generatedClasses, + directories.classes ) - val taskWithAdditionalSources = context.execute("expand sources") { expandWithSourceJarSources() } - return context.execute({ - "kapt (${inputs.processorsList.joinToString(", ")})" - }) { taskWithAdditionalSources.runAnnotationProcessors(context, pluginArgsEncoder, compiler) } + return context.execute("expand sources") { expandWithSourceJarSources() } } internal fun JvmCompilationTask.produceSourceJar() { @@ -99,42 +85,35 @@ internal fun JvmCompilationTask.produceSourceJar() { } } -internal fun JvmCompilationTask.runAnnotationProcessor( - context: CompilationTaskContext, - pluginArgsEncoder: KotlinCompilerPluginArgsEncoder, - compiler: KotlinToolchain.KotlincInvoker, - printOnSuccess: Boolean = true -): List { - check(inputs.processorsList.isNotEmpty()) { "method called without annotation processors" } - return getCommonArgs().let { args -> - args.addAll(pluginArgsEncoder.encode(context, this)) - args.addAll(inputs.kotlinSourcesList) - args.addAll(inputs.javaSourcesList) - context.executeCompilerTask(args, compiler::compile, printOnSuccess = printOnSuccess) - } -} - internal fun JvmCompilationTask.runAnnotationProcessors( context: CompilationTaskContext, pluginArgsEncoder: KotlinCompilerPluginArgsEncoder, compiler: KotlinToolchain.KotlincInvoker -): JvmCompilationTask = - if (inputs.processorsList.isEmpty()) { - this - } else { - runAnnotationProcessor( - context, - pluginArgsEncoder, - compiler, - printOnSuccess = context.whenTracing { false } ?: true).let { outputLines -> - // if tracing is enabled the output should be formatted in a special way, if we aren't tracing then any - // compiler output would make it's way to the console as is. - context.whenTracing { - printLines("kapt output", outputLines) - } - expandWithGeneratedSources() - } +): JvmCompilationTask { + if (inputs.processorsList.isEmpty()) { + return this + } else { + return context.execute("kapt (${inputs.processorsList.joinToString(", ")})") + { + commonArgs() + .values(pluginArgsEncoder.encode(context, this)) + .values(inputs.kotlinSourcesList) + .values(inputs.javaSourcesList).list().let { args -> + context.executeCompilerTask( + args, + compiler::compile, + printOnSuccess = context.whenTracing { false } ?: true) + }.let { outputLines -> + // if tracing is enabled the output should be formatted in a special way, if we aren't + // tracing then any compiler output would make it's way to the console as is. + context.whenTracing { + printLines("kapt output", outputLines) + } + return@let expandWithGeneratedSources() + } } + } +} /** * Produce the primary output jar. @@ -151,32 +130,6 @@ internal fun JvmCompilationTask.createOutputJar() = it.execute() } -internal fun JvmCompilationTask.compileAll( - context: CompilationTaskContext, - compiler: KotlinToolchain.KotlincInvoker, - javaCompiler: JavaCompiler -) { - ensureDirectories( - directories.classes - ) - var kotlinError: CompilationStatusException? = null - var result: List? = null - context.execute("kotlinc") { - result = try { - compileKotlin(context, compiler, printOnFail = false) - } catch (ex: CompilationStatusException) { - kotlinError = ex - ex.lines - } - } - try { - context.execute("javac") { javaCompiler.compile(context, this) } - } finally { - checkNotNull(result).also(context::printCompilerOutput) - kotlinError?.also { throw it } - } -} - /** * Compiles Kotlin sources to classes. Does not compile Java sources. */ @@ -185,11 +138,12 @@ internal fun JvmCompilationTask.compileKotlin( compiler: KotlinToolchain.KotlincInvoker, printOnFail: Boolean = true ) = - getCommonArgs().let { args -> - args.addAll(inputs.javaSourcesList) - args.addAll(inputs.kotlinSourcesList) - context.executeCompilerTask(args, compiler::compile, printOnFail = printOnFail) - } + commonArgs() + .values(inputs.javaSourcesList) + .values(inputs.kotlinSourcesList) + .list().let { args -> + return@let context.executeCompilerTask(args, compiler::compile, printOnFail = printOnFail) + } /** * If any srcjars were provided expand the jars sources and create a new [JvmCompilationTask] with the @@ -220,14 +174,14 @@ internal fun JvmCompilationTask.expandWithGeneratedSources(): JvmCompilationTask .iterator() ) -internal fun JvmCompilationTask.expandWithSources(sources: Iterator): JvmCompilationTask = +private fun JvmCompilationTask.expandWithSources(sources: Iterator): JvmCompilationTask = updateBuilder { builder -> sources.partitionJvmSources( { builder.inputsBuilder.addKotlinSources(it) }, { builder.inputsBuilder.addJavaSources(it) }) } -internal fun JvmCompilationTask.updateBuilder( +private fun JvmCompilationTask.updateBuilder( block: (JvmCompilationTask.Builder) -> Unit ): JvmCompilationTask = toBuilder().let { diff --git a/src/main/kotlin/io/bazel/kotlin/builder/utils/ArgMap.kt b/src/main/kotlin/io/bazel/kotlin/builder/utils/ArgMap.kt index 01988d587..3f13d79f7 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/utils/ArgMap.kt +++ b/src/main/kotlin/io/bazel/kotlin/builder/utils/ArgMap.kt @@ -19,61 +19,97 @@ package io.bazel.kotlin.builder.utils import java.io.File class ArgMap(private val map: Map>) { - /** - * Get the mandatory single value from a key - */ - fun mandatorySingle(key: String): String = - optionalSingle(key) ?: throw IllegalArgumentException("$key is not optional") - - fun labelDepMap(key: String) = optional(key)?.asSequence()?.windowed(2, 2)?.map { it[0] to it[1] }?.toMap() ?: emptyMap() - - fun optionalSingle(key: String): String? = - optional(key)?.let { - when (it.size) { - 0 -> throw IllegalArgumentException("$key did not have a value") - 1 -> it[0] - else -> throw IllegalArgumentException("$key should have a single value") - } + /** + * Get the mandatory single value from a key + */ + private fun mandatorySingle(key: String): String = + optionalSingle(key) ?: throw IllegalArgumentException("$key is not optional") + + private fun labelDepMap(key: String) = + optional(key) + ?.asSequence() + ?.windowed(2, 2) + ?.map { it[0] to it[1] } + ?.toMap() + ?: emptyMap() + + private fun optionalSingle(key: String): String? = + optional(key)?.let { + when (it.size) { + 0 -> throw IllegalArgumentException("$key did not have a value") + 1 -> it[0] + else -> throw IllegalArgumentException("$key should have a single value") } + } + + private fun optionalSingleIf(key: String, condition: () -> Boolean): String? { + return if (condition()) { + optionalSingle(key) + } else { + mandatorySingle(key) + } + } + + private fun hasAll(keys: Array): Boolean { + return keys.all { optional(it)?.isNotEmpty() ?: false } + } + + private fun hasAny(keys: Array): Boolean { + return keys.any { optional(it)?.isNotEmpty() ?: false } + } + + private fun mandatory(key: String): List = optional(key) + ?: throw IllegalArgumentException( + "$key is not optional") + + private fun optional(key: String): List? = map[key] + + fun mandatorySingle(key: Flag) = mandatorySingle(key.flag) + fun optionalSingle(key: Flag) = optionalSingle(key.flag) + fun optionalSingleIf(key: Flag, condition: () -> Boolean) = + optionalSingleIf(key.flag, condition) + + fun hasAll(vararg keys: Flag) = hasAll(keys.map(Flag::flag).toTypedArray()) + fun hasAny(vararg keys: Flag) = hasAny(keys.map(Flag::flag).toTypedArray()) + fun mandatory(key: Flag) = mandatory(key.flag) + fun optional(key: Flag) = optional(key.flag) + fun labelDepMap(key: Flag) = labelDepMap(key.flag) - fun mandatory(key: String): List = optional(key) ?: throw IllegalArgumentException("$key is not optional") - fun optional(key: String): List? = map[key] } interface Flag { - val flag: String + val flag: String } -fun ArgMap.mandatorySingle(key: Flag) = mandatorySingle(key.flag) -fun ArgMap.optionalSingle(key: Flag) = optionalSingle(key.flag) -fun ArgMap.mandatory(key: Flag) = mandatory(key.flag) -fun ArgMap.optional(key: Flag) = optional(key.flag) -fun ArgMap.labelDepMap(key: Flag) = labelDepMap(key.flag) - object ArgMaps { - @JvmStatic - fun from(args: List): ArgMap = - mutableMapOf>() - .also { argsToMap(args, it) } - .let(::ArgMap) - - @JvmStatic - fun from(file: File): ArgMap = from(file.reader().readLines()) - - private fun argsToMap(args: List, argMap: MutableMap>, isFlag: (String) -> Boolean = { it.startsWith("--") }) { - var currentKey: String = args.first().also { require(isFlag(it)) { "first arg must be a flag" } } - val currentValue = mutableListOf() - val mergeCurrent = { - argMap.computeIfAbsent(currentKey) { mutableListOf() }.addAll(currentValue) - currentValue.clear() - } - args.drop(1).forEach { - if (it.startsWith("--")) { - mergeCurrent() - currentKey = it - } else { - currentValue.add(it) - } - }.also { mergeCurrent() } + @JvmStatic + fun from(args: List): ArgMap = + mutableMapOf>() + .also { argsToMap(args, it) } + .let(::ArgMap) + + @JvmStatic + fun from(file: File): ArgMap = from(file.reader().readLines()) + + private fun argsToMap( + args: List, + argMap: MutableMap>, + isFlag: (String) -> Boolean = { it.startsWith("--") } + ) { + var currentKey: String = + args.first().also { require(isFlag(it)) { "first arg must be a flag" } } + val currentValue = mutableListOf() + val mergeCurrent = { + argMap.computeIfAbsent(currentKey) { mutableListOf() }.addAll(currentValue) + currentValue.clear() } -} \ No newline at end of file + args.drop(1).forEach { + if (it.startsWith("--")) { + mergeCurrent() + currentKey = it + } else { + currentValue.add(it) + } + }.also { mergeCurrent() } + } +} diff --git a/src/test/kotlin/io/bazel/kotlin/builder/Deps.java b/src/test/kotlin/io/bazel/kotlin/builder/Deps.java index 1793879a3..7f754cfe4 100644 --- a/src/test/kotlin/io/bazel/kotlin/builder/Deps.java +++ b/src/test/kotlin/io/bazel/kotlin/builder/Deps.java @@ -19,12 +19,13 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import io.bazel.kotlin.builder.utils.BazelRunFiles; - -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; +import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; public final class Deps { @SuppressWarnings({"WeakerAccess", "unused"}) @@ -82,12 +83,16 @@ public Dep build() { } } - /** Collect all of the compile jars of all the dependencies. */ + /** + * Collect all of the compile jars of all the dependencies. + */ public static Stream classpathOf(Dep... dependencies) { return Stream.of(dependencies).flatMap(it -> it.compileJars().stream()); } - /** Import a single dep. Similar to a `kt_jvm_import` or a `kt_js_import`. */ + /** + * Import a single dep. Similar to a `kt_jvm_import` or a `kt_js_import`. + */ public static Dep importJar(String label, String compileJar) { return Dep.builder() .label(label) @@ -95,6 +100,35 @@ public static Dep importJar(String label, String compileJar) { ImmutableList.of(BazelRunFiles.resolveVerified(compileJar).getAbsolutePath())) .build(); } + + /** + * Reads dependency path from jvm_args for a given label, provided that the label has been added + * as a jvm property. + *

+ * See src/test/kotlin/io/bazel/kotlin/defs.bzl for an example of proper label and runfile + * passing. + * + * @param label The label of the resource expected to be included + * @return Dep reprenseting the resource + * @throws IllegalArgumentException if the label does not exist. + */ + protected static Dep fromLabel(String label) { + // jvm properties do not allow slashes or :. + String key = label.replaceAll("/", ".").replaceAll(":", "."); + Properties properties = System.getProperties(); + Preconditions.checkArgument(properties.containsKey(key), + String.format("Unable to find %s in properties:\n%s", key, + properties.keySet() + .stream() + .map(Object::toString) + .collect(Collectors.joining("\n")))); + return Dep.builder() + .label(label) + .compileJars( + ImmutableList.of( + BazelRunFiles.resolveVerified(properties.getProperty(key)).getPath())) + .build(); + } } @AutoValue diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinAbstractTestBuilder.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinAbstractTestBuilder.java index ab0e5e03e..0569e975e 100644 --- a/src/test/kotlin/io/bazel/kotlin/builder/KotlinAbstractTestBuilder.java +++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinAbstractTestBuilder.java @@ -17,9 +17,19 @@ import io.bazel.kotlin.builder.toolchain.CompilationStatusException; import io.bazel.kotlin.builder.toolchain.CompilationTaskContext; -import io.bazel.kotlin.model.*; - -import java.io.*; +import io.bazel.kotlin.model.CompilationTaskInfo; +import io.bazel.kotlin.model.KotlinToolchainInfo; +import io.bazel.kotlin.model.Platform; +import io.bazel.kotlin.model.RuleKind; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -38,7 +48,7 @@ abstract class KotlinAbstractTestBuilder { private static final Path BAZEL_TEST_DIR = - Paths.get(Objects.requireNonNull(System.getenv("TEST_TMPDIR"))); + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")); private static final AtomicInteger counter = new AtomicInteger(0); private final CompilationTaskInfo.Builder infoBuilder = CompilationTaskInfo.newBuilder(); @@ -80,7 +90,7 @@ public final void resetForNext() { .setLanguageVersion("1.2")) .setJvm(KotlinToolchainInfo.Jvm.newBuilder().setJvmTarget("1.8"))); try { - this.instanceRoot = Files.createDirectory(BAZEL_TEST_DIR.resolve(Paths.get(label))); + this.instanceRoot = Files.createTempDirectory(BAZEL_TEST_DIR, label); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinJvmTestBuilder.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinJvmTestBuilder.java index 051cb3660..c9c9f66a3 100644 --- a/src/test/kotlin/io/bazel/kotlin/builder/KotlinJvmTestBuilder.java +++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinJvmTestBuilder.java @@ -19,39 +19,27 @@ import com.google.common.collect.ImmutableList; import io.bazel.kotlin.builder.Deps.AnnotationProcessor; import io.bazel.kotlin.builder.Deps.Dep; +import io.bazel.kotlin.builder.toolchain.CompilationTaskContext; import io.bazel.kotlin.builder.toolchain.KotlinToolchain; import io.bazel.kotlin.model.CompilationTaskInfo; import io.bazel.kotlin.model.JvmCompilationTask; - import java.util.EnumSet; import java.util.HashSet; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; public final class KotlinJvmTestBuilder extends KotlinAbstractTestBuilder { + @SuppressWarnings({"unused", "WeakerAccess"}) public static Dep - KOTLIN_ANNOTATIONS = - Dep.importJar( - "kotlin-annotations", - "external/com_github_jetbrains_kotlin/lib/annotations-13.0.jar"), - KOTLIN_STDLIB = - Dep.importJar( - "kotlin-stdlib", "external/com_github_jetbrains_kotlin/lib/kotlin-stdlib.jar"), - KOTLIN_STDLIB_JDK7 = - Dep.importJar( - "kotlin-stdlib-jdk7", - "external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk7.jar"), - KOTLIN_STDLIB_JDK8 = - Dep.importJar( - "kotlin-stdlib-jdk8", - "external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk8.jar"); + KOTLIN_ANNOTATIONS = Dep.fromLabel("@com_github_jetbrains_kotlin//:annotations"), + KOTLIN_STDLIB = Dep.fromLabel("@com_github_jetbrains_kotlin//:kotlin-stdlib"), + KOTLIN_STDLIB_JDK7 = Dep.fromLabel("@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7"), + KOTLIN_STDLIB_JDK8 = Dep.fromLabel("@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8"); private static final JvmCompilationTask.Builder taskBuilder = JvmCompilationTask.newBuilder(); - private static final KotlinBuilderComponent component = - DaggerKotlinBuilderComponent.builder().toolchain(KotlinToolchain.createToolchain()).build(); - private static final EnumSet ALL_DIRECTORY_TYPES = EnumSet.of( DirectoryType.SOURCES, @@ -73,12 +61,6 @@ void setupForNext(CompilationTaskInfo.Builder taskInfo) { .setTemp(directory(DirectoryType.TEMP).toAbsolutePath().toString()) .setGeneratedClasses( directory(DirectoryType.GENERATED_CLASSES).toAbsolutePath().toString()); - - taskBuilder - .getOutputsBuilder() - .setJar(instanceRoot().resolve("jar_file.jar").toAbsolutePath().toString()) - .setJdeps(instanceRoot().resolve("jdeps_file.jdeps").toAbsolutePath().toString()) - .setSrcjar(instanceRoot().resolve("jar_file-sources.jar").toAbsolutePath().toString()); } @Override @@ -86,6 +68,47 @@ public JvmCompilationTask buildTask() { return taskBuilder.build(); } + private TaskBuilder taskBuilderInstance = new TaskBuilder(); + + @SafeVarargs + public final Dep runCompileTask(Consumer... setup) { + return executeTask(component().jvmTaskExecutor()::execute, setup); + } + + private KotlinBuilderComponent component() { + return DaggerKotlinBuilderComponent.builder() + .toolchain(KotlinToolchain.createToolchain()) + .build(); + } + + private Dep executeTask( + BiConsumer executor, + Consumer[] setup) { + resetForNext(); + Stream.of(setup).forEach(it -> it.accept(taskBuilderInstance)); + return runCompileTask( + (taskContext, task) -> { + executor.accept(taskContext, task); + + JvmCompilationTask.Outputs outputs = task.getOutputs(); + assertFilesExist( + Stream.of( + outputs.getJar(), + outputs.getJdeps(), + outputs.getSrcjar()) + .filter(p -> !p.isEmpty()) + .toArray(String[]::new) + ); + + return Dep.builder() + .label(taskBuilder.getInfo().getLabel()) + .compileJars(ImmutableList.of(outputs.getJar())) + .runtimeDeps(ImmutableList.copyOf(taskBuilder.getInputs().getClasspathList())) + .sourceJar(taskBuilder.getOutputs().getSrcjar()) + .build(); + }); + } + public class TaskBuilder { public TaskBuilder() {} @@ -123,24 +146,23 @@ public void addDirectDependencies(Dep... dependencies) { Dep.classpathOf(dependencies) .forEach((dependency) -> taskBuilder.getInputsBuilder().addClasspath(dependency)); } - } - private TaskBuilder taskBuilderInstance = new TaskBuilder(); + public TaskBuilder outputSrcJar() { + taskBuilder.getOutputsBuilder() + .setSrcjar(instanceRoot().resolve("jar_file-sources.jar").toAbsolutePath().toString()); + return this; + } - @SafeVarargs - public final Dep runCompileTask(Consumer... setup) { - resetForNext(); - Stream.of(setup).forEach(it -> it.accept(taskBuilderInstance)); - return runCompileTask( - (taskContext, task) -> { - component.jvmTaskExecutor().execute(taskContext, task); - assertFilesExist(task.getOutputs().getJar(), task.getOutputs().getJdeps()); - return Dep.builder() - .label(taskBuilder.getInfo().getLabel()) - .compileJars(ImmutableList.of(taskBuilder.getOutputs().getJar())) - .runtimeDeps(ImmutableList.copyOf(taskBuilder.getInputs().getClasspathList())) - .sourceJar(taskBuilder.getOutputs().getSrcjar()) - .build(); - }); + public TaskBuilder outputJar() { + taskBuilder.getOutputsBuilder() + .setJar(instanceRoot().resolve("jar_file.jar").toAbsolutePath().toString()); + return this; + } + + public TaskBuilder outputJdeps() { + taskBuilder.getOutputsBuilder() + .setJdeps(instanceRoot().resolve("jdeps_file.jdeps").toAbsolutePath().toString()); + return this; + } } } diff --git a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmBasicTest.java b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmBasicTest.java index 75ebc75d6..dc21fafdb 100644 --- a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmBasicTest.java +++ b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmBasicTest.java @@ -18,16 +18,14 @@ import io.bazel.kotlin.builder.Deps; import io.bazel.kotlin.builder.DirectoryType; import io.bazel.kotlin.builder.KotlinJvmTestBuilder; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.function.Consumer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import static com.google.common.truth.Truth.assertThat; @@ -35,22 +33,53 @@ public class KotlinBuilderJvmBasicTest { private static final KotlinJvmTestBuilder ctx = new KotlinJvmTestBuilder(); + private static final Consumer SETUP_NORMALIZATION_TEST_SOURCES = + ctx -> { + ctx.addSource("AClass.kt", "package something;\n" + "class AClass{}"); + ctx.addSource("BClass.kt", "package something;\n" + "class BClass{}"); + ctx.outputJar().outputSrcJar(); + }; + @Test public void testSimpleMixedModeCompile() { ctx.runCompileTask( c -> { c.addSource("AClass.kt", "package something;" + "class AClass{}"); c.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}"); + c.outputJar().outputSrcJar(); }); ctx.assertFilesExist( DirectoryType.CLASSES, "something/AClass.class", "something/AnotherClass.class"); } + @Test + public void testGeneratesJDeps() { + ctx.runCompileTask( + c -> { + c.addSource("AClass.kt", "package something;" + "class AClass{}"); + c.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}"); + // declaring outputJdeps also asserts existance after compile. + c.outputJar().outputSrcJar().outputJdeps(); + }); + } + + @Test + public void testKotlinErrorRendering() { + ctx.runFailingCompileTaskAndValidateOutput( + () -> + ctx.runCompileTask( + c -> { + c.addSource("AClass.kt", "package something;" + "class AClass{"); + c.outputJar().outputSrcJar(); + }), + lines -> assertThat(lines.get(0)).startsWith(ctx.toPlatform("sources/AClass"))); + } + @Test public void testMixedBiReferences() { ctx.runCompileTask( - it -> { - it.addSource( + ctx -> { + ctx.addSource( "AClass.java", "package a;", "", @@ -59,7 +88,7 @@ public void testMixedBiReferences() { "public class AClass {", " static BClass b = new BClass();", "}"); - it.addSource( + ctx.addSource( "BClass.kt", "package b", "", @@ -68,17 +97,20 @@ public void testMixedBiReferences() { "class BClass() {", " val a = AClass()", "}"); + ctx.outputSrcJar() + .outputJar(); }); ctx.assertFilesExist(DirectoryType.CLASSES, "a/AClass.class", "b/BClass.class"); } @Test - public void testKotlinErrorRendering() { - ctx.runFailingCompileTaskAndValidateOutput( - () -> - ctx.runCompileTask( - c -> c.addSource("AClass.kt", "package something;" + "class AClass{")), - lines -> assertThat(lines.get(0)).startsWith(ctx.toPlatform("sources/AClass"))); + public void testCompileSingleJavaFile() { + ctx.runCompileTask( + (ctx) -> { + ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}"); + ctx.outputSrcJar() + .outputJar(); + }); } @Test @@ -89,23 +121,11 @@ public void testJavaErrorRendering() { c -> { c.addSource("AClass.kt", "package something;" + "class AClass{}"); c.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{"); + c.outputJar().outputSrcJar(); }), lines -> assertThat(lines.get(0)).startsWith(ctx.toPlatform("sources/AnotherClass"))); } - @Test - @Ignore("The Kotlin compiler expects a single kotlin file at least.") - public void testCompileSingleJavaFile() { - ctx.runCompileTask( - (c) -> c.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}")); - } - - private static final Consumer SETUP_NORMALIZATION_TEST_SOURCES = - ctx -> { - ctx.addSource("AClass.kt", "package something;\n" + "class AClass{}"); - ctx.addSource("BClass.kt", "package something;\n" + "class BClass{}"); - }; - @Test public void testCompiledJarIsNormalized() { Deps.Dep previous = ctx.runCompileTask(SETUP_NORMALIZATION_TEST_SOURCES); diff --git a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmKaptTest.java b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmKaptTest.java index e68f5b3d9..311c26047 100644 --- a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmKaptTest.java +++ b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmKaptTest.java @@ -19,35 +19,35 @@ import io.bazel.kotlin.builder.Deps.Dep; import io.bazel.kotlin.builder.DirectoryType; import io.bazel.kotlin.builder.KotlinJvmTestBuilder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - import java.io.File; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import static io.bazel.kotlin.builder.KotlinJvmTestBuilder.KOTLIN_ANNOTATIONS; import static io.bazel.kotlin.builder.KotlinJvmTestBuilder.KOTLIN_STDLIB; @RunWith(JUnit4.class) public class KotlinBuilderJvmKaptTest { - private static final Dep AUTO_VALUE_ANNOTATIONS = - Dep.importJar( - "autovalue_annotations", - System.getProperty("auto_value_annotations") - .replaceFirst("external" + File.separator, "")); - private static final Dep AUTO_VALUE = - Dep.importJar( - "autovalue", - System.getProperty("auto_value") - .replaceFirst("external" + File.separator, "")); - private static final AnnotationProcessor AUTO_VALUE_ANNOTATION_PROCESSOR = - AnnotationProcessor.builder() - .processClass("com.google.auto.value.processor.AutoValueProcessor") - .processorPath( - Dep.classpathOf(AUTO_VALUE_ANNOTATIONS, AUTO_VALUE, KOTLIN_ANNOTATIONS).collect(Collectors.toSet())) - .build(); + private static final Dep AUTO_VALUE_ANNOTATIONS = + Dep.importJar( + "autovalue_annotations", + System.getProperty("auto_value_annotations") + .replaceFirst("external" + File.separator, "")); + private static final Dep AUTO_VALUE = + Dep.importJar( + "autovalue", + System.getProperty("auto_value") + .replaceFirst("external" + File.separator, "")); + private static final AnnotationProcessor AUTO_VALUE_ANNOTATION_PROCESSOR = + AnnotationProcessor.builder() + .processClass("com.google.auto.value.processor.AutoValueProcessor") + .processorPath( + Dep.classpathOf(AUTO_VALUE_ANNOTATIONS, AUTO_VALUE, KOTLIN_ANNOTATIONS) + .collect(Collectors.toSet())) + .build(); private static final KotlinJvmTestBuilder ctx = new KotlinJvmTestBuilder(); @@ -61,24 +61,27 @@ public class KotlinBuilderJvmKaptTest { public void testKaptKt() { ctx.runCompileTask( ADD_AUTO_VALUE_PLUGIN, - c -> - c.addSource( - "TestKtValue.kt", - "package autovalue\n" - + "\n" - + "import com.google.auto.value.AutoValue\n" - + "\n" - + "@AutoValue\n" - + "abstract class TestKtValue {\n" - + " abstract fun name(): String\n" - + " fun builder(): Builder = AutoValue_TestKtValue.Builder()\n" - + "\n" - + " @AutoValue.Builder\n" - + " abstract class Builder {\n" - + " abstract fun setName(name: String): Builder\n" - + " abstract fun build(): TestKtValue\n" - + " }\n" - + "}")); + c -> { + c.addSource( + "TestKtValue.kt", + "package autovalue\n" + + "\n" + + "import com.google.auto.value.AutoValue\n" + + "\n" + + "@AutoValue\n" + + "abstract class TestKtValue {\n" + + " abstract fun name(): String\n" + + " fun builder(): Builder = AutoValue_TestKtValue.Builder()\n" + + "\n" + + " @AutoValue.Builder\n" + + " abstract class Builder {\n" + + " abstract fun setName(name: String): Builder\n" + + " abstract fun build(): TestKtValue\n" + + " }\n" + + "}"); + c.outputJar().outputSrcJar(); + } + ); ctx.assertFilesExist( DirectoryType.CLASSES, @@ -91,8 +94,8 @@ public void testKaptKt() { public void testMixedKaptBiReferences() { ctx.runCompileTask( ADD_AUTO_VALUE_PLUGIN, - it -> { - it.addSource( + ctx -> { + ctx.addSource( "TestKtValue.kt", "package autovalue.a\n" + "\n" @@ -111,7 +114,7 @@ public void testMixedKaptBiReferences() { + " }\n" + "}"); - it.addSource( + ctx.addSource( "TestAutoValue.java", "package autovalue.b;\n" + "\n" @@ -134,6 +137,7 @@ public void testMixedKaptBiReferences() { + " }\n" + "\n" + "}"); + ctx.outputJar().outputSrcJar(); }); ctx.assertFilesExist( DirectoryType.SOURCE_GEN, diff --git a/src/test/kotlin/io/bazel/kotlin/defs.bzl b/src/test/kotlin/io/bazel/kotlin/defs.bzl index badf3b08f..2e4132963 100644 --- a/src/test/kotlin/io/bazel/kotlin/defs.bzl +++ b/src/test/kotlin/io/bazel/kotlin/defs.bzl @@ -24,14 +24,28 @@ def _get_class_name(kwargs): return kwargs["test_classes"] def kt_rules_test(name, **kwargs): - kwargs.setdefault("size", "small") - kwargs["deps"] = kwargs.setdefault("deps", []) + ["//src/test/kotlin/io/bazel/kotlin/builder:test_lib"] - kwargs.setdefault("test_class", _get_class_name(kwargs)) - for f in kwargs.get("srcs"): + args = dict(kwargs.items()) + args.setdefault("size", "small") + args.setdefault("data", []) + args.setdefault("jvm_flags", []) + args["deps"] = args.setdefault("deps", []) + ["//src/test/kotlin/io/bazel/kotlin/builder:test_lib"] + for dep in [ + "//src/main/kotlin/io/bazel/kotlin/compiler", + "@com_github_jetbrains_kotlin//:annotations", + "@com_github_jetbrains_kotlin//:kotlin-stdlib", + "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7", + "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8", + ] + args["data"]: + if dep not in args["data"]: + args["data"] += [dep] + args["jvm_flags"] += ["-D%s=$(rootpath %s)" %(dep.replace("/",".").replace(":","."), dep)] + + args.setdefault("test_class", _get_class_name(kwargs)) + for f in args.get("srcs"): if f.endswith(".kt"): - kt_jvm_test(name = name, **kwargs) + kt_jvm_test(name = name, **args) return - java_test(name = name, **kwargs) + java_test(name = name, **args) def kt_rules_e2e_test(name, **kwargs): kwargs.setdefault("size", "small")