From ebf4d13461dc5b6a803df00685e29da33cf01e48 Mon Sep 17 00:00:00 2001 From: daz Date: Sat, 6 Apr 2024 16:35:03 -0600 Subject: [PATCH] Convert `dependency-submission` action to Typescript Instead of being a thin wrapper over `setup-gradle`, the `dependency-submission` action is now a fully-fledged action sharing implementation with `setup-gradle`. --- .github/workflows/ci-full-check.yml | 7 + .github/workflows/ci-quick-check.yml | 9 ++ .../integ-test-dependency-submission.yml | 148 ++++++++++++++++++ dependency-submission/action.yml | 61 +++----- sources/package.json | 2 + sources/src/dependency-graph.ts | 14 ++ sources/src/dependency-submission/main.ts | 58 +++++++ sources/src/dependency-submission/post.ts | 35 +++++ 8 files changed, 299 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/integ-test-dependency-submission.yml create mode 100644 sources/src/dependency-submission/main.ts create mode 100644 sources/src/dependency-submission/post.ts diff --git a/.github/workflows/ci-full-check.yml b/.github/workflows/ci-full-check.yml index 0e6554e..7fedc09 100644 --- a/.github/workflows/ci-full-check.yml +++ b/.github/workflows/ci-full-check.yml @@ -43,6 +43,13 @@ jobs: with: cache-key-prefix: ${{github.run_number}}- + dependency-submission: + uses: ./.github/workflows/integ-test-dependency-submission.yml + permissions: + contents: write + with: + cache-key-prefix: ${{github.run_number}}- + execution-with-caching: uses: ./.github/workflows/integ-test-execution-with-caching.yml with: diff --git a/.github/workflows/ci-quick-check.yml b/.github/workflows/ci-quick-check.yml index 22d5826..de7607e 100644 --- a/.github/workflows/ci-quick-check.yml +++ b/.github/workflows/ci-quick-check.yml @@ -69,6 +69,15 @@ jobs: runner-os: '["ubuntu-latest"]' download-dist: true + dependency-submission: + needs: build-distribution + uses: ./.github/workflows/integ-test-dependency-submission.yml + permissions: + contents: write + with: + runner-os: '["ubuntu-latest"]' + download-dist: true + execution-with-caching: needs: build-distribution uses: ./.github/workflows/integ-test-execution-with-caching.yml diff --git a/.github/workflows/integ-test-dependency-submission.yml b/.github/workflows/integ-test-dependency-submission.yml new file mode 100644 index 0000000..261c176 --- /dev/null +++ b/.github/workflows/integ-test-dependency-submission.yml @@ -0,0 +1,148 @@ +name: Test dependency graph + +on: + workflow_call: + inputs: + cache-key-prefix: + type: string + runner-os: + type: string + default: '["ubuntu-latest", "windows-latest", "macos-latest"]' + download-dist: + type: boolean + default: false + +permissions: + contents: write + +env: + DOWNLOAD_DIST: ${{ inputs.download-dist }} + GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }} + +jobs: + groovy-generate-and-upload: + strategy: + 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: Generate dependency graph + uses: ./dependency-submission + with: + dependency-graph: generate-and-upload + build-root-directory: .github/workflow-samples/groovy-dsl + + groovy-download-and-submit: + needs: [groovy-generate-and-upload] + runs-on: "ubuntu-latest" + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Initialize integ-test + uses: ./.github/actions/init-integ-test + + - name: Submit dependency graph + uses: ./dependency-submission + with: + dependency-graph: download-and-submit + + kotlin-generate-and-submit: + strategy: + 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: Generate and submit dependency graph + uses: ./dependency-submission + with: + build-root-directory: .github/workflow-samples/kotlin-dsl + + # TODO - Test this scenario (and make it work) + # multiple-builds: + # strategy: + # 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: Setup Gradle for dependency-graph generate + # uses: ./setup-gradle + # with: + # dependency-graph: generate-and-submit + # - id: gradle-assemble + # run: ./gradlew assemble + # working-directory: .github/workflow-samples/groovy-dsl + # - id: gradle-build + # run: ./gradlew build + # working-directory: .github/workflow-samples/groovy-dsl + # - id: gradle-build-again + # run: ./gradlew build + # working-directory: .github/workflow-samples/groovy-dsl + # - name: Check generated dependency graphs + # shell: bash + # run: | + # echo "gradle-assemble report file: ${{ steps.gradle-assemble.outputs.dependency-graph-file }}" + # echo "gradle-build report file: ${{ steps.gradle-build.outputs.dependency-graph-file }}" + # echo "gradle-build-again report file: ${{ steps.gradle-build-again.outputs.dependency-graph-file }}" + # ls -l dependency-graph-reports + # if [ ! -e "${{ steps.gradle-assemble.outputs.dependency-graph-file }}" ]; then + # echo "Did not find gradle-assemble dependency graph file" + # exit 1 + # fi + # if [ ! -e "${{ steps.gradle-build.outputs.dependency-graph-file }}" ]; then + # echo "Did not find gradle-build dependency graph files" + # exit 1 + # fi + # if [ ! -e "${{ steps.gradle-build-again.outputs.dependency-graph-file }}" ]; then + # echo "Did not find gradle-build-again dependency graph files" + # exit 1 + # fi + + config-cache: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Initialize integ-test + uses: ./.github/actions/init-integ-test + + - id: config-cache-store + uses: ./dependency-submission + with: + build-root-directory: .github/workflow-samples/groovy-dsl + additional-arguments: --configuration-cache + - name: Check and delete generated dependency graph + shell: bash + run: | + if [ ! -e "${{ steps.config-cache-store.outputs.dependency-graph-file }}" ]; then + echo "Did not find config-cache-store dependency graph files" + exit 1 + fi + rm ${{ steps.config-cache-store.outputs.dependency-graph-file }} + - id: config-cache-reuse + uses: ./dependency-submission + with: + build-root-directory: .github/workflow-samples/groovy-dsl + additional-arguments: --configuration-cache + - name: Check no dependency graph is generated + shell: bash + run: | + if [ ! -z "$(ls -A dependency-graph-reports)" ]; then + echo "Expected no dependency graph files to be generated" + ls -l dependency-graph-reports + exit 1 + fi diff --git a/dependency-submission/action.yml b/dependency-submission/action.yml index 8ba77c1..2164b0b 100644 --- a/dependency-submission/action.yml +++ b/dependency-submission/action.yml @@ -47,6 +47,29 @@ inputs: description: Indicate that you agree to the Build ScanĀ® terms of use. This input value must be "yes". required: false + # HARD-CODED DEFAULTS - These should be removed from here and set directly in code + dependency-graph-continue-on-failure: + required: false + default: false + artifact-retention-days: + required: false + default: 1 + add-job-summary: + required: false + default: 'always' + add-job-summary-as-pr-comment: + required: false + default: 'never' + workflow-job-context: + required: false + default: ${{ toJSON(matrix) }} + github-token: + default: ${{ github.token }} + required: false + cache-disabled: + required: false + default: true + # 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'. @@ -59,38 +82,6 @@ inputs: deprecation-message: The input has been renamed to align with the Develocity API. Use 'build-scan-terms-of-use-agree' instead. runs: - using: "composite" - steps: - - name: Check no setup-gradle - shell: bash - run: | - if [ -n "${GRADLE_BUILD_ACTION_SETUP_COMPLETED}" ]; then - echo "The dependency-submission action cannot be used in the same Job as the setup-gradle action. Please use a separate Job for dependency submission." - exit 1 - fi - - name: Generate dependency graph - if: ${{ inputs.dependency-graph == 'generate-and-submit' || inputs.dependency-graph == 'generate-and-upload' }} - uses: gradle/actions/setup-gradle@v3.2.0 - with: - dependency-graph: ${{ inputs.dependency-graph }} - dependency-graph-continue-on-failure: false - gradle-version: ${{ inputs.gradle-version }} - build-root-directory: ${{ inputs.build-root-directory }} - cache-encryption-key: ${{ inputs.cache-encryption-key }} - build-scan-publish: ${{ inputs.build-scan-publish }} - build-scan-terms-of-use-url: ${{ inputs.build-scan-terms-of-use-url || inputs.build-scan-terms-of-service-url }} - build-scan-terms-of-use-agree: ${{ inputs.build-scan-terms-of-use-agree || inputs.build-scan-terms-of-service-agree }} - artifact-retention-days: 1 - arguments: | - -Dorg.gradle.configureondemand=false - -Dorg.gradle.dependency.verification=off - -Dorg.gradle.unsafe.isolated-projects=false - :ForceDependencyResolutionPlugin_resolveAllDependencies - ${{ inputs.additional-arguments }} - - name: Download and submit dependency graph - if: ${{ inputs.dependency-graph == 'download-and-submit' }} - uses: gradle/actions/setup-gradle@v3.2.0 - with: - dependency-graph: download-and-submit - dependency-graph-continue-on-failure: false - cache-disabled: true + using: 'node20' + main: '../dist/dependency-submission/main/index.js' + post: '../dist/dependency-submission/post/index.js' diff --git a/sources/package.json b/sources/package.json index 9aa0222..e366242 100644 --- a/sources/package.json +++ b/sources/package.json @@ -8,6 +8,8 @@ "format": "prettier --write 'src/**/*.ts'", "format-check": "prettier --check 'src/**/*.ts'", "lint": "eslint 'src/**/*.ts'", + "compile-dependency-submission-main": "ncc build src/dependency-submission/main.ts --out ../dist/dependency-submission/main --source-map --no-source-map-register", + "compile-dependency-submission-post": "ncc build src/dependency-submission/post.ts --out ../dist/dependency-submission/post --source-map --no-source-map-register", "compile-setup-gradle-main": "ncc build src/setup-gradle/main.ts --out ../dist/setup-gradle/main --source-map --no-source-map-register", "compile-setup-gradle-post": "ncc build src/setup-gradle/post.ts --out ../dist/setup-gradle/post --source-map --no-source-map-register", "compile": "npm-run-all --parallel compile-*", diff --git a/sources/src/dependency-graph.ts b/sources/src/dependency-graph.ts index 8311321..44277ae 100644 --- a/sources/src/dependency-graph.ts +++ b/sources/src/dependency-graph.ts @@ -58,6 +58,11 @@ function maybeExportVariable(variableName: string, value: unknown): void { } export async function complete(option: DependencyGraphOption): Promise { + if (isRunningInActEnvironment()) { + core.info('Dependency graph upload and submit not supported in the ACT environment.') + return + } + try { switch (option) { case DependencyGraphOption.Disabled: @@ -96,6 +101,11 @@ async function uploadDependencyGraphs(dependencyGraphFiles: string[]): Promise { + if (isRunningInActEnvironment()) { + core.info('Dependency graph download and submit not supported in the ACT environment.') + return + } + try { await submitDependencyGraphs(await downloadDependencyGraphs()) } catch (e) { @@ -242,3 +252,7 @@ function sanitize(value: string): string { .replace(/\s+/g, '_') .toLowerCase() } + +function isRunningInActEnvironment(): boolean { + return process.env.ACT !== undefined +} diff --git a/sources/src/dependency-submission/main.ts b/sources/src/dependency-submission/main.ts new file mode 100644 index 0000000..fae38bf --- /dev/null +++ b/sources/src/dependency-submission/main.ts @@ -0,0 +1,58 @@ +import * as core from '@actions/core' + +import * as setupGradle from '../setup-gradle' +import * as execution from '../execution' +import * as provisioner from '../provision' +import * as layout from '../repository-layout' +import {parseArgsStringToArgv} from 'string-argv' +import {DependencyGraphOption, getDependencyGraphOption} from '../input-params' + +/** + * The main entry point for the action, called by Github Actions for the step. + */ +export async function run(): Promise { + try { + if (process.env['GRADLE_BUILD_ACTION_SETUP_COMPLETED']) { + core.setFailed( + 'The dependency-submission action cannot be used in the same Job as the setup-gradle action. Please use a separate Job for dependency submission.' + ) + return + } + + // Configure Gradle environment (Gradle User Home) + await setupGradle.setup() + + if (getDependencyGraphOption() === DependencyGraphOption.DownloadAndSubmit) { + // No execution to perform + return + } + + // Download and install Gradle if required + const executable = await provisioner.provisionGradle() + + // Only execute if arguments have been provided + const additionalArgs = core.getInput('additional-arguments') + const executionArgs = ` + -Dorg.gradle.configureondemand=false + -Dorg.gradle.dependency.verification=off + -Dorg.gradle.unsafe.isolated-projects=false + :ForceDependencyResolutionPlugin_resolveAllDependencies + ${additionalArgs} + ` + + const args: string[] = parseArgsStringToArgv(executionArgs) + core.info(args.join('!!!')) + const buildRootDirectory = layout.buildRootDirectory() + await execution.executeGradleBuild(executable, buildRootDirectory, args) + } catch (error) { + core.setFailed(String(error)) + if (error instanceof Error && error.stack) { + core.info(error.stack) + } + } + + // Explicit process.exit() to prevent waiting for hanging promises. + process.exit() +} + +run() diff --git a/sources/src/dependency-submission/post.ts b/sources/src/dependency-submission/post.ts new file mode 100644 index 0000000..b57c600 --- /dev/null +++ b/sources/src/dependency-submission/post.ts @@ -0,0 +1,35 @@ +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 +// throw an uncaught exception. Instead of failing this action, just warn. +process.on('uncaughtException', e => handleFailure(e)) + +/** + * The post-execution entry point for the action, called by Github Actions after completing all steps for the Job. + */ +export async function run(): Promise { + try { + await setupGradle.complete() + } catch (error) { + if (error instanceof PostActionJobFailure) { + core.setFailed(String(error)) + } else { + handleFailure(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()