Files
gradle/sources/src/job-summary.ts
T
Daz DeBoer a0ee12f71e Extract caching logic into a separate gradle-actions-caching component (#885)
With this change, the caching functionality of `setup-gradle` and
`dependency-submission` is now provided by `gradle-actions-caching`, a
closed-source library distributed under our [Terms of
Use](https://gradle.com/legal/terms-of-use/). The rest of the action
implementation remains open source.

Using `setup-gradle` or `dependency-submission` with caching enabled
involves loading and using the `gradle-actions-caching` component,
requiring acceptance of the [Terms of
Use](https://gradle.com/legal/terms-of-use/). There are no functional
changes to caching provided by these actions: all workflows will
continue to function as before.

The non-caching aspects of action implementation remain open source. By
running these actions with caching disabled they can be used without
ever loading `gradle-actions-caching` or accepting the license terms.

Supporting the caching infrastructure in this project requires a
substantial engineering investment by Gradle Technologies, which we can
sustain thanks to Develocity, our commercial offering. Caching
technologies are a core part of the Develocity offering, and the caching
in `setup-gradle` fits squarely in that space.

This licensing change lets us continue to build advanced capabilities
that go beyond what we would offer as open source. Proper
production-ready Configuration Cache support will be the first
capability. Improving build performance for self-hosted runners will
follow.

We may introduce functionality restrictions in future updates. However,
caching functionality will remain free for public repositories.
We have a long-standing commitment to open source, as maintainers of
Gradle Build Tool, and by [sponsoring the open source
community](https://gradle.com/oss-sponsored-by-develocity/) with free
Develocity licenses. Public repositories are primarily used by open
source projects, and we remain committed to supporting them.

- Implementation of caching logic to save and restore Gradle User Home
content has been removed, replaced by the `gradle-actions-caching`
component.
- The `@actions/caching` library is still used to cache Gradle
distributions that are downloaded and provisioned by `setup-gradle`.
This PR updates to the latest version of `@actions/caching`, and removes
the patch that is no longer required.
- License notices are now displayed in documentation, logs and the
generated Job Summary.
2026-03-18 14:57:27 -06:00

204 lines
6.2 KiB
TypeScript

import * as core from '@actions/core'
import * as github from '@actions/github'
import {BuildResult} from './build-results'
import {SummaryConfig, getActionId, getGithubToken} from './configuration'
import {Deprecation, getDeprecations, getErrors} from './deprecation-collector'
export async function generateJobSummary(
buildResults: BuildResult[],
cachingReport: string,
config: SummaryConfig
): Promise<void> {
const errors = renderErrors()
if (errors) {
core.summary.addRaw(errors)
await core.summary.write()
return
}
const summaryTable = renderSummaryTable(buildResults)
const hasFailure = anyFailed(buildResults)
if (config.shouldGenerateJobSummary(hasFailure)) {
core.info('Generating Job Summary')
core.summary.addRaw(summaryTable)
core.summary.addRaw(cachingReport)
await core.summary.write()
} else {
core.info('============================')
core.info(summaryTable)
core.info('============================')
core.info(cachingReport)
core.info('============================')
}
if (config.shouldAddPRComment(hasFailure)) {
await addPRComment(summaryTable)
}
}
async function addPRComment(jobSummary: string): Promise<void> {
const context = github.context
if (context.payload.pull_request == null) {
core.info('No pull_request trigger detected: not adding PR comment')
return
}
const pull_request_number = context.payload.pull_request.number
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`)
const prComment = `<h3>Job Summary for Gradle</h3>
<a href="${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}" target="_blank">
<h5>${context.workflow} :: <em>${context.job}</em></h5>
</a>
${jobSummary}`
const github_token = getGithubToken()
const octokit = github.getOctokit(github_token)
try {
await octokit.rest.issues.createComment({
...context.repo,
issue_number: pull_request_number,
body: prComment
})
} catch (error) {
if (error instanceof Error && error.name === 'HttpError') {
core.warning(buildWarningMessage(error))
} else {
throw error
}
}
}
function buildWarningMessage(error: Error): string {
const mainWarning = `Failed to generate PR comment.\n${String(error)}`
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'pull-requests: write' permission is available for the workflow job.
Note that this permission is never available for a workflow triggered from a repository fork.
`
}
return mainWarning
}
export function renderSummaryTable(results: BuildResult[]): string {
return `${renderDeprecations()}\n${renderBuildResults(results)}`
}
function renderErrors(): string | undefined {
const errors = getErrors()
if (errors.length === 0) {
return undefined
}
return errors.map(error => `<b>:x: ${error}</b>`).join('\n')
}
function renderDeprecations(): string {
const deprecations = getDeprecations()
if (deprecations.length === 0) {
return ''
}
return `
<h4>Deprecation warnings</h4>
This job uses deprecated functionality from the <code>${getActionId()}</code> action. Follow the links for upgrade details.
<ul>
${deprecations.map(deprecation => `<li>${getDeprecationHtml(deprecation)}</li>`).join('')}
</ul>
<h4>Gradle Build Results</h4>`
}
function getDeprecationHtml(deprecation: Deprecation): string {
return `<a href="${deprecation.getDocumentationLink()}" target="_blank">${deprecation.message}</a>`
}
function renderBuildResults(results: BuildResult[]): string {
if (results.length === 0) {
return '<b>No Gradle build results detected.</b>'
}
return `
<table>
<tr>
<th>Gradle Root Project</th>
<th>Requested Tasks</th>
<th>Gradle Version</th>
<th>Build Outcome</th>
<th>Build&nbsp;Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table>
`
}
function anyFailed(results: BuildResult[]): boolean {
return results.some(result => result.buildFailed)
}
function renderBuildResultRow(result: BuildResult): string {
return `
<tr>
<td>${truncateString(result.rootProjectName, 30)}</td>
<td>${truncateString(result.requestedTasks, 60)}</td>
<td align='center'>${result.gradleVersion}</td>
<td align='center'>${renderOutcome(result)}</td>
<td>${renderBuildScan(result)}</td>
</tr>`
}
function renderOutcome(result: BuildResult): string {
return result.buildFailed ? ':x:' : ':white_check_mark:'
}
interface BadgeSpec {
text: string
alt: string
color: string
logo: boolean
targetUrl: string
}
function renderBuildScan(result: BuildResult): string {
if (result.buildScanFailed) {
return renderBuildScanBadge({
text: 'Publish failed',
alt: 'Build Scan publish failed',
color: 'orange',
logo: false,
targetUrl: 'https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting'
})
}
if (result.buildScanUri) {
return renderBuildScanBadge({
text: 'Build Scan®',
alt: 'Build Scan published',
color: '06A0CE',
logo: true,
targetUrl: result.buildScanUri
})
}
return renderBuildScanBadge({
text: 'Not published',
alt: 'Build Scan not published',
color: 'lightgrey',
logo: false,
targetUrl: 'https://scans.gradle.com'
})
}
function renderBuildScanBadge({text, alt, color, logo, targetUrl}: BadgeSpec): string {
const encodedText = encodeURIComponent(text)
const badgeUrl = `https://img.shields.io/badge/${encodedText}-${color}${logo ? '?logo=Gradle' : ''}`
const badgeHtml = `<img src="${badgeUrl}" alt="${alt}" />`
return `<a href="${targetUrl}" rel="nofollow" target="_blank">${badgeHtml}</a>`
}
function truncateString(str: string, maxLength: number): string {
if (str.length > maxLength) {
return `<div title='${str}'>${str.slice(0, maxLength - 1)}…</div>`
} else {
return str
}
}