Kotlin
Kotlin support for Pants.
Kotlin support in Pants is still under active development, but currently supports compilation and testing. It has been tested with Kotlin v1.6.20.
Please share feedback for what you need to use Pants with your Kotlin project by either opening a GitHub issue or joining our Slack!
Check out github.com/pantsbuild/example-kotlin to try a sample Pants project with Kotlin support.
Overview
Kotlin is a programming language from Jetbrains that runs on the JVM and certain other platforms. The Kotlin backend in Pants supports compilation, testing, and linting of Kotlin code for the JVM. (The other Kotlin platforms including Kotlin Multiplatform Mobile and Kotlin/JS are not currently supported, nor are there currently any plans to do so.)
Initial Setup
First, activate the Kotlin backend in pants.toml
plus the ktlint
backend if you would like to use ktlint
for code formatting and linting:
[GLOBAL]
backend_packages = [
"pants.backend.experimental.kotlin",
# Activate the following backend if you want to use `ktlint` for code formatting and linting.
"pants.backend.experimental.kotlin.lint.ktlint",
]
Setting up targets
Run pants tailor ::
to generate BUILD files. This will create kotlin_sources
targets in every directory containing library code, as well as kotlin_junit_tests
targets for filenames that look like tests.
❯ pants tailor ::
Created src/jvm/org/pantsbuild/example/app/BUILD:
- Add kotlin_sources target app
Created src/jvm/org/pantsbuild/example/json/BUILD:
- Add kotlin_sources target json
Created src/jvm/org/pantsbuild/example/lib/BUILD:
- Add java_sources target lib
You can run pants list ::
to see all targets in your project:
❯ pants list ::
...
src/jvm/org/pantsbuild/example/app:app
src/jvm/org/pantsbuild/example/app/ExampleApp.kt
src/jvm/org/pantsbuild/example/json:json
src/jvm/org/pantsbuild/example/json/JsonExample.kt
src/jvm/org/pantsbuild/example/lib:lib
src/jvm/org/pantsbuild/example/lib/ExampleLib.java
Choosing JDK and Kotlin versions
Pants supports choosing the JDK and Kotlin versions per target in your repository. To reduce the amount of boilerplate required, however, most users set repository-wide defaults in pants.toml
, and then only override them when necessary for particular targets.
JDK
JDKs used by Pants are automatically fetched using Coursier, and are chosen using the [jvm].jdk
option to set a repository-wide default.
To override the default on a particular target, you can use the jdk=
field. It can be useful to use the parametrize
builtin with the jdk=
field, particularly to run test targets under multiple JDKs.
Kotlin version
The Kotlin version to use is configured on a resolve-by-resolve basis (see the "Third-party dependencies" section below) using the [kotlin].version_for_resolve
option. The default Kotlin version for your repository will thus be whichever Kotlin version is configured for the "default" resolve, which is configured by the [jvm].default_resolve
option.
Each resolve must contain the following jars for the Kotlin runtime with the version matching the version specified for the resolve in the [kotlin].version_for_resolve
option:
org.jetbrains.kotlin:kotlin-stdlib
org.jetbrains.kotlin:kotlin-reflect
org.jetbrains.kotlin:kotlin-script-runtime
To use multiple Kotlin versions in a repository, you would define multiple resolves, and then adjust the resolve
field of any targets which should be used with the non-default_resolve
resolve.
To cross-build a set of Kotlin targets for multiple Kotlin versions, you can use the parametrize
builtin with the resolve=
field of the target and its dependencies.
jvm_artifact
targets for the Kotlin runtime must be explicitly defined.The Kotlin backend currently requires that a jvm_artifact
target for each Kotlin runtime jars be present in any resolve used for Kotlin. If any of the required jvm_artifact
targets are missing, Pants will error. Pants will automatically inject a dependency on the runtime into Kotlin targets. (These targets may be automatically supplied by Pants in a future version, but that is not currently implemented.)
Dependencies
First-party dependencies
In many cases, the dependencies of your first-party code are automatically inferred via dependency inference based on import
statements in the code. If you do need to declare additional dependencies for any reason, you can do so using Pants' syntax for declaring dependencies for targets.
Third-party dependencies and lockfiles
Third-party dependencies (i.e. those from repositories like Maven central) are also automatically inferred via dependency inference, but must first be declared once per repository as jvm_artifact
targets:
jvm_artifact(
group="com.google.guava",
artifact="guava",
version="31.0.1-jre",
# See the callout below for more information on the `packages` argument.
packages=["com.google.common.**"],
)
Pants requires use of a lockfile for third-party dependencies. After adding or editing jvm_artifact
targets, you will need to update affected lockfiles by running pants generate-lockfiles
. The default lockfile is located at 3rdparty/jvm/default.lock
, but it can be relocated (as well as additional resolves declared) via the [jvm].resolves
option.
packages
argumentTo efficiently determine which symbols are provided by third-party code (i.e., without hitting the network in order to compute dependencies in the common case), Pants relies on a static mapping of which artifacts provide which symbols, and defaults to treating each jvm_artifact
as providing symbols within its group
.
The packages
argument allows you to override which symbols a jvm_artifact
provides. See the jvm_artifact
docs for more information.
resource
targets
To have your code load files as "resources":
- Add a
resource
orresources
target with the relevant files in thesource
/sources
field, respectively. - Ensure that an appropriate
source_root
is detected for theresources
target, in order to trim the relevant prefix from the filename to align with the layout of your JVM packages. - Add that target to the
dependencies
field of the relevant JVM target (usually the one that uses the JVM APIs to load the resource).
For example:
- pants.toml
- src/jvm/org/pantsbuild/example/lib/BUILD
- src/jvm/org/pantsbuild/example/lib/Loader.java
- src/jvm/org/pantsbuild/example/lib/hello.txt
[source]
# In order for the resource to be loadable as `org/pantsbuild/example/lib/hello.txt`,
# the `/src/jvm/ prefix needs to be stripped.
root_patterns = ["/src/*"]
kotlin_sources(dependencies=[":hello"])
resources(name="hello", sources=["hello.txt"])
package org.pantsbuild.example.lib
import com.google.common.io.Resources
fun load() {
... = Resources.getResource(Loader.class, "hello.txt")
}
Hello world!
Tasks
Compile code
To manually check that sources compile, use pants check
:
# Check a single file
❯ pants check src/jvm/org/pantsbuild/example/lib/ExampleLib.kt
# Check files located recursively under a directory
❯ pants check src/jvm::
# Check the whole repository
❯ pants check ::
Run tests
To run tests, use pants test
:
# Run a single test file
❯ pants test tests/jvm/org/pantsbuild/example/lib/ExampleLibTest.kt
# Test all files in and under a directory
❯ pants test tests/jvm::
# Test the whole repository
❯ pants test ::
The Kotlin backend currently supports JUnit tests specified using the kotlin_junit_tests
target type.
Retries
Pants can automatically retry failed tests. This can help keep your builds passing even with flaky tests, like integration tests.
- pants.toml
[test]
attempts_default = 3
Setting environment variables
Test runs are hermetic, meaning that they are stripped of the parent pants
process's environment variables. This is important for reproducibility, and it also increases cache hits.
To add any arbitrary environment variable back to the process, you can either add the environment variable to the specific tests with the extra_env_vars
field on kotlin_junit_test
/ kotlin_junit_tests
targets or to all your tests with the [test].extra_env_vars
option. Generally, prefer the field extra_env_vars
field so that more of your tests are hermetic.
With both [test].extra_env_vars
and the extra_env_vars
field, you can either hardcode a value or leave off a value to "allowlist" it and read from the parent pants
process's environment.
- pants.toml
- project/BUILD
[test]
extra_env_vars = ["VAR1", "VAR2=hardcoded_value"]
kotlin_junit_tests(
name="tests",
# Adds to all generated `kotlin_junit_test` targets,
# i.e. each file in the `sources` field.
extra_env_vars=["VAR3", "VAR4=hardcoded"],
# Even better, use `overrides` to be more granular.
overrides={
"StrUtilTest.kt": {"extra_env_vars": ["VAR"]},
("DirUtilTest.kt", "OSUtilTest.kt"): {"extra_env_vars": ["VAR5"]},
},
)
Lint and Format
ktlint
can be enabled by adding the pants.backend.experimental.kotlin.lint.ktlint
backend to backend_packages
in the [GLOBAL]
section of pants.toml
.
Once enabled, lint
and fmt
will check and automatically reformat your code:
# Format this directory and all subdirectories
❯ pants fmt src/jvm::
# Check that the whole project is formatted
❯ pants lint ::
# Format all changed files
❯ pants --changed-since=HEAD fmt
Caveats
The Kotlin backend is currently experimental since many features are not implemented including:
- Kotlin modules. We would love to hear from Kotlin developers for advice on how modules are used and could be potentially supported by Pants.
- Non-JVM backends including Kotlin Multiplatform Mobile and Kotlin/JS