How to parse and modify build.gradle.kts Kotlin Gradle build script? - spring

I want to parse a build.gradle.kts (Gradle build script in Kotlin), so I can find out what values are currently set and I also want to modify or add new entries in some categories.
Example (build.gradle.kts):
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.2.6.RELEASE"
kotlin("jvm") version "1.3.71"
etc...
}
group = "net.myProject"
version = "1.0"
java.sourceCompatibility = JavaVersion.VERSION_11
val developmentOnly by configurations.creating
configurations {
runtimeClasspath {
extendsFrom(developmentOnly)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
etc...
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
It´s basically a classical Spring Boot application. What I would want to be able to do is:
Get some kind of structured representation of the file
So I can append a fixed version to a dependency (e.g. implementation("org.springframework.boot:spring-boot-starter-actuator :2.2.6.RELEASE")
And so I could append new dependencies into the dependencies list
I know that this is a special DSL for Gradle Build Scripts here, but where can I find this and how can I parse/use it?
Thanks!

Unfortunately kotlin doesn't seem to provide it's own parser, which means there won't be a simple answer and you'll have to deal with language updates down the line. You'll probably also want to make sure that the parsed structure allows you to preserve white-spaces to keep your formatting intact.
ktlint might be an interesting starting point. It uses the PSI-Elements from IntelliJ and also reuses IntelliJ's parser.
val normalizedText = text.replace("\r\n", "\n").replace("\r", "\n")
val positionByOffset = calculateLineColByOffset(normalizedText)
val fileName = if (script) "file.kts" else "file.kt"
val psiFile = psiFileFactory.createFileFromText(fileName, KotlinLanguage.INSTANCE, normalizedText) as KtFile
val errorElement = psiFile.findErrorElement()
if (errorElement != null) {
val (line, col) = positionByOffset(errorElement.textOffset)
throw ParseException(line, col, errorElement.errorDescription)
}
val rootNode = psiFile.node
// use visitor pattern on rootNode
Frankly, unless this brings a lot of value to your project, I'd try to find a different solution. Maybe you can read the values in your build.gradle.kts from an easily parsable source like a json file?
Hope that helps.

Related

How to consolidate imported plugins to custom plugin in Gradle using Kotlin

I have microservices that will share some of the same configuration between all of them, mainly Jib, publish, and release. Not sure if it's possible to do the same for dependencies but it would be beneficial to include actuator and log4j2 in each. Here is the build.gradle.kts for one of my projects.
import net.researchgate.release.BaseScmAdapter
import net.researchgate.release.GitAdapter
import net.researchgate.release.ReleaseExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("com.gorylenko.gradle-git-properties") version "1.5.1"
id("com.google.cloud.tools.jib") version "1.6.1"
id("io.spring.dependency-management") version "1.0.7.RELEASE"
id("net.researchgate.release") version "2.8.1"
id("org.sonarqube") version "2.7.1"
id("org.springframework.boot") version "2.1.6.RELEASE"
kotlin("jvm") version "1.2.71"
kotlin("plugin.spring") version "1.2.71"
jacoco
`maven-publish`
}
java.sourceCompatibility = JavaVersion.VERSION_1_8
springBoot {
buildInfo {
group = project.properties["group"].toString()
version = project.properties["version"].toString()
description = project.properties["description"].toString()
}
}
repositories {
maven(url = uri(project.properties["nexus.url.gateway"].toString()))
mavenCentral()
}
dependencies {
// Kotlin
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// Spring
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.cloud:spring-cloud-config-server")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR3")
}
}
configurations.all {
exclude(group = "ch.qos.logback", module = "logback-classic")
exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging")
}
tasks {
withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
build { dependsOn(clean) }
afterReleaseBuild { dependsOn(publish) }
publish { dependsOn(build) }
jibDockerBuild { dependsOn(build) }
jacocoTestReport {
reports {
html.isEnabled = false
xml.isEnabled = true
}
}
}
publishing {
publications {
create<MavenPublication>(project.name) {
from(components["java"])
pom {
scm {
connection.set("scm:git:git#github.com:company/${project.name}.git")
developerConnection.set("scm:git:git#github.com:company/${project.name}.git")
url.set("https://github.com/company/${project.name}/")
}
}
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
repositories {
maven {
val releasesRepoUrl = "${project.properties["nexus.url.publish"].toString()}/releases"
val snapshotsRepoUrl = "${project.properties["nexus.url.publish"].toString()}/snapshots"
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
credentials {
username = project.properties["nexus.user"].toString()
password = project.properties["nexus.password"].toString()
}
}
}
}
fun ReleaseExtension.git(configureFn : GitAdapter.GitConfig.() -> Unit) {
(propertyMissing("git") as GitAdapter.GitConfig).configureFn()
}
release {
scmAdapters = mutableListOf<Class<out BaseScmAdapter>> ( GitAdapter::class.java )
git {
requireBranch = "develop"
pushToRemote = project.properties["release.git.remote"].toString()
pushReleaseVersionBranch = "master"
tagTemplate = "${project.name}.${project.version}"
}
}
jib {
from {
image = "openjdk:8-jdk-alpine"
}
to {
image = "host:port/${project.name}:${project.version}"
auth {
username = project.properties["nexus.user"].toString()
password = project.properties["nexus.password"].toString()
}
}
container {
workingDirectory = "/"
ports = listOf("8080")
environment = mapOf(
"SPRING_OUTPUT_ANSI_ENABLED" to "ALWAYS",
"SPRING_CLOUD_BOOTSTRAP_LOCATION" to "/path/to/bootstrap.yml"
)
useCurrentTimestamp = true
}
setAllowInsecureRegistries(true)
}
I was able to get a custom plugin created and added to this project using git#github.com:klg71/kotlintestplugin.git and git#github.com:klg71/kotlintestpluginproject.git but I have no idea how to implement these existing plugins and their configurations. In the main Plugin class in the apply function I am able to call the project.pluginManager.apply(PublishingPlugin::class.java) which causes the task to show in the project referencing the custom plugin but I can't figure out how to configure it and it does not successfully publish to the nexus server. I can publish the plugin itself to the nexus server and reference it in the microservice but it skips running the task, which I assume is caused by the configuration not being included. Also, when trying to apply/configure the Jib plugin, all of the classes are not visible when attempting to import.
So the above answer isn't super long and to preserve the issues I ran into I am posting a new answer.
PLUGIN
This portion of the answer is going to discuss the actual custom plugin project.
Because the plugins wrapper in the build.gradle.kts is runtime, the CustomPlugin.kt does not have access to it at compile time. My boss who is much smarter than me was kind enough to point this out to me even though he has never worked with gradle. Although I looked pretty dumb in front of him he still got me up and running by basically following the 'legacy' way of applying plugins in gradle.
plugins { // This is a runtime script preventing plugins declared here to be accessible in CustomPlugin.kt but is used to actually publish/release this plugin itself
id("net.researchgate.release") version "2.8.1"
kotlin("jvm") version "1.3.0"
`maven-publish`
}
repositories {
maven { url = uri("https://plugins.gradle.org/m2/") } // This is required to be able to import plugins below in the dependencies
jcenter()
}
dependencies {
compile(kotlin("stdlib"))
compile(kotlin("reflect"))
// These must be declared here (at compile-time) in order to access in CustomPlugin.kt
compile(group = "gradle.plugin.com.gorylenko.gradle-git-properties", name = "gradle-git-properties", version = "2.2.0")
compile(group = "gradle.plugin.com.google.cloud.tools", name = "jib-gradle-plugin", version = "1.7.0")
compile(group = "net.researchgate", name = "gradle-release", version = "2.8.1")
compile(group = "org.asciidoctor", name = "asciidoctor-gradle-plugin", version = "1.5.9.2")
compile(group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version = "0.9.18")
compile(group = "org.sonarsource.scanner.gradle", name = "sonarqube-gradle-plugin", version = "2.8")
implementation(gradleApi()) // This exposes the gradle API to CustomPlugin.kt
}
This allowed me to have access to jib and everything else in the CustomPlugin.kt.
The plugins jacoco and maven-publish are automatically accessible in the plugin project but still need to be added in the microservice project referencing the plugin. I was unable to find a workaround for this unfortunately.
I included the typical maven-publish plugin in the build.gradle.kts to push to nexus with the publishing task configurations in the build.gradle.kts as well so I could pull this from nexus in the microservice that wanted to use the plugin.
publishing {
publications {
create<MavenPublication>(project.name) {
from(components["java"])
pom {
scm {
connection.set("scm:git:git#github.com:diendanyoi54/${project.name}.git")
developerConnection .set("scm:git:git#github.com:diendanyoi54/${project.name}.git")
url.set("https://github.com/diendanyoi54/${project.name}/")
}
}
}
}
repositories {
maven {
val baseUrl = "https://${project.properties["nexus.host"].toString()}:${project.properties["nexus.port.jar"].toString()}/repository"
url = uri(if (version.toString().endsWith("SNAPSHOT")) "$baseUrl/maven-snapshots" else "$baseUrl/maven-releases")
credentials {
username = project.properties["nexus.user"].toString()
password = project.properties["nexus.password"].toString()
}
}
}
}
Lastly, you want to make sure you include the properties file that will tell the microservices where the plugin class is. In Intellij's IDEA, when typing the path to the implementation-class it auto completed for me.
The name of this file should reflect apply(plugin = "string") in the microservice's build.gradle.kts.
IMPLEMENTATION
This portion of the answer is going to reflect the microservice project that will be referencing the plugin. As stated above, jacoco and maven-publish still need to be added to the plugin block in the build.gradle.kts for some reason (I think because they are official gradle plugins).
To reference the plugin from the nexus server it was published to, the microservice must reference it in the buildscript.
buildscript { // Custom plugin must be accessed by this buildscript
repositories {
maven {
url = uri("https://${project.properties["nexus.host"].toString()}:${project.properties["nexus.port.jar"].toString()}/repository/maven-public")
credentials {
username = project.properties["nexus.user"].toString()
password = project.properties["nexus.password"].toString()
}
}
}
dependencies { classpath("com.company:kotlin-consolidated-plugin:1.0.0-SNAPSHOT") }
}
Lastly, the plugin must be applied using the properties file name referenced above.
apply(plugin = "com.company.kotlinconsolidatedplugin") // Custom plugin cannot reside in plugin declaration above
I created sample projects of these and posted them to Github so feel free to clone or take a look:
git#github.com:diendanyoi54/kotlin-consolidated-plugin.git
git#github.com:diendanyoi54/kotlin-consolidated-plugin-implementation.git
I was able to successfully able to use the github repo examples referenced above to accomplish what I needed with the publish task. Here is my custom plugin's build.gradle.kts.
plugins {
id("com.google.cloud.tools.jib") version "1.6.1"
id("org.sonarqube") version "2.7.1"
kotlin("jvm") version "1.3.0"
`maven-publish`
}
dependencies {
compile(kotlin("stdlib"))
compile(kotlin("reflect"))
implementation(gradleApi())
}
repositories {
jcenter()
}
publishing {
publications {
create<MavenPublication>(project.name) {
from(components["java"])
pom {
scm {
connection.set("scm:git:git#github.com:company/${project.name}.git")
developerConnection.set("scm:git:git#github.com:company/${project.name}.git")
url.set("https://github.com/company/${project.name}/")
}
}
}
}
repositories {
maven {
val baseUrl = "https://${project.properties["nexus.host"].toString()}:${project.properties["nexus.port.jar"].toString()}/repository"
url = uri(if (version.toString().endsWith("SNAPSHOT")) "$baseUrl/maven-snapshots" else "$baseUrl/maven-releases")
credentials {
username = project.properties["nexus.user"].toString()
password = project.properties["nexus.password"].toString()
}
}
}
}
Here is the CustomPlugin.kt class.
package com.company.gradlemicroserviceplugin
//import com.google.cloud.tools.jib.gradle.JibExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.plugins.PublishingPlugin
import org.gradle.testing.jacoco.tasks.JacocoReport
import java.net.URI
open class CustomPlugin : Plugin<Project> {
override fun apply(project: Project) {
// applySonar(project)
applyPublish(project)
// applyJib(project)
}
// private fun applySonar(project: Project) {
// project.pluginManager.apply("sonarqube")
// val task = project.task("jacocoTestReport") as JacocoReport
// task.reports = JacocoReport()
// jacocoTestReport { This was nested in the tasks declaration in build.gradle.kts so the fields below are the fields I'm trying to set in task.reports
// reports {
// html.isEnabled = false
// xml.isEnabled = true
// }
// }
// }
private fun applyPublish(project: Project) {
project.pluginManager.apply(PublishingPlugin::class.java)
val publishingExtension = project.extensions.findByType(PublishingExtension::class.java)
val mavenPublication = publishingExtension?.publications?.create(project.name, MavenPublication::class.java)
publishingExtension?.repositories?.maven {
val baseUrl = "https://${project.properties["nexus.host"].toString()}:${project.properties["nexus.port.jar"].toString()}/repository"
it.url = URI(if (project.version.toString().endsWith("SNAPSHOT")) "$baseUrl/maven-snapshots" else "$baseUrl/maven-releases")
it.credentials { cred ->
cred.username = project.properties["nexus.user"].toString()
cred.password = project.properties["nexus.password"].toString()
}
}
mavenPublication?.from(project.components.findByName("java"))
mavenPublication?.pom?.scm {
it.connection.set("scm:git:git#github.com:company/${project.name}.git")
it.developerConnection.set("scm:git:git#github.com:company/${project.name}.git")
it.url.set("https://github.com/company/${project.name}/")
}
}
// private fun applyJib(project: Project) {
// project.pluginManager.apply(JibPlugin::class.java)
//
// }
}
There are definitely areas of improvement on this but at least I got something working here. There is maven-publish logic in both build.gradle.kts because I push to the custom plugin to nexus and the maven-publish logic is in the CustomPlugin.kt class so the microservice that references this plugin can use it. However, I am unable to successfully setup Jib and Sonar. Jib doesn't give me access to anything in com.google.cloud.tools.jib.gradle preventing me from using the same approach as I used in maven-publish.
For Sonar I think I'm on the right track with retrieving the task by its name but I'm unable to set any fields that belong to task.reports because they are all final and this is necessary for Sonar to properly analyze Kotlin.
Applying built-in plugins
plugins {
java
id("jacoco")
}
You can also use the older apply syntax:
apply(plugin = "checkstyle")
Applying external plugins
plugins {
id("org.springframework.boot") version "2.0.1.RELEASE"
}
i am not good in kotlin but here is link to better understanding missing migration guide to the Gradle Kotlin DSL

How do I create distributions with different dependencies using Gradle application/distribution plugin?

I am building a small Kotlin project in IntelliJ, Idea, and trying to figure out how to create multiple tar/zip files with customizations for each OS I want to support.
It seems like the distribution plugin (which is included when you use the application plugin) is the right direction, but I can't seem to figure out how to get it to do what I want.
I have read the documentation on the plugin, which can be found here, but it's not really clear to me how to accomplish what I want to do.
Here is an example build.gradle that shows at least the idea of what I want to do, that is to have a base application setup and then have some minor tailoring for each of the 3 OSs.
For example each of the 3 OSs need a unique version of the SWT library. The macos version needs a specific JVM setting, and for the linux version, I need to tailor the startup script to add some environment variables.
Is this possible with the distribution plugin? If not can someone suggest a different solution?
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
}
apply plugin: 'application'
mainClassName = "MainKt"
version '1.0-SNAPSHOT'
repositories {
// my common required repos
}
dependencies {
// my common dependencies
}
distributions {
macos {
contents { from 'src' }
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
}
linux {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, '# add some stuff')
println lines.add(2, '# add some more stuff')
unixScript.text = lines.join("\n")
}
}
windows {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.5.2"
}
}
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Update
This is what I am doing now, but I would like to improve on this.
I have a variable
def deploy = false
if (!deploy) {
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
} else {
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export foo=foo')
println lines.add(2, 'export bar=bar')
}
}
Right now I develop on my mac and set deploy to false. When I want to generate the distribution for linux I set deploy to true. I could add more code and do the same thing for windows, but I would like to just generate all the code in one task, and have it in different tar/zip files.
Opal provided some very good suggestions in the question comments that I tried, but in the end, I was not able to really get what I wanted.
Here is my current workaround solution it does pretty much what I was hoping I could have done with the distribution plugin.
Basically I create an osType property and set it's "default" to the os I am developing on, in this case macos. Then I have all my common dependencies in one dependency closure and add in the the os specific stuff in a groovy switch statement. I then just coded up a short shell script that overrides the osType property to each of the os's I want to support and calls the gradle task to build each in turn.
Here is my build.gradle file.
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
id 'application'
}
ext {
swtVersion = "4.5.2"
}
ext.osType = project.properties['osType'] ?: 'macos'
println "building for ${osType}"
mainClassName = "MainKt"
repositories {
mavenCentral()
maven { url "http://maven-eclipse.github.io/maven" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
switch(osType) {
case 'macos':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:${swtVersion}"
}
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
break
case 'linux':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:${swtVersion}"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export FOO=foo')
println lines.add(2, 'export BAR=bar')
unixScript.text = lines.join("\n")
}
break
case 'windows':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:${swtVersion}"
}
}
version "${osType}-1.0-SNAPSHOT"
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
and here is my shell script (buildAll.sh) that I run from the terminal in Idea.
#!/bin/sh
./gradlew -PosType=macos distTar
./gradlew -PosType=linux distTar
./gradlew -PosType=windows distTar
./gradlew -PosType=windows distZip

