Kotlin Extracted as the Reference Plugin (v1.11)
A few hours after v1.10 introduced the plugin architecture, v1.11 ships the first proof. devrail-plugin-kotlin is now a separate repo, a separate release cadence, and – for consumers who declare it – the canonical way to use Kotlin tooling under DevRail.
In v1.11, the extraction is additive: dev-toolchain still ships Kotlin in core (so existing languages: [kotlin] consumers see zero change). v2.0.0 will retire the in-core path and make the plugin the only path. This release is the rehearsal.
What changed
github.com/devrail-dev/devrail-plugin-kotlin is a new public repository tagged v1.0.0. It contains:
plugin.devrail.yml– the manifest that declaresname: kotlin,devrail_min_version: 1.10.0, and the same five targets (lint,format_check,format_fix,test,security) the in-core blocks ship.install.sh– a self-contained port of dev-toolchain’sscripts/install-kotlin.sh. Same versions: ktlint 1.5.0, detekt 1.23.7, Gradle 8.12. JDK 21 is COPY’d fromeclipse-temurin:21-jdkvia the manifest’scontainer.copy_from_builderblock.- DevRail-standard scaffolding (Makefile,
.devrail.yml, pre-commit, CI). The plugin repo itself passesmake check. .github/workflows/ci.yml– runsmake checkand validates the manifest with dev-toolchain’splugin-validator.shon every push.
To use it instead of the in-core Kotlin path:
# .devrail.yml
plugins:
- source: github.com/devrail-dev/devrail-plugin-kotlin
rev: v1.0.0
languages: [kotlin]
Then make plugins-update && make check. The build pipeline (Story 13.4) renders the manifest into a project-local Dockerfile.devrail, builds devrail-local:<hash>, and the dispatcher runs ktlint / detekt / gradle alongside any core-language tools.
Loader precedence note. If a consumer also lists
kotlinin the top-levellanguages:array, the loader hits the in-core path FIRST and the plugin doesn’t run. Until v2.0.0 removes the in-core path, the way to exercise this plugin is to putkotlinonly inside the plugin’slanguages:block. Document this for any plugin you ship that overlaps with a core language.
Why Kotlin
We picked Kotlin as the reference extraction because:
- It’s the freshest core language. Added in March 2026; the install logic is recent and well-tested. Lower risk of “I forget what this Dockerfile bit does.”
- Its install script is the heaviest in the matrix. ktlint, detekt, and gradle each download from a separate upstream. JDK 21 from a builder stage. If extraction works for Kotlin, it works for almost anything.
- The
copy_from_builderpattern is non-trivially exercised. A whole JDK tree gets COPY’d fromeclipse-temurin:21-jdk. The plugin manifest reproduces that exactly without dev-toolchain having to know about Kotlin.
The extraction recipe
The single biggest deliverable in v1.11 is documentation – a step-by-step recipe for extracting any core language as a plugin. It lives in devrail-standards/standards/contributing.md and covers:
- Inventory the surface –
grep -nE "HAS_<LANG>|<lang>" Makefile Dockerfile scripts/install-<lang>.sh tests/test-<lang>.shto find every place dev-toolchain touches the language. - Map Makefile blocks to manifest targets – the rules for translating an
if [ -n "$(HAS_LANG)" ]; then ...block intotargets.<name>.cmd+gates.<name>. Notably: multi-tool blocks (Kotlin’sktlint && detekt) collapse into one cmd via&&. The v1 contract is one cmd per target. - Port the install script – strip every dependency on
lib/log.shandlib/platform.sh. Plugin install scripts run duringdocker buildof the consumer’sDockerfile.devrail, before the dev-toolchain libs are in the layer being built. Replacelog_infowithprintf '[install-<lang>] %s\n' "$msg" >&2. - Write the container fragment –
base_image,copy_from_builder,env,install_script. Reproduces the dev-toolchain runtime layer. - Initialize the plugin repo with DevRail standards – copy the reference Makefile,
.devrail.yml: { languages: [bash] }, pre-commit, gitignore. The plugin’s owninstall.shlints cleanly. - Validate end-to-end via
file://– in a test consumer workspace, pointplugins:at your local checkout, runmake plugins-update && make check, watch the build pipeline builddevrail-local:<hash>and the dispatcher run your tools. - Tag and announce – annotated semver tag (
git tag -a v1.0.0), CHANGELOG entry on dev-toolchain, blog post.
The full text is here. It’s written so a contributor with no prior plugin work can follow it from scratch.
What’s regression-tested
Plus the manifest-shape side: dev-toolchain ships a new smoke test in tests/test-kotlin-plugin-extraction.sh that:
- Validates
devrail-plugin-kotlin/plugin.devrail.ymlagainst schema_version 1. - Resolves the plugin via
file://URL from a vendored fixture (tests/fixtures/kotlin-via-plugin/) and confirms.devrail.lockrecords the resolved SHA + content_hash. - Loads the plugin into the dispatcher cache and asserts name / version / devrail_min_version match.
- Walks every target (lint / format_check / format_fix / test / security) and confirms the cmd + gate shape parity with the in-core HAS_KOTLIN behaviour. Specific assertions on
ktlint && detekt-clichaining catch regressions where someone changes the manifest but forgets to keep both tools wired up.
The full docker-build of devrail-local:<hash> with real ktlint/detekt/gradle downloads (~5 minutes) is NOT in CI — it’s a maintainer-run manual check, same trade-off we made for the minimal-v1 fixture in v1.10. CI gets a fast hermetic regression; humans do the heavy validation.
What’s coming in v2.0.0
The reason this extraction is additive is that we’ve committed to back-compat through v1.x. Existing consumers with languages: [kotlin] should not have to do anything when v1.11 lands. Their make check runs unchanged.
v2.0.0 (Story 13.9, no scheduled date yet) flips the model:
- Remove the in-core
HAS_KOTLINblocks and Kotlin Dockerfile bits. Same for every other core language as we extract them. - Ship
devrail-init migrate --to v2to walk consumer.devrail.ymlfiles: anylanguages:entry that’s no longer in core gets moved toplugins:with the appropriate plugin source pinned. - Major version bump signals the breaking change.
Between now and then, every other core language gets the same extraction treatment (Swift, Ruby, Go, etc.). Each will follow the recipe documented from this Kotlin pass. We’ll likely ship them as separate v1.x minor releases so the migration burden is incremental rather than one big v2 cliff.
Try it
ghcr.io/devrail-dev/dev-toolchain:v1.11.0 is up. The floating :v1 tag now points there. To use the plugin, declare it as shown above and run make plugins-update. To author a similar extraction, follow the recipe and use devrail-plugin-kotlin as your worked example.
If you extract another core language, open a PR against the (forthcoming) awesome-devrail discovery list. Or just publish it on GitHub and link from your team’s README – the plugin model doesn’t require any central registry.