How to get value from gradle.properties into Kotlin file? - kotlin

I have this constant in gradle.properties.
VERSION_NAME=0.1.0
I want to be able to use this value in my Kotlin files (a data class). Something like trying to pull it from System.getProperty("VERSION_NAME").
This is the build.gradle.kts (Kotlin DSL) on the module where the data class lives
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
id("com.vanniktech.maven.publish")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation(Dependencies.Squareup.Retrofit2.retrofit)
implementation(Dependencies.Squareup.Retrofit2.converterGson)
implementation(Dependencies.Squareup.Okhttp3.okhttp)
implementation(Dependencies.Squareup.Okhttp3.loggingInterceptor)
implementation(Dependencies.Google.Code.gson)
}
I'm more familiar with Android where'd you have a BuildConfig static constant. However since this is not an Android project, I need to somehow get the value differently.
I tried to follow How do I get a custom property defined in 'gradle.properties in kotlin code? which is very similar to what I need, except tasks.named<JavaExec>("run")... wouldn't work as the "run" task isn't found in the project.
I'm new to these types of Kotlin projects and gradle in general so I'm lost.

For non-Android projects also exists Gradle plugins, like this:
https://github.com/mfuerstenau/gradle-buildconfig-plugin

Related

Configure Kotlin extension for Gradle subprojects

