Use Kotlin plugins from buildSrc - gradle

How can I apply the Kotlin plugins from a buildSrc plugin?
I have a Kotlin project with a build.gradle.kts file containing this:
plugins {
application
kotlin("jvm")
kotlin("plugin.serialization")
}
I want to create a custom plugin in buildSrc:
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
class MyPlugin: Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply("org.gradle.application") //This works
project.pluginManager.apply("¿kotlin(jvm)?") //<-- Here is my doubt
project.pluginManager.apply("¿kotlin(plugin.serialization)?") //<-- Here is my doubt
}
}
And use it like this:
plugins {
id("com.example.myplugin")
}

To apply Gradle plugins from within buildSrc plugins you need to do two things
Add the plugins as dependencies in buildSrc/build.gradle.kts
Plugins must be added as dependencies using the Maven coordinates, not the plugin ID. The Maven coordinates of plugins can be found in the Gradle plugin portal.
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.serialization
// buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
// the Maven coordinates of the Kotlin Gradle and Serialization plugins
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
implementation("org.jetbrains.kotlin:kotlin-serialization:1.7.20")
}
apply the plugins, either using the class, or the plugin ID.
(Note that kotlin("jvm") is a helper function that obscures the actual Gradle plugin ID, which is org.jetbrains.kotlin.jvm)
class MyPlugin: Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply("org.jetbrains.kotlin.jvm")
project.pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
// the plugin class for the Kotlin JVM & Serialization plugins
project.plugins.apply(org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper::class)
project.plugins.apply(org.jetbrains.kotlinx.serialization.gradle.SerializationGradleSubplugin::class)
}
}
(it wasn't easy to find the plugin classes - I had to dig around in the jar to find the plugin marker artifact, e.g. kotlin-serialization-1.7.20-gradle71.jar!/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.serialization.properties)
You might also like to use precompiled script plugins. They allow for writing buildSrc script plugins that much more similar to standard build.gradle.kts files, and so you can apply plugins in the plugins block.
// buildSrc/src/main/kotlin/conventions/kotlin-jvm.gradle.kts
plugins {
kotlin("jvm")
}

Related

How to change name of java library when build with gradle?

I'm trying to build a java library for my other java projects. I'm also trying to learn gradle. There is a tutorial : https://docs.gradle.org/current/samples/sample_building_java_libraries.html shows how to build libraries with gradle.
But somehow when I use gradlew build it always gives me lib-< version >.jar and creates a folder called lib and I can't change it.
This is my settings.gradle
rootProject.name = 'myOwnLibrary'
include('lib')
this is my build.gradle (inside lib folder)
plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
}
version = "0.1.1"
tasks.named('jar') {
manifest {
attributes('Implementation-Title': project.name,
'Implementation-Version': project.version)
}
}
repositories {
// Use JCenter for resolving dependencies.
jcenter()
}
dependencies {
// Use JUnit test framework.
testImplementation 'junit:junit:4.13'
// This dependency is exported to consumers, that is to say, found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:29.0-jre'
}
With Kotlin DSL example, you can add in your tasks jar the following snippet:
tasks.jar {
manifest {
attributes(mapOf("Implementation-Title" to rootProject.name,
"Implementation-Version" to project.version))
}
archiveBaseName.set(rootProject.name)
}
where rootProject.name, is the value localized into settings.gradle.kts file.

Gradle Kotlin DSL multi project build with Java Modules

I'm creating a new project (using IntelliJ IDEA) that will be using:
Gradle as the build system
Kotlin DSL for build scripts
Java 9 modules for "organisation"
Kotlin as the primary language
I'm having problems setting up Gradle to properly build my project. Most examples I've found are for Groovy and not Kotlin DSL, and most only cover some of the features I want, but not all.
Right now I have two modules, core and lib, where the core module requires the lib module. My gradle build scripts are:
build.gradle.kts
plugins {
base
kotlin("jvm") version "1.3.41" apply false
}
subprojects {
afterEvaluate {
tasks.withType<JavaCompile> {
inputs.property("moduleName", extra["moduleName"])
options.compilerArgs.addAll(arrayOf("--module-path", classpath.asPath))
classpath = files()
}
}
repositories {
mavenCentral()
jcenter()
}
}
core/build.gradle.kts
extra.set("moduleName", "myproject.core")
plugins {
kotlin("jvm")
}
dependencies {
compile(kotlin("stdlib"))
compile(project(":networking"))
}
lib/build.gradle.kts
extra.set("moduleName", "myproject.lib")
plugins {
kotlin("jvm")
}
dependencies {
compile(kotlin("stdlib"))
}
Doing this, configuration fails with:
A problem occurred configuring project ':core'.
Cannot get property 'moduleName' on extra properties extension as it does not exist
If I remove the inputs.property() line the configuration succeeds, but the core compilation fails (lib compiles successfully) with :
Task :core:compileKotlin
e: Module myproject.lib cannot be found in the module graph
I assume the issue is is my root build.gradle.kts, but I cannot figure out how to make it work. Googling around, Kotlin DSL for Gradle is somewhat new and not as widely used, and documentation is pretty scarce.
Any advice would be appreciated!
Naturally after posting the question I found the solution. There exists a Gradle plugin that does exactly what's needed in this situation, with a KotlinDSL example: https://github.com/java9-modularity/gradle-modules-plugin/tree/master/test-project-kotlin
Using the plugin, all I needed to do is change the root build.gradle.kts file:
plugins {
base
kotlin("jvm") version "1.3.41" apply false
id("org.javamodularity.moduleplugin") version "1.5.0" apply false
}
subprojects {
apply(plugin = "org.javamodularity.moduleplugin")
repositories {
mavenCentral()
jcenter()
}
}
Note: Make sure that your module-info.java file is in the java src folder, and not in the kotlin src folder, otherwise the plugin will not detect the module.

How to share boilerplate Kotlin configuration across multiple Gradle projects?

The typical Kotlin configuration in a Gradle project is very boilerplate, and I'm looking for a way of abstracting it out into an external build script so that it can be reused.
I have a working solution (below), but it feels like a bit of a hack as the kotlin-gradle-plugin doesn't work out of the box this way.
It's messy to apply any non-standard plugin from an external script as you can't apply the plugin by id, i.e.
apply plugin: 'kotlin' will result in Plugin with id 'kotlin' not found.
The simple (well, usually) workaround is to apply by the fully qualified classname of the plugin, i.e.
apply plugin: org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
which in this case throws a nice little exception indicating that the plugin probably wasn't meant to be called this way:
Failed to determine source cofiguration of kotlin plugin.
Can not download core. Please verify that this or any parent project
contains 'kotlin-gradle-plugin' in buildscript's classpath configuration.
So I managed to hack together a plugin (just a modified version of the real plugin) which forces it to find the plugin from the current buildscript.
kotlin.gradle
buildscript {
ext.kotlin_version = "1.0.3"
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
apply plugin: CustomKotlinPlugin
import org.jetbrains.kotlin.gradle.plugin.CleanUpBuildListener
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
/**
* Wrapper around the Kotlin plugin wrapper (this code is largely a refactoring of KotlinBasePluginWrapper).
* This is required because the default behaviour expects the kotlin plugin to be applied from the project,
* not from an external buildscript.
*/
class CustomKotlinPlugin extends KotlinBasePluginWrapper {
#Override
void apply(Project project) {
// use String literal as KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY constant isn't available
System.setProperty("kotlin.environment.keepalive", "true")
// just use the kotlin version defined in this script
project.extensions.extraProperties?.set("kotlin.gradle.plugin.version", project.property('kotlin_version'))
// get the plugin using the current buildscript
def plugin = getPlugin(this.class.classLoader, project.buildscript)
plugin.apply(project)
def cleanUpBuildListener = new CleanUpBuildListener(this.class.classLoader, project)
cleanUpBuildListener.buildStarted()
project.gradle.addBuildListener(cleanUpBuildListener)
}
#Override
Plugin<Project> getPlugin(ClassLoader pluginClassLoader, ScriptHandler scriptHandler){
return new KotlinPlugin(scriptHandler, new KotlinTasksProvider(pluginClassLoader));
}
}
This can then be applied in any project (i.e. apply from: "kotlin.gradle") and you're up and running for Kotlin development.
It works, and I haven't had any issues yet, but I'm wondering if there is a better way? I'm not really keen on merging in changes to the plugin every time there's a new version of Kotlin.
Check out the nebula-kotlin-plugin. It seems very close to what you're trying to achieve there.
The problem here is that there is a known gradle bug about the inability to apply plugins by id from init scripts. That's why you need to use fully qualified class name as a workaround.
E.g. I have the following in the init script and it works:
apply plugin: org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
By the way, I created a gradle plugin for preparing custom gradle distributions with common setup defined in init script - custom-gradle-dist. It works perfectly for my projects, e.g. a build.gradle for a library project looks like this (this is a complete file, all repository, apply plugin, dependencies etc setup is defined in the init script):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}

specify classpath for plugin inside apply method

How to specify classpath for custom gradle plugin inside class, that implements interface Plugin?
class TaskPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('task') << {
println 'simple task'
}
}
}
I saw something similar there
https://github.com/gradle/gradle/blob/6277a4dc70fbeea83c111e75c95ba851d1e56ffc/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java#L25
However I don't know how to apply it for my case. Specifically I want to use both test and main sourcesets.
I want to get rid of classpath dependencies, related to my project. Because I have the same dependencies in dependencies and in buildscript { dependencies {
Every time I add plugin I have to add classpath dependencies, which that plugin is using.
apply plugin: 'my-plugin'
task {
... do something
}
buildscript {
repositories {
flatDirs dir('.')
}
dependencies {
classpath ("my-group:my-plugin:my-version")
classpath ("dependency:dependency:dependency")
}
}
The problem is that when I specify basic dependencies (not inside buildscript) I may have the same dependencies
dependencies {
compile ("dependency:dependency:dependency")
}
Sometimes I need some compiled project dependencies compile project(":my-project") which I have to specify using classpath files("path to compiled").
How to adjust plugin implementation to remove those dependency duplicates? Like for example if I declared dependency using compile or runtime - plugin will know about it and there is no need to declare dependency in classpath explicitly.

Creating a Gradle plugin with a dependency on another (external) plugin

I want to create a plugin that automatically applies other (external plugins). This requires setting the buildscript dependency for the plugin before I call "apply plugin". However it seems like I can't add buildscript dependencies in a plugin or I get:
You can't change a configuration which is not in unresolved state!
Is there a solution to this ?
My sample (non-working) code:
import org.gradle.api.Project
import org.gradle.api.Plugin
class SamplePlugin implements Plugin<Project>{
void apply(Project project) {
project.buildscript.dependencies.add("classpath","net.sourceforge.cobertura:cobertura:1.9.4.1");
project.configure(project){
apply plugin: 'cobertura'
}
}
}
The way to go about this is to publish a pom.xml or ivy.xml along with the plugin Jar that describes the plugin's dependencies. Alternatively, you can write a script plugin that declares its dependencies in a buildscript {} section. A script plugin is simply a reusable build script that gets applied with apply from: ....

Resources