mirror of
https://github.com/gradle/actions.git
synced 2026-09-10 19:28:33 +08:00
This PR reports the support status of every Gradle version used in a workflow: - **End-of-life** — two or more major versions behind the latest release. - **Out of date** — one major version behind the latest release, or more than two minor versions behind on the current major. The information is surfaced as job annotations and in the Job Summary: an icon beside the Gradle version in the build results table, and a message below it. | version kind | job annotation | version table | below the table | | ------ | ----- | ---- | --------- | | EOL | warning | ⚠️ | expandable section, pointing to the Gradle Security Subscription | | Out of date | notice | ℹ️ | one-line legend, pointing to the Gradle release lifecycle docs | | Current | none | — | — | A single expandable section covers every end-of-life version, naming them in its summary line and listing the affected release lines in its body. The upgrade legend likewise appears once per job, however many versions carry the info icon. Notes on what is deliberately *not* reported: - **Patch releases.** Only the major and minor version are considered, so being on `9.7.0` when `9.7.1` exists is not flagged. - **Pre-releases.** Release candidates, milestones and snapshots are never reported, so testing against a nightly or an RC produces no annotations. The latest Gradle release is determined from the wrapper checksum data already bundled with the action, so no network access is required. That data is refreshed weekly, and the two-minor grace band absorbs the lag. Note that the annotations are emitted independently of `add-job-summary`: setting it to `never` suppresses the Job Summary itself, but the warning and notice annotations remain. ### Examples The `demo-job-summary` workflow has a `support-status-eol-and-outdated` job that builds with three end-of-life versions (`6.9.4`, `7.4`, `7.6.6`), two out-of-date versions (`8.0.2`, `8.14.5`) and the current release, so all three statuses appear in one summary: * **Job Summary**: https://github.com/gradle/actions/actions/runs/34166688755#summary-101879071306 * **Annotations**: https://github.com/gradle/actions/actions/runs/34166688755/job/101879071306 (expand annotations) That workflow is never triggered automatically; run it manually against a branch to review the rendering. ### Implementation * Adds `GradleVersion`, parsing and ordering versions the same way Gradle's own `org.gradle.util.GradleVersion` does. This replaces the previous `versionIsAtLeast` helper and removes the `semver` dependency. * Adds `gradle-support-status.ts`, which owns the classification policy and both of its presentations (annotations and Job Summary section) behind a three-function API, so `job-summary.ts` only asks for the icon and the rendered block. ### Testing * Unit tests for version ordering, classification, the annotations and the rendered summary. * `integ-test-gradle-support-status.yml`, which derives its expected versions from the bundled release data so the assertions cannot drift from the action's own view, then asserts against the real job log. * The demo workflow job linked above, for reviewing the rendering by eye. Documented under [Build reporting](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#gradle-version-support-status) in `docs/setup-gradle.md`. --------- Co-authored-by: Louis Jacomet <louis@gradle.com> Co-authored-by: Daz DeBoer <daz@gradle.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
279 lines
12 KiB
TypeScript
279 lines
12 KiB
TypeScript
import dedent from 'dedent'
|
|
import * as github from '@actions/github'
|
|
import {afterEach, describe, expect, it, jest} from '@jest/globals'
|
|
|
|
import {BuildResult} from '../../src/build-results'
|
|
|
|
// The support-status policy itself is covered by gradle-support-status.test.ts; these tests only
|
|
// check that its output reaches the table. '8.0' is the sole release so the tables below, all of
|
|
// which build with Gradle 8.0, render without a status sign.
|
|
let releasedVersions = [{version: '8.0', checksum: ''}]
|
|
jest.unstable_mockModule('../../src/wrapper-validation/wrapper-checksums.json', () => ({
|
|
get default() {
|
|
return releasedVersions
|
|
}
|
|
}))
|
|
|
|
const {jobMarker, renderSummaryTable} = await import('../../src/job-summary')
|
|
|
|
/** Renders the summary table with the release index built from `versions` rather than the bundled data. */
|
|
async function renderWith(versions: string[], results: BuildResult[]): Promise<string> {
|
|
releasedVersions = versions.map(version => ({version, checksum: ''}))
|
|
jest.resetModules()
|
|
const {renderSummaryTable: render} = await import('../../src/job-summary')
|
|
return render(results)
|
|
}
|
|
|
|
const DOC = 'https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support'
|
|
const SECURITY_SUBSCRIPTION = 'https://gradle.org/security-subscription/?utm_source=github-action'
|
|
|
|
const MATRIX_INPUT_ENV = 'INPUT_WORKFLOW-JOB-CONTEXT'
|
|
|
|
function fakeContext(workflow: string, job: string): typeof github.context {
|
|
return {workflow, job} as unknown as typeof github.context
|
|
}
|
|
|
|
const successfulHelpBuild: BuildResult = {
|
|
rootProjectName: 'root',
|
|
rootProjectDir: '/',
|
|
requestedTasks: 'help',
|
|
gradleVersion: '8.0',
|
|
gradleHomeDir: '/opt/gradle',
|
|
buildFailed: false,
|
|
configCacheHit: false,
|
|
buildScanUri: 'https://scans.gradle.com/s/abc123',
|
|
buildScanFailed: false
|
|
}
|
|
|
|
const failedHelpBuild: BuildResult = {
|
|
...successfulHelpBuild,
|
|
buildFailed: true
|
|
}
|
|
|
|
const longArgsBuild: BuildResult = {
|
|
...successfulHelpBuild,
|
|
requestedTasks:
|
|
'check publishMyLongNamePluginPublicationToMavenCentral publishMyLongNamePluginPublicationToPluginPortal'
|
|
}
|
|
|
|
const scanPublishDisabledBuild: BuildResult = {
|
|
...successfulHelpBuild,
|
|
buildScanUri: '',
|
|
buildScanFailed: false
|
|
}
|
|
|
|
const scanPublishFailedBuild: BuildResult = {
|
|
...successfulHelpBuild,
|
|
buildScanUri: '',
|
|
buildScanFailed: true
|
|
}
|
|
|
|
describe('renderSummaryTable', () => {
|
|
describe('renders', () => {
|
|
it('successful build', () => {
|
|
const table = renderSummaryTable([successfulHelpBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
it('failed build', () => {
|
|
const table = renderSummaryTable([failedHelpBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:x:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
describe('when build scan', () => {
|
|
it('publishing disabled', () => {
|
|
const table = renderSummaryTable([scanPublishDisabledBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://scans.gradle.com" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Not%20published-lightgrey" alt="Build Scan not published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
it('publishing failed', () => {
|
|
const table = renderSummaryTable([scanPublishFailedBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Publish%20failed-orange" alt="Build Scan publish failed" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
})
|
|
it('multiple builds', () => {
|
|
const table = renderSummaryTable([successfulHelpBuild, failedHelpBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:x:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
it('truncating long requested tasks', () => {
|
|
const table = renderSummaryTable([longArgsBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td><div title='check publishMyLongNamePluginPublicationToMavenCentral publishMyLongNamePluginPublicationToPluginPortal'>check publishMyLongNamePluginPublicationToMavenCentral publ…</div></td>
|
|
<td align='center'>8.0</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
`)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Gradle version support status', () => {
|
|
it('warns on an end-of-life version and folds the detail below the table', async () => {
|
|
const table = await renderWith(['10.0.0', '8.0'], [successfulHelpBuild])
|
|
expect(table.trim()).toBe(dedent`
|
|
<table>
|
|
<tr>
|
|
<th>Gradle Root Project</th>
|
|
<th>Requested Tasks</th>
|
|
<th>Gradle Version</th>
|
|
<th>Build Outcome</th>
|
|
<th>Build Scan®</th>
|
|
</tr>
|
|
<tr>
|
|
<td>root</td>
|
|
<td>help</td>
|
|
<td align='center'>8.0 :warning:</td>
|
|
<td align='center'>:white_check_mark:</td>
|
|
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<details>
|
|
<summary>:warning: Gradle 8.0 is end-of-life</summary>
|
|
<p>Gradle 8.x releases receive no further fixes, security fixes included. Update to the latest Gradle version.</p>
|
|
<p>If you cannot upgrade, see the <a href="${SECURITY_SUBSCRIPTION}">Gradle Security Subscription</a> for options.</p>
|
|
</details>
|
|
`)
|
|
})
|
|
it('signs a still-supported but outdated version with the info sign and one legend', async () => {
|
|
const table = await renderWith(['9.0.0', '8.1', '8.0'], [successfulHelpBuild])
|
|
expect(table).toContain(`<td align='center'>8.0 :information_source:</td>`)
|
|
expect(table).toContain(
|
|
`<p>:information_source: Gradle version is out of date — consider upgrading. ` +
|
|
`See <a href="${DOC}">Gradle release lifecycle</a></p>`
|
|
)
|
|
expect(table).not.toContain('<details>')
|
|
})
|
|
it('adds nothing for a version inside the grace band', async () => {
|
|
const table = await renderWith(['8.2', '8.0'], [successfulHelpBuild])
|
|
expect(table).toContain(`<td align='center'>8.0</td>`)
|
|
expect(table).not.toContain(':information_source:')
|
|
expect(table).not.toContain('consider upgrading')
|
|
})
|
|
})
|
|
|
|
describe('jobMarker', () => {
|
|
const original = process.env[MATRIX_INPUT_ENV]
|
|
|
|
afterEach(() => {
|
|
if (original === undefined) {
|
|
delete process.env[MATRIX_INPUT_ENV]
|
|
} else {
|
|
process.env[MATRIX_INPUT_ENV] = original
|
|
}
|
|
})
|
|
|
|
it('builds a hidden marker from the workflow and job', () => {
|
|
process.env[MATRIX_INPUT_ENV] = 'null'
|
|
const marker = jobMarker(fakeContext('CI', 'build'))
|
|
expect(marker).toBe('<!-- gradle-job-summary: ci-build -->')
|
|
})
|
|
|
|
it('includes the job matrix in the marker', () => {
|
|
process.env[MATRIX_INPUT_ENV] = JSON.stringify({os: 'ubuntu', java: '17'})
|
|
const marker = jobMarker(fakeContext('CI', 'build'))
|
|
expect(marker).toBe('<!-- gradle-job-summary: ci-build-ubuntu-17 -->')
|
|
})
|
|
})
|