mirror of
https://github.com/gradle/actions.git
synced 2026-06-27 22:08:29 +00:00
cbc6f4a545
Render the caching report from structured CacheReport data using a single unified layout, replacing the three divergent code paths (NoOp / basic / enhanced) that previously each rendered their own markdown. Every variant now shares one skeleton: a section heading (`#### <icon> Gradle Caching — <Provider> (<status>)`), a status line, an integrated provider note, and an expandable cache-entry-details section. The Enhanced/Basic provider note is woven into the report under the heading and is now shown unconditionally (no longer gated on license acceptance). The two disabled variants (explicitly disabled, and skipped due to a pre-existing Gradle User Home) render as compact callouts with no expandable section. - caching-report.ts: new central renderer + all framing copy. - cache-service.ts: CacheReport/CacheEntryReport/status types; save() returns CacheReport. - cache-service-loader.ts: NoOp returns a CacheReport, drop LicenseWarningCacheService, add getProviderNote(). - cache-service-basic.ts: build a CacheReport, so basic caching now also gets expandable entry details. - job-summary.ts / setup-gradle.ts: thread CacheReport + ProviderNote. - configuration.ts: remove now-unused isCacheLicenseAccepted(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
452 lines
14 KiB
TypeScript
452 lines
14 KiB
TypeScript
import * as core from '@actions/core'
|
|
import * as github from '@actions/github'
|
|
import * as cache from '@actions/cache'
|
|
import * as deprecator from './deprecation-collector'
|
|
|
|
import * as path from 'path'
|
|
|
|
const ACTION_ID_VAR = 'GRADLE_ACTION_ID'
|
|
const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
|
|
|
|
export const ACTION_METADATA_DIR = '.setup-gradle'
|
|
|
|
export class DependencyGraphConfig {
|
|
getDependencyGraphOption(): DependencyGraphOption {
|
|
const val = core.getInput('dependency-graph')
|
|
switch (val.toLowerCase().trim()) {
|
|
case 'disabled':
|
|
return DependencyGraphOption.Disabled
|
|
case 'generate':
|
|
return DependencyGraphOption.Generate
|
|
case 'generate-and-submit':
|
|
return DependencyGraphOption.GenerateAndSubmit
|
|
case 'generate-submit-and-upload':
|
|
return DependencyGraphOption.GenerateSubmitAndUpload
|
|
case 'generate-and-upload':
|
|
return DependencyGraphOption.GenerateAndUpload
|
|
case 'download-and-submit':
|
|
return DependencyGraphOption.DownloadAndSubmit
|
|
}
|
|
throw TypeError(
|
|
`The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-submit-and-upload, generate-and-upload, download-and-submit].`
|
|
)
|
|
}
|
|
|
|
getDependencyGraphContinueOnFailure(): boolean {
|
|
return getBooleanInput('dependency-graph-continue-on-failure', true)
|
|
}
|
|
|
|
getArtifactRetentionDays(): number {
|
|
const val = core.getInput('artifact-retention-days')
|
|
return parseNumericInput('artifact-retention-days', val, 0)
|
|
// Zero indicates that the default repository settings should be used
|
|
}
|
|
|
|
getJobCorrelator(): string {
|
|
return DependencyGraphConfig.constructJobCorrelator(github.context.workflow, github.context.job, getJobMatrix())
|
|
}
|
|
|
|
getReportDirectory(): string {
|
|
const param = core.getInput('dependency-graph-report-dir')
|
|
return path.resolve(getWorkspaceDirectory(), param)
|
|
}
|
|
|
|
getDownloadArtifactName(): string | undefined {
|
|
return process.env['DEPENDENCY_GRAPH_DOWNLOAD_ARTIFACT_NAME']
|
|
}
|
|
|
|
getExcludeProjects(): string | undefined {
|
|
return getOptionalInput('dependency-graph-exclude-projects')
|
|
}
|
|
|
|
getIncludeProjects(): string | undefined {
|
|
return getOptionalInput('dependency-graph-include-projects')
|
|
}
|
|
|
|
getExcludeConfigurations(): string | undefined {
|
|
return getOptionalInput('dependency-graph-exclude-configurations')
|
|
}
|
|
|
|
getIncludeConfigurations(): string | undefined {
|
|
return getOptionalInput('dependency-graph-include-configurations')
|
|
}
|
|
|
|
getPluginRepository(): PluginRepositoryConfig {
|
|
return new PluginRepositoryConfig()
|
|
}
|
|
|
|
static constructJobCorrelator(workflow: string, jobId: string, matrixJson: string): string {
|
|
const matrixString = this.describeMatrix(matrixJson)
|
|
const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`
|
|
return this.sanitize(label)
|
|
}
|
|
|
|
private static describeMatrix(matrixJson: string): string {
|
|
core.debug(`Got matrix json: ${matrixJson}`)
|
|
const matrix = JSON.parse(matrixJson)
|
|
if (matrix) {
|
|
return Object.values(matrix).join('-')
|
|
}
|
|
return ''
|
|
}
|
|
|
|
private static sanitize(value: string): string {
|
|
return value
|
|
.replace(/[^a-zA-Z0-9_-\s]/g, '')
|
|
.replace(/\s+/g, '_')
|
|
.toLowerCase()
|
|
}
|
|
}
|
|
|
|
export enum DependencyGraphOption {
|
|
Disabled = 'disabled',
|
|
Generate = 'generate',
|
|
GenerateAndSubmit = 'generate-and-submit',
|
|
GenerateSubmitAndUpload = 'generate-submit-and-upload',
|
|
GenerateAndUpload = 'generate-and-upload',
|
|
DownloadAndSubmit = 'download-and-submit'
|
|
}
|
|
|
|
export class CacheConfig {
|
|
isCacheDisabled(): boolean {
|
|
if (!cache.isFeatureAvailable()) {
|
|
return true
|
|
}
|
|
|
|
return getBooleanInput('cache-disabled')
|
|
}
|
|
|
|
isCacheReadOnly(): boolean {
|
|
return !this.isCacheWriteOnly() && getBooleanInput('cache-read-only')
|
|
}
|
|
|
|
isCacheWriteOnly(): boolean {
|
|
return getBooleanInput('cache-write-only')
|
|
}
|
|
|
|
isCacheOverwriteExisting(): boolean {
|
|
return getBooleanInput('cache-overwrite-existing')
|
|
}
|
|
|
|
isCacheStrictMatch(): boolean {
|
|
return getBooleanInput('gradle-home-cache-strict-match')
|
|
}
|
|
|
|
getCacheCleanupOption(): string {
|
|
const legacyVal = getOptionalBooleanInput('gradle-home-cache-cleanup')
|
|
if (legacyVal !== undefined) {
|
|
deprecator.recordDeprecation(
|
|
'The `gradle-home-cache-cleanup` input parameter has been replaced by `cache-cleanup`'
|
|
)
|
|
return legacyVal ? CacheCleanupOption.Always.toString() : CacheCleanupOption.Never.toString()
|
|
}
|
|
|
|
const val = core.getInput('cache-cleanup')
|
|
switch (val.toLowerCase().trim()) {
|
|
case 'always':
|
|
return CacheCleanupOption.Always.toString()
|
|
case 'on-success':
|
|
return CacheCleanupOption.OnSuccess.toString()
|
|
case 'never':
|
|
return CacheCleanupOption.Never.toString()
|
|
}
|
|
throw TypeError(
|
|
`The value '${val}' is not valid for cache-cleanup. Valid values are: [never, always, on-success].`
|
|
)
|
|
}
|
|
|
|
getCacheEncryptionKey(): string {
|
|
return core.getInput('cache-encryption-key')
|
|
}
|
|
|
|
getCacheIncludes(): string[] {
|
|
return core.getMultilineInput('gradle-home-cache-includes')
|
|
}
|
|
|
|
getCacheExcludes(): string[] {
|
|
return core.getMultilineInput('gradle-home-cache-excludes')
|
|
}
|
|
|
|
getCacheProvider(): CacheProvider {
|
|
const val = core.getInput('cache-provider')
|
|
switch (val.toLowerCase().trim()) {
|
|
case 'basic':
|
|
return CacheProvider.Basic
|
|
case 'enhanced':
|
|
case '':
|
|
return CacheProvider.Enhanced
|
|
}
|
|
throw TypeError(`The value '${val}' is not valid for 'cache-provider'. Valid values are: [basic, enhanced].`)
|
|
}
|
|
}
|
|
|
|
export enum CacheProvider {
|
|
Basic = 'basic',
|
|
Enhanced = 'enhanced'
|
|
}
|
|
|
|
export enum CacheCleanupOption {
|
|
Never = 'never',
|
|
OnSuccess = 'on-success',
|
|
Always = 'always'
|
|
}
|
|
|
|
export class SummaryConfig {
|
|
shouldGenerateJobSummary(hasFailure: boolean): boolean {
|
|
// Check if Job Summary is supported on this platform
|
|
if (!process.env[SUMMARY_ENV_VAR]) {
|
|
return false
|
|
}
|
|
|
|
return this.shouldAddJobSummary(this.getJobSummaryOption(), hasFailure)
|
|
}
|
|
|
|
canAddPRComment(): boolean {
|
|
return this.getPRCommentOption() !== JobSummaryOption.Never
|
|
}
|
|
|
|
shouldAddPRComment(hasFailure: boolean): boolean {
|
|
return this.shouldAddJobSummary(this.getPRCommentOption(), hasFailure)
|
|
}
|
|
|
|
private shouldAddJobSummary(option: JobSummaryOption, hasFailure: boolean): boolean {
|
|
switch (option) {
|
|
case JobSummaryOption.Always:
|
|
return true
|
|
case JobSummaryOption.Never:
|
|
return false
|
|
case JobSummaryOption.OnFailure:
|
|
return hasFailure
|
|
}
|
|
}
|
|
|
|
private getJobSummaryOption(): JobSummaryOption {
|
|
return this.parseJobSummaryOption('add-job-summary')
|
|
}
|
|
|
|
private getPRCommentOption(): JobSummaryOption {
|
|
return this.parseJobSummaryOption('add-job-summary-as-pr-comment')
|
|
}
|
|
|
|
private parseJobSummaryOption(paramName: string): JobSummaryOption {
|
|
const val = core.getInput(paramName)
|
|
switch (val.toLowerCase().trim()) {
|
|
case 'never':
|
|
return JobSummaryOption.Never
|
|
case 'always':
|
|
return JobSummaryOption.Always
|
|
case 'on-failure':
|
|
return JobSummaryOption.OnFailure
|
|
}
|
|
throw TypeError(
|
|
`The value '${val}' is not valid for ${paramName}. Valid values are: [never, always, on-failure].`
|
|
)
|
|
}
|
|
}
|
|
|
|
export enum JobSummaryOption {
|
|
Never = 'never',
|
|
Always = 'always',
|
|
OnFailure = 'on-failure'
|
|
}
|
|
|
|
export class DevelocityConfig {
|
|
static DevelocityAccessKeyEnvVar = 'DEVELOCITY_ACCESS_KEY'
|
|
static GradleEnterpriseAccessKeyEnvVar = 'GRADLE_ENTERPRISE_ACCESS_KEY'
|
|
|
|
getBuildScanPublishEnabled(): boolean {
|
|
return getBooleanInput('build-scan-publish') && this.verifyTermsOfUseAgreement()
|
|
}
|
|
|
|
getTermsOfUseUrl(): string {
|
|
return core.getInput('build-scan-terms-of-use-url')
|
|
}
|
|
|
|
getTermsOfUseAgree(): string {
|
|
return core.getInput('build-scan-terms-of-use-agree')
|
|
}
|
|
|
|
getDevelocityAccessKey(): string {
|
|
return (
|
|
core.getInput('develocity-access-key') ||
|
|
process.env[DevelocityConfig.DevelocityAccessKeyEnvVar] ||
|
|
process.env[DevelocityConfig.GradleEnterpriseAccessKeyEnvVar] ||
|
|
''
|
|
)
|
|
}
|
|
|
|
getDevelocityTokenExpiry(): string {
|
|
return core.getInput('develocity-token-expiry')
|
|
}
|
|
|
|
getDevelocityInjectionEnabled(): boolean | undefined {
|
|
return getOptionalBooleanInput('develocity-injection-enabled')
|
|
}
|
|
|
|
getDevelocityUrl(): string {
|
|
return core.getInput('develocity-url')
|
|
}
|
|
|
|
getDevelocityAllowUntrustedServer(): boolean | undefined {
|
|
return getOptionalBooleanInput('develocity-allow-untrusted-server')
|
|
}
|
|
|
|
getDevelocityCaptureFileFingerprints(): boolean | undefined {
|
|
return getOptionalBooleanInput('develocity-capture-file-fingerprints')
|
|
}
|
|
|
|
getDevelocityEnforceUrl(): boolean | undefined {
|
|
return getOptionalBooleanInput('develocity-enforce-url')
|
|
}
|
|
|
|
getDevelocityPluginVersion(): string {
|
|
return core.getInput('develocity-plugin-version')
|
|
}
|
|
|
|
getDevelocityCcudPluginVersion(): string {
|
|
return core.getInput('develocity-ccud-plugin-version')
|
|
}
|
|
|
|
getPluginRepository(): PluginRepositoryConfig {
|
|
return new PluginRepositoryConfig()
|
|
}
|
|
|
|
hasTermsOfUseAgreement(): boolean {
|
|
const develocityAccessKeySet = this.getDevelocityAccessKey() !== ''
|
|
const termsUrlSet =
|
|
this.getTermsOfUseUrl() === 'https://gradle.com/terms-of-service' ||
|
|
this.getTermsOfUseUrl() === 'https://gradle.com/help/legal-terms-of-use'
|
|
const termsAgreed = this.getTermsOfUseAgree() === 'yes'
|
|
return develocityAccessKeySet || (termsUrlSet && termsAgreed)
|
|
}
|
|
|
|
private verifyTermsOfUseAgreement(): boolean {
|
|
if (!this.hasTermsOfUseAgreement()) {
|
|
core.warning(
|
|
`Terms of use at 'https://gradle.com/help/legal-terms-of-use' must be agreed in order to publish build scans.`
|
|
)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
export class PluginRepositoryConfig {
|
|
getUrl(): string | undefined {
|
|
return getOptionalInput('gradle-plugin-repository-url')
|
|
}
|
|
|
|
getUsername(): string | undefined {
|
|
return getOptionalInput('gradle-plugin-repository-username')
|
|
}
|
|
|
|
getPassword(): string | undefined {
|
|
return getOptionalInput('gradle-plugin-repository-password')
|
|
}
|
|
}
|
|
|
|
export class GradleExecutionConfig {
|
|
getGradleVersion(): string {
|
|
return core.getInput('gradle-version')
|
|
}
|
|
|
|
getBuildRootDirectory(): string {
|
|
const baseDirectory = getWorkspaceDirectory()
|
|
const buildRootDirectoryInput = core.getInput('build-root-directory')
|
|
const resolvedBuildRootDirectory =
|
|
buildRootDirectoryInput === ''
|
|
? path.resolve(baseDirectory)
|
|
: path.resolve(baseDirectory, buildRootDirectoryInput)
|
|
return resolvedBuildRootDirectory
|
|
}
|
|
|
|
getDependencyResolutionTask(): string {
|
|
return core.getInput('dependency-resolution-task') || ':ForceDependencyResolutionPlugin_resolveAllDependencies'
|
|
}
|
|
|
|
getAdditionalArguments(): string {
|
|
return core.getInput('additional-arguments')
|
|
}
|
|
|
|
verifyNoArguments(): void {
|
|
const input = core.getInput('arguments')
|
|
if (input.length !== 0) {
|
|
deprecator.failOnUseOfRemovedFeature(
|
|
`The 'arguments' parameter is no longer supported for ${getActionId()}`,
|
|
'Using the action to execute Gradle via the `arguments` parameter is deprecated'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
export class WrapperValidationConfig {
|
|
doValidateWrappers(): boolean {
|
|
return getBooleanInput('validate-wrappers')
|
|
}
|
|
|
|
allowSnapshotWrappers(): boolean {
|
|
return getBooleanInput('allow-snapshot-wrappers')
|
|
}
|
|
}
|
|
|
|
// Internal parameters
|
|
export function getJobMatrix(): string {
|
|
return core.getInput('workflow-job-context')
|
|
}
|
|
|
|
export function getGithubToken(): string {
|
|
return core.getInput('github-token', {required: true})
|
|
}
|
|
|
|
export function getWorkspaceDirectory(): string {
|
|
return process.env[`GITHUB_WORKSPACE`] || ''
|
|
}
|
|
|
|
export function getActionId(): string | undefined {
|
|
return process.env[ACTION_ID_VAR]
|
|
}
|
|
|
|
export function setActionId(id: string): void {
|
|
core.exportVariable(ACTION_ID_VAR, id)
|
|
}
|
|
|
|
export function parseNumericInput(paramName: string, paramValue: string, paramDefault: number): number {
|
|
if (paramValue.length === 0) {
|
|
return paramDefault
|
|
}
|
|
const numericValue = parseInt(paramValue)
|
|
if (isNaN(numericValue)) {
|
|
throw TypeError(`The value '${paramValue}' is not a valid numeric value for '${paramName}'.`)
|
|
}
|
|
return numericValue
|
|
}
|
|
|
|
function getOptionalInput(paramName: string): string | undefined {
|
|
const paramValue = core.getInput(paramName)
|
|
if (paramValue.length > 0) {
|
|
return paramValue
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function getBooleanInput(paramName: string, paramDefault = false): boolean {
|
|
const paramValue = core.getInput(paramName)
|
|
switch (paramValue.toLowerCase().trim()) {
|
|
case '':
|
|
return paramDefault
|
|
case 'false':
|
|
return false
|
|
case 'true':
|
|
return true
|
|
}
|
|
throw TypeError(`The value '${paramValue} is not valid for '${paramName}. Valid values are: [true, false]`)
|
|
}
|
|
|
|
function getOptionalBooleanInput(paramName: string): boolean | undefined {
|
|
const paramValue = core.getInput(paramName)
|
|
if (paramValue === '') {
|
|
return undefined
|
|
}
|
|
return getBooleanInput(paramName)
|
|
}
|