[JEWEL-940] Add Jewel build information

This adds the Jewel API version to the build. The version is updated
via a new script, and has been included in the release process docs.

closes https://github.com/JetBrains/intellij-community/pull/3179

(cherry picked from commit 4bf7de427012daf3271fc39d9cc7555aede43e97)


(cherry picked from commit 2e78325dd76fe00ca0ff5b0ce6c978fefdb2deaa)

IJ-MR-173046

GitOrigin-RevId: af8846a7f15b16938e9648d8407db825e406762a
This commit is contained in:
Sebastiano Poggi
2025-08-13 19:18:49 +02:00
committed by intellij-monorepo-bot
parent 1907095613
commit 116a46b795
11 changed files with 206 additions and 3 deletions

View File

View File

@@ -10,6 +10,23 @@ artefacts to Maven Central.
Please ping Jakub, Nebojsa, or Sasha for help and guidance.
High-level steps:
1. Bump the Jewel API version in [`gradle.properties`](../gradle.properties)
2. Run the [version updater script](../scripts/jewel-version-updater.main.kts)
3. Cherry-pick the changes to the target release branches (e.g., `252`)
1. Make sure you've not included IJP major release-specific changes
2. Update the `ijp.target` entry in [`gradle.properties`](../gradle.properties)
3. Update the Kotlin version in the [Gradle version catalog](../gradle/libs.versions.toml) to match the IJP's Kotlin version
4. Update other related versions if needed
5. Run all Gradle-based checks
6. Run all IJ tests (e.g., via the `tests.cmd` script)
7. Verify everything works in the Jewel Standalone sample (components, Markdown rendering)
8. Verify everything works in the Jewel IDE samples (toolwindow, component showcase)
9. Verify that the publishing works locally (including POMs, especially for newly added/changed modules — see below)
10. Open a MR for each cherry-pick branch on Space
4. When both MRs are approved and merged, run the TeamCity job to publish the artefacts to Maven Central
## Testing publishing locally
Before pulling the trigger on a release process, it's a good idea to make sure that all and only the artefacts that

View File

@@ -23,7 +23,7 @@ jvm_library(
name = "foundation",
module_name = "intellij.platform.jewel.foundation",
visibility = ["//visibility:public"],
srcs = glob(["src/main/kotlin/**/*.kt", "src/main/kotlin/**/*.java"], allow_empty = True),
srcs = glob(["src/main/kotlin/**/*.kt", "src/main/kotlin/**/*.java", "src/main/generated-kotlin/**/*.kt", "src/main/generated-kotlin/**/*.java"], allow_empty = True),
kotlinc_opts = ":custom",
deps = [
"@lib//:kotlin-stdlib",
@@ -62,4 +62,4 @@ jvm_test(
name = "foundation_test",
runtime_deps = [":foundation_test_lib"]
)
### auto-generated section `build intellij.platform.jewel.foundation` end
### auto-generated section `build intellij.platform.jewel.foundation` end

View File

@@ -48,6 +48,11 @@ f:org.jetbrains.jewel.foundation.GlobalMetricsKt
- sf:getLocalGlobalMetrics():androidx.compose.runtime.ProvidableCompositionLocal
@:org.jetbrains.jewel.foundation.InternalJewelApi
- java.lang.annotation.Annotation
f:org.jetbrains.jewel.foundation.JewelApiVersionKt
- sf:getApiVersionString(org.jetbrains.jewel.foundation.JewelBuild):java.lang.String
f:org.jetbrains.jewel.foundation.JewelBuild
- sf:$stable:I
- sf:INSTANCE:org.jetbrains.jewel.foundation.JewelBuild
f:org.jetbrains.jewel.foundation.JewelFlags
- sf:$stable:I
- sf:INSTANCE:org.jetbrains.jewel.foundation.JewelFlags

View File

@@ -7,6 +7,8 @@ plugins {
alias(libs.plugins.compose.compiler)
}
sourceSets { test { kotlin { srcDirs("src/main/generated-kotlin") } } }
private val composeVersion
get() = ComposeBuildConfig.composeVersion

View File

@@ -27,6 +27,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/generated-kotlin" isTestSource="false" generated="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />

View File

@@ -0,0 +1,8 @@
// ATTENTION: this file is auto-generated. DO NOT EDIT MANUALLY!
// Use the jewel-version-updater script instead.
package org.jetbrains.jewel.foundation
/** The Jewel API version for this build, expressed as a string. E.g.: "0.30.0" */
public val JewelBuild.apiVersionString: String
get() = "0.29.0"

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.foundation
/** Contains information about the Jewel build in use. */
public object JewelBuild

View File

@@ -0,0 +1,71 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.foundation
import java.io.File
import java.util.Properties
import org.junit.Test
private const val JEWEL_MARKER_FILE_NAME = "JEWEL_MARKER"
internal class JewelBuildTest {
@Test
fun `apiVersionString should have the same value as the one in the gradle properties`() {
val jewelHome = findJewelHomeDir()
val propertiesFile = jewelHome.resolve("gradle.properties")
if (!propertiesFile.isFile) {
error("Cannot load the gradle.properties file from ${propertiesFile.absolutePath}")
}
val expected = loadApiVersion(propertiesFile)
if (expected.isBlank()) {
error("The jewel.release.version value in the gradle.properties file must not be blank")
}
assert(JewelBuild.apiVersionString == expected) {
"The version defined by the `jewel.release.version` value ($expected) in the gradle.properties file does " +
"not match the one defined in `JewelBuild.apiVersionString` (${JewelBuild.apiVersionString}).\n\n" +
"You can fix this by running the Jewel version updater script in the `jewel/scripts` folder."
}
}
private fun findJewelHomeDir(): File {
val initialFile = File(".").canonicalFile
val ultimateJewel = initialFile.resolve("community/platform/jewel")
if (ultimateJewel.isDirectory && ultimateJewel.resolve(JEWEL_MARKER_FILE_NAME).isFile) {
println("Found Jewel folder at ${ultimateJewel.absolutePath} (ultimate checkout)")
return ultimateJewel
}
val communityJewel = initialFile.resolve("platform/jewel")
if (communityJewel.isDirectory && communityJewel.resolve(JEWEL_MARKER_FILE_NAME).isFile) {
println("Found Jewel folder at ${communityJewel.absolutePath} (community checkout)")
return communityJewel
}
var current = initialFile
while (true) {
if (!current.canRead()) {
error("Directory is not readable, stopping search: ${current.absolutePath}")
}
val marker = current.resolve(JEWEL_MARKER_FILE_NAME)
if (marker.isFile) {
return current
}
current =
current.parentFile
?: error(
"Could not find a $JEWEL_MARKER_FILE_NAME file in any parent directory.\n" +
"Searched up from ${initialFile.absolutePath}"
)
}
}
private fun loadApiVersion(file: File): String {
val properties = Properties().apply { file.inputStream().use { load(it) } }
return properties.getProperty("jewel.release.version").orEmpty()
}
}

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env kotlin
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Import("utils.main.kts")
import kotlin.system.exitProcess
import java.io.File
import java.util.Properties
// =============================== HELPERS =============================== //
private fun loadJewelVersionFromProperties(jewelDir: File): String {
val propertiesFile = File(jewelDir, "gradle.properties")
if (!propertiesFile.isFile) {
printlnErr("Could not find the gradle.properties file.")
exitProcess(1)
}
val properties = Properties().apply { propertiesFile.inputStream().use { load(it) } }
val version = properties.getProperty("jewel.release.version")?.trim()
if (version.isNullOrBlank()) {
printlnErr("Could not find the Jewel API version in gradle.properties (jewel.release.version)")
exitProcess(1)
}
return version
}
private fun updateJewelApiVersion(jewelDir: File, apiVersion: String): File {
val outFile =
File(jewelDir, "foundation/src/main/generated-kotlin/org/jetbrains/jewel/foundation/JewelApiVersion.kt")
outFile.parentFile.mkdirs()
outFile.writeText(jewelApiVersionTemplate.replace(jewelApiVersionPlaceholder, apiVersion))
return outFile
}
private val jewelApiVersionPlaceholder = "%%JEWEL_VERSION%%"
private val jewelApiVersionTemplate =
"""
|// ATTENTION: this file is auto-generated. DO NOT EDIT MANUALLY!
|// Use the jewel-version-updater script instead.
|
|package org.jetbrains.jewel.foundation
|
|/** The Jewel API version for this build, expressed as a string. E.g.: "0.30.0" */
|public val JewelBuild.apiVersionString: String
| get() = "0.29.0"
|
"""
.trimMargin()
// =============================== ENTRY POINT =============================== //
print("⏳ Locating Jewel folder...")
private val jewelDir = findJewelRoot()
if (jewelDir == null || !jewelDir.isDirectory) {
printlnErr("Could not find the Jewel folder. Please make sure you're running the script from somewhere inside it.")
exitProcess(1)
}
println(" DONE: ${jewelDir!!.absolutePath}")
print("🔍 Looking up Jewel API version...")
val apiVersion = loadJewelVersionFromProperties(jewelDir!!)
println(" DONE: $apiVersion")
print("✍️ Writing updated Jewel API version...")
val outFile = updateJewelApiVersion(jewelDir!!, apiVersion)
print(" DONE\n Generated ")
println(outFile.toRelativeString(jewelDir!!).asLink("file://${outFile.absolutePath}"))

View File

@@ -4,13 +4,13 @@
import com.github.pgreze.process.Redirect
import com.github.pgreze.process.process
import java.io.File
import kotlin.system.exitProcess
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.intellij.lang.annotations.Language
import java.io.File
fun checkGhTool() = runBlocking { runCommand(command = "which gh", workingDir = null, exitOnError = false).isSuccess }
@@ -25,6 +25,18 @@ fun requireGhTool() {
exitProcess(1)
}
fun findJewelRoot(base: File = File("").canonicalFile): File? {
fun isJewelDir(file: File): Boolean = file.name == "jewel" && file.parentFile.name == "platform"
var file = base
while (file.isDirectory) {
if (isJewelDir(file)) return file.canonicalFile
if (file.parentFile == null) break
file = file.parentFile
}
return null
}
fun requirePrNumber() {
if (checkPrNumber()) return