From ba79f71e36cc59e6d6ede599509658b06ada6d44 Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 11 Apr 2024 10:18:07 -0600 Subject: [PATCH 1/5] Consolidate error processing in actions --- sources/src/dependency-submission/main.ts | 8 ++---- sources/src/dependency-submission/post.ts | 18 +++----------- sources/src/errors.ts | 30 +++++++++++++++++++++++ sources/src/setup-gradle/main.ts | 8 ++---- sources/src/setup-gradle/post.ts | 18 +++----------- sources/src/wrapper-validation/main.ts | 12 ++------- 6 files changed, 42 insertions(+), 52 deletions(-) diff --git a/sources/src/dependency-submission/main.ts b/sources/src/dependency-submission/main.ts index c9e99a9..0e560db 100644 --- a/sources/src/dependency-submission/main.ts +++ b/sources/src/dependency-submission/main.ts @@ -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 { 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. diff --git a/sources/src/dependency-submission/post.ts b/sources/src/dependency-submission/post.ts index 50ab70e..743478e 100644 --- a/sources/src/dependency-submission/post.ts +++ b/sources/src/dependency-submission/post.ts @@ -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 { 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() diff --git a/sources/src/errors.ts b/sources/src/errors.ts index 5236ec4..ad2e1e2 100644 --- a/sources/src/errors.ts +++ b/sources/src/errors.ts @@ -1,3 +1,5 @@ +import * as core from '@actions/core' + export class PostActionJobFailure extends Error { constructor(error: unknown) { if (error instanceof Error) { @@ -9,3 +11,31 @@ 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 { + core.setFailed(String(error)) + if (error instanceof Error && error.stack) { + core.info(error.stack) + } + } +} + +export function handlePostActionError(error: unknown): void { + if (error instanceof PostActionJobFailure) { + 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) + } + } +} diff --git a/sources/src/setup-gradle/main.ts b/sources/src/setup-gradle/main.ts index 1cada07..c66c47e 100644 --- a/sources/src/setup-gradle/main.ts +++ b/sources/src/setup-gradle/main.ts @@ -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' @@ -12,6 +10,7 @@ import { 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. @@ -41,10 +40,7 @@ export async function run(): Promise { 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. diff --git a/sources/src/setup-gradle/post.ts b/sources/src/setup-gradle/post.ts index 4f7b9d4..b39f77d 100644 --- a/sources/src/setup-gradle/post.ts +++ b/sources/src/setup-gradle/post.ts @@ -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 { 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() diff --git a/sources/src/wrapper-validation/main.ts b/sources/src/wrapper-validation/main.ts index 9fb3070..6523658 100644 --- a/sources/src/wrapper-validation/main.ts +++ b/sources/src/wrapper-validation/main.ts @@ -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 { try { @@ -22,16 +23,7 @@ export async function run(): Promise { } } } 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) } } From ea328a863dc3cabab8c732eca8a9418854098b54 Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 11 Apr 2024 10:23:52 -0600 Subject: [PATCH 2/5] Update documentation link --- sources/src/wrapper-validation/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/src/wrapper-validation/main.ts b/sources/src/wrapper-validation/main.ts index 6523658..34b1387 100644 --- a/sources/src/wrapper-validation/main.ts +++ b/sources/src/wrapper-validation/main.ts @@ -16,7 +16,7 @@ export async function run(): Promise { 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('|')}`) From 33741bd2bbc6ee13da90d769b278e1c47f156aa6 Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 11 Apr 2024 11:56:01 -0600 Subject: [PATCH 3/5] Make it easier to run workflows locally with 'act' --- .github/actions/init-integ-test/action.yml | 1 + build | 23 +++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/actions/init-integ-test/action.yml b/.github/actions/init-integ-test/action.yml index 8124f3e..4ef52d4 100644 --- a/.github/actions/init-integ-test/action.yml +++ b/.github/actions/init-integ-test/action.yml @@ -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 diff --git a/build b/build index 01c22e1..eb576c6 100755 --- a/build +++ b/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 \ No newline at end of file From 62557f3635561c1c87913ef0282e1b50ebead1db Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 11 Apr 2024 11:56:55 -0600 Subject: [PATCH 4/5] Enable wrapper validation with setup-gradle action --- .github/workflows/ci-integ-test.yml | 2 ++ .../integ-test-wrapper-validation.yml | 36 ++++++++++++++++--- setup-gradle/action.yml | 8 +++++ sources/src/configuration.ts | 4 +++ sources/src/dependency-graph.ts | 4 +-- sources/src/errors.ts | 6 ++-- sources/src/setup-gradle.ts | 15 ++++++++ sources/src/setup-gradle/main.ts | 6 ++++ 8 files changed, 72 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-integ-test.yml b/.github/workflows/ci-integ-test.yml index b04db0b..970c7da 100644 --- a/.github/workflows/ci-integ-test.yml +++ b/.github/workflows/ci-integ-test.yml @@ -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"]' diff --git a/.github/workflows/integ-test-wrapper-validation.yml b/.github/workflows/integ-test-wrapper-validation.yml index ce1df32..5dde6db 100644 --- a/.github/workflows/integ-test-wrapper-validation.yml +++ b/.github/workflows/integ-test-wrapper-validation.yml @@ -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 diff --git a/setup-gradle/action.yml b/setup-gradle/action.yml index 6a9ccc4..48ec0f3 100644 --- a/setup-gradle/action.yml +++ b/setup-gradle/action.yml @@ -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'. diff --git a/sources/src/configuration.ts b/sources/src/configuration.ts index 0b23c61..f59cfbf 100644 --- a/sources/src/configuration.ts +++ b/sources/src/configuration.ts @@ -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') diff --git a/sources/src/dependency-graph.ts b/sources/src/dependency-graph.ts index 4116191..a12fb13 100644 --- a/sources/src/dependency-graph.ts +++ b/sources/src/dependency-graph.ts @@ -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)}`) diff --git a/sources/src/errors.ts b/sources/src/errors.ts index ad2e1e2..f3c4cb5 100644 --- a/sources/src/errors.ts +++ b/sources/src/errors.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core' -export class PostActionJobFailure extends Error { +export class JobFailure extends Error { constructor(error: unknown) { if (error instanceof Error) { super(error.message) @@ -21,6 +21,8 @@ export function handleMainActionError(error: unknown): void { 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) { @@ -30,7 +32,7 @@ export function handleMainActionError(error: unknown): void { } export function handlePostActionError(error: unknown): void { - if (error instanceof PostActionJobFailure) { + if (error instanceof JobFailure) { core.setFailed(String(error)) } else { core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`) diff --git a/sources/src/setup-gradle.ts b/sources/src/setup-gradle.ts index d4d3a33..bcf2d39 100644 --- a/sources/src/setup-gradle.ts +++ b/sources/src/setup-gradle.ts @@ -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 { core.debug(`Determined user.home from java -version output: '${userHome}'`) return userHome } + +export async function checkNoInvalidWrapperJars(rootDir = getWorkspaceDirectory()): Promise { + 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()}` + ) + } +} diff --git a/sources/src/setup-gradle/main.ts b/sources/src/setup-gradle/main.ts index c66c47e..f5bc5c4 100644 --- a/sources/src/setup-gradle/main.ts +++ b/sources/src/setup-gradle/main.ts @@ -6,6 +6,7 @@ import { CacheConfig, DependencyGraphConfig, GradleExecutionConfig, + doValidateWrappers, getActionId, setActionId } from '../configuration' @@ -25,6 +26,11 @@ export async function run(): Promise { 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()) From e9d1819b96d30d81695b6e9886dede2526bc6221 Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 11 Apr 2024 12:35:15 -0600 Subject: [PATCH 5/5] Document 'validate-wrappers' input --- docs/setup-gradle.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/setup-gradle.md b/docs/setup-gradle.md index 4b3f622..fa7fcb8 100644 --- a/docs/setup-gradle.md +++ b/docs/setup-gradle.md @@ -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: