diff --git a/.github/workflows/integ-test-dependency-graph-failures.yml b/.github/workflows/integ-test-dependency-graph-failures.yml index e09e87b..00d2629 100644 --- a/.github/workflows/integ-test-dependency-graph-failures.yml +++ b/.github/workflows/integ-test-dependency-graph-failures.yml @@ -20,8 +20,6 @@ env: jobs: unsupported-gradle-version-failure: runs-on: ubuntu-latest - permissions: - contents: read steps: - name: Checkout sources uses: actions/checkout@v4 @@ -32,6 +30,7 @@ jobs: with: gradle-version: 7.0.1 dependency-graph: generate + dependency-graph-continue-on-failure: false - name: Run with unsupported Gradle version working-directory: .github/workflow-samples/groovy-dsl run: | @@ -42,8 +41,6 @@ jobs: unsupported-gradle-version-warning: runs-on: ubuntu-latest - permissions: - contents: read steps: - name: Checkout sources uses: actions/checkout@v4 @@ -54,11 +51,7 @@ jobs: with: gradle-version: 7.0.1 dependency-graph: generate - - name: Run with unsupported Gradle version - working-directory: .github/workflow-samples/groovy-dsl - run: | - gradle help - + dependency-graph-continue-on-failure: true - name: Run with unsupported Gradle version working-directory: .github/workflow-samples/groovy-dsl run: | diff --git a/action.yml b/action.yml index a76489b..0738cde 100644 --- a/action.yml +++ b/action.yml @@ -73,6 +73,11 @@ inputs: required: false default: 'disabled' + dependency-graph-continue-on-failure: + description: When 'false' a failure to generate or submit a dependency graph will fail the Step or Job. When 'true' a warning will be emitted but no failure will result. + required: false + default: true + artifact-retention-days: description: Specifies the number of days to retain any artifacts generated by the action. If not set, the default retention settings for the repository will apply. required: false diff --git a/src/dependency-graph.ts b/src/dependency-graph.ts index 14b5d66..9b2f6b2 100644 --- a/src/dependency-graph.ts +++ b/src/dependency-graph.ts @@ -10,7 +10,13 @@ import * as path from 'path' import fs from 'fs' import * as layout from './repository-layout' -import {DependencyGraphOption, getJobMatrix, getArtifactRetentionDays} from './input-params' +import {PostActionJobFailure} from './errors' +import { + DependencyGraphOption, + getDependencyGraphContinueOnFailure, + getJobMatrix, + getArtifactRetentionDays +} from './input-params' const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_' @@ -26,6 +32,7 @@ export async function setup(option: DependencyGraphOption): Promise { core.info('Enabling dependency graph generation') core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true') + core.exportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', getDependencyGraphContinueOnFailure()) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator()) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref) @@ -51,7 +58,7 @@ export async function complete(option: DependencyGraphOption): Promise { await uploadDependencyGraphs(await findGeneratedDependencyGraphFiles()) } } catch (e) { - core.warning(`Failed to ${option} dependency graph. Will continue. ${String(e)}`) + warnOrFail(option, e) } } @@ -78,7 +85,7 @@ async function downloadAndSubmitDependencyGraphs(): Promise { try { await submitDependencyGraphs(await downloadDependencyGraphs()) } catch (e) { - core.warning(`Download and submit dependency graph failed. Will continue. ${String(e)}`) + warnOrFail(DependencyGraphOption.DownloadAndSubmit, e) } } @@ -88,7 +95,7 @@ async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise { return graphFiles } +function warnOrFail(option: String, error: unknown): void { + if (!getDependencyGraphContinueOnFailure()) { + throw new PostActionJobFailure(error) + } + + core.warning(`Failed to ${option} dependency graph. Will continue.\n${String(error)}`) +} + function getOctokit(): InstanceType { return github.getOctokit(getGithubToken()) } diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..5236ec4 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,11 @@ +export class PostActionJobFailure extends Error { + constructor(error: unknown) { + if (error instanceof Error) { + super(error.message) + this.name = error.name + this.stack = error.stack + } else { + super(String(error)) + } + } +} diff --git a/src/input-params.ts b/src/input-params.ts index 62be313..9f498cc 100644 --- a/src/input-params.ts +++ b/src/input-params.ts @@ -107,6 +107,10 @@ export function getDependencyGraphOption(): DependencyGraphOption { ) } +export function getDependencyGraphContinueOnFailure(): boolean { + return getBooleanInput('dependency-graph-continue-on-failure', true) +} + export function getArtifactRetentionDays(): number { const val = core.getInput('artifact-retention-days') return parseNumericInput('artifact-retention-days', val, 0) diff --git a/src/post.ts b/src/post.ts index d81a8ca..f410889 100644 --- a/src/post.ts +++ b/src/post.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core' import * as setupGradle from './setup-gradle' +import {PostActionJobFailure} 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 @@ -13,6 +14,11 @@ export async function run(): Promise { try { await setupGradle.complete() } catch (error) { + if (error instanceof PostActionJobFailure) { + core.setFailed(String(error)) + return + } + handleFailure(error) } } diff --git a/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle b/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle index 9131788..f0472ce 100644 --- a/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle +++ b/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle @@ -9,6 +9,9 @@ if (getVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED') != "true") { def gradleVersion = GradleVersion.current().baseVersion if (gradleVersion < GradleVersion.version("5.2") || (gradleVersion >= GradleVersion.version("7.0") && gradleVersion < GradleVersion.version("7.1"))) { + if (getVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE') != "true") { + throw new GradleException("Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated.") + } println "::warning::Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated." return } diff --git a/test/init-scripts/src/test/groovy/com/gradle/gradlebuildaction/TestDependencyGraph.groovy b/test/init-scripts/src/test/groovy/com/gradle/gradlebuildaction/TestDependencyGraph.groovy index 7ad51b0..c413445 100644 --- a/test/init-scripts/src/test/groovy/com/gradle/gradlebuildaction/TestDependencyGraph.groovy +++ b/test/init-scripts/src/test/groovy/com/gradle/gradlebuildaction/TestDependencyGraph.groovy @@ -61,7 +61,23 @@ class TestDependencyGraph extends BaseInitScriptTest { then: assert !reportsDir.exists() - assert result.output.contains("::warning::Dependency Graph is not supported") + assert result.output.contains("::warning::Dependency Graph is not supported for ${testGradleVersion}") + + where: + testGradleVersion << NO_DEPENDENCY_GRAPH_VERSIONS + } + + def "fails build when enabled for older Gradle versions if continue-on-failure is false"() { + assumeTrue testGradleVersion.compatibleWithCurrentJvm + + when: + def vars = envVars + vars.put('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', 'false') + def result = runAndFail(['help'], initScript, testGradleVersion.gradleVersion, [], vars) + + then: + assert !reportsDir.exists() + assert result.output.contains("Dependency Graph is not supported for ${testGradleVersion}") where: testGradleVersion << NO_DEPENDENCY_GRAPH_VERSIONS @@ -114,6 +130,7 @@ class TestDependencyGraph extends BaseInitScriptTest { def getEnvVars() { return [ GITHUB_DEPENDENCY_GRAPH_ENABLED: "true", + GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE: "true", GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR: "CORRELATOR", GITHUB_DEPENDENCY_GRAPH_JOB_ID: "1", GITHUB_DEPENDENCY_GRAPH_REF: "main",