syncVersions.main.kts (160 lines of code) (raw):

/* * Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. */ @file:OptIn(ExperimentalPathApi::class) import java.io.File import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse import java.nio.file.FileVisitResult import java.nio.file.Path import kotlin.io.path.* /* This script is meant to be used to update several user-facing versions in: - the Amper project itself - our examples - our docs The source of truth is the list of versions at the top of this file. */ val bootstrapAmperVersion = "0.10.0-dev-3602" // AUTO-UPDATED BY THE CI - DO NOT RENAME /** * This is the version of the JetBrains Runtime that Amper wrappers use to run the Amper dist. * * See glibc compatibility: https://youtrack.jetbrains.com/issue/JBR-7511/Centos-7-support-is-over * We need the JBR for 2024.2 to be compatible with glibc 2.17. * Check https://github.com/JetBrains/JetBrainsRuntime?tab=readme-ov-file#releases-based-on-jdk-21 */ val amperInternalJbrVersion = "21.0.8b1038.68" /** * The Kotlin version used in Amper projects (not customizable yet). * Make sure we respect the constraints of the Android Gradle plugin (used internally in delegated Gradle builds). * See the [compatiblity table](https://developer.android.com/build/kotlin-support). */ val kotlinVersion = "2.2.21" // /!\ Remember to update the KotlinVersion enum with outdated/experimental versions val composeHotReloadVersion = "1.0.0-rc01" val composeVersion = "1.8.2" val jdkVersion = "21" // TODO bump to 25 when Kotlin supports it val junitPlatformVersion = "6.0.1" val kotlinxRpcVersion = "0.10.1" val kotlinxSerializationVersion = "1.9.0" val kspVersion = "2.3.0" val ktorVersion = "3.2.3" val springBootVersion = "3.5.5" val amperMavenRepoUrl = "https://packages.jetbrains.team/maven/p/amper/amper" val amperRootDir: Path = __FILE__.toPath().absolute().parent // __FILE__ is this script val amperWrapperModuleDir = amperRootDir / "sources/amper-wrapper" val docsDir = amperRootDir / "docs" val versionsCatalogToml = amperRootDir / "libs.versions.toml" val defaultVersionsKt = amperRootDir / "sources/frontend-api/src/org/jetbrains/amper/frontend/schema/DefaultVersions.kt" fun syncVersions() { println("Making sure user-visible versions are aligned in Amper, docs, and examples...") updateVersionsCatalog() updateDefaultVersionsKt() updateAmperWrappers() updateWrapperTemplates() println("Done.") } fun updateVersionsCatalog() { versionsCatalogToml.replaceFileText { text -> text .replaceCatalogVersionVariable(variableName = "kotlin", newValue = kotlinVersion) .replaceCatalogVersionVariable(variableName = "ksp", newValue = kspVersion) .replaceCatalogVersionVariable(variableName = "compose-hot-reload-version", newValue = composeHotReloadVersion) .replaceCatalogVersionVariable(variableName = "junit-platform", newValue = junitPlatformVersion) } } fun String.replaceCatalogVersionVariable(variableName: String, newValue: String): String = replaceRegexGroup1( regex = Regex("""^${Regex.escape(variableName)}\s*=\s*\"([^"]+)\"""", RegexOption.MULTILINE), replacement = newValue, ) fun updateDefaultVersionsKt() { defaultVersionsKt.replaceFileText { text -> text .replaceDefaultVersionVariable(variableName = "compose", newValue = composeVersion) .replaceDefaultVersionVariable(variableName = "composeHotReload", newValue = composeHotReloadVersion) .replaceDefaultVersionIntVariable(variableName = "jdk", newValue = jdkVersion) .replaceDefaultVersionVariable(variableName = "junitPlatform", newValue = junitPlatformVersion) .replaceDefaultVersionVariable(variableName = "kotlin", newValue = kotlinVersion) .replaceDefaultVersionVariable(variableName = "kotlinxRpc", newValue = kotlinxRpcVersion) .replaceDefaultVersionVariable(variableName = "kotlinxSerialization", newValue = kotlinxSerializationVersion) .replaceDefaultVersionVariable(variableName = "ksp", newValue = kspVersion) .replaceDefaultVersionVariable(variableName = "ktor", newValue = ktorVersion) .replaceDefaultVersionVariable(variableName = "springBoot", newValue = springBootVersion) } } fun String.replaceDefaultVersionVariable(variableName: String, newValue: String): String = replaceRegexGroup1( regex = Regex("""\/\*managed_default\*\/\s*val\s+${Regex.escape(variableName)}\s*=\s*\"([^"]+)\""""), replacement = newValue, ) fun String.replaceDefaultVersionIntVariable(variableName: String, newValue: String): String = replaceRegexGroup1( regex = Regex("""\/\*managed_default\*\/\s*val\s+${Regex.escape(variableName)}\s*=\s*(\d+)"""), replacement = newValue, ) fun updateAmperWrappers() { val shellWrapperText = fetchContent("$amperMavenRepoUrl/org/jetbrains/amper/amper-cli/$bootstrapAmperVersion/amper-cli-$bootstrapAmperVersion-wrapper") val batchWrapperText = fetchContent("$amperMavenRepoUrl/org/jetbrains/amper/amper-cli/$bootstrapAmperVersion/amper-cli-$bootstrapAmperVersion-wrapper.bat") amperRootDir.forEachWrapperFile { path -> when (path.name) { "amper" -> path.replaceFileText { shellWrapperText } "amper.bat" -> path.replaceFileText { batchWrapperText } } } } private val excludedDirs = setOf("build", "build-from-sources", ".gradle", ".kotlin", ".git", "shared test caches") fun Path.forEachWrapperFile(action: (Path) -> Unit) { visitFileTree { onPreVisitDirectory { dir, _ -> if (dir.name in excludedDirs) { FileVisitResult.SKIP_SUBTREE } else { FileVisitResult.CONTINUE } } onVisitFile { file, _ -> if (file.name in setOf("amper", "amper.bat")) { action(file) } FileVisitResult.CONTINUE } } } fun updateWrapperTemplates() { val jbrVersionRegex = Regex("""(?<version>\d+\.\d+\.\d+)-?(?<build>b.*)""") val match = jbrVersionRegex.matchEntire(amperInternalJbrVersion) ?: error("Invalid JBR version '$jbrVersionRegex'") val jvmVersion = match.groups["version"]!!.value val jbrBuild = match.groups["build"]!!.value val jbrs = getJbrChecksums(jvmVersion, jbrBuild) sequenceOf( amperWrapperModuleDir / "resources/wrappers/amper.template.sh", amperRootDir / "amper-from-sources", ).replaceEachFileText { initialText -> val textWithVersion = initialText .replaceRegexGroup1(Regex("""\bjbr_version=(\S+)"""), jvmVersion) .replaceRegexGroup1(Regex("""\bjbr_build=(\S+)"""), jbrBuild) jbrs.fold(textWithVersion) { text, (os, arch, checksum) -> text.replaceRegexGroup1(Regex(""""$os $arch"\)\s+jbr_sha512=(\S+)\s*;;"""), checksum) } } sequenceOf( amperWrapperModuleDir / "resources/wrappers/amper.template.bat", amperRootDir / "amper-from-sources.bat", ).replaceEachFileText { initialText -> val textWithVersion = initialText .replaceRegexGroup1(Regex("""\bset\s+jbr_version=(\S+)"""), jvmVersion) .replaceRegexGroup1(Regex("""\bset\s+jbr_build=(\S+)"""), jbrBuild) jbrs.filter { it.os == "windows" }.fold(textWithVersion) { text, (_, arch, checksum) -> text.replaceRegexGroup1(Regex("""set jbr_arch=$arch\s+set jbr_sha512=(\S+)""", RegexOption.MULTILINE), checksum) } } } data class Jbr(val os: String, val arch: String, val sha512: String) fun getJbrChecksums(jvmVersion: String, jbrBuild: String): List<Jbr> = listOf("windows", "linux", "osx").flatMap { os -> listOf("aarch64", "x64").map { arch -> Jbr( os = os, arch = arch, sha512 = fetchContent("https://cache-redirector.jetbrains.com/intellij-jbr/jbr-$jvmVersion-$os-$arch-$jbrBuild.tar.gz.checksum") .trim() .split(" ") .first() ) } } fun fetchContent(url: String): String { val client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build() val request = HttpRequest.newBuilder().uri(URI.create(url)).build() return client.send(request, HttpResponse.BodyHandlers.ofString()).body() } /** * Finds all matches for the given [regex] in this string, and replaces the matched group 1 with the given replacement. */ fun String.replaceRegexGroup1(regex: Regex, replacement: String) = replace(regex) { it.value.replace(it.groupValues[1], replacement) } /** * Replaces the contents of each file in this sequence using the given [transform] on the existing contents. */ fun Sequence<Path>.replaceEachFileText(transform: (text: String) -> String) = forEach { it.replaceFileText(transform) } /** * Replaces the contents of the file at this [Path] using the given [transform] on the existing contents. */ fun Path.replaceFileText(transform: (text: String) -> String) { val oldText = readText() val newTest = transform(oldText) if (oldText == newTest) { return } writeText(newTest) println("Updated file .${File.separator}${relativeTo(amperRootDir)}") } // actually runs the script syncVersions()