I am relatively new to using Gradle over Maven, but I assumed it would be more fitting for a Kotlin project.
I am writing a Spring Boot application using a MySQL database and want to set up Liquibase for it. The project was initialized with Spring Initializr with the Liquibase dependency. I have added the (as far as I understand) necessary dependencies and the plugin that runs Liquibase when running the Spring Boot project. I also configured Liquibase in the Gradle build file as I found online. The Liquibase changelog file exists and seems valid; it only adds a single table with 2 columns.
When I try to build (or do anything with the Gradle wrapper), Gradle fails with
Expression 'main' cannot be invoked as a function. The function 'invoke()' is not found
in the line of the configuration that contains "main" in my build file.
Here are the excerpts of my gradle.build.kts file that seem relevant:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.liquibase.gradle") version "2.2.1"
kotlin("jvm") version "1.9.23"
}
java {
sourceCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
}
dependencies {
implementation("org.liquibase:liquibase-core")
runtimeOnly("com.mysql:mysql-connector-j")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
jvmTarget = "21"
}
}
liquibase {
activities {
main {
changeLogFile = "some/url/to/changelog/yaml/file"
url = "jdbc:mysql://localhost:3306/someDatabase"
username = "some-user"
password = "some-password"
}
}
}
This is the output from Gradle wrapper without the paths and line numbers:
$ ./gradlew build
> Configure project :
e: Expression 'main' cannot be invoked as a function. The function 'invoke()' is not found
e: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val NamedDomainObjectContainer<KotlinSourceSet>.main: NamedDomainObjectProvider<KotlinSourceSet> defined in org.gradle.kotlin.dsl
public val SourceSetContainer.main: NamedDomainObjectProvider<SourceSet> defined in org.gradle.kotlin.dsl
e: Unresolved reference: changeLogFile
e: Unresolved reference: url
e: Unresolved reference: username
e: Unresolved reference: password
When I run ./gradlew clean instead it fails the same way. When I comment out the liquibase { ... } configuration block ./gradlew build runs fine and ./gradlew tasks lists the Liquibase-specific tasks.
Can someone explain why this is failing and what I can do about it?
It looks like you have a bit of a 'translation problem': you have written code suitable for the Groovy DSL (the other Gradle language) rather than for the Kotlin DSL.
When you write code within activities {}
, you are running code that will execute in the scope of an object of type NamedDomainObjectContainer<Activity>
. These kind of containers are everywhere in Gradle (see discussion in Kotlin DSL guide), and the TaskContainer
(accessed via tasks
in a build script) is probably the most common.
When you write something like main {}
you are calling a 'method', but one that is not defined in the class you are calling it on. In this situation in a Groovy Gradle build script for a NamedDomainObjectContainer
, this is treated as the configuration of a domain object named main
, which is created immediately if needed.
There is no such magic in Kotlin1. Being a statically typed language, you need to call methods that exist on the object in scope, or are extension methods for such an object (which may have been automatically created as a result of a plugin). The Kotlin DSL documentation shows what is available natively.
In your case, what you want to do instead is to call register
2:
activities {
register("main") {
// do the configuration
}
}
This is actually an improvement on the previous Groovy approach, because register
configures main
lazily and will not run the configuration code if and until main
is required. (In fact, in optimal Groovy code you would end up writing code the same as the Kotlin in order to benefit from this lazy evaluation.)
1 There is a different kind of magic in the Kotlin DSL, where sometimes objects defined in plugins trigger the generation of code, so you then can write code like the Groovy example. See documentation. However I do not believe that is the case here.
2 Assuming the main
object has not been created, in which case you can call named