mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[JEWEL-1339] Decouple .main.kts CI scripts from runner Kotlin by pinning Kotlin 2.3.20
The Jewel Checks workflow ran its .main.kts scripts via the `/usr/bin/env kotlin` shebang, i.e. whatever Kotlin the GitHub runner image happened to ship. A runner image refresh rolled that to Kotlin 2.4.0, which has a .main.kts regression resolving top-level helpers/extensions imported across a `@file:Import` boundary (FIR crash "Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl"), breaking the API-dump and Metalava jobs. Runner image drift was the trigger; the actual bug is in Kotlin 2.4.0's .main.kts import handling. Make CI deterministic and independent of the runner toolchain: every job that runs a .main.kts script (annotate API dumps, check API dumps, Metalava) now installs a pinned Kotlin (2.3.20, matching Jewel's build) through a local composite action (.github/actions/setup-kotlin) and invokes that binary directly, instead of relying on the shebang/PATH. The action caches the compiler per OS and version, so it is only downloaded on a cache miss rather than on every run. Pinning below 2.4.0 makes the import bug unreachable, so the scripts themselves are left unchanged. tools/kotlin.cmd was intentionally not used: it pins a different Kotlin (2.3.10) and is a shared tool outside this workflow's control that could be bumped to 2.4.0 and silently reintroduce the failure. Signed-off-by: Nebojsa.Vuksic <nebojsa.vuksic@jetbrains.com> closes https://github.com/JetBrains/intellij-community/pull/3544 (cherry picked from commit 636a35d3ab1c8fff0566344a6542134fe8994a72) (cherry picked from commit 42d85f991d8b2160e877dc78e3cc0a424d9b4f62) IJ-MR-211495 GitOrigin-RevId: 0fd9c154a2463af9e44f38ec0a574bb814ad5d06
This commit is contained in:
committed by
intellij-monorepo-bot
parent
89d3c37504
commit
49f92d712d
@@ -0,0 +1,54 @@
|
||||
name: Set up Kotlin
|
||||
description: >
|
||||
Installs a pinned Kotlin compiler and exposes its bin directory. The download is cached per OS and version, so it is
|
||||
only fetched on a cache miss (first run on a runner, or after a version bump) rather than on every run.
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: The Kotlin version to install (e.g., 2.3.20).
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
bin-dir:
|
||||
description: Absolute path to the installed Kotlin bin directory.
|
||||
value: ${{ steps.expose.outputs.bin-dir }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Restore cached Kotlin ${{ inputs.version }}
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: kotlin-${{ runner.os }}-${{ inputs.version }}
|
||||
path: ${{ runner.temp }}/kotlin-${{ inputs.version }}
|
||||
|
||||
- name: Install Kotlin ${{ inputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
KOTLIN_HOME="${RUNNER_TEMP}/kotlin-${{ inputs.version }}"
|
||||
KOTLINC="${KOTLIN_HOME}/bin/kotlinc"
|
||||
|
||||
# Trust the restored cache only if the compiler is present and reports the expected version.
|
||||
kotlin_cached_install_is_valid() {
|
||||
[ -x "${KOTLINC}" ] || return 1
|
||||
"${KOTLINC}" -version 2>&1 | grep -qF "kotlinc-jvm ${{ inputs.version }} "
|
||||
}
|
||||
|
||||
# Self-healing: a missing, partial/corrupt, or version-mismatched cache falls through to (re)download.
|
||||
if kotlin_cached_install_is_valid; then
|
||||
echo "Kotlin ${{ inputs.version }} already present (cache hit), skipping download."
|
||||
exit 0
|
||||
fi
|
||||
KOTLIN_ARCHIVE="${RUNNER_TEMP}/kotlin-compiler-${{ inputs.version }}.zip"
|
||||
rm -rf "${KOTLIN_HOME}" "${RUNNER_TEMP}/kotlinc"
|
||||
curl -fsSL --retry 3 --retry-all-errors \
|
||||
-o "${KOTLIN_ARCHIVE}" \
|
||||
"https://github.com/JetBrains/kotlin/releases/download/v${{ inputs.version }}/kotlin-compiler-${{ inputs.version }}.zip"
|
||||
unzip -q "${KOTLIN_ARCHIVE}" -d "${RUNNER_TEMP}"
|
||||
mv "${RUNNER_TEMP}/kotlinc" "${KOTLIN_HOME}"
|
||||
|
||||
- name: Expose Kotlin bin directory
|
||||
id: expose
|
||||
shell: bash
|
||||
run: echo "bin-dir=${RUNNER_TEMP}/kotlin-${{ inputs.version }}/bin" >> "$GITHUB_OUTPUT"
|
||||
@@ -9,6 +9,15 @@ defaults:
|
||||
run:
|
||||
working-directory: platform/jewel
|
||||
|
||||
# Kotlin used to run the .main.kts CI scripts (annotate/check API dumps, Metalava). It is pinned
|
||||
# here so the scripts do not depend on whatever Kotlin the runner image happens to ship.
|
||||
#
|
||||
# When Jewel's build Kotlin is updated, bump KOTLIN_VERSION to match so CI and the build stay aligned.
|
||||
# Keep it below 2.4.0 until the .main.kts cross-`@file:Import` regression is fixed (FIR crash
|
||||
# "Expected FirResolvedTypeRef ... but was FirUserTypeRefImpl"); see JEWEL-1339.
|
||||
env:
|
||||
KOTLIN_VERSION: 2.3.20
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
name: CI code checks
|
||||
@@ -110,14 +119,17 @@ jobs:
|
||||
fetch-depth: '2'
|
||||
name: Check out repository
|
||||
|
||||
- name: Grant execute permission to the validation script
|
||||
run: chmod +x ./scripts/annotate-api-dump-changes.main.kts
|
||||
- name: Set up Kotlin ${{ env.KOTLIN_VERSION }}
|
||||
id: kotlin
|
||||
uses: ./.github/actions/setup-kotlin
|
||||
with:
|
||||
version: ${{ env.KOTLIN_VERSION }}
|
||||
|
||||
- name: Annotate breaking API changes
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: ./scripts/annotate-api-dump-changes.main.kts
|
||||
run: ${{ steps.kotlin.outputs.bin-dir }}/kotlin ./scripts/annotate-api-dump-changes.main.kts
|
||||
|
||||
check_ij_api_dumps:
|
||||
name: Check that the IJP API dumps are up-to-date
|
||||
@@ -139,11 +151,14 @@ jobs:
|
||||
build/jps-bootstrap-work
|
||||
build/download
|
||||
|
||||
- name: Grant execute permission to the validation script
|
||||
run: chmod +x platform/jewel/scripts/check-api-dumps.main.kts
|
||||
- name: Set up Kotlin ${{ env.KOTLIN_VERSION }}
|
||||
id: kotlin
|
||||
uses: ./.github/actions/setup-kotlin
|
||||
with:
|
||||
version: ${{ env.KOTLIN_VERSION }}
|
||||
|
||||
- name: Check that the IJP API dumps are up-to-date
|
||||
run: ./scripts/check-api-dumps.main.kts
|
||||
run: ${{ steps.kotlin.outputs.bin-dir }}/kotlin ./scripts/check-api-dumps.main.kts
|
||||
working-directory: platform/jewel
|
||||
|
||||
check_bazel_build:
|
||||
@@ -214,8 +229,11 @@ jobs:
|
||||
distribution: jetbrains
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission to the Metalava script
|
||||
run: chmod +x ./scripts/metalava-signatures.main.kts
|
||||
- name: Set up Kotlin ${{ env.KOTLIN_VERSION }}
|
||||
id: kotlin
|
||||
uses: ./.github/actions/setup-kotlin
|
||||
with:
|
||||
version: ${{ env.KOTLIN_VERSION }}
|
||||
|
||||
- name: Grant execute permission to gradlew
|
||||
run: chmod +x gradlew
|
||||
@@ -224,4 +242,4 @@ jobs:
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: ./scripts/metalava-signatures.main.kts validate
|
||||
run: ${{ steps.kotlin.outputs.bin-dir }}/kotlin ./scripts/metalava-signatures.main.kts validate
|
||||
|
||||
@@ -10,6 +10,20 @@ There are two API surfaces:
|
||||
The current release version is read from [`gradle.properties`](../gradle.properties) via `jewel.release.version`. By
|
||||
default, the Metalava scripts validate and update dumps for that version.
|
||||
|
||||
## CI and the pinned Kotlin version
|
||||
|
||||
These validation and update scripts are `.main.kts` Kotlin scripts. The
|
||||
[Jewel Checks workflow](../../../.github/workflows/jewel-checks.yml) runs them (API dump checks and Metalava) using a
|
||||
Kotlin compiler pinned via its `KOTLIN_VERSION` env and installed by the
|
||||
[`setup-kotlin`](../../../.github/actions/setup-kotlin/action.yml) action, so CI does not depend on whichever Kotlin the
|
||||
runner image happens to ship.
|
||||
|
||||
[`gradle/libs.versions.toml`](../gradle/libs.versions.toml) (`kotlin`) is the source of truth for the Kotlin version;
|
||||
`KOTLIN_VERSION` in the workflow must match it. **When you upgrade Kotlin, bump both in the same change** so the build
|
||||
and CI stay aligned. A `JewelBuildTest` test asserts the two values are equal, so CI fails fast if they drift. Keep the
|
||||
pinned version below 2.4.0 until the `.main.kts` regression resolving helpers imported across a `@file:Import` boundary
|
||||
is fixed (see JEWEL-1339); otherwise the API dump and Metalava jobs will fail.
|
||||
|
||||
## Validate API dumps
|
||||
|
||||
To validate all Jewel API dumps, run this from the Jewel root:
|
||||
|
||||
+53
@@ -3,6 +3,8 @@ package org.jetbrains.jewel.foundation
|
||||
|
||||
import java.io.File
|
||||
import java.util.Properties
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
|
||||
private const val JEWEL_MARKER_FILE_NAME = "JEWEL_MARKER"
|
||||
@@ -29,6 +31,45 @@ internal class JewelBuildTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `KOTLIN_VERSION in the jewel-checks workflow should match the build Kotlin in libs versions toml`() {
|
||||
val jewelHome = findJewelHomeDir()
|
||||
|
||||
val libsVersionsToml = jewelHome.resolve("gradle/libs.versions.toml")
|
||||
if (!libsVersionsToml.isFile) {
|
||||
error("Cannot load the libs.versions.toml file from ${libsVersionsToml.absolutePath}")
|
||||
}
|
||||
|
||||
// The workflow lives at the repository root, outside the Jewel tree, so it is not present in
|
||||
// every environment that runs these tests (e.g. the Bazel sandbox). Skip when it is missing.
|
||||
val workflowFile = jewelHome.parentFile?.parentFile?.resolve(".github/workflows/jewel-checks.yml")
|
||||
assumeTrue(
|
||||
"Skipping: the jewel-checks workflow is not available in this environment " +
|
||||
"(${workflowFile?.absolutePath}).",
|
||||
workflowFile?.isFile == true,
|
||||
)
|
||||
|
||||
// libs.versions.toml is the source of truth; the workflow must follow it.
|
||||
val buildKotlin = readBuildKotlinVersion(libsVersionsToml)
|
||||
check(buildKotlin.isNotBlank()) {
|
||||
"Could not find a `kotlin = \"...\"` entry under [versions] in ${libsVersionsToml.absolutePath}"
|
||||
}
|
||||
|
||||
val ciKotlin = readWorkflowKotlinVersion(workflowFile!!)
|
||||
check(ciKotlin.isNotBlank()) { "Could not find a `KOTLIN_VERSION:` env entry in ${workflowFile.absolutePath}" }
|
||||
|
||||
val errorMessage = buildString {
|
||||
appendLine("The Kotlin version is out of sync between the Jewel build and the Jewel Checks CI workflow!")
|
||||
appendLine("Build Kotlin (source of truth): '$buildKotlin'")
|
||||
appendLine("Workflow KOTLIN_VERSION: '$ciKotlin'")
|
||||
appendLine("----------------------------------------------------------")
|
||||
appendLine("To fix, set KOTLIN_VERSION in '.github/workflows/jewel-checks.yml' to '$buildKotlin'")
|
||||
appendLine("so it matches the 'kotlin' version in 'platform/jewel/gradle/libs.versions.toml'.")
|
||||
}
|
||||
|
||||
assertEquals(errorMessage, buildKotlin, ciKotlin)
|
||||
}
|
||||
|
||||
private fun findJewelHomeDir(): File {
|
||||
val initialFile = File(".").canonicalFile
|
||||
|
||||
@@ -68,4 +109,16 @@ internal class JewelBuildTest {
|
||||
val properties = Properties().apply { file.inputStream().use { load(it) } }
|
||||
return properties.getProperty("jewel.release.version").orEmpty()
|
||||
}
|
||||
|
||||
// Matches the `kotlin = "<version>"` entry under [versions] (not kotlinpoet, kotlinx*, etc.).
|
||||
private fun readBuildKotlinVersion(libsVersionsToml: File): String =
|
||||
Regex("""(?m)^\s*kotlin\s*=\s*"([^"]+)"""").find(libsVersionsToml.readText())?.groupValues?.get(1).orEmpty()
|
||||
|
||||
// Matches the `KOTLIN_VERSION: <version>` workflow env entry (with or without surrounding quotes).
|
||||
private fun readWorkflowKotlinVersion(workflowFile: File): String =
|
||||
Regex("""(?m)^\s*KOTLIN_VERSION:\s*"?([^"\s#]+)"?""")
|
||||
.find(workflowFile.readText())
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user