I'm setting up a multi-module Gradle project based on Kotlin for the JVM. Since the root project does not contain any code, the Kotlin plugin should only be applied to subprojects.
build.gradle.kts (root project)
plugins {
kotlin("jvm") version "1.6.20" apply false
}
subprojects {
apply(plugin = "kotlin")
group = "com.example"
repositories {
mavenCentral()
}
dependencies {}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
Trying to set a toolchain causes the build to fail at the kotlin {...} extension:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun DependencyHandler.kotlin(module: String, version: String? = ...): Any defined in org.gradle.kotlin.dsl
public fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec defined in org.gradle.kotlin.dsl
It works fine if I copy the extension definition to each subproject build script, but why isn't it available in the main script?
This is one of my favourite things to fix in Gradle, and really shows off the flexibility that's possible (as well as demonstrating why Gradle can be complicated!)
First I'll give a bit of background info on the subprojects {} DSL, then I'll show how to fix your script, and finally I'll show the best way to share build logic with buildSrc convention plugins. (Even though it's last, I really recommend using buildSrc!)
Composition vs Inheritance
Using allprojects {} and subprojects {} is really common, I see it a lot. It's more similar to how Maven works, where all the configuration is defined in a 'parent' build file. However it's not recommended by Gradle.
[A], discouraged, way to share build logic between subproject is cross project configuration via the subprojects {} and allprojects {} DSL constructs.
Gradle Docs: Sharing Build Logic between Subprojects
(It's probably common because it's easy to understand - it makes Gradle work more like Maven, so each project inherits from one parent. But Gradle is designed for composition. Further reading: Composition over inheritance: Gradle vs Maven)
Quick fix: 'Unresolved reference'
The error you're seeing is basically because you haven't applied the Kotlin plugin.
plugins {
kotlin("jvm") version "1.6.20" apply false // <- Kotlin DSL won't be loaded
}
The kotlin { } configuration block is a very helpful extension function that is loaded when the Kotlin plugin is applied. Here's what it looks like:
/**
* Configures the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension] extension.
*/
fun org.gradle.api.Project.`kotlin`(configure: Action<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", configure)
// (note: this is generated code)
So if we don't have the extension function, we can just call configure directly, and thus configure the Kotlin extension.
subprojects {
// this is the traditional Gradle way of configuring extensions,
// and what the `kotlin { }` helper function will call.
configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension> {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
// without the Kotlin Gradle plugin, this helper function isn't available
// kotlin {
// jvmToolchain {
// check(this is JavaToolchainSpec)
// languageVersion.set(JavaLanguageVersion.of(11))
// }
// }
}
However, even though this works, using subprojects {} has problems. There's a better way...
buildSrc and Convention Plugins
buildSrc is, basically, a standalone Gradle project, the output of which we can use in the main project's build scripts. So we can write our own custom Gradle plugins, defining conventions, which we can selectively apply to any subproject in the 'main' build.
(This is the key difference between Gradle and Maven. In Gradle, a subproject can be configured by any number of plugins. In Maven, there's only one parent. Composition vs Inheritance!)
The Gradle docs have a full guide on setting up convention plugins, so only I'll briefly summarise the solution here.
1. Set up ./buildSrc
Create a directory named buildSrc in your project root.
Because buildSrc is a standalone project, create a ./buildSrc/build.gradle.kts and ./buildSrc/settings.gradle.kts files, like usual for a project.
In ./buildSrc/build.gradle.kts,
apply the kotlin-dsl plugin
add dependencies on Gradle plugins that you want to use anywhere in your project
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl` // this will create our Gradle convention plugins
// don't add the Kotlin JVM plugin
// kotlin("jvm") version embeddedKotlinVersion
// Why? It's a long story, but Gradle uses an embedded version of Kotlin,
// (which is provided by the `kotlin-dsl` plugin)
// which means importing an external version _might_ cause issues
// It's annoying but not important. The Kotlin plugin version below,
// in dependencies { }, will be used for building our 'main' project.
// https://github.com/gradle/gradle/issues/16345
}
val kotlinVersion = "1.6.20"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
Note that I've used the Maven repository coordinates for the Kotlin Gradle plugin, not the plugin ID!
You can also add other dependencies into ./buildSrc/build.gradle.kts if you like. If you wanted to parse JSON in a build script, then add a dependency on a JSON parser, like kotlinx-serialization.
2. Create a convention plugin
Create your Kotlin JVM convention that you can apply to any Kotlin JVM subproject.
// ./buildSrc/src/main/kotlin/my/project/convention/kotlin-jvm.gradle.kts
package my.project.convention
plugins {
kotlin("jvm") // don't include a version - that's provided by ./buildSrc/build.gradle.kts
}
dependencies {
// you can define default dependencies, if desired
// testImplementation(kotlin("test"))
}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
Don't forget to add the package declaration! I've forgotten it a few times, and it causes errors that are hard to figure out.
3. Applying the convention plugin
Just like how Gradle plugins have IDs, so do our convention plugins. It's the package name + the bit before .gradle.kts. So in our case the ID is my.project.convention.kotlin-jvm
We can apply this like a regular Gradle plugin...
// ./subprojects/my-project/build.gradle.kts
plugins {
id("my.project.convention.kotlin-jvm")
}
(Convention plugins can also import other convention plugins, using id("..."))
Also, since we're using Kotlin, there's an even nicer way. You know how there are included Gradle plugins, like java and java-library. We can import our convention plugins the same way!
// ./subprojects/my-project/build.gradle.kts
plugins {
// id("my.project.convention.kotlin-jvm")
my.project.convention.`kotlin-jvm` // this works just like id("...") does
}
Note the backticks around the plugin ID - they're needed because of the hyphen.
(caveat: this non-id("...") way doesn't work inside buildSrc, only in the main project)
Result
Now the root ./build.gradle.kts can be kept really clean and tidy - it only needs to define the group and version of the project.
Because we're using convention plugins and not blanket subprojects, each subproject can be specialised and only import convention plugins that it needs, without repetition.
Site note: sharing repositories between buildSrc and the main project
Usually you want to share repositories between buildSrc and the main project. Because Gradle plugins are not specifically for projects, we can write a plugin for anything, including settings.gradle.kts!
What I do is create a file with all the repositories I want to use...
// ./buildSrc/repositories.settings.gradle.kts
#Suppress("UnstableApiUsage") // centralised repository definitions are incubating
dependencyResolutionManagement {
repositories {
mavenCentral()
jitpack()
gradlePluginPortal()
}
pluginManagement {
repositories {
jitpack()
gradlePluginPortal()
mavenCentral()
}
}
}
fun RepositoryHandler.jitpack() {
maven("https://jitpack.io")
}
(the name, repositories.settings.gradle.kts, isn't important - but naming it *.settings.gradle.kts should mean IntelliJ provides suggestions, however this is bugged at the moment.)
I can then import this as a plugin in the other settings.gradle.kts files, just like how you were applying the Kotlin JVM plugin to subprojects.
// ./buildSrc/settings.gradle.kts
apply(from = "./repositories.settings.gradle.kts")
// ./settings.gradle.kts
apply(from = "./buildSrc/repositories.settings.gradle.kts")

How to import a file from another directory in Kotlin?

I have a gradle kotlin project, and I'm generating a kotlin file from a Rust project, so it ends up in a totally different place with no gradle project structure, etc.
How do I import this file into my gradle project?
It has its own package but it's a completely standalone file. This is my gradle file:
rootProject.name = "my_project"
include("app")
It's a desktop project, NOT android.
My build.gradle.kts:
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
id("org.jetbrains.kotlin.jvm") version "1.5.31"
// Apply the application plugin to add support for building a CLI application in Java.
application
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// This dependency is used by the application.
implementation("com.google.guava:guava:30.1.1-jre")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
application {
// Define the main class for the application.
mainClass.set("my_project.ffi.AppKt")
}
Adding the following code to your build.gradle.kts should do the trick (tested with Gradle 7.3.2):
// TODO: replace this dummy task with the task from your Rust project which
// generates the Kotlin source directory. Make sure that the generated
// directory (with the Kotlin file(s)) is the task output directory.
val rustTask by tasks.registering(Copy::class) {
// To test this, I had simply put a Kotlin file into this "somewhere"
// directory.
from("somewhere")
into(temporaryDir)
}
sourceSets {
main {
java {
srcDir(rustTask)
}
}
}
tasks {
compileKotlin {
dependsOn(rustTask)
}
}
So, we’re simply adding the generated sources as an additional source directory to the default SourceSet which is consumed by the compileKotlin task. In addition, we make sure that the sources are generated before compileKotlin runs.

Serialize kotlin data class in gradle's buildSrc/ during build

I'm looking to produce json files during the build cycle of my kotlin gradle app. My intent is to be able to instantiate data classes with a combination of public and private app configuration values that get put into the build's resources directory.
I'm looking at kotlinx.serialization, and I'd like to define these classes ideally in the projet's buildSrc/.
I haven't found any resources online for trying to setup serialization within gradle's build process, and not just configuring it for the app at runtime. This is what I've put together as my buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
kotlin("plugin.serialization") version "1.3.72"
}
repositories {
jcenter()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}
And this is my test data class:
#Serializable
data class JsonGenerator(val x: String = "")
The error I get is:
Cannot access 'Serializable': it is internal in 'kotlin.io'
It seems that this error can happen when the dependency isn't properly declared. But I'm still unclear whether buildSrc has restrictions that make this impossible or not. I'm not married to this approach, but this seemed like the best solution.
Edit:
I've changed my buildSrc/build.gradle.kts to:
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
dependencies {
implementation(kotlin("serialization"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
}
Structure:
buildSrc
main
Kotlin
MyTempClass.kt
It is important for the kt file to not be nested, I think that will only work if the file is the same name as the nesting directory
Inside of my data class file:
import kotlinx.serialization.*
#Serializable
data class MyTempClass(val name: String)
I had a specific implementation of serialization, without the plugin.
Then in the top level build.gradle.kts, the MyTempClass was accessible

Using gradle convention plugin to set kotlin jvmTarget option

Using a Gradle convention plugin to share common configuration between subprojects, what is the correct way to set Kotlin compiler options, like jvmTarget?
This plugin setup results in a working build, but IntelliJ doesn't understand it, which leads me to think this isn't the correct approach:
plugins {
id 'org.jetbrains.kotlin.jvm'
}
repositories {
jcenter()
}
dependencies {
// shared dependencies
}
tasks.named('test') {
useJUnitPlatform()
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
This plugin is applied in subprojects like this:
plugins {
id 'my.kotlin-conventions'
id 'java-library'
}
I'm using Gradle 6.7, Kotlin 1.4.20.
Edit: More info regarding IntelliJ problem
In IntelliJ, in sub-projects, I am seeing the warning Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option. In the root project I don't see the warning, only in sub-projects. Building and running the project produces no problems (or warnings) at all.
Sample: https://github.com/nieldw/IntelliJ_Gradle_Convention_Plugin_Issue
IntelliJ seems to not like the buildSrc/settings.gradle file. When I remove it and “Reload All Gradle Projects”, then the errors that you’ve described disappear.
Commenting the rootProject.name=… line in the buildSrc/settings.gradle file works, too. Interestingly, uncommenting the line later (and reloading the Gradle configuration) is possible without breaking the IntelliJ configuration. So this should be your fix, I suppose.
Your build configuration looks correct to me. This rather sounds like a bug in IntelliJ’s Gradle support.

Unresolved references for Kotlin (JVM) Standard Library Elements

After adding gradle to an existing Kotlin project in IntelliJ, I started having issues with references to some standard library elements. For instance, Kotlin String type is recognized, but the mutableMapOf is giving me
Error:(11, 60) Kotlin: Unresolved reference: mutableMapOf
another is:
Error:(9, 78) Kotlin: Unresolved reference: Array
during compilation. They are marked red in the IDE, as well (not isolated to only compilation)
Another error that appears in IntelliJ is Kotlin not configured, with no options to configure Kotlin
Here is my gradle build file:
apply plugin: 'kotlin'
group = "com.serguei.myproject"
version = "1.0"
buildscript {
ext.kotlin_version = '1.2.10'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile 'com.google.code.gson:gson:2.8.2'
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
Thank you for the comments Ice100 and Preston Garno, they have helped greatly in finding a solution.
The issue was that my project was not structured correctly. I had:
MyProject -> src -> [Source Files]
-> build.gradle
however, after consulting Using Gradle more closely, it appears that the recommended project structure should be
MyProject -> src -> main -> kotlin -> [Source Files]
-> build.gradle
Once the project structure is adjusted, go into File -> Invalidate Caches / Restart and click the Invalidate and Restart. Once the indexer is rerun, the Kotlin standard library types, functions, etc. should now be available.
For any one coming to this page because of problem that IntelliJ suddenly does not find stuff in kotlin standard library: File -> Invalidate Caches / Restart helped me in a situation which didn't even involve gradle.
After migrating my Java app to Kotlin I find myself in this situation.
After checking gradle structure here
You can sync your project with gradle using sync button from menu bar.
this will download necessary files for Kotlin
Then you can just validate cache & Restart if necessary.