Templates¶
In modularized projects, some parts of the configuration are usually the same for some or all modules. The most common are the JDK version, the Kotlin version, compiler arguments, repositories, and publishing configuration.
The Kotlin Toolchain offers a way to extract whole sections or their parts into reusable template files.
Basics¶
Template files are named <name>.module-template.yaml and have the same structure as module.yaml files.
Note
Template files cannot have a product: section, but @platform-qualifiers are supported.
A template is applied to a module.yaml file by listing it in the apply: section.
The path to a template usually starts with // and is relative to the project root directory
(where project.yaml is located).
product: jvm/app
apply:
- //common.module-template.yaml
dependencies:
- io.ktor:ktor-client:3.5.1
repositories:
- https://my.company/maven
settings:
kotlin:
version: 2.4.10
When doing this, the contents of the template are merged with that of the module file, to give an effective configuration that looks like this:
product: jvm/app
repositories:
- https://my.company/maven
dependencies:
- io.ktor:ktor-client:3.5.1
settings:
kotlin:
version: 2.4.10
You can see the effective configuration of a module using the show settings command:
kotlin show settings --module=my-module
Nested templates¶
It is possible to apply templates to other templates by using the same apply section in the template files:
settings:
jvm:
release: 11
apply:
- //java.module-template.yaml
settings:
springBoot: enabled
product: jvm/app
apply:
- //spring.module-template.yaml
The resulting effective module is:
product: jvm/app
settings:
jvm:
release: 11
springBoot: enabled
Resolution rules¶
Precedence¶
The precedence is determined between entire files (module.yaml and templates).
The position of the apply section within a file doesn't matter.
The basic rules are simple:
- The
module.yamlalways takes precedence over the templates that it applies. - A template takes precedence over the other templates that it applies (and so on, transitively).
Some pairs of templates do not apply each other even transitively, so they don't have any precedence over each other. If such templates happen to disagree on the value of a property in the configuration, we may have a conflict (see conflict resolution below).
The module.yaml and templates essentially form a graph via apply:.
To respect the rules above, the effective configuration is constructed by starting from the deepest nested template(s),
and merging the contents by going level by level in that graph (topological order), following the
merging rules (see below).
For example:
flowchart TD
common["common template"]
android["android template<br><sub>apply: common</sub>"]
metro["metro template<br><sub>apply: common</sub>"]
module["module.yaml<br><sub>apply: [android, metro]</sub>"]
android --> common
metro --> common
module --> android
module --> metro
- the contents of the
commontemplates are a starting point - the contents of
androidandmetroare merged on top. They have precedence overcommon, but they don't have precedence over each other, so their order doesn't matter here. If they try to set the same property to different values, we have a conflict (see conflict resolution below). - the contents of the
module.yamlfile are added last.
Merging rules¶
Templates are applied using the same merging rules as platform-specific dependencies and settings:
- Scalar values (strings, numbers etc.) are overridden.
- Mappings and lists are appended.
To determine who overrides who, we use the precedence rules defined in the previous section. Here is an example:
product: jvm/app
apply:
- //common.module-template.yaml
dependencies:
- //jvm-util
settings:
kotlin:
version: 2.3.21
jvm:
release: 17
dependencies:
- //shared
settings:
kotlin:
version: 2.4.10
compose: enabled
After applying the template, the resulting effective module is:
product: jvm/app
dependencies: # lists appended
- //shared
- //jvm-util
settings: # objects merged
kotlin:
version: 2.3.21 # module.yaml value takes precedence
compose: enabled # from the template
jvm:
release: 17 # from the module.yaml
Each template is applied to the resulting module only once even if it is applied in multiple templates used in a module. E.g.:
dependencies:
- //core-lib
apply:
- //common.module-template.yaml
dependencies:
- //client-lib
apply:
- //common.module-template.yaml
dependencies:
- //server-lib
product: jvm/app
apply:
- //client.module-template.yaml
- //server.module-template.yaml
will result in the effective module:
product: jvm/app
dependencies:
- //core-lib # core-lib is added to the list only once
- //client-lib
- //server-lib
Conflict resolution¶
If two templates define different scalar values for the same property and neither template has precedence over the other
in the apply graph, the Kotlin Toolchain reports a conflict.
settings:
jvm:
release: 17
settings:
jvm:
release: 21
With only java17-compatible and java21-compatible, settings.jvm.release is conflicting (17 vs 21)
because these templates are siblings.
product: jvm/app
apply:
- //java17-compatible.module-template.yaml
- //java21-compatible.module-template.yaml
# Error: Conflicting values for property `release`
You can solve the conflict by explicitly setting the property value in the module applying both templates:
product: jvm/app
apply:
- //java17-compatible.module-template.yaml
- //java21-compatible.module-template.yaml
settings:
jvm:
release: 21 #(1)!
- The explicitly set value
21takes precedence over conflicting values, and no conflict is reported
If you still want to keep the setting as a template, you can resolve it by introducing a template that applies both conflicting templates and defines the final value.
apply:
- //java17-compatible.module-template.yaml
- //java21-compatible.module-template.yaml
settings:
jvm:
release: 21
product: jvm/app
apply:
- //java-runtime-policy.module-template.yaml #(1)!
- The value of
jvm.releasecoming fromjava-runtime-policytemplate takes precedence over conflicting values fromjava17andjava21templates, and no conflict is reported