mirror of
https://github.com/gradle/actions
synced 2025-01-22 07:12:40 +00:00
Allow wrapper validation via the 'setup-gradle' action (#162)
Adds a 'validate-wrappers' option to `gradle/actions/setup-gradle`, which defaults to 'false'. When 'true', the action will first validate all Gradle wrappers in the repository before proceeding. Fixes #161
This commit is contained in:
commit
38e549269f
15 changed files with 148 additions and 66 deletions
1
.github/actions/init-integ-test/action.yml
vendored
1
.github/actions/init-integ-test/action.yml
vendored
|
@ -11,6 +11,7 @@ runs:
|
|||
|
||||
# Downloads a 'dist' directory artifact that was uploaded in an earlier 'build-dist' step
|
||||
- name: Download dist
|
||||
if: ${{ !env.ACT }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
|
|
2
.github/workflows/ci-integ-test.yml
vendored
2
.github/workflows/ci-integ-test.yml
vendored
|
@ -183,3 +183,5 @@ jobs:
|
|||
wrapper-validation:
|
||||
needs: [determine-suite, build-distribution]
|
||||
uses: ./.github/workflows/integ-test-wrapper-validation.yml
|
||||
with:
|
||||
runner-os: '["ubuntu-latest"]'
|
||||
|
|
|
@ -1,12 +1,40 @@
|
|||
name: Test sample Kotlin DSL project
|
||||
name: Test wrapper validation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runner-os:
|
||||
type: string
|
||||
default: '["ubuntu-latest", "windows-latest", "macos-latest"]'
|
||||
|
||||
jobs:
|
||||
# Integration test for successful validation of wrappers
|
||||
test-setup-gradle-validation:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ${{fromJSON(inputs.runner-os)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Initialize integ-test
|
||||
uses: ./.github/actions/init-integ-test
|
||||
|
||||
- name: Run wrapper-validation-action
|
||||
id: setup-gradle
|
||||
uses: ./setup-gradle
|
||||
with:
|
||||
validate-wrappers: true
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check failure
|
||||
run: |
|
||||
if [ "${{ steps.setup-gradle.outcome}}" != "failure" ] ; then
|
||||
echo "Expected validation to fail, but it didn't"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-validation-success:
|
||||
name: 'Test: Validation success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
|
@ -33,9 +61,7 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Integration test for failing validation of wrappers
|
||||
test-validation-error:
|
||||
name: 'Test: Validation error'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
|
|
23
build
23
build
|
@ -3,8 +3,21 @@
|
|||
cd sources
|
||||
npm install
|
||||
|
||||
if [ "$1" == "all" ]; then
|
||||
npm run all
|
||||
else
|
||||
npm run build
|
||||
fi
|
||||
case "$1" in
|
||||
all)
|
||||
nprm run all
|
||||
;;
|
||||
act)
|
||||
# Build and copy outputs to the dist directory
|
||||
npm run build
|
||||
cd ..
|
||||
cp -r sources/dist .
|
||||
# Run act
|
||||
$@
|
||||
# Revert the changes to the dist directory
|
||||
git co -- dist
|
||||
;;
|
||||
*)
|
||||
npm run build
|
||||
;;
|
||||
esac
|
|
@ -503,6 +503,21 @@ located at `USER_HOME/.gradle/init.d/gradle-actions.build-result-capture.init.gr
|
|||
If you are adding any custom init scripts to the `USER_HOME/.gradle/init.d` directory, it may be necessary to ensure these files are applied before `gradle-actions.build-result-capture.init.gradle`.
|
||||
Since Gradle applies init scripts in alphabetical order, one way to ensure this is via file naming.
|
||||
|
||||
## Gradle Wrapper validation
|
||||
|
||||
Instead of using the [wrapper-validation action](./wrapper-validation.md) separately, you can enable
|
||||
wrapper validation directly in your Setup Gradle step.
|
||||
|
||||
```yaml
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
validate-wrappers: true
|
||||
```
|
||||
|
||||
If you need more advanced configuration, then you're advised to continue using a separate workflow step
|
||||
with `gradle/actions/wrapper-validation`.
|
||||
|
||||
## Support for GitHub Enterprise Server (GHES)
|
||||
|
||||
You can use the `setup-gradle` action on GitHub Enterprise Server, and benefit from the improved integration with Gradle. Depending on the version of GHES you are running, certain features may be limited:
|
||||
|
|
|
@ -100,6 +100,14 @@ inputs:
|
|||
description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes".
|
||||
required: false
|
||||
|
||||
# Wrapper validation configuration
|
||||
validate-wrappers:
|
||||
description: |
|
||||
When 'true', the action will perform the 'wrapper-validation' action automatically.
|
||||
If the wrapper checksums are not valid, the action will fail.
|
||||
required: false
|
||||
default: false
|
||||
|
||||
# DEPRECATED ACTION INPUTS
|
||||
build-scan-terms-of-service-url:
|
||||
description: The URL to the Build Scan® terms of use. This input must be set to 'https://gradle.com/terms-of-service'.
|
||||
|
|
|
@ -261,6 +261,10 @@ export class GradleExecutionConfig {
|
|||
}
|
||||
}
|
||||
|
||||
export function doValidateWrappers(): boolean {
|
||||
return getBooleanInput('validate-wrappers')
|
||||
}
|
||||
|
||||
// Internal parameters
|
||||
export function getJobMatrix(): string {
|
||||
return core.getInput('workflow-job-context')
|
||||
|
|
|
@ -9,7 +9,7 @@ import type {PullRequestEvent} from '@octokit/webhooks-types'
|
|||
import * as path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
import {PostActionJobFailure} from './errors'
|
||||
import {JobFailure} from './errors'
|
||||
import {DependencyGraphConfig, DependencyGraphOption, getGithubToken, getWorkspaceDirectory} from './configuration'
|
||||
|
||||
const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_'
|
||||
|
@ -208,7 +208,7 @@ function markProcessed(dependencyGraphFile: string): void {
|
|||
|
||||
function warnOrFail(config: DependencyGraphConfig, option: String, error: unknown): void {
|
||||
if (!config.getDependencyGraphContinueOnFailure()) {
|
||||
throw new PostActionJobFailure(error)
|
||||
throw new JobFailure(error)
|
||||
}
|
||||
|
||||
core.warning(`Failed to ${option} dependency graph. Will continue.\n${String(error)}`)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import * as core from '@actions/core'
|
||||
|
||||
import * as setupGradle from '../setup-gradle'
|
||||
import * as gradle from '../execution/gradle'
|
||||
import * as dependencyGraph from '../dependency-graph'
|
||||
|
@ -14,6 +12,7 @@ import {
|
|||
setActionId
|
||||
} from '../configuration'
|
||||
import {saveDeprecationState} from '../deprecation-collector'
|
||||
import {handleMainActionError} from '../errors'
|
||||
|
||||
/**
|
||||
* The main entry point for the action, called by Github Actions for the step.
|
||||
|
@ -56,10 +55,7 @@ export async function run(): Promise<void> {
|
|||
|
||||
saveDeprecationState()
|
||||
} catch (error) {
|
||||
core.setFailed(String(error))
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
handleMainActionError(error)
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as setupGradle from '../setup-gradle'
|
||||
|
||||
import {CacheConfig, SummaryConfig} from '../configuration'
|
||||
import {PostActionJobFailure} from '../errors'
|
||||
import {handlePostActionError} from '../errors'
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on('uncaughtException', e => handleFailure(e))
|
||||
process.on('uncaughtException', e => handlePostActionError(e))
|
||||
|
||||
/**
|
||||
* The post-execution entry point for the action, called by Github Actions after completing all steps for the Job.
|
||||
|
@ -16,22 +15,11 @@ export async function run(): Promise<void> {
|
|||
try {
|
||||
await setupGradle.complete(new CacheConfig(), new SummaryConfig())
|
||||
} catch (error) {
|
||||
if (error instanceof PostActionJobFailure) {
|
||||
core.setFailed(String(error))
|
||||
} else {
|
||||
handleFailure(error)
|
||||
}
|
||||
handlePostActionError(error)
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||
process.exit()
|
||||
}
|
||||
|
||||
function handleFailure(error: unknown): void {
|
||||
core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`)
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export class PostActionJobFailure extends Error {
|
||||
import * as core from '@actions/core'
|
||||
|
||||
export class JobFailure extends Error {
|
||||
constructor(error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
super(error.message)
|
||||
|
@ -9,3 +11,33 @@ export class PostActionJobFailure extends Error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMainActionError(error: unknown): void {
|
||||
if (error instanceof AggregateError) {
|
||||
core.setFailed(`Multiple errors returned`)
|
||||
for (const err of error.errors) {
|
||||
core.error(`Error ${error.errors.indexOf(err)}: ${err.message}`)
|
||||
if (err.stack) {
|
||||
core.info(err.stack)
|
||||
}
|
||||
}
|
||||
} else if (error instanceof JobFailure) {
|
||||
core.setFailed(String(error)) // No stack trace for JobFailure: these are known errors
|
||||
} else {
|
||||
core.setFailed(String(error))
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handlePostActionError(error: unknown): void {
|
||||
if (error instanceof JobFailure) {
|
||||
core.setFailed(String(error))
|
||||
} else {
|
||||
core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`)
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import {loadBuildResults, markBuildResultsProcessed} from './build-results'
|
|||
import {CacheListener, generateCachingReport} from './caching/cache-reporting'
|
||||
import {DaemonController} from './daemon-controller'
|
||||
import {BuildScanConfig, CacheConfig, SummaryConfig, getWorkspaceDirectory} from './configuration'
|
||||
import {findInvalidWrapperJars} from './wrapper-validation/validate'
|
||||
import {JobFailure} from './errors'
|
||||
|
||||
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
|
||||
const USER_HOME = 'USER_HOME'
|
||||
|
@ -96,3 +98,16 @@ async function determineUserHome(): Promise<string> {
|
|||
core.debug(`Determined user.home from java -version output: '${userHome}'`)
|
||||
return userHome
|
||||
}
|
||||
|
||||
export async function checkNoInvalidWrapperJars(rootDir = getWorkspaceDirectory()): Promise<void> {
|
||||
const allowedChecksums = process.env['ALLOWED_GRADLE_WRAPPER_CHECKSUMS']?.split(',') || []
|
||||
const result = await findInvalidWrapperJars(rootDir, 1, false, allowedChecksums)
|
||||
if (result.isValid()) {
|
||||
core.info(result.toDisplayString())
|
||||
} else {
|
||||
core.info(result.toDisplayString())
|
||||
throw new JobFailure(
|
||||
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/actions/blob/main/docs/wrapper-validation.md#reporting-failures\n${result.toDisplayString()}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import * as core from '@actions/core'
|
||||
|
||||
import * as setupGradle from '../setup-gradle'
|
||||
import * as gradle from '../execution/gradle'
|
||||
import * as dependencyGraph from '../dependency-graph'
|
||||
|
@ -8,10 +6,12 @@ import {
|
|||
CacheConfig,
|
||||
DependencyGraphConfig,
|
||||
GradleExecutionConfig,
|
||||
doValidateWrappers,
|
||||
getActionId,
|
||||
setActionId
|
||||
} from '../configuration'
|
||||
import {recordDeprecation, saveDeprecationState} from '../deprecation-collector'
|
||||
import {handleMainActionError} from '../errors'
|
||||
|
||||
/**
|
||||
* The main entry point for the action, called by Github Actions for the step.
|
||||
|
@ -26,6 +26,11 @@ export async function run(): Promise<void> {
|
|||
setActionId('gradle/actions/setup-gradle')
|
||||
}
|
||||
|
||||
// Check for invalid wrapper JARs if requested
|
||||
if (doValidateWrappers()) {
|
||||
await setupGradle.checkNoInvalidWrapperJars()
|
||||
}
|
||||
|
||||
// Configure Gradle environment (Gradle User Home)
|
||||
await setupGradle.setup(new CacheConfig(), new BuildScanConfig())
|
||||
|
||||
|
@ -41,10 +46,7 @@ export async function run(): Promise<void> {
|
|||
|
||||
saveDeprecationState()
|
||||
} catch (error) {
|
||||
core.setFailed(String(error))
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
handleMainActionError(error)
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as setupGradle from '../setup-gradle'
|
||||
import * as dependencyGraph from '../dependency-graph'
|
||||
|
||||
import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../configuration'
|
||||
import {PostActionJobFailure} from '../errors'
|
||||
import {handlePostActionError} from '../errors'
|
||||
import {emitDeprecationWarnings, restoreDeprecationState} from '../deprecation-collector'
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on('uncaughtException', e => handleFailure(e))
|
||||
process.on('uncaughtException', e => handlePostActionError(e))
|
||||
|
||||
/**
|
||||
* The post-execution entry point for the action, called by Github Actions after completing all steps for the Job.
|
||||
|
@ -24,22 +23,11 @@ export async function run(): Promise<void> {
|
|||
await dependencyGraph.complete(new DependencyGraphConfig())
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof PostActionJobFailure) {
|
||||
core.setFailed(String(error))
|
||||
} else {
|
||||
handleFailure(error)
|
||||
}
|
||||
handlePostActionError(error)
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||
process.exit()
|
||||
}
|
||||
|
||||
function handleFailure(error: unknown): void {
|
||||
core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`)
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.info(error.stack)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as path from 'path'
|
|||
import * as core from '@actions/core'
|
||||
|
||||
import * as validate from './validate'
|
||||
import {handleMainActionError} from '../errors'
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
try {
|
||||
|
@ -15,23 +16,14 @@ export async function run(): Promise<void> {
|
|||
core.info(result.toDisplayString())
|
||||
} else {
|
||||
core.setFailed(
|
||||
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/wrapper-validation-action#reporting-failures\n${result.toDisplayString()}`
|
||||
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/actions/blob/main/docs/wrapper-validation.md#reporting-failures\n${result.toDisplayString()}`
|
||||
)
|
||||
if (result.invalid.length > 0) {
|
||||
core.setOutput('failed-wrapper', `${result.invalid.map(w => w.path).join('|')}`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AggregateError) {
|
||||
core.setFailed(`Multiple errors returned`)
|
||||
for (const err of error.errors) {
|
||||
core.error(`Error ${error.errors.indexOf(err)}: ${err.message}`)
|
||||
}
|
||||
} else if (error instanceof Error) {
|
||||
core.setFailed(error.message)
|
||||
} else {
|
||||
core.setFailed(`Unknown object was thrown: ${error}`)
|
||||
}
|
||||
handleMainActionError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue