All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.freeletics.gradle.monorepo.tasks.ComputeVersion.kt Maven / Gradle / Ivy

package com.freeletics.gradle.monorepo.tasks

import com.freeletics.gradle.monorepo.util.Git
import java.time.LocalDateTime

/**
 * Get the app version name, which is computed using the branch name or `git describe`.
 */
public fun computeVersionName(git: Git, gitTagName: String): String {
    val version = versionFromTag(git, gitTagName)
    checkNotNull(version) { "Did not find a previous release/tag" }
    return version
}

/**
 * Get the app version name, which is computed using the branch name or `git describe`.
 */
public fun computeVersionCode(git: Git, gitTagName: String, localDate: LocalDateTime): Int {
    val major: Int
    val minor: Int
    val patch: Int
    val extra: Int

    val version = versionFromTag(git, gitTagName, exactMatch = true)
    if (version != null) {
        // if we are building a tagged commit use the version to compute the version code
        //
        // New (since major v24):
        //
        // 2_100_000_000 - maximum allowed value
        //    24_xxx_xxx - major version 24
        //    xx_x31_xxx - minor version 31
        //    xx_xxx_x00 - patch version 0
        //
        // Old (until major v24):
        //
        // 2_100_000_000 - maximum allowed value
        //    22_xxx_xxx - major version 22
        //    xx_31x_xxx - minor version 31
        //    xx_xxx_000 - patch version 0

        val parts = version.split(".")
        check(parts.size == 3)
        major = parts[0].toInt()
        minor = parts[1].toInt()
        patch = parts[2].toInt()
        checkVersions(major, minor, patch)
        extra = 0
    } else {
        check(git.branch() == "main") {
            "Version code can only be computed on the main branch and tagged commits"
        }

        // Untagged builds will use the major and minor version of the last version for
        // the computed build number.
        //
        // New (since major v24):
        //
        // We use the day of week in the 100 digit to get a higher build number than
        // anything the last release can reach. The last 2 digits are used for the time
        // of day to generally have the ability to produce multiple builds per day
        // for testing (in practice we get a new build number every 15 minutes which is
        // good enough for one-off manual tests).
        //
        // 2_100_000_000 - maximum allowed value
        //    24_xxx_xxx - major version 24
        //    xx_x31_xxx - minor version 31
        //    xx_xxx_1xx - day of week
        //    xx_xxx_x99 - time of day in 15 minute intervals
        //
        // Old (until major v24):
        //
        // We use the day of week in the 100 digit to get a higher build number than
        // anything the last release can reach. The last 3 digits are used for the
        // number of commits since the last release.
        //
        // 2_100_000_000 - maximum allowed value
        //    22_xxx_xxx - major version 22
        //    xx_31x_xxx - minor version 31
        //    xx_xx1_xxx - day of week
        //    xx_xxx_099 - commit count 99

        val lastRelease = versionFromTag(git, gitTagName, initialRelease = true)
        checkNotNull(lastRelease) { "Did not find a previous release/tag" }
        val versionParts = lastRelease.split(".")
        check(versionParts.size == 3)
        val suffixParts = versionParts[2].split("-")
        check(suffixParts.size == 3)
        major = versionParts[0].toInt()
        minor = versionParts[1].toInt()
        patch = 0
        checkVersions(major, minor, patch)

        extra = if (major >= 24) {
            // Monday = 100, Sunday = 700
            val dayOfWeek = localDate.dayOfWeek.value * 100
            // Time of day in 15 minute intervals -> values from 0 to 96
            val time = (localDate.hour * 60 + localDate.minute) / 15
            dayOfWeek + time
        } else {
            // Monday = 1000, Sunday = 7000
            val dayOfWeek = localDate.dayOfWeek.value * 1_000
            // the returned version has 7.4.0-32-g5e2416d73f as format where the 32 is the commit count since the tag
            val commitsSinceLastRelease = suffixParts[1].toInt()
            check(commitsSinceLastRelease < 1_000) { "More than 999 commits found since the last release was created" }
            dayOfWeek + commitsSinceLastRelease
        }
    }

    val versionCode = if (major >= 24) {
        major * 1_000_000 +
            minor * 1_000 +
            patch +
            extra
    } else {
        major * 1_000_000 +
            minor * 10_000 +
            patch +
            extra
    }
    check(versionCode < 1_000_000_000) { "Version code should always be lower than 1 billion, was $versionCode" }

    return versionCode
}

private fun checkVersions(major: Int, minor: Int, patch: Int) {
    // major is limited to 100 which will only be reached in year 2100
    check(major < 100) { "Major version is limited to 99, was $major" }
    // minor is limited to the number of weeks in a year, so 52 or 53 in practice
    check(minor < 100) { "Minor version is limited to 99, was $minor" }
    // patch is limited to three digits
    check(patch < 100) { "Patch is limited to 99, was $patch" }
}

/**
 * If a tag exists `describe` matches the tag, e.g. `fl/v7.4.0` -> 7.4.0
 * otherwise it's `[tag]-[commits-since-tag]-[current-sha]`, e.g. `fl/v7.4.0-32-g5e2416d73f` -> 7.4.0-32-g5e2416d73f
 *
 * The [exactMatch] option makes it so that only the former would be returned and `null` if the current commit
 * is untagged.
 *
 * If [initialRelease] is set to `true` only tags ending in `.0` will be matched.
 */
private fun versionFromTag(
    git: Git,
    gitTagName: String,
    initialRelease: Boolean = false,
    exactMatch: Boolean = false,
): String? {
    val patchVersion = if (initialRelease) "0" else "[0-9]*"
    // match will filter tags to consider based on a regex
    val describe = git.describe(match = "\"$gitTagName/v[1-9][0-9]\\.[0-9]*\\.$patchVersion\"", exactMatch)

    if (describe.isBlank()) {
        return null
    }
    return describe.substringAfter("$gitTagName/v")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy