In our team we have lot of projects built with Gradle. Some parts in the Gradle files are all the same. For example, we use Java 11 in all our projects. So my idea was that I could split up my build.gradle files into a common part, that is then synced from a central repository into every Gradle project while the project specific parts remain in build.gradle.
build.gradle:
plugins { id 'java' //... } apply from: "common.gradle.kts" dependencies { // ... }
common.gradle.kts
java { toolchain { languageVersion = JavaLanguageVersion.of(11) } } test { useJUnitPlatform() }
Now I get the error message by Gradle
* Where: Script '/Users/.../common.gradle.kts' line: 4 * What went wrong: Script compilation errors: Line 04: java { ^ Expression 'java' cannot be invoked as a function. The function 'invoke()' is not found Line 04: java { ^ Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public val PluginDependenciesSpec.java: PluginDependencySpec defined in org.gradle.kotlin.dsl Line 05: toolchain { ^ Unresolved reference: toolchain Line 06: languageVersion = JavaLanguageVersion.of(11) ^ Unresolved reference: languageVersion Line 09: test { ^ Unresolved reference: test Line 10: useJUnitPlatform() ^ Unresolved reference: useJUnitPlatform 6 errors
For some configurations I found an alternative using a more generic API that works, though it is a lot of effort to find the corresponding alternatives and in the end no one can guarantee that they do exactly the same thing:
tasks.withType<JavaCompile> { options.release.set(11) }
So the question remains: why can’t I use the DSL functions java
or test
in my externalized common.gradle.kts?
It seems it has to do something with the use of Kotlin script, at least if I use Groovy too for my externalized script, it works.
Advertisement
Answer
In your common.gradle.kts
, java { }
is generated helper Kotlin DSL function. Gradle doesn’t know about the Kotlin DSL helpers unless
- it’s part of a build (not using
apply(from = "...")
- the java plugin is applied
Understanding when type-safe model accessors are available
Only the main project build scripts and precompiled project script plugins have type-safe model accessors. Initialization scripts, settings scripts, script plugins do not. These limitations will be removed in a future Gradle release.
Reacting to plugins
https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#reacting_to_plugins
It’s still possible to have your common.gradle.kts
– but it needs to configure the Java Plugin without the Kotlin DSLs
// common.gradle.kts plugins.withType(JavaBasePlugin::class).configureEach { // the project has the Java plugin project.extensions.getByType<JavaPluginExtension>().apply { toolchain { languageVersion.set(JavaLanguageVersion.of(11)) } } tasks.withType<Test>().configureEach { useJUnitPlatform() } }
This is a little more clunky because the Kotlin DSL helpers aren’t available.
buildSrc convention plugins
If you want to create conventions for a single project, then the standard way is to create buildSrc convention plugins.
https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources
This is best for projects that have lots of subprojects.
// $projectRoot/buildSrc/src/main/kotlin/java-convention.gradle.kts plugins { java } java { toolchain { languageVersion = JavaLanguageVersion.of(11) } } test { useJUnitPlatform() }
See the answer here for more detail: https://stackoverflow.com/a/71892685/4161471
Sharing plugins between projects
https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html
It’s possible to share convention plugins between projects, so long as you have a Maven repo to deploy your plugins.
It’s even possible to create your own Gradle distribution, so the plugins are included along with the Gradle wrapper! https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:custom_gradle_distribution
However I’d advise against these approaches. Generally the time invested in creating shared plugins will never be faster than just copy and pasting buildSrc convention plugins. And more importantly, it’s best to keep projects independent. While sharing build conventions seems like a good idea, it introduces dependencies that make it hard to track problems, and makes updating the shared plugins hard as you’re not sure what the consequences might be. This article explains more https://phauer.com/2016/dont-share-libraries-among-microservices/