import java.util.Properties plugins { // this is necessary to avoid the plugins to be loaded multiple times // in each subproject's classloader alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.androidMultiplatformLibrary) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.gobleyCargo) apply false alias(libs.plugins.gobleyUniffi) apply false alias(libs.plugins.kotlinAtomicfu) apply false alias(libs.plugins.kotlinAndroid) apply false alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false } val versionFile = layout.projectDirectory.file("version.properties") val versionProperties = Properties().apply { versionFile.asFile.inputStream().use(::load) } fun requiredVersionProperty(name: String): String = versionProperties.getProperty(name)?.takeIf { it.isNotBlank() } ?: error("Missing $name in ${versionFile.asFile}") fun canonicalInteger(name: String, value: String, range: LongRange): Long { require(value.matches(Regex("0|[1-9][0-9]*"))) { "$name must be a canonical non-negative integer" } val number = value.toLongOrNull() require(number != null && number in range) { "$name must be between ${range.first} and ${range.last}" } return number } val productVersion = requiredVersionProperty("PRODUCT_VERSION") val productVersionMatch = Regex("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)") .matchEntire(productVersion) ?: error("PRODUCT_VERSION must use canonical MAJOR.MINOR.PATCH integers") val productVersionParts = productVersionMatch.groupValues.drop(1).map(String::toLong) require(productVersionParts[0] <= 2099 && productVersionParts.drop(1).all { it <= 999 }) { "PRODUCT_VERSION must use a major no greater than 2099 and minor/patch no greater than 999" } val releaseChannel = requiredVersionProperty("RELEASE_CHANNEL") require(releaseChannel.matches(Regex("[a-z][a-z0-9-]*"))) { "RELEASE_CHANNEL contains unsupported characters" } val androidVersionCode = (productVersionParts[0] * 1_000_000L + productVersionParts[1] * 1_000L + productVersionParts[2]) .also { require(it in 1L..2_100_000_000L) { "Derived Android version code is out of range" } } .toInt() val windowsVersionEpoch = canonicalInteger( "WINDOWS_VERSION_EPOCH", requiredVersionProperty("WINDOWS_VERSION_EPOCH"), 1L..65535L, ) val windowsMajor = productVersionParts[0] + windowsVersionEpoch require(windowsMajor <= 65535) { "Derived Windows package major exceeds 65535" } val windowsPackageVersion = "$windowsMajor.${productVersionParts[1]}.${productVersionParts[2]}.0" extra["vnidrop.productVersion"] = productVersion extra["vnidrop.releaseChannel"] = releaseChannel extra["vnidrop.androidVersionCode"] = androidVersionCode extra["vnidrop.windowsPackageVersion"] = windowsPackageVersion tasks.register("verifyVersion") { group = "verification" description = "Validates the canonical cross-platform application version." inputs.file(versionFile) inputs.property("productVersion", productVersion) inputs.property("releaseChannel", releaseChannel) inputs.property("androidVersionCode", androidVersionCode) inputs.property("windowsPackageVersion", windowsPackageVersion) }