Kotlin DSL: Import a versions.gradle.kts into another build.gradle.kts

I have created a versions.gradle.kts just like that:
object Defines {
const val kotlinVersion = "1.2.61"
const val junitVersion = "5.3.0"
}
Now I want to import and use that files like that:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
group = "io.github.deglans"
version = "0.0.1-SNAPSHOT"
plugins {
application
kotlin("jvm") version Defines.kotlinVersion
}
application {
mainClassName = "io.github.deglans.polishnotation.MainKt"
}
dependencies {
compile(kotlin("stdlib-jdk8"))
testCompile("org.junit.jupiter", "junit-jupiter-api", Defines.junitVersion)
testRuntime("org.junit.jupiter", "junit-jupiter-engine", Defines.junitVersion)
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
How can I do that?
NOTE:
I have already seen this post but it is not exactly that I search...
While I think it should be possible to import another gradle.kts file, I couldn't get it to work properly.
However, I did manage to define my dependencies in a separate Kotlin file in the buildSrc directory.
Create a buildSrc folder in the root of your project (same level as build.gradle.kts)
Add a build.gradle.kts in that buildSrc folder. Here, you need to define the kotlin-dsl plugin. You also need to define the repository where to get the plugin.
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
Create a Kotlin file where you define your dependencies in src/main/kotlin inside the buildSrcfolder. You need to create a normal Kotlin .kt file, not a gradle.kts.
Reimport your Gradle config and you can now use the variables you defined in your Kotlin file created in step #3 in your build.gradle.kts.

Gradle Kotlin DSL: Define Kotlin version in unique place

For describing Gradle build scripts, we can use Kotlin via build.gradle.kts files. It's a common problem to globally define the Kotlin version to be used, both in the dependencies and also in the build plugin section (It's rather uncommon to have different versions in use for the given case).
Consider the following code (Gradle 4.3.1):
plugins {
var pluginVersion = "1.2.30"
kotlin("jvm").version(kotlinVersion)
// more
}
var dependencyVersion = "1.2.30"
dependencies {
compile(kotlin("stdlib", kotlinVersion))
compile(kotlin("reflect", kotlinVersion))
testCompile(kotlin("test", kotlinVersion))
// more
}
As you can see, the kotlin version (1.2.30 in this case) is defined twice: dependencyVersion and pluginVersion, which very often does not differ. Due to DSL restrictions, it is impossible to access the pluginVersion from outside the plugins block or access the dependencyVersion from within the plugins block.
How can the version string, "1.2.30" be extracted to a single place?
In later versions of Gradle you no longer need to specify the version of your kotlin(stdlib|reflect|test) dependencies, the Kotlin plugin will automatically configure them for you.
As for extracting the dependency to a single place, there are two main patterns:
define the constants you want to share in an object within buildSrc/src/main/kotlin/ and use that object in your build script, code from buildSrc is available to the whole script including the plugins block
use a system property, you can define a system property in gradle.properties by prefixing its name with systemProp. and you can access system properties via System.getProperties(), for example:
// build.gradle.kts
plugins {
val kotlinVersion by System.getProperties()
println("Kotlin version is $kotlinVersion")
}
// gradle.properties
systemProp.kotlinVersion=1.2.20
What I just stumbled upon was using Kotlin classes ins my build.gradle.kts.
I had to:
create a module called buildSrc with src/main/kotlin and a build.gradle.kts in its root.
(obsolete) include("buildSrc") in settings.gradle.kts
The buildSrc/build.gradle.kts is very minimal:
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
In buildSrc/src/main/kotlin I've added a Config.kt
const val GROUP_ID = "my-company"
const val VERSION = "0.1.0-SNAPSHOT"
const val POM_NAME = "my-library-name"
const val POM_DESCRIPTION = "A library doing stuff."
const val POM_URL = "https://github.com/${GROUP_ID}/${POM_NAME}/"
const val POM_SCM_URL = POM_URL
const val POM_SCM_CONNECTION = "scm:git:git://github.com/${GROUP_ID}/${POM_NAME}.git"
const val POM_SCM_DEV_CONNECTION = "scm:git:ssh://git#github.com/${GROUP_ID}/${POM_NAME}.git"
const val POM_LICENCE_NAME = "The Apache Software License, Version 2.0"
const val POM_LICENCE_URL = "http://www.apache.org/licenses/LICENSE-2.0.txt"
const val POM_LICENCE_DIST = "repo"
const val POM_DEVELOPER_ID = "me"
const val POM_DEVELOPER_NAME = "meeee"
const val POM_DEVELOPER_EMAIL = "me#foo.com"
And a Dependencies.kt
#file:Suppress("MemberVisibilityCanBePrivate")
object Jvm {
const val version = "1.8"
}
object Kotlin {
const val version = "1.3.50"
const val stdlibJdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
const val jvmId = "jvm"
const val kaptId = "kapt"
}
object MavenPublish {
const val id = "maven-publish"
}
object Arrow {
const val version = "0.10.1"
const val core = "io.arrow-kt:arrow-core:$version"
const val syntax = "io.arrow-kt:arrow-syntax:$version"
const val optics = "io.arrow-kt:arrow-optics:$version"
const val fx = "io.arrow-kt:arrow-fx:$version"
const val meta = "io.arrow-kt:arrow-meta:$version"
}
object Versions {
const val version = "0.27.0"
const val versions = "com.github.ben-manes:gradle-versions-plugin:$version"
const val id = "com.github.ben-manes.versions"
}
So I could use it in my root build.gradle.kts like
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin(Kotlin.jvmId) version Kotlin.version
kotlin(Kotlin.kaptId) version Kotlin.version
id(Versions.id) version Versions.version
id(MavenPublish.id)
}
group = GROUP_ID
version = VERSION
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation(Kotlin.stdlibJdk8)
implementation(Arrow.core)
implementation(Arrow.syntax)
kapt(Arrow.meta)
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = Jvm.version
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
#Suppress("UnstableApiUsage")
pom {
name.set(POM_NAME)
description.set(POM_DESCRIPTION)
url.set(POM_URL)
licenses {
license {
name.set(POM_LICENCE_NAME)
url.set(POM_LICENCE_URL)
distribution.set(POM_LICENCE_DIST)
}
}
developers {
developer {
id.set(POM_DEVELOPER_ID)
name.set(POM_DEVELOPER_NAME)
email.set(POM_DEVELOPER_EMAIL)
}
}
scm {
connection.set(POM_SCM_CONNECTION)
developerConnection.set(POM_SCM_DEV_CONNECTION)
url.set(POM_SCM_URL)
}
}
}
}
}
I am quite happy with this, but when it comes down to automatically increment the version I may fall back to maintain it in the gradle.properties.
Edit:
It is no longer necessary (and allowed) to add buildSrc to the settings.gradle.kts, instead it will automatically get picked up if present.
You can extract the version from the plugin class:
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
plugins {
kotlin("jvm") version "1.2.0"
}
val kotlinVersion = plugins.getPlugin(KotlinPluginWrapper::class.java).kotlinPluginVersion
Once you've defined a version for the Kotlin plugin all other Kotlin libraries will use the same version, and do not need a specific version set.
So the only place you need to set the version is when defining the classpath for the plugin, e.g.:
buildscript {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")
}
}
If you then need the version for some other purpose (such as in a resolutionStrategy or just for information) you can get it from org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION
For example:
println("Kotlin version used is ${org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION}")
There's a workaround available, which searches the version defined for the kotlin plugin and assignes this one to the outer variable. The following demonstrates this:
val kotlinVersion: String? by extra {
buildscript.configurations["classpath"]
.resolvedConfiguration.firstLevelModuleDependencies
.find { it.moduleName == "kotlin-gradle-plugin" }?.moduleVersion
}
plugins {
kotlin("jvm").version("1.2.30")
//more
}
The variable kotlinVersion can then be used in the dependencies without further trouble.
Answer for #s1m0nw1 comment (too long for comment):
No you can't use buildSrc/src stuff in buildSrc/build.gradle. I had exactly this problem as I wrote android-based plugin and I need android gradle plugin dependency in buildsSrc but I also declare this dependency in project. So I had two different places and two versions to maintain.
I resolved this by creating gradle.properties file in buildSrc directory.
In it I've created prop androidGradlePluginVersion=3.6.0-rc02
buildSrc/build.gradle:
val androidGradlePluginVersion: String by project
dependencies {
implementation("com.android.tools.build:gradle:$androidGradlePluginVersion")
buildSrc/src/.../Versions.kt:
var ANDROID_PLUGIN = loadAndroidGradlePluginVersion()
Util for props:
val GRADLE_PROPERTIES = "buildSrc/gradle.properties"
val ANDROID_PLUGIN_VERSION_PROP = "androidGradlePluginVersion"
fun loadAndroidGradlePluginVersion(): String {
Properties().apply { load(FileInputStream(GRADLE_PROPERTIES)) }.let {
return it.getProperty(ANDROID_PLUGIN_VERSION_PROP)
}
error("Provide $ANDROID_PLUGIN_VERSION_PROP in $GRADLE_PROPERTIES")
}
Easy and fast workaroud:
In buildscript section set value as system property:
buildscript {
val version = "X.X.X"
System.setProperty("version", version)
}
In plugins section get this property:
plugins {
val version = System.getProperty("version")
}

ext in buildscript can not be recognised by Gradle Kotlin DSL

In these days, I am trying to write some codes to experience the Spring reactive features and kotlin extension in Spring 5, and I also prepared a gradle Kotlin DSL build.gradle.kt to configure the gradle build.
The build.gradle.kt is converted from Spring Boot template codes generated by http://start.spring.io.
But the ext in the buildscript can not be detected by Gradle.
buildscript {
ext { }
}
The ext will cause Gradle build error.
To make the variables in classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") and compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion") work, I added the variables in the hard way.
val kotlinVersion = "1.1.4"
val springBootVersion = "2.0.0.M3"
But I have to declare them in global top location and duplicate them in the buildscript.
Code: https://github.com/hantsy/spring-reactive-sample/blob/master/kotlin-gradle/build.gradle.kts
Is there a graceful approach to make ext work?
Update: There are some ugly approaches:
From Gradle Kotlin DSL example, https://github.com/gradle/kotlin-dsl/tree/master/samples/project-properties, declares the properties in gradel.properties.
kotlinVersion = 1.1.4
springBootVersion = 2.0.0.M3
And use it in build.gradle.kts.
buildScript{
val kotlinVersion by project
}
val kotlinVersion by project //another declare out of buildscript block.
Similar with above declare them in buildScript block:
buildScript{
extra["kotlinVersion"] = "1.1.4"
extra["springBootVersion"] = "2.0.0.M3"
val kotlinVersion: String by extra
}
val kotlinVersion: String by extra//another declare out of buildscript block.
How can I avoid the duplication of val kotlinVersion: String by extra?
With Kotlin DSL ext has been changed to extra and it can be used under buildscript.
Eg :-
buildscript {
// Define versions in a single place
extra.apply{
set("minSdkVersion", 26)
set("targetSdkVersion", 27)
}
}
It is possible to use constants defined in .kt file in .gradle.kts files.
create buildSrc folder in root folder of your project
create buildSrc/build.gradle.kts file with the following content
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
create file buildSrc/src/main/kotlin/Constants.kt with the following content
object Constants {
const val kotlinVersion = "1.3.70"
const val targetSdkVersion = 28
}
Synchronize. Now you may reference created constants in various .gradle.kts files like this
...
classpath(kotlin("gradle-plugin", version = Constants.kotlinVersion))
...
...
targetSdkVersion(Constants.targetSdkVersion)
...
What is working for me is using ext in allprojects instead of buildscript, so in your top-level build.gradle.kts
allprojects {
ext {
set("supportLibraryVersion", "26.0.1")
}
}
then you can use it in build.gradle.kts files in modules like this:
val supportLibraryVersion = ext.get("supportLibraryVersion") as String
None of these answers felt clear to me.
So here's my explanation:
/build.gradle.kts:
buildscript {
extra.apply {
set("compose_version", "1.0.3")
}
...
}
/app/build.gradle.kts:
val composeVersion = rootProject.extra["compose_version"]
implementation("androidx.compose.ui:ui:$composeVersion")
implementation("androidx.compose.material:material:$composeVersion")
There is a new possibility with Kotlin we can use:
object DependencyVersions {
const val JETTY_VERSION = "9.4.12.v20180830"
}
dependencies{
implementation("org.eclipse.jetty:jettyserver:${DependencyVersions.JETTY_VERSION}")
}
Here, DependencyVersions is a name I chose. You can choose another name,
like "MyProjectVariables". This is a way to avoid using the extra or ext properties.
Global properties in kotlin-gradle-dsl:
https://stackoverflow.com/a/53594357/3557894
Kotlin version is embedded into kotlin-gradle-dsl.
You can use dependecies with embedded version as follows:
implementation(embeddedKotlin("stdlib-jdk7"))
classpath(embeddedKotlin("gradle-plugin"))
val junitVersion by extra("4.13.2")
testImplementation("junit:junit:$junitVersion")
In Kotlin, the way to do this is with by extra or an ext block.
With by extra:
val kotlinVersion = "95" by extra
val kotlinCompiler = true by extra
With ext:
ext {
set("kotlinVersion", "95")
set("kotlinCompiler", true)
}
Set it like this:
val kotlinVersion by extra("1.1.4")
Use it like this:
val kotlinVersion: String by rootProject.extra
It's a possibility to define global properties within gradle.properties:
xyzVersion=1.0.0
And then use them in your module's build.gradle.kts:
val xyzVersion: String by project

Resources