kotlin-tooling-kotlin-toolchain-plugin-authoring
>
它会碰到什么
这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。
技能内容
Kotlin Toolchain Plugin Authoring
Local plugins are the official escape hatch from declarative YAML: a jvm/amper-plugin module shipping
task actions, settings, and generated sources/resources alongside your project. Code patterns to adapt are
in [references/examples.md](references/examples.md).
When to write a plugin
Write one when you need:
- A build-time step a library's workflow expects (code generation, schema compilation, resource
transformation, version stamping).
- Custom verification wired into the build (pre-release checks, schema validation, contract tests).
- A build-time value published into the JAR classpath or downstream tasks — the closest analog to Gradle's
project.version.
- A named CLI command for a repeated workflow (
./kotlin do release).
Don't write one when module.yaml already covers it (dependencies, JDK provisioning, source layouts,
basic packaging), and never to reuse a Gradle plugin — the Kotlin Toolchain cannot consume them.
Layout
repo-root/
├── kotlin, kotlin.bat # wrappers (from `kotlin init`)
├── project.yaml # registers the plugin
├── plugins/<name>/
│ ├── module.yaml # product: jvm/amper-plugin
│ ├── plugin.yaml # tasks: + commands: + generated:
│ └── src/
│ ├── Settings.kt # @Configurable interface
│ ├── tasks/ # one @TaskAction per file
│ │ ├── Foo.kt
│ │ └── FooSteps.kt # internal shared helpers (not @TaskAction)
│ └── <domain logic>/
└── <consumer-module>/
└── module.yaml # plugins: { <name>: enabled: true, ... }
Keep at least one consumer module in the repo — it is the only way to exercise the plugin end-to-end, and
plugins cannot be published to any public registry yet.
project.yaml
modules:
- consumer-app
- plugins/<name>
plugins:
- ./plugins/<name>
Without the top-level plugins: block the plugin id is unresolvable from any consumer.
module.yaml — the plugin module
product: jvm/amper-plugin # marks the module as a plugin
dependencies:
- <coordinate>:<version>
- <coordinate>:<version>: runtime-only # required only at runtime
- <coordinate>:<version>: compile-only
pluginInfo:
id: <plugin-id> # what consumers write under `plugins:`
settingsClass: <fully.qualified.Settings> # the @Configurable interface
settings:
jvm:
jdk:
version: 21
kotlin:
languageVersion: 2.1
@Configurable interface Settings
Defaults go in interface property getters; nested blocks become nested @Configurable interfaces.
@Configurable
interface Settings {
val someValue: String get() = "default"
val checks: ChecksSettings
}
@Configurable
interface ChecksSettings {
val strict: Boolean get() = true
}
Consumers override what they need in module.yaml; omitted values fall back to the getter default:
plugins:
<plugin-id>:
enabled: true
someValue: "override"
checks:
strict: false
@TaskAction
Task actions are top-level funs, called when the matching plugin.yaml entry executes.
@TaskAction
fun foo(
@Input moduleRootDir: Path,
@Output outputDir: Path,
settings: Settings,
) {
// body
}
@Input path: Path— declared input; Kotlin Toolchain snapshots its contents for execution avoidance.@Output path: Path— declared output directory; Kotlin Toolchain creates it and passes the path in. Write
to the exact Path you received, or downstream references won't find the result.
settings: Settings(or any@Configurable) — typed configuration, wired inplugin.yaml.- Plain
Path/ primitives — passed literally fromplugin.yaml. println(...)is the output channel; Kotlin Toolchain captures stdout.
Execution avoidance
A @TaskAction is skipped when its declared inputs are unchanged. Tasks whose real inputs are Git history,
the network, or environment variables cannot be fingerprinted, so opt out:
@TaskAction(executionAvoidance = ExecutionAvoidance.Disabled)
fun foo(@Output outputDir: Path, settings: Settings) { /* ... */ }
Tasks with no @Output are never cached and always re-run — correct for purely side-effecting tasks
(releases, deployments, pushes).
plugin.yaml
tasks:
foo:
action: !<fully.qualified.foo>
moduleRootDir: ${module.rootDir}
outputDir: ${taskOutputDir}
settings: ${pluginSettings}
bar:
action: !<fully.qualified.bar>
input: ${tasks.foo.action.outputDir}/result.txt
settings: ${pluginSettings}
generated:
resources:
- directory: ${tasks.foo.action.outputDir}
commands:
- foo
| Reference | Resolves to |
|---|---|
| ${module.rootDir} | Directory containing the consumer's module.yaml. Pass as @Input to inspect the consumer's tree. |
| ${taskOutputDir} | Toolchain-managed per-task output directory. Pass as @Output. |
| ${pluginSettings} | The @Configurable object built from the consumer's module.yaml. |
| ${tasks.<task>.action.<param>} | Another task's parameter — used in generated.* and to wire one task's @Input to another's @Output. |
generated.resources / generated.sources
Both register a directory (usually a task's @Output) as a contribution to the consumer's build, and both
auto-wire the producing task to run first:
generated.resources— added to the JAR classpath, reachable viagetResourceAsStream("/path/in/jar").generated.sources— added as a Kotlin source root and compiled with the consumer'ssrc/.
Tasks vs commands
Tasks are the implementation, addressed as ./kotlin task :<module>:<task>@<plugin-id> — the docs advise
against relying on that mangled name. Commands are the public API: ./kotlin do <command-name>, listed via
./kotlin show commands (-m <module> to scope).
- A task whose
@Outputfeedsgenerated.resources/generated.sourcesis a build-graph contributor. Keep
it out of commands:; it runs automatically and exposing it invites users to run it by hand.
- A task users invoke directly must be in
commands:.
File-based task communication
There is no shared mutable build state — no project.version, no extension property maps. Tasks talk
through matched paths:
- The producer takes
@Output outputDir: Pathand writes files into it. plugin.yamlpoints a consumer task's@Inputat${tasks.<producer>.action.outputDir}/<file>.- The Toolchain infers the dependency from the path match — no
dependsOnAPI needed.
The same @Output directory can serve build-time consumers (via @Input) and runtime consumers
(registered under generated.resources, read via getResourceAsStream) simultaneously.
Runtime overrides via environment variables
There is no -Pkey=value. Read env vars inside the action for ephemeral overrides:
val forced = System.getenv("MYPLUGIN_FORCE_VALUE")?.takeIf { it.isNotBlank() }
val skipChecks = System.getenv("MYPLUGIN_SKIP_CHECKS")?.equals("true", ignoreCase = true) == true
Pass the env map in as a constructor parameter rather than calling System.getenv() deep in the call stack,
so logic stays unit-testable. Document every recognised variable in the plugin's README. Env vars are
ephemeral overrides, not a trust boundary — validate a value before using it in a file path or process
argument.
Sharing logic across task actions
Tasks often share steps (verify → create → push). Don't compose an atomic user-facing task from a chain of
build-graph tasks: separate invocations re-open shared resources and open a window where another process
observes intermediate state.
Limitations to design around
- Plugins are local-only; no public registry publishing yet.
- Plugins are module-level; there is no project-wide plugin. Every consumer module lists it under
plugins:, and cross-module effects flow through files.
- No
${...}interpolation inmodule.yaml(as of 0.11) — consumer settings are literal values. - No
Project.afterEvaluate, no lazyProvider/Propertygraph. Compute derived values in the action body. -h/--helpdoes not list plugin commands; use./kotlin show commands.
Validate against a consumer
- Add a small consumer module (
demo-app/,sample/) enabling the plugin with realistic settings. - Have its
main.ktor a test read whatever the plugin publishes. - Put the exact commands and expected output in the plugin's README, so a fresh clone can paste and compare.
Plugin docs: <https://kotlin-toolchain.org/dev/user-guide/plugins/>
想直接用这个技能?
本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。
它属于哪个仓库
skills/kotlin-tooling-kotlin-toolchain-plugin-authoring/SKILL.md同一个仓库里的其他技能
- kotlin-backend-jpa-entity-mapping
- kotlin-tooling-agp9-migration
- kotlin-tooling-cocoapods-spm-migration
- kotlin-tooling-gradle-to-kotlin-toolchain-plugin
- kotlin-tooling-gradle-to-kotlin-toolchain-project
- kotlin-tooling-immutable-collections-0-5-x-migration
- kotlin-tooling-java-to-kotlin
- kotlin-tooling-kotlin-toolchain
- kotlin-tooling-native-build-performance