Use git hash as version number in build.gradle.kts

Hi, I’m new to Kotlin, Gradle and build.gradle.kts

I’d like to add the git hash to the version number (e.g. “0.8.2-a7b3d”) and use this constant in my Kotlin/jvm project.

I found about a dozen howtos for Java/Android but non for build.gradle.kts (also searching for “git hash” in any combination with other words seams to include a lot of “noise”).

Please point me into the right direction!

Something like this but for build.gradle.kts

// from https://stackoverflow.com/a/64311102

import kotlinx.cinterop.*
import platform.posix.*

fun executeCommand(
    command: String,
    trim: Boolean = true,
    redirectStderr: Boolean = true
): String {
    val commandToExecute = if (redirectStderr) "$command 2>&1" else command
    val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")

    val stdout = buildString {
        val buffer = ByteArray(4096)
        while (true) {
            val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
            append(input.toKString())
        }
    }

    val status = pclose(fp)
    if (status != 0) {
        error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
    }

    return if (trim) stdout.trim() else stdout
}

fun main() {
    println("Git Info in local directory:")

    println(executeCommand("git rev-parse --abbrev-ref HEAD"))
    println(executeCommand("git tag -l --points-at HEAD"))
    println(executeCommand("git rev-parse --short=8 HEAD"))
}

i’ve got this in my code, although i’m not sure where org.apache.commons stuff is coming from:

fun versionBanner(): String {
    val os = org.apache.commons.io.output.ByteArrayOutputStream()
    project.exec {
        commandLine = "git describe --long".split(" ")
        standardOutput = os
    }
    return String(os.toByteArray()).trim()
}

I have this extension function for my gradle builds

fun String.runCommand(
   workingDir: File = File("."),
   timeoutAmount: Long = 60,
   timeoutUnit: TimeUnit = TimeUnit.SECONDS
): String = ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`])[^'\"`]*\\1)*[^'\"`]*$)".toRegex()))
    .directory(workingDir)
    .redirectOutput(ProcessBuilder.Redirect.PIPE)
    .redirectError(ProcessBuilder.Redirect.PIPE)
    .start()
    .apply { waitFor(timeoutAmount, timeoutUnit) }
    .run {
        val error = errorStream.bufferedReader().readText().trim()
        if (error.isNotEmpty()) {
            throw IOException(error)
        }
        inputStream.bufferedReader().readText().trim()
    }

and then you can call it like

"git rev-list --count HEAD".runCommand(workingDir = rootDir)
1 Like

Hi @oakkitten, thank you for your reply!
Where (in build.gradle.kts ?) do I put this?

How can I add this to version? Like this:

plugins {
    kotlin("multiplatform") version "1.4.10"
}
group = "me.eugen"
version = "1.0-" + versionBanner()

repositories {
    mavenCentral()
}
kotlin {
...
1 Like

Hi @chrimaeon, thank you for your reply!
Where (in build.gradle.kts ?) do I put this?

How can I add this to version ? Like this:

plugins {
    kotlin("multiplatform") version "1.4.10"
}
group = "me.eugen"
version = "1.0-" + "git rev-list --count HEAD".runCommand("./")

repositories {
    mavenCentral()
}
kotlin {
...

well i have this as a top-level function. git describe --long will give you version as well as long as you are using tags, e.g. you can get something like v1.2-119-g17dd14b7. as to how you use that, that’s up to you; i’m doing android so i’m using

android {
    defaultConfig {
            buildConfigField("String", "VERSION", "\"${versionBanner()}\"")
...

and then i use that in the about window.

1 Like

I found a (temporary?) solution:

build.gradle.kts:

plugins {
    kotlin("multiplatform") version "1.4.10"
    id("com.github.gmazzo.buildconfig") version "2.0.1"
}

group = "me.eugen"
version = "1.0.0"

...

fun String.runCommand(
    workingDir: File = File("."),
    timeoutAmount: Long = 60,
    timeoutUnit: TimeUnit = TimeUnit.SECONDS
): String = ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`])[^'\"`]*\\1)*[^'\"`]*$)".toRegex()))
    .directory(workingDir)
    .redirectOutput(ProcessBuilder.Redirect.PIPE)
    .redirectError(ProcessBuilder.Redirect.PIPE)
    .start()
    .apply { waitFor(timeoutAmount, timeoutUnit) }
    .run {
        val error = errorStream.bufferedReader().readText().trim()
        if (error.isNotEmpty()) {
            throw Exception(error)
        }
        inputStream.bufferedReader().readText().trim()
    }

buildConfig {
    sourceSets.getByName("nativeMain") {
        val branch = "git rev-parse --abbrev-ref HEAD".runCommand(workingDir = rootDir)
        val tag = "git tag -l --points-at HEAD".runCommand(workingDir = rootDir)
        val commitId = "git rev-parse --short=8 HEAD".runCommand(workingDir = rootDir)

        className("BuildConfig")
        buildConfigField("String", "version", "\"${version.toString()}\"")
        buildConfigField("String", "branch", "\"${branch}\"")
        buildConfigField("String", "tag", "\"${tag}\"")
        buildConfigField("String", "commit", "\"${commitId}\"")
    }
}

main.kt:

import me.eugen.githash.BuildConfig

fun main() {
    val version = BuildConfig.version + "-" + BuildConfig.commit;
    println("githash version: " + version)
}

Any suggestions to improve this further?

If you need the project.version always be the concat version with the commit you can just write

plugins {
    kotlin("multiplatform") version "1.4.10"
    id("com.github.gmazzo.buildconfig") version "2.0.1"
}

group = "me.eugen"
version = "1.0.0" + "git rev-parse --short=8 HEAD".runCommand(workingDir = rootDir)
...
1 Like