Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow wrapper validation via the 'setup-gradle' action #162

Merged
merged 5 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/init-integ-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci-integ-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]'
36 changes: 31 additions & 5 deletions .github/workflows/integ-test-wrapper-validation.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
23 changes: 18 additions & 5 deletions build
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 15 additions & 0 deletions docs/setup-gradle.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions setup-gradle/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down
4 changes: 4 additions & 0 deletions sources/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions sources/src/dependency-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_'
Expand Down Expand Up @@ -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)}`)
Expand Down
8 changes: 2 additions & 6 deletions sources/src/dependency-submission/main.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 3 additions & 15 deletions sources/src/dependency-submission/post.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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()
34 changes: 33 additions & 1 deletion sources/src/errors.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
}
}
}
15 changes: 15 additions & 0 deletions sources/src/setup-gradle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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()}`
)
}
}
14 changes: 8 additions & 6 deletions sources/src/setup-gradle/main.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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.
Expand All @@ -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())

Expand All @@ -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.
Expand Down
18 changes: 3 additions & 15 deletions sources/src/setup-gradle/post.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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()