Declare Gradle buildSrc plugin using Kotlin DSL - gradle

I'm trying to figure out how to convert this configuration to the Kotlin DSL, but I can't find much in the way of examples:
gradlePlugin {
plugins {
javaConventionsPlugin {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
What would this declaration look like using Kotlin?

It is documented in the guide: https://docs.gradle.org/current/userguide/java_gradle_plugin.html#sec:gradle_plugin_dev_usage
The way you have also works. Any of the following would also work:
gradlePlugin {
plugins {
register("javaConventionsPlugin") {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
gradlePlugin {
plugins {
create("javaConventionsPlugin") {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
The former uses Gradle's lazy configuration.

This is what I've found so far, not sure if there's a more fluent way to do it:
gradlePlugin {
val javaConventionsPlugion = plugins.register("javaConventionsPlugin")
javaConventionsPlugion.configure {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}

Related

Publish Kotlin MPP metadata with Gradle Kotlin DSL

I have created a Kotlin MPP to share Json utilities between JVM and JS. All the code lies in the common source set and I have configured the necessary targets with their respective dependencies. Without further configuration I'm able to use the utilities from both JVM and JS but not from the common source set of another MPP, which has to do with the way Gradle handles metadata.
I already found the solution (taken from https://medium.com/xorum-io/crafting-and-publishing-kotlin-multiplatform-library-to-bintray-cbc00a4f770)
afterEvaluate {
project.publishing.publications.all {
groupId = group
if (it.name.contains('metadata')) {
artifactId = "$libraryName"
} else {
artifactId = "$libraryName-$name"
}
}
}
and I also got it to work with the Gradle Kotlin DSL:
afterEvaluate {
publishing.publications.all {
this as MavenPublication
artifactId = project.name + "-$name".takeUnless { "metadata" in name }.orEmpty()
}
}
However, this doesn't feel quite right yet.
There is no such code snippet in the official documentation.
The documentation advertises that a single dependency from the common source set should suffice to automatically resolve target specific dependencies: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#metadata-publishing. I had to add the dependency for each target, respectively, for it to work.
this as MavenPublication is necessary because Publication has no field artifactId.
I use project.name instead of libraryName.
Is this even remotely the right way to do things or am I missing some other option which would make the whole process trivial?
Right now I'm using Kotlin 1.3.72 and Gradle 5.2.1 with enableFeaturePreview("GRADLE_METADATA") in settings.gradle.kts. I also tried it with Gradle 6.5.1 (latest) but it behaves exactly the same.
For now I'm glad that it's working at all but I suspect there is a cleaner way to do this. I'd really appreciate if someone with a bit more Gradle expertise could clear things up for me or point me into the right direction.
Edit:
gradle.build.kts for completeness. Although there isn't much going on here.
group = "org.example"
version = "1.0-SNAPSHOT"
plugins {
kotlin("multiplatform") version "1.3.72"
`maven-publish`
}
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib"))
}
}
}
}
There wasn't really a problem after all. The solution is to simply add enableFeaturePreview("GRADLE_METADATA") to the consuming project too.
According to https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#metadata-publishing this shouldn't be necessary:
In earlier Gradle versions starting from 5.3, the module metadata is
used during dependency resolution, but publications don't include any
module metadata by default. To enable module metadata publishing, add
enableFeaturePreview("GRADLE_METADATA") to the root project's
settings.gradle file.
Weirdly it only works when both publishing project and consuming project have metadata enabled, even when both use the latest Gradle version.
enableFeaturePreview("GRADLE_METADATA") is enabled by default in latest gradle.
According to this you need to substitute "kotlinMultiplatform" by "" an empty string.
I finally managed to accomplish publishing to bintray with maven-publish plugin only, without outdated bintray library.
Here is my full maven.publish.gradle.kts:
import java.io.FileInputStream
import java.util.*
import org.gradle.api.publish.PublishingExtension
apply(plugin = "maven-publish")
val fis = FileInputStream("local.properties")
val properties = Properties().apply {
load(fis)
}
val bintrayUser = properties.getProperty("bintray.user")
val bintrayApiKey = properties.getProperty("bintray.apikey")
val bintrayPassword = properties.getProperty("bintray.gpg.password")
val libraryVersion: String by project
val publishedGroupId: String by project
val artifact: String by project
val bintrayRepo: String by project
val libraryName: String by project
val bintrayName: String by project
val libraryDescription: String by project
val siteUrl: String by project
val gitUrl: String by project
val licenseName: String by project
val licenseUrl: String by project
val developerOrg: String by project
val developerName: String by project
val developerEmail: String by project
val developerId: String by project
project.group = publishedGroupId
project.version = libraryVersion
afterEvaluate {
configure<PublishingExtension> {
publications.all {
val mavenPublication = this as? MavenPublication
mavenPublication?.artifactId =
"${project.name}${"-$name".takeUnless { "kotlinMultiplatform" in name }.orEmpty()}"
}
}
}
configure<PublishingExtension> {
publications {
withType<MavenPublication> {
groupId = publishedGroupId
artifactId = artifact
version = libraryVersion
pom {
name.set(libraryName)
description.set(libraryDescription)
url.set(siteUrl)
licenses {
license {
name.set(licenseName)
url.set(licenseUrl)
}
}
developers {
developer {
id.set(developerId)
name.set(developerName)
email.set(developerEmail)
}
}
organization {
name.set(developerOrg)
}
scm {
connection.set(gitUrl)
developerConnection.set(gitUrl)
url.set(siteUrl)
}
}
}
}
repositories {
maven("https://api.bintray.com/maven/${developerOrg}/${bintrayRepo}/${artifact}/;publish=1") {
credentials {
username = bintrayUser
password = bintrayApiKey
}
}
}
}
And build.gradle.kts:
plugins {
id("kotlin-multiplatform")
}
kotlin {
sourceSets {
jvm()
js() {
browser()
nodejs()
}
linuxX64()
linuxArm64()
mingwX64()
macosX64()
iosArm64()
iosX64()
val commonMain by getting {
dependencies {
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jsMain by getting {
dependencies {
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
val jvmMain by getting {
dependencies {
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val nativeMain by creating {
dependsOn(commonMain)
dependencies {
}
}
val linuxX64Main by getting {
dependsOn(nativeMain)
}
val linuxArm64Main by getting {
dependsOn(nativeMain)
}
val mingwX64Main by getting {
dependsOn(nativeMain)
}
val macosX64Main by getting {
dependsOn(nativeMain)
}
val iosArm64Main by getting {
dependsOn(nativeMain)
}
val iosX64Main by getting {
dependsOn(nativeMain)
}
}
}
apply(from = "maven.publish.gradle.kts")
Please note, there are bintray.user and bintray.apikey properties in local.properties file.
Also gradle.properties contains rest listed properties above:
libraryVersion = 0.5.22
libraryName = MultiplatformCommon
libraryDescription = Kotlin multiplatform extensions
publishedGroupId = com.olekdia
artifact = multiplatform-common
bintrayRepo = olekdia
bintrayName = multiplatform-common
siteUrl = https://gitlab.com/olekdia/common/libraries/multiplatform-common
gitUrl = https://gitlab.com/olekdia/common/libraries/multiplatform-common.git
.........
kotlin.mpp.enableGranularSourceSetsMetadata = true
systemProp.org.gradle.internal.publish.checksums.insecure = true
If you haven't created organisation in bintray you need to change in this url:
https://api.bintray.com/maven/${developerOrg}/${bintrayRepo}/${artifact}/;publish=1
developerOrg by bintrayUser, where the last one is your user name at bintray.com

Kotlin build FatFramework with defined minimum iOS version

I want to deploy my multiplatform project to .framework with supported iOS architecture using FatFramework using this gradle configuration. It's work but I founded in info.plist that it have default MinimumOSVersion to iOS 9 so I can't used my library below that version.
Is there any way / possible to configure the minimum iOS version for my lib ?
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
plugins {
id("org.jetbrains.kotlin.multiplatform") version "1.3.61"
id("maven-publish")
id("com.jfrog.bintray") version "1.8.4"
id("org.jetbrains.dokka") version "0.10.0"
}
group = "com.example.lib"
version = "1.0.0"
repositories {
mavenCentral()
jcenter()
}
kotlin {
jvm()
val iosX64 = iosX64("ios")
val iosArm64 = iosArm64("iosArm64")
val iosArm32 = iosArm32("iosArm32")
val frameworkName = "EXAMPLE-LIB"
configure(listOf(iosX64, iosArm64, iosArm32)) {
binaries.framework {
baseName = frameworkName
}
}
sourceSets {
named("commonMain") {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(kotlin("stdlib-jdk7"))
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.0-RC")
implementation(kotlin("reflect"))
}
}
named("commonTest") {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(kotlin("reflect"))
}
}
named("jvmMain") {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
named("jvmTest") {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val iosMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
val iosArm64Main by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
val iosArm32Main by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
}
val debugFatFramework by tasks.creating(FatFrameworkTask::class) {
baseName = frameworkName
from(
iosArm32.binaries.getFramework("debug"),
iosArm64.binaries.getFramework("debug"),
iosX64.binaries.getFramework("debug")
)
destinationDir = buildDir.resolve("fat-framework/debug")
group = "Universal framework"
description = "Builds a debug universal (fat) framework"
}
val releaseFatFramework by tasks.creating(FatFrameworkTask::class) {
baseName = frameworkName
from(
iosArm32.binaries.getFramework("release"),
iosArm64.binaries.getFramework("release"),
iosX64.binaries.getFramework("release")
)
destinationDir = buildDir.resolve("fat-framework/release")
group = "Universal framework"
description = "Builds a release universal (fat) framework"
}
val zipDebugFatFramework by tasks.creating(Zip::class) {
dependsOn(debugFatFramework)
from(debugFatFramework)
from("LICENSE.md")
}
val zipReleaseFatFramework by tasks.creating(Zip::class) {
dependsOn(releaseFatFramework)
from(releaseFatFramework)
from("LICENSE.md")
}
}
tasks.register<Exec>("generateThriftModels") {
executable = "java"
args(
"-jar", "thrifty-compiler.jar",
"--lang", "kotlin",
"--omit-generated-annotations",
"--list-type=kotlin.collections.ArrayList",
"--set-type=kotlin.collections.LinkedHashSet",
"--out", "src/commonMain/kotlin",
"--path", "./thrift/",
"./thrift/nativeapp.thrift"
)
}
This parameter is derived from the Kotlin/Native's compiler properties list. It can be found there:~/.konan/kotlin-native-prebuilt-<hostname>-<version>/konan/konan.properties. The value responsible for the MinimumOSVersion is the osVersionMin.<target_name>. This should be set per target. It can be changed manually, and this new value will be used by all your projects. Also, after 1.4.30 there is a new way to specify compiler properties. See in this document for example. I would recommend using it as follows:
...
configure(listOf(iosX64, iosArm64, iosArm32)) {
binaries.framework {
freeCompilerArgs += listOf("-Xoverride-konan-properties=osVersionMin.ios_arm32=7;osVersionMin.ios_arm64=7;osVersionMin.ios_x64=7")
}
}
...

Customize selecting dependency version

in my project dependency which I'm using has many similar version names, eg: 1.0.0, 1.0.0-dev, 1.0.0-dev2... Is there a way to list all versions starting with 1.0.0 and select interesting version from that list?
I was thinking about resolutionStrategy, but it doesn't contain list of possible versions
You could do this
configurations.all.resolutionStrategy {
List<DependencyResolveDetails> drdList = []
eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'foo' && details.requested.name = 'bar') {
drdList << details
}
}
if (drdList.size() > 1) {
List<String> versionOptions = drdList*.requested*.version
String selectedVersion = selectBestVersion(versionOptions) // TODO: implement
drdList.each { DependencyResolveDetails details ->
if (details.requested.version != selectedVersion) {
details.useVersion(selectedVersion).because("I picked $selectedVersion using a custom strategy")
}
}
}
}
Perhaps you could create a plugin for this so that applying a custom strategy for a group/name is a bit cleaner possibly by registering a Comparator<String> for a group/name combination
Eg:
apply plugin: 'x.y.custom-version-strategy'
Comparator<String> customVersionComparator = (String version1, String version2) -> { ... }
customVersionStrategy {
strategy 'foo', 'bar', customVersionComparator
}
It looks like, that finnaly I have found a solution:
configurations.all{
resolutionStrategy {
componentSelection {
all { ComponentSelection selection ->
if(notInteresting(selection.candidate.version))
selection.reject("")
}
}
}
}

How to add new sourceset with gradle kotlin-dsl

I want to add a sourceset src/gen/java. With groovy this is rather easy and already described in https://discuss.gradle.org/t/how-to-use-gradle-with-generated-sources/9401/5
sourceSets {
gen {
java.srcDir "src/gen/java"
}
}
But I stuck with the kotlin-dsl to add a new one. All I've got is:
java {
sourceSets {
}
}
Can anyone help here to
The answer of #s1m0nw1 is correct to add a new sourceset.
But to just add a new source-folder in an existing sourceset, this can be used:
java.sourceSets["main"].java {
srcDir("src/gen/java")
}
You should try the following:
java.sourceSets.create("src/gen/java")
Hope it's what you need!
Worked for me on Gradle 4.10.2:
sourceSets.getByName("main") {
java.srcDir("src/main/java")
java.srcDir("src/main/kotlin")
}
sourceSets.getByName("test") {
java.srcDir("src/test/java")
java.srcDir("src/test/kotlin")
}
The codes above can also be used in subprojects block.
Worked for me on Gradle 4.10.2:
sourceSets.create("integrationTest") {
java.srcDir("src/integrationTest/java")
java.srcDir("build/generated/source/apt/integrationTest")
resources.srcDir("src/integrationTest/resources")
}
kotlin-dsl
sourceSets {
this.getByName("androidTest"){
//Adds the given source directory to this set.
this.java.srcDir("src/mock/java")
}
this.getByName("test"){
this.java.srcDir("src/mock/java")
}
}
I wanted to add a source set with the name "test-integration" and the source directory src/test-integration/kotlin. I was able to accomplish that by combining the two pre-existing answers:
java.sourceSets.create("test-integration").java {
srcDir("src/test-integration/kotlin")
}
As of Gradle 7.5:
sourceSets {
main {
java.sourceSets {
create("gen"){
java.srcDir("src/gen/java")
}
}
}
}
This is what I had before:
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
Above now translates to:
sourceSets {
main {
java {
srcDirs("src")
}
resources {
srcDirs("resources")
}
}
test {
java {
srcDirs("test")
}
resources {
srcDirs("testresources")
}
}}

How to configure the processResources task in a Gradle Kotlin build

I have the following in a groovy-based build script. How do I do the same in a kotlin-based script?
processResources {
filesMatching('application.properties'){
expand(project.properties)
}
}
Why not to just use "withType" ?
I just mean (IMHO)
tasks {
withType<ProcessResources> {
..
}
looks much better than
tasks {
"processResources"(ProcessResources::class) {
..
}
So,
tasks.withType<ProcessResources> {
//from("${project.projectDir}src/main/resources")
//into("${project.buildDir}/whatever/")
filesMatching("*.cfg") {
expand(project.properties)
}
}
EDIT:
With newer release you can just do:
tasks.processResources {}
or
tasks { processResources {} }
generated accessors are "lazy" so it has all the benefits and no downsides.
I think task should look like:
Edit: According this comment in gradle/kotlin-dsl repository. Task configuration should work this way:
import org.gradle.language.jvm.tasks.ProcessResources
apply {
plugin("java")
}
(tasks.getByName("processResources") as ProcessResources).apply {
filesMatching("application.properties") {
expand(project.properties)
}
}
Which is pretty ugly. So i suggest following utility function for this purpose, until one upstream done:
configure<ProcessResources>("processResources") {
filesMatching("application.properties") {
expand(project.properties)
}
}
inline fun <reified C> Project.configure(name: String, configuration: C.() -> Unit) {
(this.tasks.getByName(name) as C).configuration()
}
With updates to the APIs in newer release of the Kotlin DSL and Gradle, you can do something like:
import org.gradle.language.jvm.tasks.ProcessResources
plugins {
java
}
tasks {
"processResources"(ProcessResources::class) {
filesMatching("application.properties") {
expand(project.properties)
}
}
}
And also:
val processResources by tasks.getting(ProcessResources::class) {
filesMatching("application.properties") {
expand(project.properties)
}
}

Resources