diff --git a/README.md b/README.md index 97447ea..f3f77c7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ + + + + + + +
BazelKotlin
+ # Kotlin Rules for Bazel [![Build Status](https://travis-ci.org/pubref/rules_kotlin.svg?branch=master)](https://travis-ci.org/pubref/rules_kotlin) @@ -9,6 +17,7 @@ These rules are for building [Kotlin][kotlin] source with with 1. [kotlin_repositories](#kotlin_repositories) 1. [kotlin_library](#kotlin_library) 1. [kotlin_binary](#kotlin_binary) +1. [kotlin_test](#kotlin_test) ## Workspace rules @@ -18,7 +27,7 @@ Add the following to your `WORKSPACE` file: git_repository( name = "org_pubref_rules_kotlin", remote = "https://github.com/pubref/rules_kotlin.git", - tag = "v0.3.0", # update as needed + tag = "v0.3.1", # update as needed ) load("@org_pubref_rules_kotlin//kotlin:rules.bzl", "kotlin_repositories") @@ -40,7 +49,7 @@ dagger (used to build the `KotlinCompiler` bazel worker). Add the following to your BUILD file: ```python -load("@org_pubref_rules_kotlin//kotlin:rules.bzl", "kotlin_library", "kotlin_binary") +load("@org_pubref_rules_kotlin//kotlin:rules.bzl", "kotlin_library") ``` ### kotlin_library @@ -95,18 +104,18 @@ android_binary( | `srcs` | `label_list` | Kotlin source files `*.kt` | | `deps` | `label_list` | List of `kotlin_library` targets | | `java_deps` | `label_list` | List of java provider targets (`java_library`, `java_import`, `...`) | +| `android_deps` | `label_list` | List of android provider targets (`android_library`) | | `jars` | `label_list` | List of jar file targets (`*.jar`) | | `x_opts` | `string_list` | List of additional `-X` options to `kotlinc` | | `plugin_opts` | `string_dict` | List of additional `-P` options to `kotlinc` | -| `use_worker` | `boolean` | Assign to `False` to disable the use of [bazel workers](https://bazel.build/blog/2015/12/10/java-workers.html). | ### kotlin_binary -A `kotlin_binary` rule takes the same arguments as a `kotlin_library`, -plus a required `main_class` argument (the name of the compiled kotlin -class to run, in java package notation). This class should have a -`fun main(...)` entrypoint. Example: +A `kotlin_binary` macro takes the same arguments as a +`kotlin_library`, plus a required `main_class` argument (the name of +the compiled kotlin class to run, in java package notation). This +class should have a `fun main(...)` entrypoint. Example: ```python kotlin_binary( @@ -128,6 +137,8 @@ Target :main_kt_deploy.jar up-to-date: $ java -jar ./bazel-bin/.../main_kt_deploy.jar ``` +> The `kotlin-runtime.jar` is implicitly included by the `kotlin_binary` rule. + #### kotlin_binary attributes Includes all `kotlin_library` attributes as well as: @@ -137,15 +148,42 @@ Includes all `kotlin_library` attributes as well as: | `main_class` | `string` | Main class to run with the `kotlin_binary` rule | +### kotlin_test + +The `kotlin_test` rule is nearly identical the `kotlin_binary` rule +(other than calling `java_test` internally rather than `java_binary`). + + +```python +kotlin_test( + name = "main_kt_test", + test_class = "examples.helloworld.MainKtTest", + srcs = ["MainKtTest.kt"], + size = "small", + deps = [ + ":rules", + ], + java_deps = [ + "@junit4//jar", + ], +) +``` + +```sh +$ bazel test :main_kt_test.jar +``` + +> The `kotlin-test.jar` is implicitly included by the `kotlin_test` rule. + ### kotlin_compile -The `kotlin_compile` rule runs the `kotlinc` tool to generate a `.jar` -file from a list of kotlin source files. The `kotlin_library` rule -(actually, macro) calls this internally and then makes the jarfile -available to other java rules via a `java_import` rule. +> TL;DR; You most likely do not need to interact with the +> `kotlin_compile` rule directly. -In summary, you most likely do not need to interact with the -`kotlin_compile` rule directly. +The `kotlin_compile` rule runs the kotlin compiler to generate a +`.jar` file from a list of kotlin source files. The `kotlin_library` +rule calls this internally and then makes the jarfile available to +other java rules via a `java_import` rule. # Summary @@ -153,9 +191,9 @@ That's it! Hopefully these rules with make it easy to mix kotlin and traditional java code in your projects and take advantage of bazel's approach to fast, repeatable, and reliable builds. -> Note: if you have a bunch of maven (central) dependencies, consider -> [rules_maven](https://github.com/pubref/rules_maven) for taming the -> issue of transitive dependencies with your java/kotlin projects. +> Note: Consider [rules_maven](https://github.com/pubref/rules_maven) +> for handling transitive maven dependencies with your java/kotlin +> projects. ## Examples @@ -167,13 +205,14 @@ $ cd rules_kotlin $ bazel query //... --output label_kind $ bazel run examples/helloworld:main_kt $ bazel run examples/helloworld:main_java +$ bazel test examples/helloworld:main_test +$ bazel test examples/helloworld:main_kt_test ``` ## TODO -1. Implement a `kotlin_test` rule. 1. Proper `data` and runfiles support. -2. Android support. +2. Proper android support. 4. kapt support. 3. Incremental compilation. diff --git a/examples/helloworld/BUILD b/examples/helloworld/BUILD index 39e238e..b2a5788 100644 --- a/examples/helloworld/BUILD +++ b/examples/helloworld/BUILD @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//kotlin:rules.bzl", "kotlin_library", "kotlin_binary") +load("//kotlin:rules.bzl", "kotlin_library", "kotlin_binary", "kotlin_test") # A kotlin binary that depends on another kotlin rule (using kotlin @@ -52,6 +52,20 @@ java_test( ] ) +# A kotlin test rule that depends on a kotlin rule +kotlin_test( + name = "main_kt_test", + test_class = "examples.helloworld.MainKtTest", + srcs = ["MainKtTest.kt"], + size = "small", + deps = [ + ":rules", + ], + java_deps = [ + "@junit4//jar", + ], +) + # Included to test dependent java providers java_library( name = "guava", diff --git a/examples/helloworld/MainKtTest.kt b/examples/helloworld/MainKtTest.kt new file mode 100644 index 0000000..da7b57e --- /dev/null +++ b/examples/helloworld/MainKtTest.kt @@ -0,0 +1,13 @@ +package examples.helloworld + +import kotlin.test.assertEquals +import org.junit.Test + +public class MainKtTest { + + @Test fun testRuleName(): Unit { + val rule = KotlinLibraryRule("foo", emptyList(), emptyList()) + assertEquals("foo", rule.name) + } + +} diff --git a/java/org/pubref/rules/kotlin/BUILD b/java/org/pubref/rules/kotlin/BUILD index 61c7ae0..bfc2e52 100644 --- a/java/org/pubref/rules/kotlin/BUILD +++ b/java/org/pubref/rules/kotlin/BUILD @@ -7,6 +7,7 @@ java_binary( visibility = ["//visibility:public"], deps = [ ":compiler", + ":preloader", "//java/io/bazel/rules/closure:BazelWorker", "//java/io/bazel/rules/closure/program", "@com_google_dagger", @@ -27,3 +28,16 @@ java_library( "@com_github_jetbrains_kotlin//:compiler", ], ) + +java_library( + name = "preloader", + srcs = [ + "KotlinPreloader.java", + ], + visibility = ["//visibility:public"], + deps = [ + "//java/io/bazel/rules/closure/program", + "@com_google_guava", + "@com_github_jetbrains_kotlin//:preloader", + ], +) diff --git a/java/org/pubref/rules/kotlin/KotlinPreloader.java b/java/org/pubref/rules/kotlin/KotlinPreloader.java new file mode 100644 index 0000000..0f4e485 --- /dev/null +++ b/java/org/pubref/rules/kotlin/KotlinPreloader.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 PubRef.org. 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 org.pubref.rules.kotlin; + +import com.google.common.collect.Iterables; +import io.bazel.rules.closure.program.CommandLineProgram; +import org.jetbrains.kotlin.preloading.Preloader; + +/** + * CommandLineProgram Wrapper for the Kotlin Preloader. + */ +public final class KotlinPreloader implements CommandLineProgram { + + @Override + public Integer apply(Iterable args) { + try { + Preloader.main(Iterables.toArray(args, String.class)); + return 0; + } catch (Exception ex) { + ex.printStackTrace(); + return 1; + } + } + +} diff --git a/java/org/pubref/rules/kotlin/KotlinWorker.java b/java/org/pubref/rules/kotlin/KotlinWorker.java index 90d92d9..b1feec2 100644 --- a/java/org/pubref/rules/kotlin/KotlinWorker.java +++ b/java/org/pubref/rules/kotlin/KotlinWorker.java @@ -50,6 +50,8 @@ public Integer apply(Iterable args) { switch (head) { case "KotlinCompiler": return new KotlinCompiler().apply(tail); + case "KotlinPreloader": + return new KotlinPreloader().apply(tail); default: output.println( "\nERROR: First flag to KotlinWorker should be specific compiler to run, " diff --git a/kotlin/kotlin_repositories.bzl b/kotlin/kotlin_repositories.bzl index 7a5aaf3..29ec198 100644 --- a/kotlin/kotlin_repositories.bzl +++ b/kotlin/kotlin_repositories.bzl @@ -10,10 +10,22 @@ java_import( name = "runtime", jars = ["lib/kotlin-runtime.jar"], ) +java_import( + name = "stdlib", + jars = ["lib/kotlin-stdlib.jar"], +) java_import( name = "compiler", jars = ["lib/kotlin-compiler.jar"], ) +java_import( + name = "preloader", + jars = ["lib/kotlin-preloader.jar"], +) +java_import( + name = "test", + jars = ["lib/kotlin-test.jar"], +) sh_binary( name = "kotlin", srcs = ["bin/kotlin"], diff --git a/kotlin/rules.bzl b/kotlin/rules.bzl index f151910..ad93ed0 100644 --- a/kotlin/rules.bzl +++ b/kotlin/rules.bzl @@ -7,7 +7,12 @@ load("//kotlin:kotlin_repositories.bzl", "kotlin_repositories") def _kotlin_compile_impl(ctx): kt_jar = ctx.outputs.kt_jar inputs = [] - args = [] + + args = [ + "KotlinPreloader", + "-cp", ctx.file._kotlin_compiler.path, + "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler", + ] # Single output jar args += ["-d", kt_jar.path] @@ -20,8 +25,12 @@ def _kotlin_compile_impl(ctx): args += ["-P"] args += ["plugin:%s=\"%s\"" % (k, v)] - # Kotlin home - typically the dir 'external/com_github_jetbrains_kotlin' - args += ["-kotlin-home", ctx.file._runtime.dirname + '/..'] + # Kotlin home - typically the dir + # 'external/com_github_jetbrains_kotlin' + args += ["-kotlin-home", ctx.file._kotlin_compiler.dirname + '/..'] + # Add all home libs to sandbox so they are discoverable by the + # preloader + inputs += ctx.files._kotlin_home # Make classpath if needed. Include those from this and dependent rules. jars = [] @@ -66,29 +75,16 @@ def _kotlin_compile_impl(ctx): inputs += [file] args += [file.path] - # Add all home libs to sandbox - inputs += ctx.files._kotlin_home - # Run the compiler - - if ctx.attr.use_worker: - ctx.action( - mnemonic = "KotlinCompile", - inputs = inputs, - outputs = [kt_jar], - executable = ctx.executable._kotlinw, - execution_requirements={"supports-workers": "1"}, - arguments = ['KotlinCompiler'] + args, - progress_message="Compiling %d Kotlin source files to %s" % (len(ctx.files.srcs), ctx.outputs.kt_jar.short_path) - ) - else: - ctx.action( - mnemonic = "KotlinCompile", - inputs = inputs, - outputs = [kt_jar], - executable = ctx.executable._kotlinc, - arguments = args, - ) + ctx.action( + mnemonic = "KotlinCompile", + inputs = inputs, + outputs = [kt_jar], + executable = ctx.executable._kotlinw, + execution_requirements = {"supports-workers": "1"}, + arguments = args, + progress_message="Compiling %d Kotlin source files to %s" % (len(ctx.files.srcs), ctx.outputs.kt_jar.short_path) + ) return struct( files = set([kt_jar]), @@ -125,7 +121,7 @@ _kotlin_compile_attrs = { providers = ["java"], ), - # Dependent java rules. + # Dependent android rules. "android_deps": attr.label_list( providers = ["android"], ), @@ -147,18 +143,17 @@ _kotlin_compile_attrs = { # Plugin options "plugin_opts": attr.string_dict(), - # kotlin compiler (a shell script) - "_kotlinc": attr.label( - default=Label("@com_github_jetbrains_kotlin//:kotlinc"), - executable = True, - cfg = 'host', - ), - # kotlin home (for runtime libraries discovery) "_kotlin_home": attr.label( default=Label("@com_github_jetbrains_kotlin//:home"), ), + # kotlin compiler jar + "_kotlin_compiler": attr.label( + default=Label("@com_github_jetbrains_kotlin//:compiler"), + single_file = True, + ), + # kotlin compiler worker (a java executable defined in this repo) "_kotlinw": attr.label( default=Label("//java/org/pubref/rules/kotlin:worker"), @@ -166,17 +161,6 @@ _kotlin_compile_attrs = { cfg = 'host', ), - # kotlin runtime - "_runtime": attr.label( - default=Label("@com_github_jetbrains_kotlin//:runtime"), - single_file = True, - ), - - # Advanced options - "use_worker": attr.bool( - default = True, - ), - } @@ -215,7 +199,6 @@ def kotlin_binary(name, jars = [], srcs = [], deps = [], - use_worker = False, x_opts = [], plugin_opts = {}, java_deps = [], @@ -227,7 +210,6 @@ def kotlin_binary(name, java_deps = java_deps, srcs = srcs, deps = deps, - use_worker = use_worker, x_opts = x_opts, plugin_opts = plugin_opts, ) @@ -236,7 +218,36 @@ def kotlin_binary(name, name = name, runtime_deps = [ name + "_kt.jar", - "@com_github_jetbrains_kotlin//:runtime", - ] + java_deps, + ] + java_deps + [dep + "_kt" for dep in deps], + **kwargs + ) + + +def kotlin_test(name, + jars = [], + srcs = [], + deps = [], + x_opts = [], + plugin_opts = {}, + java_deps = [], + **kwargs): + + java_deps.append("@com_github_jetbrains_kotlin//:test") + + kotlin_compile( + name = name + "_kt", + jars = jars, + java_deps = java_deps, + srcs = srcs, + deps = deps, + x_opts = x_opts, + plugin_opts = plugin_opts, + ) + + native.java_test( + name = name, + runtime_deps = [ + name + "_kt.jar", + ] + java_deps + [dep + "_kt" for dep in deps], **kwargs )