Compare commits

...

39 Commits

Author SHA1 Message Date
daz
42452daeb5 Add explicit process.exit() to avoid wait for hanging promises
When using the `@actions/cache` library to save cache entries, it seems that one
or more Promises remain unresolved after the save completes.
With Node20 this causes a delay when exiting the process: the default behaviour
now wait for these Promises to complete. Adding an explicit `Process.exit()`
removes the delay, returning to the Node 16 behaviour.

Fixes #1038
2024-01-16 18:01:46 -07:00
daz
346645706f Don't overwrite dependency-graph env vars
This allows these vars to be explicitly set, which is required for
testing (and could prove useful for debugging).
2024-01-16 09:43:56 -07:00
daz
5516b39940 Fix docs for dependency-graph param 2024-01-13 12:55:55 -07:00
Daz DeBoer
7099569988 Improve dependency graph failure handling (#1036)
One goal for the original dependency-graph support was to minimize it's
impact on existing workflows, by operating transparently and not
impacting the build outcome. This meant that any failures in
dependency-graph generation or submission were logged as warnings, but
did not cause the workflow to fail.

However, in some cases the primary purpose of a workflow is to generate
and submit a dependency graph: in these cases it is desirable to have
the workflow fail when this process breaks.

This PR introduces a new `dependency-graph-continue-on-failure`
parameter, which when `false` will enable the latter behaviour. It also
adds test coverage for different failures in dependency graph generation
and submission.

Fixes #1034 
Fixes #997
2024-01-13 15:32:28 +01:00
daz
610728fa8c Build outputs 2024-01-13 07:21:40 -07:00
daz
a835cbb991 Use latest release of dependency-graph plugin 2024-01-13 07:21:40 -07:00
daz
ee4d92bb22 Document 'dependency-graph-continue-on-error' 2024-01-13 07:21:40 -07:00
daz
173b6ae553 Improve testing for dependency graph failures
- Update test to use input param
- Rename Job to indicate expected failure
2024-01-13 07:21:08 -07:00
daz
a01f794d92 Add dependency-graph-continue-on-failure input param
- Translate to env var for init-script support
- Use when deciding whether to log or rethrow errors
- Add a custom error type to trigger failure in post action
2024-01-13 07:20:45 -07:00
daz
369fcc54d8 Add tests for dependency graph failures 2024-01-13 07:20:45 -07:00
daz
6523a87c8f Update supported Gradle versions to match plugin 2024-01-12 13:33:42 -07:00
daz
11693a1169 Run npm tasks concurrently 2024-01-12 07:22:24 -07:00
daz
0e6b90783e Fix dependency-graph with configuration-cache
When state is reused from the configuration cache, no dependencies are resolved.
This fix prevents the action from submitting an empty dependency graph in this case.
2024-01-11 21:25:29 -07:00
Iurii Ignatko
932abbbe13 Update GE injection script for Develocity rename
- Rename all env vars expected by init-script: `s/GRADLE_ENTERPRISE/DEVELOCITY`
- Update README for changes
- Use `Develocity` consistently in tests
2024-01-10 20:30:40 -07:00
Daz DeBoer
1a18d0b2d3 Update description of cache key 2024-01-09 20:10:49 -07:00
Daz DeBoer
7af89832c5 Further improvements to Merge Queue docs 2024-01-08 13:00:48 -07:00
Daz DeBoer
b5ebb0cc96 Document caching with merge-queue 2024-01-08 12:58:38 -07:00
daz
3a75647ad4 Remove 'gradle-executable' input param 2024-01-04 13:53:16 -07:00
daz
4dda5928c7 Update CodeQL config to latest default 2024-01-04 13:03:33 -07:00
Daz DeBoer
6c7537229b Merge pull request #1026 from gradle/dd/java-toolchains
Use Maven toolchains.xml to register pre-installed JDKs as java toolchains
2024-01-04 05:34:34 +01:00
daz
650620f9f9 Build outputs 2024-01-03 21:12:13 -07:00
daz
d4e24dfc10 Register pre-installed JDKs in .m2/toolchains.xml
Since adding these to the `org.gradle.java.installations.fromEnv` property
is problematic (#1024), this mechanism allows the default toolchains to
be discovered by Gradle via a different mechanism.

The default JDK installations are added to `~/.m2/toolchains.xml` such that
they are discoverable by Gradle toolchain support.
The `setup-java` action also writes to this file, so we merge with any existing
content: this allows both pre-installed and "setup" JDKs to be automatically
detected by Gradle.
2024-01-03 21:08:40 -07:00
daz
7c57ba1136 Remove writing value to gradle.properties in setup
The written property will override any project-level setting, which
can lead to confusing behaviour.

Fixes #1024
2024-01-03 18:49:57 -07:00
Daz DeBoer
32bab5b15a Allow entries with same Job ID to match in different workflows
Previously, the workflow name was always included when matching a cache entry for the current job.
This can be overly restrictive when job definitions are shared between different workflows. 
The workflow name is still encoded in the cache entry key, but not in the restore key searching for entries with a matching job.

Fixes #1017
2024-01-03 18:38:36 -07:00
daz
270f30ba56 Always initialize encryption key 2024-01-02 14:28:29 -07:00
daz
c00a847f3f Enable config-cache tests
- Enable in full-build
- Pass encryption key secret to test workflow
2024-01-02 11:31:06 -07:00
daz
e2aa3f332c Test with Gradle 8.6-rc-1 2024-01-01 19:29:46 -07:00
dependabot[bot]
ff5d63e9dc Bump the npm-dependencies group with 3 updates
Bumps the npm-dependencies group with 3 updates: [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) and [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier).

Updates `@typescript-eslint/parser` from 6.15.0 to 6.17.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.17.0/packages/parser)

Updates `eslint-plugin-jest` from 27.6.0 to 27.6.1
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v27.6.0...v27.6.1)

Updates `eslint-plugin-prettier` from 5.1.1 to 5.1.2
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-plugin-jest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: eslint-plugin-prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 17:54:53 -07:00
Daz DeBoer
4faac45dc8 Merge pull request #1021 from gradle/dd/pr-comment
Automatically add Job Summary as PR comment
2024-01-02 01:51:30 +01:00
daz
45ed60450e Build outputs 2024-01-01 17:50:40 -07:00
daz
72abd931ce Set workflow permissions where required 2024-01-01 17:50:40 -07:00
daz
a4107da76d Warn when permissions not set for PR comment 2024-01-01 17:50:40 -07:00
daz
d16a3f4093 Update docs for PR comment 2024-01-01 17:50:40 -07:00
daz
333078158e Allow job-summary only on failure
Instead of a binary true/false option, it is now possible to only add
a Job Summary when the build failed. This applies both to the overall
Job Summary added to the workflow run, and to the new PR comment feature.
2024-01-01 17:50:40 -07:00
daz
34a07dced0 Automatically add Job Summary as PR comment
Rather than requiring a separate step to add a PR comment,
the `gradle-build-action` can now automatically add the Job Summary
as a PR comment

Fixes #1020
2024-01-01 17:50:15 -07:00
daz
24e9e9dc6b Refactor and simplify summary generation 2023-12-31 15:12:04 -07:00
Goooler
bc72ac9e9d Use actions/setup-java v4 in doc
https://github.com/actions/setup-java/releases/tag/v4.0.0

Signed-off-by: Goooler <wangzongler@gmail.com>
2023-12-31 21:18:55 +01:00
Daz DeBoer
c791d32284 Reference 8.6-rc-1 in docs 2023-12-31 13:17:35 -07:00
Daz DeBoer
ceb0c736c1 Document using secret for encryption key 2023-12-31 13:13:44 -07:00
41 changed files with 2608 additions and 1526 deletions

View File

@@ -1,22 +1,10 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: CI-codeql
on:
push:
branches: [ main ]
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
branches: [ "main" ]
schedule:
- cron: '25 23 * * 2'
@@ -32,9 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
language: [ 'javascript-typescript' ]
steps:
- name: Checkout repository
@@ -45,26 +31,9 @@ jobs:
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
config: |
paths:
- src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@@ -31,6 +31,13 @@ jobs:
dependency-graph:
uses: ./.github/workflows/integ-test-dependency-graph.yml
permissions:
contents: write
with:
cache-key-prefix: ${{github.run_number}}-
dependency-graph-failures:
uses: ./.github/workflows/integ-test-dependency-graph-failures.yml
with:
cache-key-prefix: ${{github.run_number}}-
@@ -44,22 +51,24 @@ jobs:
with:
cache-key-prefix: ${{github.run_number}}-
gradle-enterprise-injection:
uses: ./.github/workflows/integ-test-inject-gradle-enterprise.yml
develocity-injection:
uses: ./.github/workflows/integ-test-inject-develocity.yml
with:
cache-key-prefix: ${{github.run_number}}-
secrets:
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
provision-gradle-versions:
uses: ./.github/workflows/integ-test-provision-gradle-versions.yml
with:
cache-key-prefix: ${{github.run_number}}-
# restore-configuration-cache:
# uses: ./.github/workflows/integ-test-restore-configuration-cache.yml
# with:
# cache-key-prefix: ${{github.run_number}}-
restore-configuration-cache:
uses: ./.github/workflows/integ-test-restore-configuration-cache.yml
with:
cache-key-prefix: ${{github.run_number}}-
secrets:
GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
restore-custom-gradle-home:
uses: ./.github/workflows/integ-test-restore-custom-gradle-home.yml

View File

@@ -53,6 +53,15 @@ jobs:
dependency-graph:
needs: build-distribution
uses: ./.github/workflows/integ-test-dependency-graph.yml
permissions:
contents: write
with:
runner-os: '["ubuntu-latest"]'
download-dist: true
dependency-graph-failures:
needs: build-distribution
uses: ./.github/workflows/integ-test-dependency-graph-failures.yml
with:
runner-os: '["ubuntu-latest"]'
download-dist: true
@@ -71,14 +80,14 @@ jobs:
runner-os: '["ubuntu-latest"]'
download-dist: true
gradle-enterprise-injection:
develocity-injection:
needs: build-distribution
uses: ./.github/workflows/integ-test-inject-gradle-enterprise.yml
uses: ./.github/workflows/integ-test-inject-develocity.yml
with:
runner-os: '["ubuntu-latest"]'
download-dist: true
secrets:
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
provision-gradle-versions:
needs: build-distribution
@@ -93,6 +102,8 @@ jobs:
with:
runner-os: '["ubuntu-latest"]'
download-dist: true
secrets:
GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
restore-containerized-gradle-home:
needs: build-distribution

View File

@@ -8,7 +8,7 @@ env:
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
run-gradle-builds:
many-gradle-builds:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
@@ -42,6 +42,27 @@ jobs:
continue-on-error: true
run: ./gradlew not-a-real-task
successful-builds-with-no-summary:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Build distribution
shell: bash
run: |
npm install
npm run build
- name: Setup Gradle
uses: ./
with:
add-job-summary: on-failure
- name: Build kotlin-dsl project
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble
- name: Build kotlin-dsl project without Build Scan®
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble check --no-scan
pre-existing-gradle-home:
runs-on: ubuntu-latest
steps:

View File

@@ -2,26 +2,50 @@ name: Demo adding Build Scan® comment to PR
on:
pull_request:
types: [assigned, review_requested]
permissions:
pull-requests: write
jobs:
gradle:
successful-build-with-always-comment:
runs-on: ubuntu-latest
steps:
- name: Checkout project sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: ./
with:
add-job-summary-as-pr-comment: always
- name: Run build with Gradle wrapper
id: gradle
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew build --scan
- name: "Add Build Scan URL as PR comment"
uses: actions/github-script@v7
successful-build-with-comment-on-failure:
runs-on: ubuntu-latest
steps:
- name: Checkout project sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: ./
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'PR ready for review: ${{ steps.gradle.outputs.build-scan-url }}'
})
add-job-summary-as-pr-comment: on-failure
- name: Run build with Gradle wrapper
id: gradle
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew build --scan
failing-build-with-comment-on-failure:
runs-on: ubuntu-latest
steps:
- name: Checkout project sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: ./
with:
add-job-summary-as-pr-comment: on-failure
- name: Run build with Gradle wrapper
id: gradle
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew no-a-real-task --scan
continue-on-error: true

View File

@@ -0,0 +1,103 @@
name: Test dependency graph
on:
workflow_call:
inputs:
cache-key-prefix:
type: string
runner-os:
type: string
default: '["ubuntu-latest"]'
download-dist:
type: boolean
default: false
env:
DOWNLOAD_DIST: ${{ inputs.download-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }}
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
unsupported-gradle-version-warning:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Gradle for dependency-graph generate
uses: ./
with:
gradle-version: 7.0.1
dependency-graph: generate
dependency-graph-continue-on-failure: true
- name: Run with unsupported Gradle version
working-directory: .github/workflow-samples/groovy-dsl
run: |
if gradle help | grep -q 'warning::Dependency Graph is not supported for Gradle 7.0.1. No dependency snapshot will be generated.';
then
echo "Got the expected warning"
else
echo "Did not get the expected warning"
exit 1
fi
unsupported-gradle-version-failure:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Gradle for dependency-graph generate
uses: ./
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: |
if gradle help; then
echo "Expected build to fail with Gradle 7.0.1"
exit 1
fi
insufficient-permissions-warning:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Gradle for dependency-graph generate
uses: ./
with:
dependency-graph: generate-and-submit
dependency-graph-continue-on-failure: true
- name: Run with insufficient permissions
working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew help
# This test is primarily for demonstration: it's unclear how to check for warnings emitted in the post-action
SHOULD_FAIL-insufficient-permissions-failure:
runs-on: ubuntu-latest
permissions:
contents: read
continue-on-error: true
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Gradle for dependency-graph generate
uses: ./
with:
dependency-graph: generate-and-submit
dependency-graph-continue-on-failure: false
- name: Run with insufficient permissions
working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew help
# This test is primarily for demonstration: it's unclear how to check for a failure in the post-action

View File

@@ -12,6 +12,9 @@ on:
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 }}
@@ -87,11 +90,15 @@ jobs:
- 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"
@@ -101,3 +108,41 @@ jobs:
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: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Gradle for dependency-graph generate
uses: ./
with:
dependency-graph: generate-and-submit
- id: config-cache-store
run: ./gradlew assemble --configuration-cache
working-directory: .github/workflow-samples/groovy-dsl
- 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
run: ./gradlew assemble --configuration-cache
working-directory: .github/workflow-samples/groovy-dsl
- 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

View File

@@ -21,6 +21,7 @@ jobs:
# Test that pre-installed runner JDKs are detected
pre-installed-toolchains:
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
@@ -35,7 +36,7 @@ jobs:
shell: bash
working-directory: .github/workflow-samples/groovy-dsl
run: |
gradle -q javaToolchains > output.txt
gradle --info javaToolchains > output.txt
cat output.txt
- name: Verify detected toolchains
shell: bash
@@ -44,10 +45,12 @@ jobs:
grep -q 'Eclipse Temurin JDK 1.8' output.txt || (echo "::error::Did not detect preinstalled JDK 1.8" && exit 1)
grep -q 'Eclipse Temurin JDK 11' output.txt || (echo "::error::Did not detect preinstalled JDK 11" && exit 1)
grep -q 'Eclipse Temurin JDK 17' output.txt || (echo "::error::Did not detect preinstalled JDK 17" && exit 1)
grep -q 'Eclipse Temurin JDK 21' output.txt || (echo "::error::Did not detect preinstalled JDK 21" && exit 1)
# Test that JDKs provisioned by setup-java are detected
setup-java-installed-toolchain:
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
@@ -72,42 +75,19 @@ jobs:
shell: bash
working-directory: .github/workflow-samples/groovy-dsl
run: |
gradle -q javaToolchains > output.txt
gradle --info javaToolchains > output.txt
cat output.txt
- name: Verify detected toolchains
- name: Verify setup JDKs are detected
shell: bash
working-directory: .github/workflow-samples/groovy-dsl
run: |
grep -q 'Eclipse Temurin JDK 16' output.txt || (echo "::error::Did not detect setup-java installed JDK 16" && exit 1)
grep -q 'Eclipse Temurin JDK 20' output.txt || (echo "::error::Did not detect setup-java installed JDK 20" && exit 1)
# Test that predefined JDK detection property is not overwritten by action
check-no-overwrite:
strategy:
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Configure java installations env var in Gradle User Home
- name: Verify pre-installed toolchains are detected
shell: bash
working-directory: .github/workflow-samples/groovy-dsl
run: |
mkdir -p ~/.gradle
echo "org.gradle.java.installations.fromEnv=XXXXX" > ~/.gradle/gradle.properties
- name: Setup Gradle
uses: ./
- name: Check gradle.properties
shell: bash
run: |
cat ~/.gradle/gradle.properties
if grep -q 'org.gradle.java.installations.fromEnv=JAVA_HOME' ~/.gradle/gradle.properties ; then
echo 'Found overwritten fromEnv'
exit 1
fi
if ! grep -q 'org.gradle.java.installations.fromEnv=XXXXX' ~/.gradle/gradle.properties ; then
echo 'Did NOT find original fromEnv'
exit 1
fi
grep -q 'Eclipse Temurin JDK 1.8' output.txt || (echo "::error::Did not detect preinstalled JDK 1.8" && exit 1)
grep -q 'Eclipse Temurin JDK 11' output.txt || (echo "::error::Did not detect preinstalled JDK 11" && exit 1)
grep -q 'Eclipse Temurin JDK 17' output.txt || (echo "::error::Did not detect preinstalled JDK 17" && exit 1)
grep -q 'Eclipse Temurin JDK 21' output.txt || (echo "::error::Did not detect preinstalled JDK 21" && exit 1)

View File

@@ -46,12 +46,6 @@ jobs:
gradle-version: release-candidate
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: .github/workflow-samples/groovy-dsl/gradlew${{ matrix.script-suffix }}
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help
gradle-versions:
strategy:

View File

@@ -1,4 +1,4 @@
name: Test gradle enterprise injection
name: Test develocity injection
on:
workflow_call:
@@ -12,21 +12,21 @@ on:
type: boolean
default: false
secrets:
GRADLE_ENTERPRISE_ACCESS_KEY:
DEVELOCITY_ACCESS_KEY:
required: true
env:
DOWNLOAD_DIST: ${{ inputs.download-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: provision-gradle-versions-${{ inputs.cache-key-prefix }}
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
GRADLE_ENTERPRISE_INJECTION_ENABLED: true
GRADLE_ENTERPRISE_URL: https://ge.solutions-team.gradle.com
GRADLE_ENTERPRISE_PLUGIN_VERSION: 3.16.1
GRADLE_ENTERPRISE_CCUD_PLUGIN_VERSION: 1.12.1
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}
DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: https://ge.solutions-team.gradle.com
DEVELOCITY_PLUGIN_VERSION: 3.16.1
DEVELOCITY_CCUD_PLUGIN_VERSION: 1.12.1
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} # This env var has not (yet) been renamed/aliased in GE plugin 3.16.1
jobs:
inject-gradle-enterprise:
inject-develocity:
strategy:
matrix:
gradle: [current, 7.6.2, 6.9.4, 5.6.4]

View File

@@ -11,6 +11,9 @@ on:
download-dist:
type: boolean
default: false
secrets:
GRADLE_ENCRYPTION_KEY:
required: true
env:
DOWNLOAD_DIST: ${{ inputs.download-dist }}
@@ -34,8 +37,8 @@ jobs:
uses: ./
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Groovy build with configuration-cache enabled
working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache
@@ -57,8 +60,8 @@ jobs:
uses: ./
with:
cache-read-only: true
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Groovy build with configuration-cache enabled
id: execute
working-directory: .github/workflow-samples/groovy-dsl
@@ -92,8 +95,8 @@ jobs:
GRADLE_BUILD_ACTION_SKIP_RESTORE: "generated-gradle-jars|wrapper-zips|java-toolchains|instrumented-jars|dependencies|kotlin-dsl"
with:
cache-read-only: true
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Check execute Gradle build with configuration cache enabled (but not restored)
working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache
@@ -114,8 +117,8 @@ jobs:
uses: ./
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'help' with configuration-cache enabled
working-directory: .github/workflow-samples/kotlin-dsl
run: gradle help --configuration-cache
@@ -137,8 +140,8 @@ jobs:
uses: ./
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'test' with configuration-cache enabled
working-directory: .github/workflow-samples/kotlin-dsl
run: gradle test --configuration-cache
@@ -161,8 +164,8 @@ jobs:
uses: ./
with:
cache-read-only: true
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: release-nightly
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'test' again with configuration-cache enabled
id: execute
working-directory: .github/workflow-samples/kotlin-dsl

208
README.md
View File

@@ -4,7 +4,7 @@ This GitHub Action can be used to configure Gradle and optionally execute a Grad
## Why use the `gradle-build-action`?
It is possible to directly invoke Gradle in your workflow, and the `actions/setup-java@v3` action provides a simple way to cache Gradle dependencies.
It is possible to directly invoke Gradle in your workflow, and the `actions/setup-java@v4` action provides a simple way to cache Gradle dependencies.
However, the `gradle-build-action` offers a number of advantages over this approach:
@@ -36,7 +36,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
@@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
@@ -120,7 +120,7 @@ cache-disabled: true
By default, the `gradle-build-action` will only write to the cache from Jobs on the default (`main`/`master`) branch.
Jobs on other branches will read entries from the cache but will not write updated entries.
See [Optimizing cache effectiveness](#optimizing-cache-effectiveness) for a more detailed explanation.
See [Optimizing cache effectiveness](#select-which-branches-should-write-to-the-cache) for a more detailed explanation.
In some circumstances it makes sense to change this default, and to configure a workflow Job to read existing cache entries but not to write changes back.
@@ -167,9 +167,10 @@ in the project directory, at `<project-dir>/.gradle/configuration-cache`. Due to
secrets](https://docs.gradle.org/release-nightly/userguide/configuration_cache.html#config_cache:secrets), and this data needs to be encrypted in order to be safely stored in the GitHub Actions cache.
In order to benefit from configuration caching in your GitHub Actions workflow, you must:
- Enable the configuration cache for your build
- Execute your build with Gradle 8.6 or newer. This can be achieved directly, or via the Gradle Wrapper.
- Provide a [valid Gradle encryption key](https://docs.gradle.org/release-nightly/userguide/configuration_cache.html#config_cache:secrets:configuring_encryption_key) via the `cache-encryption-key` action parameter
- Enable the configuration cache for your build.
- Generate a [valid Gradle encryption key](https://docs.gradle.org/8.6-rc-1/userguide/configuration_cache.html#config_cache:secrets:configuring_encryption_key) and save it as a [GitHub Actions secret](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).
- Provide the secret key via the `cache-encryption-key` action parameter.
```yaml
jobs:
@@ -179,8 +180,8 @@ jobs:
- uses: actions/checkout@v4
- uses: gradle/gradle-build-action@v3
with:
gradle-version: 8.6
cache-encryption-key: Da25KUVSE5jbGds2zXmfXw==
gradle-version: 8.6-rc-1
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
- run: gradle build --configuration-cache
```
@@ -246,11 +247,11 @@ For this reason, it's very difficult to create a cache key that will determinist
The Gradle User Home cache key is composed of:
- The current operating system (`RUNNER_OS`)
- The workflow name and Job ID
- A hash of the Job matrix parameters
- The Job id
- A hash of the Job matrix parameters and the workflow name
- The git SHA for the latest commit
Specifically, the cache key is: `${cache-protocol}-gradle|${runner-os}|${workflow-name}-${job-id}[${hash-of-job-matrix}]-${git-sha}`
Specifically, the cache key is: `${cache-protocol}-gradle|${runner-os}|${job-id}[${hash-of-job-matrix-and-workflow-name}]-${git-sha}`
As such, the cache key is likely to change on each subsequent run of GitHub actions.
This allows the most recent state to always be available in the GitHub actions cache.
@@ -258,9 +259,9 @@ This allows the most recent state to always be available in the GitHub actions c
### Finding a matching cache entry
In most cases, no exact match will exist for the cache key. Instead, the Gradle User Home will be restored for the closest matching cache entry, using a set of "restore keys". The entries will be matched with the following precedence:
- An exact match on OS, workflow, job, matrix and Git SHA
- The most recent entry saved for the same OS, workflow, job and matrix values
- The most recent entry saved for the same OS, workflow and job
- An exact match on OS, job id, workflow name, matrix and Git SHA
- The most recent entry saved for the same OS, job id, workflow name and matrix values
- The most recent entry saved for the same OS and job id
- The most recent entry saved for the same OS
Due to branch scoping of cache entries, the above match will be first performed for entries from the same branch, and then for the default ('main') branch.
@@ -313,20 +314,23 @@ There are some techniques that can be used to avoid/mitigate this issue:
### Select which branches should write to the cache
GitHub cache entries are not shared between builds on different branches.
This means that each PR branch will have it's own Gradle User Home cache, and will not benefit from cache entries written by other PR branches.
An exception to this is that cache entries written in parent and upstream branches are visible to child branches, and cache entries for the default (`master`/`main`) branch can be read by actions invoked for any other branch.
Workflow runs can restore caches created in either the current branch or the default branch (usually main).
This means that each branch will have it's own Gradle User Home cache scope, and will not benefit from cache entries written for other (non-default) branches.
By default, the `gradle-build-action` will only _write_ to the cache for builds run on the default (`master`/`main`) branch.
Jobs run on other branches will only read from the cache. In most cases, this is the desired behaviour,
because Jobs run against other branches will benefit from the cache Gradle User Home from `main`,
without writing private cache entries that could lead to evicting shared entries.
Jobs run on other branches will only read from the cache. In most cases, this is the desired behavior.
This is because Jobs run on other branches will benefit from the cache Gradle User Home from `main`,
without writing private cache entries that which could lead to evicting these shared entries.
If you have other long-lived development branches that would benefit from writing to the cache,
you can configure these by overriding the `cache-read-only` action parameter.
you can configure this by disabling the `cache-read-only` action parameter for these branches.
See [Using the cache read-only](#using-the-cache-read-only) for more details.
Similarly, you could use `cache-read-only` for certain jobs in the workflow, and instead have these jobs reuse the cache content from upstream jobs.
Note there are some cases where writing cache entries is typically unhelpful (these are disabled by default):
- For `pull_request` triggered runs, the cache scope is limited to the merge ref (`refs/pull/.../merge`) and can only be restored by re-runs of the same pull request.
- For `merge_group` triggered runs, the cache scope is limited to a temporary branch with a special prefix created to validate pull request changes, and won't be available on subsequent Merge Queue executions.
### Exclude content from Gradle User Home cache
As well as any wrapper distributions, the action will attempt to save and restore the `caches` and `notifications` directories from Gradle User Home.
@@ -367,62 +371,57 @@ gradle-home-cache-cleanup: true
## Build reporting
The `gradle-build-action` collects information about any Gradle executions that occur in a workflow, and reports these via
a Job Summary, visible in the GitHub Actions UI. For each Gradle execution, details about the invocation are listed, together with
a link to any Build Scan® published.
The `gradle-build-action` collects information about any Gradle executions that occur in a workflow, including the root project,
requested tasks, build outcome and any Build Scan link generated. Details of cache entries read and written are also collected.
These details are compiled into a Job Summary, which is visible in the GitHub Actions UI.
Generation of a Job Summary is enabled by default. If this is not desired, it can be disable as follows:
Generation of a Job Summary is enabled by default for all Jobs using the `gradle-build-action`. This feature can be configured
so that a Job Summary is never generated, or so that a Job Summary is only generated on build failure:
```yaml
generate-job-summary: false
add-job-summary: 'on-failure' # Valid values are 'always' (default), 'never', and 'on-failure'
```
Note that the action collects information about Gradle invocations via an [Initialization Script](https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script)
located at `USER_HOME/.gradle/init.d/build-result-capture.init.gradle`.
If you are using init scripts for the [Gradle Enterprise Gradle Plugin](https://plugins.gradle.org/plugin/com.gradle.enterprise) like
[`scans-init.gradle` or `gradle-enterprise-init.gradle`](https://docs.gradle.com/enterprise/gradle-plugin/#scans_gradle_com),
you'll need to ensure these files are applied prior to `build-result-capture.init.gradle`.
Since Gradle applies init scripts in alphabetical order, one way to ensure this is via file naming.
### Adding Job Summary as a Pull Request comment
### Build Scan® link as Step output
It is sometimes more convenient to view the results of a GitHub Actions Job directly from the Pull Request that triggered
the Job. For this purpose you can configure the action so that Job Summary data is added as a Pull Request comment.
As well as reporting the [Build Scan](https://gradle.com/build-scans/) link in the Job Summary,
the `gradle-build-action` action makes this link available as a Step output named `build-scan-url`.
You can then use that link in subsequent actions of your workflow. For example:
```yaml
# .github/workflows/gradle-build-pr.yml
name: Run Gradle on PRs
on: pull_request
name: CI
on:
pull_request:
permissions:
pull-requests: write
jobs:
gradle:
run-gradle-build:
runs-on: ubuntu-latest
steps:
- name: Checkout project sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run build with Gradle wrapper
id: gradle
run: ./gradlew build --scan
- name: "Add Build Scan URL as PR comment"
uses: actions/github-script@v5
if: github.event_name == 'pull_request' && failure()
uses: gradle/gradle-build-action@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ ${{ github.workflow }} failed: ${{ steps.gradle.outputs.build-scan-url }}'
})
add-job-summary-as-pr-comment: on-failure # Valid values are 'never' (default), 'always', and 'on-failure'
- run: ./gradlew build --scan
```
### Saving build outputs
Note that in order to add a Pull Request comment, the workflow must be configured with the `pull-requests: write` permission.
By default, a GitHub Actions workflow using `gradle-build-action` will record the log output and any Build Scan links for your build,
but any output files generated by the build will not be saved.
### Build Scan® link as Step output
As well as reporting all [Build Scan](https://gradle.com/build-scans/) links in the Job Summary,
the `gradle-build-action` action makes this link available an an output of any Step that executes Gradle.
The output name is `build-scan-url`. You can then use the build scan link in subsequent actions of your workflow.
### Saving arbitrary build outputs
By default, a GitHub Actions workflow using `gradle-build-action` will record the log output and any Build Scan
links for your build, but any output files generated by the build will not be saved.
To save selected files from your build execution, you can use the core [Upload-Artifact](https://github.com/actions/upload-artifact) action.
For example:
@@ -446,6 +445,14 @@ jobs:
path: build/reports/
```
### Use of custom init-scripts in Gradle User Home
Note that the action collects information about Gradle invocations via an [Initialization Script](https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script)
located at `USER_HOME/.gradle/init.d/gradle-build-action.build-result-capture.init.gradle`.
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 prior to `gradle-build-action.build-result-capture.init.gradle`.
Since Gradle applies init scripts in alphabetical order, one way to ensure this is via file naming.
## Support for GitHub Enterprise Server (GHES)
You can use the `gradle-build-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:
@@ -469,9 +476,10 @@ You enable GitHub Dependency Graph support by setting the `dependency-graph` act
| Option | Behaviour |
| --- | --- |
| `disabled` | Do not generate a dependency graph for any build invocations.<p>This is the default. |
| `generate` | Generate a dependency graph snapshot for each build invocation, saving as a workflow artifact. |
| `generate-and-submit` | As per `generate`, but any generated dependency graph snapshots will be submitted at the end of the job. |
| `download-and-submit` | Download any previously saved dependency graph snapshots, submitting them via the Dependency Submission API. This can be useful to collect all snapshots in a matrix of builds and submit them in one step. |
| `generate` | Generate a dependency graph snapshot for each build invocation. |
| `generate-and-submit` | Generate a dependency graph snapshot for each build invocation, and submit these via the Dependency Submission API on completion of the job. |
| `generate-and-upload` | Generate a dependency graph snapshot for each build invocation, saving as a workflow artifact. |
| `download-and-submit` | Download any previously saved dependency graph snapshots, and submit them via the Dependency Submission API. This can be useful to submit [dependency graphs for pull requests submitted from a repository forks](#dependency-graphs-for-pull-request-workflows). |
Example of a CI workflow that generates and submits a dependency graph:
```yaml
@@ -505,6 +513,22 @@ Depending on [repository settings](https://docs.github.com/en/actions/security-g
> for a PR submitted from a forked repository.
> For a configuration that supports this setup, see [Dependency Graphs for pull request workflows](#dependency-graphs-for-pull-request-workflows).
### Making dependency graph failures cause Job failures
By default, if a failure is encountered when generating or submitting the dependency graph, the action will log the failure as a warning and continue.
This allows your workflow to be resilient to dependency graph failures, in case dependency graph production is a side-effect rather than the primary purpose of a workflow.
If instead you have a workflow that has a primary purpose to generate and submit a dependency graph, then it makes sense for this workflow to fail if the dependency
graph cannot be generated or submitted. You can enable this behaviour with the `dependency-graph-continue-on-failure` parameter, which defaults to `true`.
```yaml
# Ensure that the workflow Job will fail if the dependency graph cannot be submitted
- uses: gradle/gradle-build-action@v3
with:
dependency-graph: generate-and-submit
dependency-graph-continue-on-failure: false
```
### Using a custom plugin repository
By default, the action downloads the `github-dependency-graph-gradle-plugin` from the Gradle Plugin Portal (https://plugins.gradle.org). If your GitHub Actions environment does not have access to this URL, you can specify a custom plugin repository to use.
@@ -681,6 +705,9 @@ name: run-build-and-generate-dependency-snapshot
on:
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
@@ -689,7 +716,7 @@ jobs:
- name: Setup Gradle to generate and submit dependency graphs
uses: gradle/gradle-build-action@v2
with:
dependency-graph: generate # Only generate in this job
dependency-graph: generate-and-upload # Generate graphs and save as workflow artifacts
- name: Run a build, generating the dependency graph snapshot which will be submitted
run: ./gradlew build
```
@@ -703,6 +730,9 @@ on:
workflows: ['run-build-and-generate-dependency-snapshot']
types: [completed]
permissions:
contents: write
jobs:
submit-dependency-graph:
runs-on: ubuntu-latest
@@ -710,7 +740,7 @@ jobs:
- name: Retrieve dependency graph artifact and submit
uses: gradle/gradle-build-action@v2
with:
dependency-graph: download-and-submit
dependency-graph: download-and-submit # Download saved workflow artifacts and submit
```
### Integrating `dependency-review-action` for pull request workflows
@@ -776,27 +806,26 @@ To reduce storage costs for these artifacts, you can set the `artifact-retention
# Gradle Enterprise plugin injection
# Develocity plugin injection
The `gradle-build-action` provides support for injecting and configuring the Gradle Enterprise Gradle plugin into any Gradle build, without any modification to the project sources.
The `gradle-build-action` provides support for injecting and configuring the Develocity Gradle plugin into any Gradle build, without any modification to the project sources.
This is achieved via an init-script installed into Gradle User Home, which is enabled and parameterized via environment variables.
The same auto-injection behavior is available for the Common Custom User Data Gradle plugin, which enriches any build scans published with additional useful information.
## Enabling Gradle Enterprise injection
## Enabling Develocity injection
In order to enable Gradle Enterprise for your build, you must provide the required configuration via environment variables.
In order to enable Develocity injection for your build, you must provide the required configuration via environment variables.
Here's a minimal example:
```yaml
name: Run build with Gradle Enterprise injection
name: Run build with Develocity injection
env:
GRADLE_ENTERPRISE_INJECTION_ENABLED: true
GRADLE_ENTERPRISE_URL: https://ge.gradle.org
GRADLE_ENTERPRISE_PLUGIN_VERSION: 3.16.1
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_KEY }} # Required to publish scans to ge.gradle.org
DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: https://develocity.your-server.com
DEVELOCITY_PLUGIN_VERSION: 3.16.1
jobs:
build:
@@ -805,36 +834,37 @@ jobs:
- uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run a Gradle build with Gradle Enterprise injection enabled
- name: Run a Gradle build with Develocity injection enabled
run: ./gradlew build
```
This configuration will automatically apply `v3.16.1` of the [Gradle Enterprise Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/), and publish build scans to https://ge.gradle.org.
This configuration will automatically apply `v3.16.1` of the [Develocity Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/), and publish build scans to https://develocity.your-server.com.
Note that the `ge.gradle.org` server requires authentication in order to publish scans. The provided `GRADLE_ENTERPRISE_ACCESS_KEY` isn't required by the Gradle Enterprise injection script,
but will be used by the GE plugin in order to authenticate with the server.
This example assumes that the `develocity.your-server.com` server allows anonymous publishing of build scans.
In the likely scenario that your Develocity server requires authentication, you will also need to configure an addition environment variable
with a valid [Develocity access key](https://docs.gradle.com/enterprise/gradle-plugin/#via_environment_variable).
## Configuring Gradle Enterprise injection
## Configuring Develocity injection
The `init-script` supports a number of additional configuration parameters that you may fine useful. All configuration options (required and optional) are detailed below:
| Variable | Required | Description |
| --- | --- | --- |
| GRADLE_ENTERPRISE_INJECTION_ENABLED | :white_check_mark: | enables Gradle Enterprise injection |
| GRADLE_ENTERPRISE_URL | :white_check_mark: | the URL of the Gradle Enterprise server |
| GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER | | allow communication with an untrusted server; set to _true_ if your Gradle Enterprise instance is using a self-signed certificate |
| GRADLE_ENTERPRISE_ENFORCE_URL | | enforce the configured Gradle Enterprise URL over a URL configured in the project's build; set to _true_ to enforce publication of build scans to the configured Gradle Enterprise URL |
| GRADLE_ENTERPRISE_PLUGIN_VERSION | :white_check_mark: | the version of the [Gradle Enterprise Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/) to apply |
| GRADLE_ENTERPRISE_CCUD_PLUGIN_VERSION | | the version of the [Common Custom User Data Gradle plugin](https://github.com/gradle/common-custom-user-data-gradle-plugin) to apply, if any |
| GRADLE_ENTERPRISE_PLUGIN_REPOSITORY_URL | | the URL of the repository to use when resolving the GE and CCUD plugins; the Gradle Plugin Portal is used by default |
| Variable | Required | Description |
|-----------------------------------| --- | --- |
| DEVELOCITY_INJECTION_ENABLED | :white_check_mark: | enables Develocity injection |
| DEVELOCITY_URL | :white_check_mark: | the URL of the Develocity server |
| DEVELOCITY_ALLOW_UNTRUSTED_SERVER | | allow communication with an untrusted server; set to _true_ if your Develocity instance is using a self-signed certificate |
| DEVELOCITY_ENFORCE_URL | | enforce the configured Develocity URL over a URL configured in the project's build; set to _true_ to enforce publication of build scans to the configured Develocity URL |
| DEVELOCITY_PLUGIN_VERSION | :white_check_mark: | the version of the [Develocity Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/) to apply |
| DEVELOCITY_CCUD_PLUGIN_VERSION | | the version of the [Common Custom User Data Gradle plugin](https://github.com/gradle/common-custom-user-data-gradle-plugin) to apply, if any |
| GRADLE_PLUGIN_REPOSITORY_URL | | the URL of the repository to use when resolving the Develocity and CCUD plugins; the Gradle Plugin Portal is used by default |
## Publishing to scans.gradle.com
Gradle Enterprise injection is designed to enable publishing of build scans to a Gradle Enterprise instance,
Develocity injection is designed to enable publishing of build scans to a Develocity instance,
and is not suitable for publishing to the public Build Scans instance (https://scans.gradle.com).
In order to publish Build Scans to scans.gradle.com, you need to:
- Apply the Gradle Enterprise plugin to your build configuration ([see docs](https://docs.gradle.com/enterprise/get-started/#applying_the_plugin))
- Apply the Develocity plugin to your build configuration ([see docs](https://docs.gradle.com/enterprise/get-started/#applying_the_plugin))
- Programmatically accept the Terms of Service for scans.gradle.com ([see docs](https://docs.gradle.com/enterprise/gradle-plugin/#connecting_to_scans_gradle_com))
- Execute the build with `--scan` or configure your build with `publishAlways()` ([see docs](https://docs.gradle.com/enterprise/get-started/#always_publishing_a_build_scan))

View File

@@ -58,16 +58,26 @@ inputs:
required: false
default: false
generate-job-summary:
description: When 'false', no Job Summary will be generated for the Job.
add-job-summary:
description: Specifies when a Job Summary should be inluded in the action results. Valid values are 'never', 'always' (default), and 'on-failure'.
required: false
default: true
default: 'always'
add-job-summary-as-pr-comment:
description: Specifies when each Job Summary should be added as a PR comment. Valid values are 'never' (default), 'always', and 'on-failure'. No action will be taken if the workflow was not triggered from a pull request.
required: false
default: 'never'
dependency-graph:
description: Specifies if a GitHub dependency snapshot should be generated for each Gradle build, and if so, how. Valid values are 'disabled' (default), 'generate', 'generate-and-submit', 'generate-and-upload' and 'download-and-submit'.
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
@@ -83,10 +93,11 @@ inputs:
required: false
deprecation-message: Using the action to execute Gradle directly is deprecated in favor of using the action to setup Gradle, and executing Gradle in a subsequent Step. See https://github.com/gradle/gradle-build-action?tab=readme-ov-file#use-the-action-to-setup-gradle.
gradle-executable:
description: Path to the Gradle executable. If specified, this executable will be added to the PATH and used for invoking Gradle.
generate-job-summary:
description: When 'false', no Job Summary will be generated for the Job.
required: false
deprecation-message: Using the action to execute Gradle directly is deprecated in favor of using the action to setup Gradle, and executing Gradle in a subsequent Step. See https://github.com/gradle/gradle-build-action?tab=readme-ov-file#use-the-action-to-setup-gradle.
default: true
deprecation-message: Superceded by the new 'add-job-summary' and 'add-job-summary-as-pr-comment' parameters.
# EXPERIMENTAL & INTERNAL ACTION INPUTS
# The following action properties allow fine-grained tweaking of the action caching behaviour.

405
dist/main/index.js vendored
View File

@@ -106624,7 +106624,7 @@ var __disposeResources;
__addDisposableResource = function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object") throw new TypeError("Object expected.");
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
@@ -138599,17 +138599,18 @@ const cache_extract_entries_1 = __nccwpck_require__(76161);
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key';
exports.META_FILE_DIR = '.gradle-build-action';
class GradleStateCache {
constructor(gradleUserHome) {
constructor(userHome, gradleUserHome) {
this.userHome = userHome;
this.gradleUserHome = gradleUserHome;
this.cacheName = 'gradle';
this.cacheDescription = 'Gradle User Home';
}
init() {
const actionCacheDir = path_1.default.resolve(this.gradleUserHome, '.gradle-build-action');
fs_1.default.mkdirSync(actionCacheDir, { recursive: true });
const initScriptsDir = path_1.default.resolve(this.gradleUserHome, 'init.d');
fs_1.default.mkdirSync(initScriptsDir, { recursive: true });
this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir);
this.initializeGradleUserHome();
const encryptionKey = params.getCacheEncryptionKey();
if (encryptionKey) {
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey);
}
}
cacheOutputExists() {
const cachesDir = path_1.default.resolve(this.gradleUserHome, 'caches');
@@ -138721,32 +138722,47 @@ class GradleStateCache {
}
return path_1.default.resolve(this.gradleUserHome, rawPath);
}
initializeGradleUserHome(gradleUserHome, initScriptsDir) {
const gradleProperties = path_1.default.resolve(gradleUserHome, 'gradle.properties');
const existingGradleProperties = fs_1.default.existsSync(gradleProperties)
? fs_1.default.readFileSync(gradleProperties, 'utf8')
: '';
if (!existingGradleProperties.includes('org.gradle.java.installations.fromEnv=')) {
fs_1.default.appendFileSync(gradleProperties, `
# Auto-detect pre-installed JDKs
org.gradle.java.installations.fromEnv=JAVA_HOME_8_X64,JAVA_HOME_11_X64,JAVA_HOME_17_X64
`);
}
initializeGradleUserHome() {
const actionCacheDir = path_1.default.resolve(this.gradleUserHome, '.gradle-build-action');
fs_1.default.mkdirSync(actionCacheDir, { recursive: true });
this.copyInitScripts();
this.registerToolchains();
}
copyInitScripts() {
const initScriptsDir = path_1.default.resolve(this.gradleUserHome, 'init.d');
fs_1.default.mkdirSync(initScriptsDir, { recursive: true });
const initScriptFilenames = [
'gradle-build-action.build-result-capture.init.gradle',
'gradle-build-action.build-result-capture-service.plugin.groovy',
'gradle-build-action.github-dependency-graph.init.gradle',
'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy',
'gradle-build-action.inject-gradle-enterprise.init.gradle'
'gradle-build-action.inject-develocity.init.gradle'
];
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readInitScriptAsString(initScriptFilename);
const initScriptContent = this.readResourceFileAsString('init-scripts', initScriptFilename);
const initScriptPath = path_1.default.resolve(initScriptsDir, initScriptFilename);
fs_1.default.writeFileSync(initScriptPath, initScriptContent);
}
}
readInitScriptAsString(resource) {
const absolutePath = path_1.default.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource);
registerToolchains() {
const preInstalledToolchains = this.readResourceFileAsString('toolchains.xml');
const m2dir = path_1.default.resolve(this.userHome, '.m2');
const toolchainXmlTarget = path_1.default.resolve(m2dir, 'toolchains.xml');
if (!fs_1.default.existsSync(toolchainXmlTarget)) {
fs_1.default.mkdirSync(m2dir, { recursive: true });
fs_1.default.writeFileSync(toolchainXmlTarget, preInstalledToolchains);
core.info(`Wrote default JDK locations to ${toolchainXmlTarget}`);
}
else {
const existingToolchainContent = fs_1.default.readFileSync(toolchainXmlTarget, 'utf8');
const appendedContent = preInstalledToolchains.split('<toolchains>').pop();
const mergedContent = existingToolchainContent.replace('</toolchains>', appendedContent);
fs_1.default.writeFileSync(toolchainXmlTarget, mergedContent);
core.info(`Merged default JDK locations into ${toolchainXmlTarget}`);
}
}
readResourceFileAsString(...paths) {
const absolutePath = path_1.default.resolve(__dirname, '..', '..', 'src', 'resources', ...paths);
return fs_1.default.readFileSync(absolutePath, 'utf8');
}
debugReportGradleUserHomeSize(label) {
@@ -138946,7 +138962,6 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ConfigurationCacheEntryExtractor = exports.GradleHomeEntryExtractor = void 0;
const path_1 = __importDefault(__nccwpck_require__(71017));
const fs_1 = __importDefault(__nccwpck_require__(57147));
const crypto_1 = __importDefault(__nccwpck_require__(6113));
const core = __importStar(__nccwpck_require__(42186));
const glob = __importStar(__nccwpck_require__(28090));
const semver = __importStar(__nccwpck_require__(11383));
@@ -139184,8 +139199,6 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
this.markNotRestored(listener, 'Encryption Key was not provided');
return;
}
const encryptionKey = this.getAESEncryptionKey();
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey);
return yield _super.restore.call(this, listener);
});
}
@@ -139217,11 +139230,6 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
yield _super.extract.call(this, listener);
});
}
getAESEncryptionKey() {
const secret = params.getCacheEncryptionKey();
const key = crypto_1.default.pbkdf2Sync(secret, '', 1000, 16, 'sha256');
return key.toString('base64');
}
getExtractedCacheEntryDefinitions() {
const groupedResults = this.getConfigCacheDirectoriesWithAssociatedBuildResults();
return Object.entries(groupedResults).map(([configCachePath, pathResults]) => {
@@ -139284,8 +139292,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.logCachingReport = exports.writeCachingReport = exports.CacheEntryListener = exports.CacheListener = void 0;
const core = __importStar(__nccwpck_require__(42186));
exports.generateCachingReport = exports.CacheEntryListener = exports.CacheListener = void 0;
const cache = __importStar(__nccwpck_require__(27799));
class CacheListener {
constructor() {
@@ -139373,37 +139380,36 @@ class CacheEntryListener {
}
}
exports.CacheEntryListener = CacheEntryListener;
function writeCachingReport(listener) {
function generateCachingReport(listener) {
const entries = listener.cacheEntries;
core.summary.addRaw(`\n<details><summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>\n`);
core.summary.addTable([
[
{ data: '', header: true },
{ data: 'Count', header: true },
{ data: 'Total Size (Mb)', header: true }
],
['Entries Restored', `${getCount(entries, e => e.restoredSize)}`, `${getSize(entries, e => e.restoredSize)}`],
['Entries Saved', `${getCount(entries, e => e.savedSize)}`, `${getSize(entries, e => e.savedSize)}`]
]);
core.summary.addHeading('Cache Entry Details', 5);
const entryDetails = renderEntryDetails(listener);
core.summary.addRaw(`<pre>
${entryDetails}
return `
<details>
<summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>
${renderEntryTable(entries)}
<h5>Cache Entry Details</h5>
<pre>
${renderEntryDetails(listener)}
</pre>
</details>
`);
`;
}
exports.writeCachingReport = writeCachingReport;
function logCachingReport(listener) {
const entries = listener.cacheEntries;
core.startGroup(`Caching for gradle-build-action was ${listener.cacheStatus} - expand for details`);
core.info(`Entries Restored: ${getCount(entries, e => e.restoredSize)} (${getSize(entries, e => e.restoredSize)} Mb)`);
core.info(`Entries Saved : ${getCount(entries, e => e.savedSize)} (${getSize(entries, e => e.savedSize)} Mb)`);
core.info(`Cache Entry Details`);
core.info(renderEntryDetails(listener));
core.endGroup();
exports.generateCachingReport = generateCachingReport;
function renderEntryTable(entries) {
return `
<table>
<tr><td></td><th>Count</th><th>Total Size (Mb)</th></tr>
<tr><td>Entries Restored</td>
<td>${getCount(entries, e => e.restoredSize)}</td>
<td>${getSize(entries, e => e.restoredSize)}</td>
</tr>
<tr><td>Entries Saved</td>
<td>${getCount(entries, e => e.savedSize)}</td>
<td>${getSize(entries, e => e.savedSize)}</td>
</tr>
</table>
`;
}
exports.logCachingReport = logCachingReport;
function renderEntryDetails(listener) {
return listener.cacheEntries
.map(entry => {
@@ -139511,7 +139517,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.tryDelete = exports.handleCacheFailure = exports.cacheDebug = exports.saveCache = exports.restoreCache = exports.hashStrings = exports.hashFileNames = exports.getUniqueLabelForJobInstanceValues = exports.getUniqueLabelForJobInstance = exports.getCacheKeyForJob = exports.getCacheKeyPrefix = exports.generateCacheKey = exports.CacheKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
exports.tryDelete = exports.handleCacheFailure = exports.cacheDebug = exports.saveCache = exports.restoreCache = exports.hashStrings = exports.hashFileNames = exports.getCacheKeyPrefix = exports.generateCacheKey = exports.CacheKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
const core = __importStar(__nccwpck_require__(42186));
const cache = __importStar(__nccwpck_require__(27799));
const github = __importStar(__nccwpck_require__(95438));
@@ -139583,34 +139589,16 @@ function getCacheKeyEnvironment() {
return process.env[CACHE_KEY_OS_VAR] || runnerOs;
}
function getCacheKeyJob() {
return process.env[CACHE_KEY_JOB_VAR] || getCacheKeyForJob(github.context.workflow, github.context.job);
return process.env[CACHE_KEY_JOB_VAR] || github.context.job;
}
function getCacheKeyForJob(workflowName, jobId) {
const sanitizedWorkflow = workflowName.replace(/,/g, '').toLowerCase();
return `${sanitizedWorkflow}-${jobId}`;
}
exports.getCacheKeyForJob = getCacheKeyForJob;
function getCacheKeyJobInstance() {
const override = process.env[CACHE_KEY_JOB_INSTANCE_VAR];
if (override) {
return override;
}
const workflowName = github.context.workflow;
const workflowJobContext = params.getJobMatrix();
return hashStrings([workflowJobContext]);
}
function getUniqueLabelForJobInstance() {
return getUniqueLabelForJobInstanceValues(github.context.workflow, github.context.job, params.getJobMatrix());
}
exports.getUniqueLabelForJobInstance = getUniqueLabelForJobInstance;
function getUniqueLabelForJobInstanceValues(workflow, jobId, matrixJson) {
const matrix = JSON.parse(matrixJson);
const matrixString = Object.values(matrix).join('-');
const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`;
return sanitize(label);
}
exports.getUniqueLabelForJobInstanceValues = getUniqueLabelForJobInstanceValues;
function sanitize(value) {
return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase();
return hashStrings([workflowName, workflowJobContext]);
}
function getCacheKeyJobExecution() {
return process.env[CACHE_KEY_JOB_EXECUTION_VAR] || github.context.sha;
@@ -139782,14 +139770,14 @@ const cache_utils_1 = __nccwpck_require__(41678);
const cache_base_1 = __nccwpck_require__(47591);
const cache_cleaner_1 = __nccwpck_require__(57);
const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED';
function restore(gradleUserHome, cacheListener) {
function restore(userHome, gradleUserHome, cacheListener) {
return __awaiter(this, void 0, void 0, function* () {
if (process.env[CACHE_RESTORED_VAR]) {
core.info('Cache only restored on first action step.');
return;
}
core.exportVariable(CACHE_RESTORED_VAR, true);
const gradleStateCache = new cache_base_1.GradleStateCache(gradleUserHome);
const gradleStateCache = new cache_base_1.GradleStateCache(userHome, gradleUserHome);
if ((0, cache_utils_1.isCacheDisabled)()) {
core.info('Cache is disabled: will not restore state from previous builds.');
gradleStateCache.init();
@@ -139824,7 +139812,7 @@ function restore(gradleUserHome, cacheListener) {
});
}
exports.restore = restore;
function save(gradleUserHome, cacheListener, daemonController) {
function save(userHome, gradleUserHome, cacheListener, daemonController) {
return __awaiter(this, void 0, void 0, function* () {
if ((0, cache_utils_1.isCacheDisabled)()) {
core.info('Cache is disabled: will not save state for later builds.');
@@ -139851,7 +139839,7 @@ function save(gradleUserHome, cacheListener, daemonController) {
}
}
yield core.group('Caching Gradle state', () => __awaiter(this, void 0, void 0, function* () {
return new cache_base_1.GradleStateCache(gradleUserHome).save(cacheListener);
return new cache_base_1.GradleStateCache(userHome, gradleUserHome).save(cacheListener);
}));
});
}
@@ -139983,6 +139971,7 @@ const request_error_1 = __nccwpck_require__(10537);
const path = __importStar(__nccwpck_require__(71017));
const fs_1 = __importDefault(__nccwpck_require__(57147));
const layout = __importStar(__nccwpck_require__(28182));
const errors_1 = __nccwpck_require__(36976);
const input_params_1 = __nccwpck_require__(23885);
const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_';
function setup(option) {
@@ -139995,16 +139984,22 @@ function setup(option) {
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
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);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', (0, input_params_1.getDependencyGraphContinueOnFailure)());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
maybeExportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
});
}
exports.setup = setup;
function maybeExportVariable(variableName, value) {
if (!process.env[variableName]) {
core.exportVariable(variableName, value);
}
}
function complete(option) {
return __awaiter(this, void 0, void 0, function* () {
try {
@@ -140021,7 +140016,7 @@ function complete(option) {
}
}
catch (e) {
core.warning(`Failed to ${option} dependency graph. Will continue. ${String(e)}`);
warnOrFail(option, e);
}
});
}
@@ -140052,7 +140047,7 @@ function downloadAndSubmitDependencyGraphs() {
yield submitDependencyGraphs(yield downloadDependencyGraphs());
}
catch (e) {
core.warning(`Download and submit dependency graph failed. Will continue. ${String(e)}`);
warnOrFail(input_params_1.DependencyGraphOption.DownloadAndSubmit, e);
}
});
}
@@ -140064,7 +140059,7 @@ function submitDependencyGraphs(dependencyGraphFiles) {
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
core.warning(buildWarningMessage(jsonFile, error));
throw new Error(translateErrorMessage(jsonFile, error));
}
else {
throw error;
@@ -140073,9 +140068,9 @@ function submitDependencyGraphs(dependencyGraphFiles) {
}
});
}
function buildWarningMessage(jsonFile, error) {
function translateErrorMessage(jsonFile, error) {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
const mainWarning = `Failed to submit dependency graph ${relativeJsonFile}.\n${String(error)}`;
const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${String(error)}`;
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'contents: write' permission is available for the workflow job.
@@ -140130,6 +140125,12 @@ function findDependencyGraphFiles(dir) {
return graphFiles;
});
}
function warnOrFail(option, error) {
if (!(0, input_params_1.getDependencyGraphContinueOnFailure)()) {
throw new errors_1.PostActionJobFailure(error);
}
core.warning(`Failed to ${option} dependency graph. Will continue.\n${String(error)}`);
}
function getOctokit() {
return github.getOctokit(getGithubToken());
}
@@ -140181,6 +140182,30 @@ function sanitize(value) {
}
/***/ }),
/***/ 36976:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PostActionJobFailure = void 0;
class PostActionJobFailure extends Error {
constructor(error) {
if (error instanceof Error) {
super(error.message);
this.name = error.name;
this.stack = error.stack;
}
else {
super(String(error));
}
}
}
exports.PostActionJobFailure = PostActionJobFailure;
/***/ }),
/***/ 23584:
@@ -140300,7 +140325,7 @@ function validateGradleWrapper(buildRootDirectory) {
}
function verifyExists(file, description) {
if (!fs_1.default.existsSync(file)) {
throw new Error(`Cannot locate ${description} at '${file}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.`);
throw new Error(`Cannot locate ${description} at '${file}'. Specify 'gradle-version' for projects without Gradle wrapper configured.`);
}
}
function verifyIsExecutableScript(toExecute) {
@@ -140344,7 +140369,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.DependencyGraphOption = exports.parseNumericInput = exports.getArtifactRetentionDays = exports.getDependencyGraphOption = exports.isDependencyGraphEnabled = exports.isJobSummaryEnabled = exports.getGithubToken = exports.getJobMatrix = exports.getArguments = exports.getGradleExecutable = exports.getGradleVersion = exports.getBuildRootDirectory = exports.getCacheExcludes = exports.getCacheIncludes = exports.getCacheEncryptionKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheStrictMatch = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
exports.JobSummaryOption = exports.DependencyGraphOption = exports.parseNumericInput = exports.getArtifactRetentionDays = exports.getDependencyGraphContinueOnFailure = exports.getDependencyGraphOption = exports.getPRCommentOption = exports.getJobSummaryOption = exports.isJobSummaryEnabled = exports.getGithubToken = exports.getJobMatrix = exports.getArguments = exports.getGradleVersion = exports.getBuildRootDirectory = exports.getCacheExcludes = exports.getCacheIncludes = exports.getCacheEncryptionKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheStrictMatch = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
const core = __importStar(__nccwpck_require__(42186));
const string_argv_1 = __nccwpck_require__(19663);
function isCacheDisabled() {
@@ -140395,10 +140420,6 @@ function getGradleVersion() {
return core.getInput('gradle-version');
}
exports.getGradleVersion = getGradleVersion;
function getGradleExecutable() {
return core.getInput('gradle-executable');
}
exports.getGradleExecutable = getGradleExecutable;
function getArguments() {
const input = core.getInput('arguments');
return (0, string_argv_1.parseArgsStringToArgv)(input);
@@ -140416,10 +140437,26 @@ function isJobSummaryEnabled() {
return getBooleanInput('generate-job-summary', true);
}
exports.isJobSummaryEnabled = isJobSummaryEnabled;
function isDependencyGraphEnabled() {
return getBooleanInput('generate-dependency-graph', true);
function getJobSummaryOption() {
return parseJobSummaryOption('add-job-summary');
}
exports.getJobSummaryOption = getJobSummaryOption;
function getPRCommentOption() {
return parseJobSummaryOption('add-job-summary-as-pr-comment');
}
exports.getPRCommentOption = getPRCommentOption;
function parseJobSummaryOption(paramName) {
const val = core.getInput(paramName);
switch (val.toLowerCase().trim()) {
case 'never':
return JobSummaryOption.Never;
case 'always':
return JobSummaryOption.Always;
case 'on-failure':
return JobSummaryOption.OnFailure;
}
throw TypeError(`The value '${val}' is not valid for ${paramName}. Valid values are: [never, always, on-failure].`);
}
exports.isDependencyGraphEnabled = isDependencyGraphEnabled;
function getDependencyGraphOption() {
const val = core.getInput('dependency-graph');
switch (val.toLowerCase().trim()) {
@@ -140434,9 +140471,13 @@ function getDependencyGraphOption() {
case 'download-and-submit':
return DependencyGraphOption.DownloadAndSubmit;
}
throw TypeError(`The value '${val} is not valid for 'dependency-graph. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`);
throw TypeError(`The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`);
}
exports.getDependencyGraphOption = getDependencyGraphOption;
function getDependencyGraphContinueOnFailure() {
return getBooleanInput('dependency-graph-continue-on-failure', true);
}
exports.getDependencyGraphContinueOnFailure = getDependencyGraphContinueOnFailure;
function getArtifactRetentionDays() {
const val = core.getInput('artifact-retention-days');
return parseNumericInput('artifact-retention-days', val, 0);
@@ -140473,6 +140514,12 @@ var DependencyGraphOption;
DependencyGraphOption["GenerateAndUpload"] = "generate-and-upload";
DependencyGraphOption["DownloadAndSubmit"] = "download-and-submit";
})(DependencyGraphOption || (exports.DependencyGraphOption = DependencyGraphOption = {}));
var JobSummaryOption;
(function (JobSummaryOption) {
JobSummaryOption["Never"] = "never";
JobSummaryOption["Always"] = "always";
JobSummaryOption["OnFailure"] = "on-failure";
})(JobSummaryOption || (exports.JobSummaryOption = JobSummaryOption = {}));
/***/ }),
@@ -140515,48 +140562,89 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.logJobSummary = exports.writeJobSummary = void 0;
exports.generateJobSummary = void 0;
const core = __importStar(__nccwpck_require__(42186));
const github = __importStar(__nccwpck_require__(95438));
const summary_1 = __nccwpck_require__(81327);
const request_error_1 = __nccwpck_require__(10537);
const params = __importStar(__nccwpck_require__(23885));
const cache_reporting_1 = __nccwpck_require__(66674);
function writeJobSummary(buildResults, cacheListener) {
function generateJobSummary(buildResults, cacheListener) {
return __awaiter(this, void 0, void 0, function* () {
core.info('Writing job summary');
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.');
const summaryTable = renderSummaryTable(buildResults);
const cachingReport = (0, cache_reporting_1.generateCachingReport)(cacheListener);
if (shouldGenerateJobSummary(buildResults)) {
core.info('Generating Job Summary');
core.summary.addRaw(summaryTable);
core.summary.addRaw(cachingReport);
yield core.summary.write();
}
else {
writeSummaryTable(buildResults);
core.info('============================');
core.info(summaryTable);
core.info('============================');
core.info(cachingReport);
core.info('============================');
}
if (shouldAddPRComment(buildResults)) {
yield addPRComment(summaryTable);
}
(0, cache_reporting_1.writeCachingReport)(cacheListener);
yield core.summary.write();
});
}
exports.writeJobSummary = writeJobSummary;
function logJobSummary(buildResults, cacheListener) {
exports.generateJobSummary = generateJobSummary;
function addPRComment(jobSummary) {
return __awaiter(this, void 0, void 0, function* () {
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be logged.');
const context = github.context;
if (context.payload.pull_request == null) {
core.info('No pull_request trigger: not adding PR comment');
return;
}
else {
logSummaryTable(buildResults);
const pull_request_number = context.payload.pull_request.number;
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`);
const prComment = `<h3>Job Summary for gradle-build-action</h3>
<h5>${github.context.workflow} :: <em>${github.context.job}</em></h5>
${jobSummary}`;
const github_token = params.getGithubToken();
const octokit = github.getOctokit(github_token);
try {
yield octokit.rest.issues.createComment(Object.assign(Object.assign({}, context.repo), { issue_number: pull_request_number, body: prComment }));
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
core.warning(buildWarningMessage(error));
}
else {
throw error;
}
}
(0, cache_reporting_1.logCachingReport)(cacheListener);
});
}
exports.logJobSummary = logJobSummary;
function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3);
core.summary.addRaw(`
function buildWarningMessage(error) {
const mainWarning = `Failed to generate PR comment.\n${String(error)}`;
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'pull-requests: write' permission is available for the workflow job.
Note that this permission is never available for a workflow triggered from a repository fork.
`;
}
return mainWarning;
}
function renderSummaryTable(results) {
if (results.length === 0) {
return 'No Gradle build results detected.';
}
return `
<table>
<tr>
<th>Root Project</th>
<th>Gradle Root Project</th>
<th>Requested Tasks</th>
<th>Gradle Version</th>
<th>Build Outcome</th>
<th>Build Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table>
`);
`;
}
function renderBuildResultRow(result) {
return `
@@ -140585,16 +140673,28 @@ function renderBuildScanBadge(outcomeText, outcomeColor, targetUrl) {
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />`;
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
}
function logSummaryTable(results) {
core.info('============================');
core.info('Gradle Builds');
core.info('----------------------------');
core.info('Root Project | Requested Tasks | Gradle Version | Build Outcome | Build Scan®');
core.info('----------------------------');
for (const result of results) {
core.info(`${result.rootProjectName} | ${result.requestedTasks} | ${result.gradleVersion} | ${result.buildFailed ? 'FAILED' : 'SUCCESS'} | ${result.buildScanFailed ? 'Publish failed' : result.buildScanUri}`);
function shouldGenerateJobSummary(buildResults) {
if (!process.env[summary_1.SUMMARY_ENV_VAR]) {
return false;
}
if (!params.isJobSummaryEnabled()) {
return false;
}
return shouldAddJobSummary(params.getJobSummaryOption(), buildResults);
}
function shouldAddPRComment(buildResults) {
return shouldAddJobSummary(params.getPRCommentOption(), buildResults);
}
function shouldAddJobSummary(option, buildResults) {
switch (option) {
case params.JobSummaryOption.Always:
return true;
case params.JobSummaryOption.Never:
return false;
case params.JobSummaryOption.OnFailure:
core.info(`Got these build results: ${JSON.stringify(buildResults)}`);
return buildResults.some(result => result.buildFailed);
}
core.info('============================');
}
@@ -140662,6 +140762,7 @@ function run() {
core.info(error.stack);
}
}
process.exit();
});
}
exports.run = run;
@@ -140718,19 +140819,13 @@ const cache = __importStar(__nccwpck_require__(27799));
const toolCache = __importStar(__nccwpck_require__(27784));
const gradlew = __importStar(__nccwpck_require__(32335));
const params = __importStar(__nccwpck_require__(23885));
const layout = __importStar(__nccwpck_require__(28182));
const cache_utils_1 = __nccwpck_require__(41678);
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions';
function provisionGradle() {
return __awaiter(this, void 0, void 0, function* () {
const gradleVersion = params.getGradleVersion();
if (gradleVersion !== '' && gradleVersion !== 'wrapper') {
return addToPath(path.resolve(yield installGradle(gradleVersion)));
}
const gradleExecutable = params.getGradleExecutable();
if (gradleExecutable !== '') {
const workspaceDirectory = layout.workspaceDirectory();
return addToPath(path.resolve(workspaceDirectory, gradleExecutable));
return addToPath(yield installGradle(gradleVersion));
}
return undefined;
});
@@ -140990,22 +141085,23 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.complete = exports.setup = void 0;
const core = __importStar(__nccwpck_require__(42186));
const exec = __importStar(__nccwpck_require__(71514));
const summary_1 = __nccwpck_require__(81327);
const path = __importStar(__nccwpck_require__(71017));
const os = __importStar(__nccwpck_require__(22037));
const caches = __importStar(__nccwpck_require__(23800));
const layout = __importStar(__nccwpck_require__(28182));
const params = __importStar(__nccwpck_require__(23885));
const dependencyGraph = __importStar(__nccwpck_require__(80));
const job_summary_1 = __nccwpck_require__(87345);
const jobSummary = __importStar(__nccwpck_require__(87345));
const build_results_1 = __nccwpck_require__(82107);
const cache_reporting_1 = __nccwpck_require__(66674);
const daemon_controller_1 = __nccwpck_require__(85146);
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED';
const USER_HOME = 'USER_HOME';
const GRADLE_USER_HOME = 'GRADLE_USER_HOME';
const CACHE_LISTENER = 'CACHE_LISTENER';
function setup() {
return __awaiter(this, void 0, void 0, function* () {
const userHome = yield determineUserHome();
const gradleUserHome = yield determineGradleUserHome();
if (process.env[GRADLE_SETUP_VAR]) {
core.info('Gradle setup only performed on first gradle-build-action step in workflow.');
@@ -141013,9 +141109,10 @@ function setup() {
}
core.exportVariable(GRADLE_SETUP_VAR, true);
core.saveState(GRADLE_SETUP_VAR, true);
core.saveState(USER_HOME, userHome);
core.saveState(GRADLE_USER_HOME, gradleUserHome);
const cacheListener = new cache_reporting_1.CacheListener();
yield caches.restore(gradleUserHome, cacheListener);
yield caches.restore(userHome, gradleUserHome, cacheListener);
core.saveState(CACHE_LISTENER, cacheListener.stringify());
yield dependencyGraph.setup(params.getDependencyGraphOption());
});
@@ -141029,16 +141126,12 @@ function complete() {
}
core.info('In post-action step');
const buildResults = (0, build_results_1.loadBuildResults)();
const userHome = core.getState(USER_HOME);
const gradleUserHome = core.getState(GRADLE_USER_HOME);
const cacheListener = cache_reporting_1.CacheListener.rehydrate(core.getState(CACHE_LISTENER));
const daemonController = new daemon_controller_1.DaemonController(buildResults);
yield caches.save(gradleUserHome, cacheListener, daemonController);
if (shouldGenerateJobSummary()) {
yield (0, job_summary_1.writeJobSummary)(buildResults, cacheListener);
}
else {
(0, job_summary_1.logJobSummary)(buildResults, cacheListener);
}
yield caches.save(userHome, gradleUserHome, cacheListener, daemonController);
yield jobSummary.generateJobSummary(buildResults, cacheListener);
yield dependencyGraph.complete(params.getDependencyGraphOption());
core.info('Completed post-action step');
});
@@ -141068,12 +141161,6 @@ function determineUserHome() {
return userHome;
});
}
function shouldGenerateJobSummary() {
if (!process.env[summary_1.SUMMARY_ENV_VAR]) {
return false;
}
return params.isJobSummaryEnabled();
}
/***/ }),

File diff suppressed because one or more lines are too long

403
dist/post/index.js vendored
View File

@@ -104077,7 +104077,7 @@ var __disposeResources;
__addDisposableResource = function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object") throw new TypeError("Object expected.");
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
@@ -136052,17 +136052,18 @@ const cache_extract_entries_1 = __nccwpck_require__(76161);
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key';
exports.META_FILE_DIR = '.gradle-build-action';
class GradleStateCache {
constructor(gradleUserHome) {
constructor(userHome, gradleUserHome) {
this.userHome = userHome;
this.gradleUserHome = gradleUserHome;
this.cacheName = 'gradle';
this.cacheDescription = 'Gradle User Home';
}
init() {
const actionCacheDir = path_1.default.resolve(this.gradleUserHome, '.gradle-build-action');
fs_1.default.mkdirSync(actionCacheDir, { recursive: true });
const initScriptsDir = path_1.default.resolve(this.gradleUserHome, 'init.d');
fs_1.default.mkdirSync(initScriptsDir, { recursive: true });
this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir);
this.initializeGradleUserHome();
const encryptionKey = params.getCacheEncryptionKey();
if (encryptionKey) {
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey);
}
}
cacheOutputExists() {
const cachesDir = path_1.default.resolve(this.gradleUserHome, 'caches');
@@ -136174,32 +136175,47 @@ class GradleStateCache {
}
return path_1.default.resolve(this.gradleUserHome, rawPath);
}
initializeGradleUserHome(gradleUserHome, initScriptsDir) {
const gradleProperties = path_1.default.resolve(gradleUserHome, 'gradle.properties');
const existingGradleProperties = fs_1.default.existsSync(gradleProperties)
? fs_1.default.readFileSync(gradleProperties, 'utf8')
: '';
if (!existingGradleProperties.includes('org.gradle.java.installations.fromEnv=')) {
fs_1.default.appendFileSync(gradleProperties, `
# Auto-detect pre-installed JDKs
org.gradle.java.installations.fromEnv=JAVA_HOME_8_X64,JAVA_HOME_11_X64,JAVA_HOME_17_X64
`);
}
initializeGradleUserHome() {
const actionCacheDir = path_1.default.resolve(this.gradleUserHome, '.gradle-build-action');
fs_1.default.mkdirSync(actionCacheDir, { recursive: true });
this.copyInitScripts();
this.registerToolchains();
}
copyInitScripts() {
const initScriptsDir = path_1.default.resolve(this.gradleUserHome, 'init.d');
fs_1.default.mkdirSync(initScriptsDir, { recursive: true });
const initScriptFilenames = [
'gradle-build-action.build-result-capture.init.gradle',
'gradle-build-action.build-result-capture-service.plugin.groovy',
'gradle-build-action.github-dependency-graph.init.gradle',
'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy',
'gradle-build-action.inject-gradle-enterprise.init.gradle'
'gradle-build-action.inject-develocity.init.gradle'
];
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readInitScriptAsString(initScriptFilename);
const initScriptContent = this.readResourceFileAsString('init-scripts', initScriptFilename);
const initScriptPath = path_1.default.resolve(initScriptsDir, initScriptFilename);
fs_1.default.writeFileSync(initScriptPath, initScriptContent);
}
}
readInitScriptAsString(resource) {
const absolutePath = path_1.default.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource);
registerToolchains() {
const preInstalledToolchains = this.readResourceFileAsString('toolchains.xml');
const m2dir = path_1.default.resolve(this.userHome, '.m2');
const toolchainXmlTarget = path_1.default.resolve(m2dir, 'toolchains.xml');
if (!fs_1.default.existsSync(toolchainXmlTarget)) {
fs_1.default.mkdirSync(m2dir, { recursive: true });
fs_1.default.writeFileSync(toolchainXmlTarget, preInstalledToolchains);
core.info(`Wrote default JDK locations to ${toolchainXmlTarget}`);
}
else {
const existingToolchainContent = fs_1.default.readFileSync(toolchainXmlTarget, 'utf8');
const appendedContent = preInstalledToolchains.split('<toolchains>').pop();
const mergedContent = existingToolchainContent.replace('</toolchains>', appendedContent);
fs_1.default.writeFileSync(toolchainXmlTarget, mergedContent);
core.info(`Merged default JDK locations into ${toolchainXmlTarget}`);
}
}
readResourceFileAsString(...paths) {
const absolutePath = path_1.default.resolve(__dirname, '..', '..', 'src', 'resources', ...paths);
return fs_1.default.readFileSync(absolutePath, 'utf8');
}
debugReportGradleUserHomeSize(label) {
@@ -136399,7 +136415,6 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ConfigurationCacheEntryExtractor = exports.GradleHomeEntryExtractor = void 0;
const path_1 = __importDefault(__nccwpck_require__(71017));
const fs_1 = __importDefault(__nccwpck_require__(57147));
const crypto_1 = __importDefault(__nccwpck_require__(6113));
const core = __importStar(__nccwpck_require__(42186));
const glob = __importStar(__nccwpck_require__(28090));
const semver = __importStar(__nccwpck_require__(11383));
@@ -136637,8 +136652,6 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
this.markNotRestored(listener, 'Encryption Key was not provided');
return;
}
const encryptionKey = this.getAESEncryptionKey();
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey);
return yield _super.restore.call(this, listener);
});
}
@@ -136670,11 +136683,6 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
yield _super.extract.call(this, listener);
});
}
getAESEncryptionKey() {
const secret = params.getCacheEncryptionKey();
const key = crypto_1.default.pbkdf2Sync(secret, '', 1000, 16, 'sha256');
return key.toString('base64');
}
getExtractedCacheEntryDefinitions() {
const groupedResults = this.getConfigCacheDirectoriesWithAssociatedBuildResults();
return Object.entries(groupedResults).map(([configCachePath, pathResults]) => {
@@ -136737,8 +136745,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.logCachingReport = exports.writeCachingReport = exports.CacheEntryListener = exports.CacheListener = void 0;
const core = __importStar(__nccwpck_require__(42186));
exports.generateCachingReport = exports.CacheEntryListener = exports.CacheListener = void 0;
const cache = __importStar(__nccwpck_require__(27799));
class CacheListener {
constructor() {
@@ -136826,37 +136833,36 @@ class CacheEntryListener {
}
}
exports.CacheEntryListener = CacheEntryListener;
function writeCachingReport(listener) {
function generateCachingReport(listener) {
const entries = listener.cacheEntries;
core.summary.addRaw(`\n<details><summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>\n`);
core.summary.addTable([
[
{ data: '', header: true },
{ data: 'Count', header: true },
{ data: 'Total Size (Mb)', header: true }
],
['Entries Restored', `${getCount(entries, e => e.restoredSize)}`, `${getSize(entries, e => e.restoredSize)}`],
['Entries Saved', `${getCount(entries, e => e.savedSize)}`, `${getSize(entries, e => e.savedSize)}`]
]);
core.summary.addHeading('Cache Entry Details', 5);
const entryDetails = renderEntryDetails(listener);
core.summary.addRaw(`<pre>
${entryDetails}
return `
<details>
<summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>
${renderEntryTable(entries)}
<h5>Cache Entry Details</h5>
<pre>
${renderEntryDetails(listener)}
</pre>
</details>
`);
`;
}
exports.writeCachingReport = writeCachingReport;
function logCachingReport(listener) {
const entries = listener.cacheEntries;
core.startGroup(`Caching for gradle-build-action was ${listener.cacheStatus} - expand for details`);
core.info(`Entries Restored: ${getCount(entries, e => e.restoredSize)} (${getSize(entries, e => e.restoredSize)} Mb)`);
core.info(`Entries Saved : ${getCount(entries, e => e.savedSize)} (${getSize(entries, e => e.savedSize)} Mb)`);
core.info(`Cache Entry Details`);
core.info(renderEntryDetails(listener));
core.endGroup();
exports.generateCachingReport = generateCachingReport;
function renderEntryTable(entries) {
return `
<table>
<tr><td></td><th>Count</th><th>Total Size (Mb)</th></tr>
<tr><td>Entries Restored</td>
<td>${getCount(entries, e => e.restoredSize)}</td>
<td>${getSize(entries, e => e.restoredSize)}</td>
</tr>
<tr><td>Entries Saved</td>
<td>${getCount(entries, e => e.savedSize)}</td>
<td>${getSize(entries, e => e.savedSize)}</td>
</tr>
</table>
`;
}
exports.logCachingReport = logCachingReport;
function renderEntryDetails(listener) {
return listener.cacheEntries
.map(entry => {
@@ -136964,7 +136970,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.tryDelete = exports.handleCacheFailure = exports.cacheDebug = exports.saveCache = exports.restoreCache = exports.hashStrings = exports.hashFileNames = exports.getUniqueLabelForJobInstanceValues = exports.getUniqueLabelForJobInstance = exports.getCacheKeyForJob = exports.getCacheKeyPrefix = exports.generateCacheKey = exports.CacheKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
exports.tryDelete = exports.handleCacheFailure = exports.cacheDebug = exports.saveCache = exports.restoreCache = exports.hashStrings = exports.hashFileNames = exports.getCacheKeyPrefix = exports.generateCacheKey = exports.CacheKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
const core = __importStar(__nccwpck_require__(42186));
const cache = __importStar(__nccwpck_require__(27799));
const github = __importStar(__nccwpck_require__(95438));
@@ -137036,34 +137042,16 @@ function getCacheKeyEnvironment() {
return process.env[CACHE_KEY_OS_VAR] || runnerOs;
}
function getCacheKeyJob() {
return process.env[CACHE_KEY_JOB_VAR] || getCacheKeyForJob(github.context.workflow, github.context.job);
return process.env[CACHE_KEY_JOB_VAR] || github.context.job;
}
function getCacheKeyForJob(workflowName, jobId) {
const sanitizedWorkflow = workflowName.replace(/,/g, '').toLowerCase();
return `${sanitizedWorkflow}-${jobId}`;
}
exports.getCacheKeyForJob = getCacheKeyForJob;
function getCacheKeyJobInstance() {
const override = process.env[CACHE_KEY_JOB_INSTANCE_VAR];
if (override) {
return override;
}
const workflowName = github.context.workflow;
const workflowJobContext = params.getJobMatrix();
return hashStrings([workflowJobContext]);
}
function getUniqueLabelForJobInstance() {
return getUniqueLabelForJobInstanceValues(github.context.workflow, github.context.job, params.getJobMatrix());
}
exports.getUniqueLabelForJobInstance = getUniqueLabelForJobInstance;
function getUniqueLabelForJobInstanceValues(workflow, jobId, matrixJson) {
const matrix = JSON.parse(matrixJson);
const matrixString = Object.values(matrix).join('-');
const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`;
return sanitize(label);
}
exports.getUniqueLabelForJobInstanceValues = getUniqueLabelForJobInstanceValues;
function sanitize(value) {
return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase();
return hashStrings([workflowName, workflowJobContext]);
}
function getCacheKeyJobExecution() {
return process.env[CACHE_KEY_JOB_EXECUTION_VAR] || github.context.sha;
@@ -137235,14 +137223,14 @@ const cache_utils_1 = __nccwpck_require__(41678);
const cache_base_1 = __nccwpck_require__(47591);
const cache_cleaner_1 = __nccwpck_require__(57);
const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED';
function restore(gradleUserHome, cacheListener) {
function restore(userHome, gradleUserHome, cacheListener) {
return __awaiter(this, void 0, void 0, function* () {
if (process.env[CACHE_RESTORED_VAR]) {
core.info('Cache only restored on first action step.');
return;
}
core.exportVariable(CACHE_RESTORED_VAR, true);
const gradleStateCache = new cache_base_1.GradleStateCache(gradleUserHome);
const gradleStateCache = new cache_base_1.GradleStateCache(userHome, gradleUserHome);
if ((0, cache_utils_1.isCacheDisabled)()) {
core.info('Cache is disabled: will not restore state from previous builds.');
gradleStateCache.init();
@@ -137277,7 +137265,7 @@ function restore(gradleUserHome, cacheListener) {
});
}
exports.restore = restore;
function save(gradleUserHome, cacheListener, daemonController) {
function save(userHome, gradleUserHome, cacheListener, daemonController) {
return __awaiter(this, void 0, void 0, function* () {
if ((0, cache_utils_1.isCacheDisabled)()) {
core.info('Cache is disabled: will not save state for later builds.');
@@ -137304,7 +137292,7 @@ function save(gradleUserHome, cacheListener, daemonController) {
}
}
yield core.group('Caching Gradle state', () => __awaiter(this, void 0, void 0, function* () {
return new cache_base_1.GradleStateCache(gradleUserHome).save(cacheListener);
return new cache_base_1.GradleStateCache(userHome, gradleUserHome).save(cacheListener);
}));
});
}
@@ -137436,6 +137424,7 @@ const request_error_1 = __nccwpck_require__(10537);
const path = __importStar(__nccwpck_require__(71017));
const fs_1 = __importDefault(__nccwpck_require__(57147));
const layout = __importStar(__nccwpck_require__(28182));
const errors_1 = __nccwpck_require__(36976);
const input_params_1 = __nccwpck_require__(23885);
const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_';
function setup(option) {
@@ -137448,16 +137437,22 @@ function setup(option) {
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
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);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', (0, input_params_1.getDependencyGraphContinueOnFailure)());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
maybeExportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
});
}
exports.setup = setup;
function maybeExportVariable(variableName, value) {
if (!process.env[variableName]) {
core.exportVariable(variableName, value);
}
}
function complete(option) {
return __awaiter(this, void 0, void 0, function* () {
try {
@@ -137474,7 +137469,7 @@ function complete(option) {
}
}
catch (e) {
core.warning(`Failed to ${option} dependency graph. Will continue. ${String(e)}`);
warnOrFail(option, e);
}
});
}
@@ -137505,7 +137500,7 @@ function downloadAndSubmitDependencyGraphs() {
yield submitDependencyGraphs(yield downloadDependencyGraphs());
}
catch (e) {
core.warning(`Download and submit dependency graph failed. Will continue. ${String(e)}`);
warnOrFail(input_params_1.DependencyGraphOption.DownloadAndSubmit, e);
}
});
}
@@ -137517,7 +137512,7 @@ function submitDependencyGraphs(dependencyGraphFiles) {
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
core.warning(buildWarningMessage(jsonFile, error));
throw new Error(translateErrorMessage(jsonFile, error));
}
else {
throw error;
@@ -137526,9 +137521,9 @@ function submitDependencyGraphs(dependencyGraphFiles) {
}
});
}
function buildWarningMessage(jsonFile, error) {
function translateErrorMessage(jsonFile, error) {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
const mainWarning = `Failed to submit dependency graph ${relativeJsonFile}.\n${String(error)}`;
const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${String(error)}`;
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'contents: write' permission is available for the workflow job.
@@ -137583,6 +137578,12 @@ function findDependencyGraphFiles(dir) {
return graphFiles;
});
}
function warnOrFail(option, error) {
if (!(0, input_params_1.getDependencyGraphContinueOnFailure)()) {
throw new errors_1.PostActionJobFailure(error);
}
core.warning(`Failed to ${option} dependency graph. Will continue.\n${String(error)}`);
}
function getOctokit() {
return github.getOctokit(getGithubToken());
}
@@ -137634,6 +137635,30 @@ function sanitize(value) {
}
/***/ }),
/***/ 36976:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PostActionJobFailure = void 0;
class PostActionJobFailure extends Error {
constructor(error) {
if (error instanceof Error) {
super(error.message);
this.name = error.name;
this.stack = error.stack;
}
else {
super(String(error));
}
}
}
exports.PostActionJobFailure = PostActionJobFailure;
/***/ }),
/***/ 23885:
@@ -137665,7 +137690,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.DependencyGraphOption = exports.parseNumericInput = exports.getArtifactRetentionDays = exports.getDependencyGraphOption = exports.isDependencyGraphEnabled = exports.isJobSummaryEnabled = exports.getGithubToken = exports.getJobMatrix = exports.getArguments = exports.getGradleExecutable = exports.getGradleVersion = exports.getBuildRootDirectory = exports.getCacheExcludes = exports.getCacheIncludes = exports.getCacheEncryptionKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheStrictMatch = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
exports.JobSummaryOption = exports.DependencyGraphOption = exports.parseNumericInput = exports.getArtifactRetentionDays = exports.getDependencyGraphContinueOnFailure = exports.getDependencyGraphOption = exports.getPRCommentOption = exports.getJobSummaryOption = exports.isJobSummaryEnabled = exports.getGithubToken = exports.getJobMatrix = exports.getArguments = exports.getGradleVersion = exports.getBuildRootDirectory = exports.getCacheExcludes = exports.getCacheIncludes = exports.getCacheEncryptionKey = exports.isCacheCleanupEnabled = exports.isCacheDebuggingEnabled = exports.isCacheStrictMatch = exports.isCacheOverwriteExisting = exports.isCacheWriteOnly = exports.isCacheReadOnly = exports.isCacheDisabled = void 0;
const core = __importStar(__nccwpck_require__(42186));
const string_argv_1 = __nccwpck_require__(19663);
function isCacheDisabled() {
@@ -137716,10 +137741,6 @@ function getGradleVersion() {
return core.getInput('gradle-version');
}
exports.getGradleVersion = getGradleVersion;
function getGradleExecutable() {
return core.getInput('gradle-executable');
}
exports.getGradleExecutable = getGradleExecutable;
function getArguments() {
const input = core.getInput('arguments');
return (0, string_argv_1.parseArgsStringToArgv)(input);
@@ -137737,10 +137758,26 @@ function isJobSummaryEnabled() {
return getBooleanInput('generate-job-summary', true);
}
exports.isJobSummaryEnabled = isJobSummaryEnabled;
function isDependencyGraphEnabled() {
return getBooleanInput('generate-dependency-graph', true);
function getJobSummaryOption() {
return parseJobSummaryOption('add-job-summary');
}
exports.getJobSummaryOption = getJobSummaryOption;
function getPRCommentOption() {
return parseJobSummaryOption('add-job-summary-as-pr-comment');
}
exports.getPRCommentOption = getPRCommentOption;
function parseJobSummaryOption(paramName) {
const val = core.getInput(paramName);
switch (val.toLowerCase().trim()) {
case 'never':
return JobSummaryOption.Never;
case 'always':
return JobSummaryOption.Always;
case 'on-failure':
return JobSummaryOption.OnFailure;
}
throw TypeError(`The value '${val}' is not valid for ${paramName}. Valid values are: [never, always, on-failure].`);
}
exports.isDependencyGraphEnabled = isDependencyGraphEnabled;
function getDependencyGraphOption() {
const val = core.getInput('dependency-graph');
switch (val.toLowerCase().trim()) {
@@ -137755,9 +137792,13 @@ function getDependencyGraphOption() {
case 'download-and-submit':
return DependencyGraphOption.DownloadAndSubmit;
}
throw TypeError(`The value '${val} is not valid for 'dependency-graph. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`);
throw TypeError(`The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`);
}
exports.getDependencyGraphOption = getDependencyGraphOption;
function getDependencyGraphContinueOnFailure() {
return getBooleanInput('dependency-graph-continue-on-failure', true);
}
exports.getDependencyGraphContinueOnFailure = getDependencyGraphContinueOnFailure;
function getArtifactRetentionDays() {
const val = core.getInput('artifact-retention-days');
return parseNumericInput('artifact-retention-days', val, 0);
@@ -137794,6 +137835,12 @@ var DependencyGraphOption;
DependencyGraphOption["GenerateAndUpload"] = "generate-and-upload";
DependencyGraphOption["DownloadAndSubmit"] = "download-and-submit";
})(DependencyGraphOption || (exports.DependencyGraphOption = DependencyGraphOption = {}));
var JobSummaryOption;
(function (JobSummaryOption) {
JobSummaryOption["Never"] = "never";
JobSummaryOption["Always"] = "always";
JobSummaryOption["OnFailure"] = "on-failure";
})(JobSummaryOption || (exports.JobSummaryOption = JobSummaryOption = {}));
/***/ }),
@@ -137836,48 +137883,89 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.logJobSummary = exports.writeJobSummary = void 0;
exports.generateJobSummary = void 0;
const core = __importStar(__nccwpck_require__(42186));
const github = __importStar(__nccwpck_require__(95438));
const summary_1 = __nccwpck_require__(81327);
const request_error_1 = __nccwpck_require__(10537);
const params = __importStar(__nccwpck_require__(23885));
const cache_reporting_1 = __nccwpck_require__(66674);
function writeJobSummary(buildResults, cacheListener) {
function generateJobSummary(buildResults, cacheListener) {
return __awaiter(this, void 0, void 0, function* () {
core.info('Writing job summary');
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.');
const summaryTable = renderSummaryTable(buildResults);
const cachingReport = (0, cache_reporting_1.generateCachingReport)(cacheListener);
if (shouldGenerateJobSummary(buildResults)) {
core.info('Generating Job Summary');
core.summary.addRaw(summaryTable);
core.summary.addRaw(cachingReport);
yield core.summary.write();
}
else {
writeSummaryTable(buildResults);
core.info('============================');
core.info(summaryTable);
core.info('============================');
core.info(cachingReport);
core.info('============================');
}
if (shouldAddPRComment(buildResults)) {
yield addPRComment(summaryTable);
}
(0, cache_reporting_1.writeCachingReport)(cacheListener);
yield core.summary.write();
});
}
exports.writeJobSummary = writeJobSummary;
function logJobSummary(buildResults, cacheListener) {
exports.generateJobSummary = generateJobSummary;
function addPRComment(jobSummary) {
return __awaiter(this, void 0, void 0, function* () {
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be logged.');
const context = github.context;
if (context.payload.pull_request == null) {
core.info('No pull_request trigger: not adding PR comment');
return;
}
else {
logSummaryTable(buildResults);
const pull_request_number = context.payload.pull_request.number;
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`);
const prComment = `<h3>Job Summary for gradle-build-action</h3>
<h5>${github.context.workflow} :: <em>${github.context.job}</em></h5>
${jobSummary}`;
const github_token = params.getGithubToken();
const octokit = github.getOctokit(github_token);
try {
yield octokit.rest.issues.createComment(Object.assign(Object.assign({}, context.repo), { issue_number: pull_request_number, body: prComment }));
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
core.warning(buildWarningMessage(error));
}
else {
throw error;
}
}
(0, cache_reporting_1.logCachingReport)(cacheListener);
});
}
exports.logJobSummary = logJobSummary;
function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3);
core.summary.addRaw(`
function buildWarningMessage(error) {
const mainWarning = `Failed to generate PR comment.\n${String(error)}`;
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'pull-requests: write' permission is available for the workflow job.
Note that this permission is never available for a workflow triggered from a repository fork.
`;
}
return mainWarning;
}
function renderSummaryTable(results) {
if (results.length === 0) {
return 'No Gradle build results detected.';
}
return `
<table>
<tr>
<th>Root Project</th>
<th>Gradle Root Project</th>
<th>Requested Tasks</th>
<th>Gradle Version</th>
<th>Build Outcome</th>
<th>Build Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table>
`);
`;
}
function renderBuildResultRow(result) {
return `
@@ -137906,16 +137994,28 @@ function renderBuildScanBadge(outcomeText, outcomeColor, targetUrl) {
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />`;
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
}
function logSummaryTable(results) {
core.info('============================');
core.info('Gradle Builds');
core.info('----------------------------');
core.info('Root Project | Requested Tasks | Gradle Version | Build Outcome | Build Scan®');
core.info('----------------------------');
for (const result of results) {
core.info(`${result.rootProjectName} | ${result.requestedTasks} | ${result.gradleVersion} | ${result.buildFailed ? 'FAILED' : 'SUCCESS'} | ${result.buildScanFailed ? 'Publish failed' : result.buildScanUri}`);
function shouldGenerateJobSummary(buildResults) {
if (!process.env[summary_1.SUMMARY_ENV_VAR]) {
return false;
}
if (!params.isJobSummaryEnabled()) {
return false;
}
return shouldAddJobSummary(params.getJobSummaryOption(), buildResults);
}
function shouldAddPRComment(buildResults) {
return shouldAddJobSummary(params.getPRCommentOption(), buildResults);
}
function shouldAddJobSummary(option, buildResults) {
switch (option) {
case params.JobSummaryOption.Always:
return true;
case params.JobSummaryOption.Never:
return false;
case params.JobSummaryOption.OnFailure:
core.info(`Got these build results: ${JSON.stringify(buildResults)}`);
return buildResults.some(result => result.buildFailed);
}
core.info('============================');
}
@@ -137962,6 +138062,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.run = void 0;
const core = __importStar(__nccwpck_require__(42186));
const setupGradle = __importStar(__nccwpck_require__(18652));
const errors_1 = __nccwpck_require__(36976);
process.on('uncaughtException', e => handleFailure(e));
function run() {
return __awaiter(this, void 0, void 0, function* () {
@@ -137969,8 +138070,14 @@ function run() {
yield setupGradle.complete();
}
catch (error) {
handleFailure(error);
if (error instanceof errors_1.PostActionJobFailure) {
core.setFailed(String(error));
}
else {
handleFailure(error);
}
}
process.exit();
});
}
exports.run = run;
@@ -138075,22 +138182,23 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.complete = exports.setup = void 0;
const core = __importStar(__nccwpck_require__(42186));
const exec = __importStar(__nccwpck_require__(71514));
const summary_1 = __nccwpck_require__(81327);
const path = __importStar(__nccwpck_require__(71017));
const os = __importStar(__nccwpck_require__(22037));
const caches = __importStar(__nccwpck_require__(23800));
const layout = __importStar(__nccwpck_require__(28182));
const params = __importStar(__nccwpck_require__(23885));
const dependencyGraph = __importStar(__nccwpck_require__(80));
const job_summary_1 = __nccwpck_require__(87345);
const jobSummary = __importStar(__nccwpck_require__(87345));
const build_results_1 = __nccwpck_require__(82107);
const cache_reporting_1 = __nccwpck_require__(66674);
const daemon_controller_1 = __nccwpck_require__(85146);
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED';
const USER_HOME = 'USER_HOME';
const GRADLE_USER_HOME = 'GRADLE_USER_HOME';
const CACHE_LISTENER = 'CACHE_LISTENER';
function setup() {
return __awaiter(this, void 0, void 0, function* () {
const userHome = yield determineUserHome();
const gradleUserHome = yield determineGradleUserHome();
if (process.env[GRADLE_SETUP_VAR]) {
core.info('Gradle setup only performed on first gradle-build-action step in workflow.');
@@ -138098,9 +138206,10 @@ function setup() {
}
core.exportVariable(GRADLE_SETUP_VAR, true);
core.saveState(GRADLE_SETUP_VAR, true);
core.saveState(USER_HOME, userHome);
core.saveState(GRADLE_USER_HOME, gradleUserHome);
const cacheListener = new cache_reporting_1.CacheListener();
yield caches.restore(gradleUserHome, cacheListener);
yield caches.restore(userHome, gradleUserHome, cacheListener);
core.saveState(CACHE_LISTENER, cacheListener.stringify());
yield dependencyGraph.setup(params.getDependencyGraphOption());
});
@@ -138114,16 +138223,12 @@ function complete() {
}
core.info('In post-action step');
const buildResults = (0, build_results_1.loadBuildResults)();
const userHome = core.getState(USER_HOME);
const gradleUserHome = core.getState(GRADLE_USER_HOME);
const cacheListener = cache_reporting_1.CacheListener.rehydrate(core.getState(CACHE_LISTENER));
const daemonController = new daemon_controller_1.DaemonController(buildResults);
yield caches.save(gradleUserHome, cacheListener, daemonController);
if (shouldGenerateJobSummary()) {
yield (0, job_summary_1.writeJobSummary)(buildResults, cacheListener);
}
else {
(0, job_summary_1.logJobSummary)(buildResults, cacheListener);
}
yield caches.save(userHome, gradleUserHome, cacheListener, daemonController);
yield jobSummary.generateJobSummary(buildResults, cacheListener);
yield dependencyGraph.complete(params.getDependencyGraphOption());
core.info('Completed post-action step');
});
@@ -138153,12 +138258,6 @@ function determineUserHome() {
return userHome;
});
}
function shouldGenerateJobSummary() {
if (!process.env[summary_1.SUMMARY_ENV_VAR]) {
return false;
}
return params.isJobSummaryEnabled();
}
/***/ }),

File diff suppressed because one or more lines are too long

1966
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,9 +10,9 @@
"lint": "eslint src/**/*.ts",
"compile-main": "ncc build src/main.ts --out dist/main --source-map --no-source-map-register",
"compile-post": "ncc build src/post.ts --out dist/post --source-map --no-source-map-register",
"compile": "npm run compile-main && npm run compile-post",
"compile": "npm-run-all --parallel compile-*",
"check": "npm-run-all --parallel format lint",
"test": "jest",
"check": "npm run format && npm run lint",
"build": "npm run check && npm run compile",
"all": "npm run build && npm test"
},
@@ -45,14 +45,15 @@
"@types/jest": "29.5.11",
"@types/node": "20.10.0",
"@types/unzipper": "0.10.9",
"@typescript-eslint/parser": "6.15.0",
"@typescript-eslint/parser": "6.17.0",
"@vercel/ncc": "0.38.1",
"eslint": "8.56.0",
"eslint-plugin-github": "4.10.1",
"eslint-plugin-jest": "27.6.0",
"eslint-plugin-prettier": "5.1.1",
"eslint-plugin-jest": "27.6.1",
"eslint-plugin-prettier": "5.1.2",
"jest": "29.7.0",
"js-yaml": "4.1.0",
"npm-run-all": "4.1.5",
"patch-package": "8.0.0",
"prettier": "3.1.1",
"ts-jest": "29.1.1",

View File

@@ -17,22 +17,24 @@ export class GradleStateCache {
private cacheName: string
private cacheDescription: string
protected readonly userHome: string
protected readonly gradleUserHome: string
constructor(gradleUserHome: string) {
constructor(userHome: string, gradleUserHome: string) {
this.userHome = userHome
this.gradleUserHome = gradleUserHome
this.cacheName = 'gradle'
this.cacheDescription = 'Gradle User Home'
}
init(): void {
const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action')
fs.mkdirSync(actionCacheDir, {recursive: true})
this.initializeGradleUserHome()
const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d')
fs.mkdirSync(initScriptsDir, {recursive: true})
this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir)
// Export the GRADLE_ENCRYPTION_KEY variable if provided
const encryptionKey = params.getCacheEncryptionKey()
if (encryptionKey) {
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey)
}
}
cacheOutputExists(): boolean {
@@ -181,40 +183,59 @@ export class GradleStateCache {
return path.resolve(this.gradleUserHome, rawPath)
}
private initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
// Ensure that pre-installed java versions are detected. Only add property if it isn't already defined.
const gradleProperties = path.resolve(gradleUserHome, 'gradle.properties')
const existingGradleProperties = fs.existsSync(gradleProperties)
? fs.readFileSync(gradleProperties, 'utf8')
: ''
if (!existingGradleProperties.includes('org.gradle.java.installations.fromEnv=')) {
fs.appendFileSync(
gradleProperties,
`
# Auto-detect pre-installed JDKs
org.gradle.java.installations.fromEnv=JAVA_HOME_8_X64,JAVA_HOME_11_X64,JAVA_HOME_17_X64
`
)
}
private initializeGradleUserHome(): void {
// Create a directory for storing action metadata
const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action')
fs.mkdirSync(actionCacheDir, {recursive: true})
// Copy init scripts from src/resources
this.copyInitScripts()
// Copy the default toolchain definitions to `~/.m2/toolchains.xml`
this.registerToolchains()
}
private copyInitScripts(): void {
// Copy init scripts from src/resources to Gradle UserHome
const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d')
fs.mkdirSync(initScriptsDir, {recursive: true})
const initScriptFilenames = [
'gradle-build-action.build-result-capture.init.gradle',
'gradle-build-action.build-result-capture-service.plugin.groovy',
'gradle-build-action.github-dependency-graph.init.gradle',
'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy',
'gradle-build-action.inject-gradle-enterprise.init.gradle'
'gradle-build-action.inject-develocity.init.gradle'
]
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readInitScriptAsString(initScriptFilename)
const initScriptContent = this.readResourceFileAsString('init-scripts', initScriptFilename)
const initScriptPath = path.resolve(initScriptsDir, initScriptFilename)
fs.writeFileSync(initScriptPath, initScriptContent)
}
}
private readInitScriptAsString(resource: string): string {
private registerToolchains(): void {
const preInstalledToolchains = this.readResourceFileAsString('toolchains.xml')
const m2dir = path.resolve(this.userHome, '.m2')
const toolchainXmlTarget = path.resolve(m2dir, 'toolchains.xml')
if (!fs.existsSync(toolchainXmlTarget)) {
// Write a new toolchains.xml file if it doesn't exist
fs.mkdirSync(m2dir, {recursive: true})
fs.writeFileSync(toolchainXmlTarget, preInstalledToolchains)
core.info(`Wrote default JDK locations to ${toolchainXmlTarget}`)
} else {
// Merge into an existing toolchains.xml file
const existingToolchainContent = fs.readFileSync(toolchainXmlTarget, 'utf8')
const appendedContent = preInstalledToolchains.split('<toolchains>').pop()!
const mergedContent = existingToolchainContent.replace('</toolchains>', appendedContent)
fs.writeFileSync(toolchainXmlTarget, mergedContent)
core.info(`Merged default JDK locations into ${toolchainXmlTarget}`)
}
}
private readResourceFileAsString(...paths: string[]): string {
// Resolving relative to __dirname will allow node to find the resource at runtime
const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource)
const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', ...paths)
return fs.readFileSync(absolutePath, 'utf8')
}

View File

@@ -1,6 +1,5 @@
import path from 'path'
import fs from 'fs'
import crypto from 'crypto'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as semver from 'semver'
@@ -383,8 +382,6 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
return
}
const encryptionKey = this.getAESEncryptionKey()
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey)
return await super.restore(listener)
}
@@ -416,12 +413,6 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
await super.extract(listener)
}
private getAESEncryptionKey(): string | undefined {
const secret = params.getCacheEncryptionKey()
const key = crypto.pbkdf2Sync(secret, '', 1000, 16, 'sha256')
return key.toString('base64')
}
/**
* Extract cache entries for the configuration cache in each project.
*/

View File

@@ -1,4 +1,3 @@
import * as core from '@actions/core'
import * as cache from '@actions/cache'
/**
@@ -112,47 +111,36 @@ export class CacheEntryListener {
}
}
export function writeCachingReport(listener: CacheListener): void {
export function generateCachingReport(listener: CacheListener): string {
const entries = listener.cacheEntries
core.summary.addRaw(
`\n<details><summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>\n`
)
return `
<details>
<summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>
${renderEntryTable(entries)}
core.summary.addTable([
[
{data: '', header: true},
{data: 'Count', header: true},
{data: 'Total Size (Mb)', header: true}
],
['Entries Restored', `${getCount(entries, e => e.restoredSize)}`, `${getSize(entries, e => e.restoredSize)}`],
['Entries Saved', `${getCount(entries, e => e.savedSize)}`, `${getSize(entries, e => e.savedSize)}`]
])
core.summary.addHeading('Cache Entry Details', 5)
const entryDetails = renderEntryDetails(listener)
core.summary.addRaw(`<pre>
${entryDetails}
<h5>Cache Entry Details</h5>
<pre>
${renderEntryDetails(listener)}
</pre>
</details>
`)
`
}
export function logCachingReport(listener: CacheListener): void {
const entries = listener.cacheEntries
core.startGroup(`Caching for gradle-build-action was ${listener.cacheStatus} - expand for details`)
core.info(
`Entries Restored: ${getCount(entries, e => e.restoredSize)} (${getSize(entries, e => e.restoredSize)} Mb)`
)
core.info(`Entries Saved : ${getCount(entries, e => e.savedSize)} (${getSize(entries, e => e.savedSize)} Mb)`)
core.info(`Cache Entry Details`)
core.info(renderEntryDetails(listener))
core.endGroup()
function renderEntryTable(entries: CacheEntryListener[]): string {
return `
<table>
<tr><td></td><th>Count</th><th>Total Size (Mb)</th></tr>
<tr><td>Entries Restored</td>
<td>${getCount(entries, e => e.restoredSize)}</td>
<td>${getSize(entries, e => e.restoredSize)}</td>
</tr>
<tr><td>Entries Saved</td>
<td>${getCount(entries, e => e.savedSize)}</td>
<td>${getSize(entries, e => e.savedSize)}</td>
</tr>
</table>
`
}
function renderEntryDetails(listener: CacheListener): string {

View File

@@ -86,10 +86,10 @@ export function generateCacheKey(cacheName: string): CacheKey {
// At the most general level, share caches for all executions on the same OS
const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}`
// Prefer caches that run this job
// Then prefer caches that run job with the same ID
const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}`
// Prefer (even more) jobs that run this job with the same context (matrix)
// Prefer (even more) jobs that run this job in the same workflow with the same context (matrix)
const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]`
// Exact match on Git SHA
@@ -113,12 +113,7 @@ function getCacheKeyEnvironment(): string {
}
function getCacheKeyJob(): string {
return process.env[CACHE_KEY_JOB_VAR] || getCacheKeyForJob(github.context.workflow, github.context.job)
}
export function getCacheKeyForJob(workflowName: string, jobId: string): string {
const sanitizedWorkflow = workflowName.replace(/,/g, '').toLowerCase()
return `${sanitizedWorkflow}-${jobId}`
return process.env[CACHE_KEY_JOB_VAR] || github.context.job
}
function getCacheKeyJobInstance(): string {
@@ -127,25 +122,11 @@ function getCacheKeyJobInstance(): string {
return override
}
// By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation
// By default, we hash the workflow name and the full `matrix` data for the run, to uniquely identify this job invocation
// The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml.
const workflowName = github.context.workflow
const workflowJobContext = params.getJobMatrix()
return hashStrings([workflowJobContext])
}
export function getUniqueLabelForJobInstance(): string {
return getUniqueLabelForJobInstanceValues(github.context.workflow, github.context.job, params.getJobMatrix())
}
export function getUniqueLabelForJobInstanceValues(workflow: string, jobId: string, matrixJson: string): string {
const matrix = JSON.parse(matrixJson)
const matrixString = Object.values(matrix).join('-')
const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`
return sanitize(label)
}
function sanitize(value: string): string {
return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase()
return hashStrings([workflowName, workflowJobContext])
}
function getCacheKeyJobExecution(): string {

View File

@@ -13,7 +13,7 @@ import {CacheCleaner} from './cache-cleaner'
const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED'
export async function restore(gradleUserHome: string, cacheListener: CacheListener): Promise<void> {
export async function restore(userHome: string, gradleUserHome: string, cacheListener: CacheListener): Promise<void> {
// Bypass restore cache on all but first action step in workflow.
if (process.env[CACHE_RESTORED_VAR]) {
core.info('Cache only restored on first action step.')
@@ -21,7 +21,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen
}
core.exportVariable(CACHE_RESTORED_VAR, true)
const gradleStateCache = new GradleStateCache(gradleUserHome)
const gradleStateCache = new GradleStateCache(userHome, gradleUserHome)
if (isCacheDisabled()) {
core.info('Cache is disabled: will not restore state from previous builds.')
@@ -65,6 +65,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen
}
export async function save(
userHome: string,
gradleUserHome: string,
cacheListener: CacheListener,
daemonController: DaemonController
@@ -98,6 +99,6 @@ export async function save(
}
await core.group('Caching Gradle state', async () => {
return new GradleStateCache(gradleUserHome).save(cacheListener)
return new GradleStateCache(userHome, gradleUserHome).save(cacheListener)
})
}

View File

@@ -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_'
@@ -25,18 +31,25 @@ export async function setup(option: DependencyGraphOption): Promise<void> {
}
core.info('Enabling dependency graph generation')
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
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)
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext())
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory())
core.exportVariable(
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', getDependencyGraphContinueOnFailure())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory())
maybeExportVariable(
'DEPENDENCY_GRAPH_REPORT_DIR',
path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports')
)
}
function maybeExportVariable(variableName: string, value: unknown): void {
if (!process.env[variableName]) {
core.exportVariable(variableName, value)
}
}
export async function complete(option: DependencyGraphOption): Promise<void> {
try {
switch (option) {
@@ -51,7 +64,7 @@ export async function complete(option: DependencyGraphOption): Promise<void> {
await uploadDependencyGraphs(await findGeneratedDependencyGraphFiles())
}
} catch (e) {
core.warning(`Failed to ${option} dependency graph. Will continue. ${String(e)}`)
warnOrFail(option, e)
}
}
@@ -78,7 +91,7 @@ async function downloadAndSubmitDependencyGraphs(): Promise<void> {
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 +101,7 @@ async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<v
await submitDependencyGraphFile(jsonFile)
} catch (error) {
if (error instanceof RequestError) {
core.warning(buildWarningMessage(jsonFile, error))
throw new Error(translateErrorMessage(jsonFile, error))
} else {
throw error
}
@@ -96,9 +109,9 @@ async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<v
}
}
function buildWarningMessage(jsonFile: string, error: RequestError): string {
function translateErrorMessage(jsonFile: string, error: RequestError): string {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
const mainWarning = `Failed to submit dependency graph ${relativeJsonFile}.\n${String(error)}`
const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${String(error)}`
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'contents: write' permission is available for the workflow job.
@@ -160,6 +173,14 @@ async function findDependencyGraphFiles(dir: string): Promise<string[]> {
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<typeof GitHub> {
return github.getOctokit(getGithubToken())
}

11
src/errors.ts Normal file
View File

@@ -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))
}
}
}

View File

@@ -28,7 +28,7 @@ function validateGradleWrapper(buildRootDirectory: string): void {
function verifyExists(file: string, description: string): void {
if (!fs.existsSync(file)) {
throw new Error(
`Cannot locate ${description} at '${file}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.`
`Cannot locate ${description} at '${file}'. Specify 'gradle-version' for projects without Gradle wrapper configured.`
)
}
}

View File

@@ -49,10 +49,6 @@ export function getGradleVersion(): string {
return core.getInput('gradle-version')
}
export function getGradleExecutable(): string {
return core.getInput('gradle-executable')
}
export function getArguments(): string[] {
const input = core.getInput('arguments')
return parseArgsStringToArgv(input)
@@ -71,8 +67,25 @@ export function isJobSummaryEnabled(): boolean {
return getBooleanInput('generate-job-summary', true)
}
export function isDependencyGraphEnabled(): boolean {
return getBooleanInput('generate-dependency-graph', true)
export function getJobSummaryOption(): JobSummaryOption {
return parseJobSummaryOption('add-job-summary')
}
export function getPRCommentOption(): JobSummaryOption {
return parseJobSummaryOption('add-job-summary-as-pr-comment')
}
function parseJobSummaryOption(paramName: string): JobSummaryOption {
const val = core.getInput(paramName)
switch (val.toLowerCase().trim()) {
case 'never':
return JobSummaryOption.Never
case 'always':
return JobSummaryOption.Always
case 'on-failure':
return JobSummaryOption.OnFailure
}
throw TypeError(`The value '${val}' is not valid for ${paramName}. Valid values are: [never, always, on-failure].`)
}
export function getDependencyGraphOption(): DependencyGraphOption {
@@ -90,10 +103,14 @@ export function getDependencyGraphOption(): DependencyGraphOption {
return DependencyGraphOption.DownloadAndSubmit
}
throw TypeError(
`The value '${val} is not valid for 'dependency-graph. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`
`The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`
)
}
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)
@@ -131,3 +148,9 @@ export enum DependencyGraphOption {
GenerateAndUpload = 'generate-and-upload',
DownloadAndSubmit = 'download-and-submit'
}
export enum JobSummaryOption {
Never = 'never',
Always = 'always',
OnFailure = 'on-failure'
}

View File

@@ -1,45 +1,94 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary'
import {RequestError} from '@octokit/request-error'
import * as params from './input-params'
import {BuildResult} from './build-results'
import {writeCachingReport, CacheListener, logCachingReport} from './cache-reporting'
import {CacheListener, generateCachingReport} from './cache-reporting'
export async function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
core.info('Writing job summary')
export async function generateJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
const summaryTable = renderSummaryTable(buildResults)
const cachingReport = generateCachingReport(cacheListener)
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.')
if (shouldGenerateJobSummary(buildResults)) {
core.info('Generating Job Summary')
core.summary.addRaw(summaryTable)
core.summary.addRaw(cachingReport)
await core.summary.write()
} else {
writeSummaryTable(buildResults)
core.info('============================')
core.info(summaryTable)
core.info('============================')
core.info(cachingReport)
core.info('============================')
}
writeCachingReport(cacheListener)
await core.summary.write()
if (shouldAddPRComment(buildResults)) {
await addPRComment(summaryTable)
}
}
export async function logJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be logged.')
} else {
logSummaryTable(buildResults)
async function addPRComment(jobSummary: string): Promise<void> {
const context = github.context
if (context.payload.pull_request == null) {
core.info('No pull_request trigger: not adding PR comment')
return
}
logCachingReport(cacheListener)
const pull_request_number = context.payload.pull_request.number
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`)
const prComment = `<h3>Job Summary for gradle-build-action</h3>
<h5>${github.context.workflow} :: <em>${github.context.job}</em></h5>
${jobSummary}`
const github_token = params.getGithubToken()
const octokit = github.getOctokit(github_token)
try {
await octokit.rest.issues.createComment({
...context.repo,
issue_number: pull_request_number,
body: prComment
})
} catch (error) {
if (error instanceof RequestError) {
core.warning(buildWarningMessage(error))
} else {
throw error
}
}
}
function writeSummaryTable(results: BuildResult[]): void {
core.summary.addHeading('Gradle Builds', 3)
function buildWarningMessage(error: RequestError): string {
const mainWarning = `Failed to generate PR comment.\n${String(error)}`
if (error.message === 'Resource not accessible by integration') {
return `${mainWarning}
Please ensure that the 'pull-requests: write' permission is available for the workflow job.
Note that this permission is never available for a workflow triggered from a repository fork.
`
}
return mainWarning
}
core.summary.addRaw(`
function renderSummaryTable(results: BuildResult[]): string {
if (results.length === 0) {
return 'No Gradle build results detected.'
}
return `
<table>
<tr>
<th>Root Project</th>
<th>Gradle Root Project</th>
<th>Requested Tasks</th>
<th>Gradle Version</th>
<th>Build Outcome</th>
<th>Build Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table>
`)
`
}
function renderBuildResultRow(result: BuildResult): string {
@@ -77,18 +126,32 @@ function renderBuildScanBadge(outcomeText: string, outcomeColor: string, targetU
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`
}
function logSummaryTable(results: BuildResult[]): void {
core.info('============================')
core.info('Gradle Builds')
core.info('----------------------------')
core.info('Root Project | Requested Tasks | Gradle Version | Build Outcome | Build Scan®')
core.info('----------------------------')
for (const result of results) {
core.info(
`${result.rootProjectName} | ${result.requestedTasks} | ${result.gradleVersion} | ${
result.buildFailed ? 'FAILED' : 'SUCCESS'
} | ${result.buildScanFailed ? 'Publish failed' : result.buildScanUri}`
)
function shouldGenerateJobSummary(buildResults: BuildResult[]): boolean {
// Check if Job Summary is supported on this platform
if (!process.env[SUMMARY_ENV_VAR]) {
return false
}
// Check if Job Summary is disabled using the deprecated input
if (!params.isJobSummaryEnabled()) {
return false
}
return shouldAddJobSummary(params.getJobSummaryOption(), buildResults)
}
function shouldAddPRComment(buildResults: BuildResult[]): boolean {
return shouldAddJobSummary(params.getPRCommentOption(), buildResults)
}
function shouldAddJobSummary(option: params.JobSummaryOption, buildResults: BuildResult[]): boolean {
switch (option) {
case params.JobSummaryOption.Always:
return true
case params.JobSummaryOption.Never:
return false
case params.JobSummaryOption.OnFailure:
core.info(`Got these build results: ${JSON.stringify(buildResults)}`)
return buildResults.some(result => result.buildFailed)
}
core.info('============================')
}

View File

@@ -29,6 +29,9 @@ export async function run(): Promise<void> {
core.info(error.stack)
}
}
// Explicit process.exit() to prevent waiting for hanging promises.
process.exit()
}
run()

View File

@@ -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,8 +14,15 @@ export async function run(): Promise<void> {
try {
await setupGradle.complete()
} catch (error) {
handleFailure(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 {

View File

@@ -8,7 +8,6 @@ import * as toolCache from '@actions/tool-cache'
import * as gradlew from './gradlew'
import * as params from './input-params'
import * as layout from './repository-layout'
import {handleCacheFailure, isCacheDisabled, isCacheReadOnly} from './cache-utils'
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
@@ -20,13 +19,7 @@ const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
export async function provisionGradle(): Promise<string | undefined> {
const gradleVersion = params.getGradleVersion()
if (gradleVersion !== '' && gradleVersion !== 'wrapper') {
return addToPath(path.resolve(await installGradle(gradleVersion)))
}
const gradleExecutable = params.getGradleExecutable()
if (gradleExecutable !== '') {
const workspaceDirectory = layout.workspaceDirectory()
return addToPath(path.resolve(workspaceDirectory, gradleExecutable))
return addToPath(await installGradle(gradleVersion))
}
return undefined

View File

@@ -9,7 +9,7 @@ buildscript {
maven { url pluginRepositoryUrl }
}
dependencies {
classpath "org.gradle:github-dependency-graph-gradle-plugin:1.0.0"
classpath "org.gradle:github-dependency-graph-gradle-plugin:1.1.1"
}
}
apply plugin: org.gradle.github.GitHubDependencyGraphPlugin

View File

@@ -6,8 +6,13 @@ if (getVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED') != "true") {
}
// Do not run for unsupported versions of Gradle
if (GradleVersion.current().baseVersion < GradleVersion.version("5.0")) {
println "::warning::Dependency Graph is not supported for Gradle versions < 5.0. No dependency snapshot will be generated."
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
}
@@ -22,11 +27,6 @@ if (isTopLevelBuild) {
return
}
def githubOutput = System.getenv("GITHUB_OUTPUT")
if (githubOutput) {
new File(githubOutput) << "dependency-graph-file=${reportFile.absolutePath}\n"
}
println "Generating dependency graph into '${reportFile}'"
}

View File

@@ -15,21 +15,21 @@ initscript {
}
// finish early if injection is disabled
def gradleInjectionEnabled = getInputParam("gradle-enterprise.injection-enabled")
def gradleInjectionEnabled = getInputParam("develocity.injection-enabled")
if (gradleInjectionEnabled != "true") {
return
}
def pluginRepositoryUrl = getInputParam('gradle-enterprise.plugin-repository.url')
def gePluginVersion = getInputParam('gradle-enterprise.plugin.version')
def ccudPluginVersion = getInputParam('gradle-enterprise.ccud-plugin.version')
def pluginRepositoryUrl = getInputParam('gradle.plugin-repository.url')
def gePluginVersion = getInputParam('develocity.plugin.version')
def ccudPluginVersion = getInputParam('develocity.ccud-plugin.version')
def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0')
def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0')
if (gePluginVersion || ccudPluginVersion && atLeastGradle4) {
pluginRepositoryUrl = pluginRepositoryUrl ?: 'https://plugins.gradle.org/m2'
logger.quiet("Gradle Enterprise plugins resolution: $pluginRepositoryUrl")
logger.quiet("Develocity plugins resolution: $pluginRepositoryUrl")
repositories {
maven { url pluginRepositoryUrl }
@@ -52,9 +52,9 @@ initscript {
def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan'
def BUILD_SCAN_PLUGIN_CLASS = 'com.gradle.scan.plugin.BuildScanPlugin'
def GRADLE_ENTERPRISE_PLUGIN_ID = 'com.gradle.enterprise'
def GRADLE_ENTERPRISE_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin'
def GRADLE_ENTERPRISE_EXTENSION_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterpriseExtension'
def DEVELOCITY_PLUGIN_ID = 'com.gradle.enterprise'
def DEVELOCITY_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin'
def DEVELOCITY_EXTENSION_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterpriseExtension'
def CI_AUTO_INJECTION_CUSTOM_VALUE_NAME = 'CI auto injection'
def CI_AUTO_INJECTION_CUSTOM_VALUE_VALUE = 'gradle-build-action'
def CCUD_PLUGIN_ID = 'com.gradle.common-custom-user-data-gradle-plugin'
@@ -71,17 +71,17 @@ def getInputParam = { String name ->
}
// finish early if injection is disabled
def gradleInjectionEnabled = getInputParam("gradle-enterprise.injection-enabled")
def gradleInjectionEnabled = getInputParam("develocity.injection-enabled")
if (gradleInjectionEnabled != "true") {
return
}
def geUrl = getInputParam('gradle-enterprise.url')
def geAllowUntrustedServer = Boolean.parseBoolean(getInputParam('gradle-enterprise.allow-untrusted-server'))
def geEnforceUrl = Boolean.parseBoolean(getInputParam('gradle-enterprise.enforce-url'))
def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam('gradle-enterprise.build-scan.upload-in-background'))
def gePluginVersion = getInputParam('gradle-enterprise.plugin.version')
def ccudPluginVersion = getInputParam('gradle-enterprise.ccud-plugin.version')
def geUrl = getInputParam('develocity.url')
def geAllowUntrustedServer = Boolean.parseBoolean(getInputParam('develocity.allow-untrusted-server'))
def geEnforceUrl = Boolean.parseBoolean(getInputParam('develocity.enforce-url'))
def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam('develocity.build-scan.upload-in-background'))
def gePluginVersion = getInputParam('develocity.plugin.version')
def ccudPluginVersion = getInputParam('develocity.ccud-plugin.version')
def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0')
@@ -103,7 +103,7 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
}
if (!scanPluginComponent) {
logger.quiet("Applying $BUILD_SCAN_PLUGIN_CLASS via init script")
logger.quiet("Connection to Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
logger.quiet("Connection to Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
applyPluginExternally(pluginManager, BUILD_SCAN_PLUGIN_CLASS)
buildScan.server = geUrl
buildScan.allowUntrustedServer = geAllowUntrustedServer
@@ -115,7 +115,7 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
if (geUrl && geEnforceUrl) {
pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) {
afterEvaluate {
logger.quiet("Enforcing Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
logger.quiet("Enforcing Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
buildScan.server = geUrl
buildScan.allowUntrustedServer = geAllowUntrustedServer
}
@@ -137,11 +137,11 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
} else {
gradle.settingsEvaluated { settings ->
if (gePluginVersion) {
if (!settings.pluginManager.hasPlugin(GRADLE_ENTERPRISE_PLUGIN_ID)) {
logger.quiet("Applying $GRADLE_ENTERPRISE_PLUGIN_CLASS via init script")
logger.quiet("Connection to Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
applyPluginExternally(settings.pluginManager, GRADLE_ENTERPRISE_PLUGIN_CLASS)
extensionsWithPublicType(settings, GRADLE_ENTERPRISE_EXTENSION_CLASS).collect { settings[it.name] }.each { ext ->
if (!settings.pluginManager.hasPlugin(DEVELOCITY_PLUGIN_ID)) {
logger.quiet("Applying $DEVELOCITY_PLUGIN_CLASS via init script")
logger.quiet("Connection to Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
applyPluginExternally(settings.pluginManager, DEVELOCITY_PLUGIN_CLASS)
extensionsWithPublicType(settings, DEVELOCITY_EXTENSION_CLASS).collect { settings[it.name] }.each { ext ->
ext.server = geUrl
ext.allowUntrustedServer = geAllowUntrustedServer
ext.buildScan.publishAlways()
@@ -151,8 +151,8 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
}
if (geUrl && geEnforceUrl) {
extensionsWithPublicType(settings, GRADLE_ENTERPRISE_EXTENSION_CLASS).collect { settings[it.name] }.each { ext ->
logger.quiet("Enforcing Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
extensionsWithPublicType(settings, DEVELOCITY_EXTENSION_CLASS).collect { settings[it.name] }.each { ext ->
logger.quiet("Enforcing Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer")
ext.server = geUrl
ext.allowUntrustedServer = geAllowUntrustedServer
}
@@ -169,7 +169,7 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
}
void applyPluginExternally(def pluginManager, String pluginClassName) {
def externallyApplied = 'gradle.enterprise.externally-applied'
def externallyApplied = 'develocity.externally-applied'
def oldValue = System.getProperty(externallyApplied)
System.setProperty(externallyApplied, 'true')
try {

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<toolchains>
<!-- JDK Toolchains installed by default on GitHub-hosted runners -->
<toolchain>
<type>jdk</type>
<provides>
<version>8</version>
<vendor>Eclipse Temurin</vendor>
</provides>
<configuration>
<jdkHome>${env.JAVA_HOME_8_X64}</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>11</version>
<vendor>Eclipse Temurin</vendor>
</provides>
<configuration>
<jdkHome>${env.JAVA_HOME_11_X64}</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>17</version>
<vendor>Eclipse Temurin</vendor>
</provides>
<configuration>
<jdkHome>${env.JAVA_HOME_17_X64}</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>21</version>
<vendor>Eclipse Temurin</vendor>
</provides>
<configuration>
<jdkHome>${env.JAVA_HOME_21_X64}</jdkHome>
</configuration>
</toolchain>
</toolchains>

View File

@@ -1,23 +1,24 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary'
import * as path from 'path'
import * as os from 'os'
import * as caches from './caches'
import * as layout from './repository-layout'
import * as params from './input-params'
import * as dependencyGraph from './dependency-graph'
import * as jobSummary from './job-summary'
import {logJobSummary, writeJobSummary} from './job-summary'
import {loadBuildResults} from './build-results'
import {CacheListener} from './cache-reporting'
import {DaemonController} from './daemon-controller'
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
const USER_HOME = 'USER_HOME'
const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
const CACHE_LISTENER = 'CACHE_LISTENER'
export async function setup(): Promise<void> {
const userHome = await determineUserHome()
const gradleUserHome = await determineGradleUserHome()
// Bypass setup on all but first action step in workflow.
@@ -30,11 +31,12 @@ export async function setup(): Promise<void> {
// Record setup complete: visible in post-action, to control action completion
core.saveState(GRADLE_SETUP_VAR, true)
// Save the Gradle User Home for use in the post-action step.
// Save the User Home and Gradle User Home for use in the post-action step.
core.saveState(USER_HOME, userHome)
core.saveState(GRADLE_USER_HOME, gradleUserHome)
const cacheListener = new CacheListener()
await caches.restore(gradleUserHome, cacheListener)
await caches.restore(userHome, gradleUserHome, cacheListener)
core.saveState(CACHE_LISTENER, cacheListener.stringify())
@@ -50,17 +52,14 @@ export async function complete(): Promise<void> {
const buildResults = loadBuildResults()
const userHome = core.getState(USER_HOME)
const gradleUserHome = core.getState(GRADLE_USER_HOME)
const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
const daemonController = new DaemonController(buildResults)
await caches.save(gradleUserHome, cacheListener, daemonController)
await caches.save(userHome, gradleUserHome, cacheListener, daemonController)
if (shouldGenerateJobSummary()) {
await writeJobSummary(buildResults, cacheListener)
} else {
logJobSummary(buildResults, cacheListener)
}
await jobSummary.generateJobSummary(buildResults, cacheListener)
await dependencyGraph.complete(params.getDependencyGraphOption())
@@ -93,12 +92,3 @@ async function determineUserHome(): Promise<string> {
core.debug(`Determined user.home from java -version output: '${userHome}'`)
return userHome
}
function shouldGenerateJobSummary(): boolean {
// Check if Job Summary is supported on this platform
if (!process.env[SUMMARY_ENV_VAR]) {
return false
}
return params.isJobSummaryEnabled()
}

View File

@@ -16,7 +16,7 @@ import java.nio.file.Files
import java.util.zip.GZIPOutputStream
class BaseInitScriptTest extends Specification {
static final String GE_PLUGIN_VERSION = '3.16.1'
static final String DEVELOCITY_PLUGIN_VERSION = '3.16.1'
static final String CCUD_PLUGIN_VERSION = '1.12.1'
static final TestGradleVersion GRADLE_3_X = new TestGradleVersion(GradleVersion.version('3.5.1'), 7, 9)
@@ -24,6 +24,7 @@ class BaseInitScriptTest extends Specification {
static final TestGradleVersion GRADLE_5_X = new TestGradleVersion(GradleVersion.version('5.6.4'), 8, 12)
static final TestGradleVersion GRADLE_6_NO_BUILD_SERVICE = new TestGradleVersion(GradleVersion.version('6.5.1'), 8, 14)
static final TestGradleVersion GRADLE_6_X = new TestGradleVersion(GradleVersion.version('6.9.4'), 8, 15)
static final TestGradleVersion GRADLE_7_1 = new TestGradleVersion(GradleVersion.version('7.6.2'), 8, 19)
static final TestGradleVersion GRADLE_7_X = new TestGradleVersion(GradleVersion.version('7.6.2'), 8, 19)
static final TestGradleVersion GRADLE_8_0 = new TestGradleVersion(GradleVersion.version('8.0.2'), 8, 19)
static final TestGradleVersion GRADLE_8_X = new TestGradleVersion(GradleVersion.version('8.5'), 8, 19)
@@ -146,7 +147,7 @@ class BaseInitScriptTest extends Specification {
} else {
"""
plugins {
id 'com.gradle.enterprise' version '${GE_PLUGIN_VERSION}'
id 'com.gradle.enterprise' version '${DEVELOCITY_PLUGIN_VERSION}'
${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""}
}
gradleEnterprise {
@@ -174,7 +175,7 @@ class BaseInitScriptTest extends Specification {
} else if (gradleVersion < GradleVersion.version('6.0')) {
"""
plugins {
id 'com.gradle.build-scan' version '${GE_PLUGIN_VERSION}'
id 'com.gradle.build-scan' version '${DEVELOCITY_PLUGIN_VERSION}'
${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""}
}
gradleEnterprise {

View File

@@ -1,11 +1,16 @@
package com.gradle.gradlebuildaction
import org.gradle.util.GradleVersion
import static org.junit.Assume.assumeTrue
class TestDependencyGraph extends BaseInitScriptTest {
def initScript = 'gradle-build-action.github-dependency-graph.init.gradle'
static final List<TestGradleVersion> NO_DEPENDENCY_GRAPH_VERSIONS = [GRADLE_3_X, GRADLE_4_X]
static final TestGradleVersion GRADLE_5_1 = new TestGradleVersion(GradleVersion.version('5.1.1'), 8, 12)
static final TestGradleVersion GRADLE_7_0 = new TestGradleVersion(GradleVersion.version('7.0.1'), 8, 12)
static final List<TestGradleVersion> NO_DEPENDENCY_GRAPH_VERSIONS = [GRADLE_3_X, GRADLE_4_X, GRADLE_5_1, GRADLE_7_0]
static final List<TestGradleVersion> DEPENDENCY_GRAPH_VERSIONS = ALL_VERSIONS - NO_DEPENDENCY_GRAPH_VERSIONS
def "does not produce dependency graph when not enabled"() {
@@ -29,7 +34,6 @@ class TestDependencyGraph extends BaseInitScriptTest {
then:
assert reportFile.exists()
assert gitHubOutputFile.text == "dependency-graph-file=${reportFile.absolutePath}\n"
where:
testGradleVersion << [GRADLE_8_X]
@@ -57,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
@@ -110,13 +130,13 @@ 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",
GITHUB_DEPENDENCY_GRAPH_SHA: "123456",
GITHUB_DEPENDENCY_GRAPH_WORKSPACE: testProjectDir.absolutePath,
DEPENDENCY_GRAPH_REPORT_DIR: reportsDir.absolutePath,
GITHUB_OUTPUT: gitHubOutputFile.absolutePath
]
}
@@ -127,8 +147,4 @@ class TestDependencyGraph extends BaseInitScriptTest {
def getReportFile() {
return new File(reportsDir, "CORRELATOR.json")
}
def getGitHubOutputFile() {
return new File(testProjectDir, "GITHUB_OUTPUT")
}
}

View File

@@ -5,28 +5,28 @@ import org.gradle.util.GradleVersion
import static org.junit.Assume.assumeTrue
class TestGradleEnterpriseInjection extends BaseInitScriptTest {
class TestDevelocityInjection extends BaseInitScriptTest {
static final List<TestGradleVersion> CCUD_COMPATIBLE_VERSIONS = ALL_VERSIONS - [GRADLE_3_X]
def initScript = 'gradle-build-action.inject-gradle-enterprise.init.gradle'
def initScript = 'gradle-build-action.inject-develocity.init.gradle'
private static final GradleVersion GRADLE_6 = GradleVersion.version('6.0')
def "does not apply GE plugins when not requested"() {
def "does not apply Develocity plugins when not requested"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
def result = run([], initScript, testGradleVersion.gradleVersion)
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
where:
testGradleVersion << ALL_VERSIONS
}
def "does not override GE plugin when already defined in project"() {
def "does not override Develocity plugin when already defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
given:
@@ -36,7 +36,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig())
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
@@ -46,14 +46,14 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "applies GE plugin via init script when not defined in project"() {
def "applies Develocity plugin via init script when not defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
def result = run(testGradleVersion, testConfig())
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
@@ -63,14 +63,14 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "applies GE and CCUD plugins via init script when not defined in project"() {
def "applies Develocity and CCUD plugins via init script when not defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result)
and:
@@ -80,7 +80,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << CCUD_COMPATIBLE_VERSIONS
}
def "applies CCUD plugin via init script where GE plugin already applied"() {
def "applies CCUD plugin via init script where Develocity plugin already applied"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
given:
@@ -90,7 +90,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputContainsCcudPluginApplicationViaInitScript(result)
and:
@@ -110,7 +110,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
@@ -120,18 +120,18 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << CCUD_COMPATIBLE_VERSIONS
}
def "ignores GE URL and allowUntrustedServer when GE plugin is not applied by the init script"() {
def "ignores Develocity URL and allowUntrustedServer when Develocity plugin is not applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
given:
declareGePluginApplication(testGradleVersion.gradleVersion)
when:
def config = testConfig().withServer(URI.create('https://ge-server.invalid'))
def config = testConfig().withServer(URI.create('https://develocity-server.invalid'))
def result = run(testGradleVersion, config)
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
@@ -141,7 +141,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "configures GE URL and allowUntrustedServer when GE plugin is applied by the init script"() {
def "configures Develocity URL and allowUntrustedServer when Develocity plugin is applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
@@ -149,8 +149,8 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config)
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsGeConnectionInfo(result, mockScansServer.address.toString(), true)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityConnectionInfo(result, mockScansServer.address.toString(), true)
outputMissesCcudPluginApplicationViaInitScript(result)
outputContainsPluginRepositoryInfo(result, 'https://plugins.gradle.org/m2')
@@ -161,22 +161,22 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "enforces GE URL and allowUntrustedServer in project if enforce url parameter is enabled"() {
def "enforces Develocity URL and allowUntrustedServer in project if enforce url parameter is enabled"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
given:
declareGePluginApplication(testGradleVersion.gradleVersion, URI.create('https://ge-server.invalid'))
declareGePluginApplication(testGradleVersion.gradleVersion, URI.create('https://develocity-server.invalid'))
when:
def config = testConfig().withServer(mockScansServer.address, true)
def result = run(testGradleVersion, config)
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
outputEnforcesGeUrl(result, mockScansServer.address.toString(), true)
outputEnforcesDevelocityUrl(result, mockScansServer.address.toString(), true)
and:
outputContainsBuildScanUrl(result)
@@ -185,7 +185,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "can configure alternative repository for plugins when GE plugin is applied by the init script"() {
def "can configure alternative repository for plugins when Develocity plugin is applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
@@ -193,8 +193,8 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config)
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsGeConnectionInfo(result, mockScansServer.address.toString(), true)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityConnectionInfo(result, mockScansServer.address.toString(), true)
outputMissesCcudPluginApplicationViaInitScript(result)
outputContainsPluginRepositoryInfo(result, 'https://plugins.grdev.net/m2')
@@ -213,7 +213,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config)
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
result.output.contains('Common Custom User Data Gradle plugin must be at least 1.7. Configured version is 1.6.6.')
@@ -221,15 +221,15 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS
}
def "can configure GE via CCUD system property overrides when CCUD plugin is inject via init script"() {
def "can configure Develocity via CCUD system property overrides when CCUD plugin is inject via init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
def config = testConfig().withCCUDPlugin().withServer(URI.create('https://ge-server.invalid'))
def config = testConfig().withCCUDPlugin().withServer(URI.create('https://develocity-server.invalid'))
def result = run(testGradleVersion, config, ["help", "-Dgradle.enterprise.url=${mockScansServer.address}".toString()])
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result)
and:
@@ -247,7 +247,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config, ["help", "--configuration-cache"])
then:
outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result)
and:
@@ -257,7 +257,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
result = run(testGradleVersion, config, ["help", "--configuration-cache"])
then:
outputMissesGePluginApplicationViaInitScript(result)
outputMissesDevelocityPluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result)
and:
@@ -273,7 +273,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
assert 1 == result.output.count(message)
}
void outputContainsGePluginApplicationViaInitScript(BuildResult result, GradleVersion gradleVersion) {
void outputContainsDevelocityPluginApplicationViaInitScript(BuildResult result, GradleVersion gradleVersion) {
def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script"
def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script"
if (gradleVersion < GRADLE_6) {
@@ -287,7 +287,7 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
}
}
void outputMissesGePluginApplicationViaInitScript(BuildResult result) {
void outputMissesDevelocityPluginApplicationViaInitScript(BuildResult result) {
def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script"
def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script"
assert !result.output.contains(pluginApplicationLogMsgGradle4And5)
@@ -305,20 +305,20 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
assert !result.output.contains(pluginApplicationLogMsg)
}
void outputContainsGeConnectionInfo(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def geConnectionInfo = "Connection to Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
void outputContainsDevelocityConnectionInfo(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def geConnectionInfo = "Connection to Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
assert result.output.contains(geConnectionInfo)
assert 1 == result.output.count(geConnectionInfo)
}
void outputContainsPluginRepositoryInfo(BuildResult result, String gradlePluginRepositoryUrl) {
def repositoryInfo = "Gradle Enterprise plugins resolution: ${gradlePluginRepositoryUrl}"
def repositoryInfo = "Develocity plugins resolution: ${gradlePluginRepositoryUrl}"
assert result.output.contains(repositoryInfo)
assert 1 == result.output.count(repositoryInfo)
}
void outputEnforcesGeUrl(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def enforceUrl = "Enforcing Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
void outputEnforcesDevelocityUrl(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def enforceUrl = "Enforcing Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
assert result.output.contains(enforceUrl)
assert 1 == result.output.count(enforceUrl)
}
@@ -369,31 +369,31 @@ class TestGradleEnterpriseInjection extends BaseInitScriptTest {
def getEnvVars() {
Map<String, String> envVars = [
GRADLE_ENTERPRISE_INJECTION_ENABLED: "true",
GRADLE_ENTERPRISE_URL: serverUrl,
GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER: "true",
GRADLE_ENTERPRISE_PLUGIN_VERSION: GE_PLUGIN_VERSION,
GRADLE_ENTERPRISE_BUILD_SCAN_UPLOAD_IN_BACKGROUND: "true" // Need to upload in background since our Mock server doesn't cope with foreground upload
DEVELOCITY_INJECTION_ENABLED: "true",
DEVELOCITY_URL: serverUrl,
DEVELOCITY_ALLOW_UNTRUSTED_SERVER: "true",
DEVELOCITY_PLUGIN_VERSION: DEVELOCITY_PLUGIN_VERSION,
DEVELOCITY_BUILD_SCAN_UPLOAD_IN_BACKGROUND: "true" // Need to upload in background since our Mock server doesn't cope with foreground upload
]
if (enforceUrl) envVars.put("GRADLE_ENTERPRISE_ENFORCE_URL", "true")
if (ccudPluginVersion != null) envVars.put("GRADLE_ENTERPRISE_CCUD_PLUGIN_VERSION", ccudPluginVersion)
if (pluginRepositoryUrl != null) envVars.put("GRADLE_ENTERPRISE_PLUGIN_REPOSITORY_URL", pluginRepositoryUrl)
if (enforceUrl) envVars.put("DEVELOCITY_ENFORCE_URL", "true")
if (ccudPluginVersion != null) envVars.put("DEVELOCITY_CCUD_PLUGIN_VERSION", ccudPluginVersion)
if (pluginRepositoryUrl != null) envVars.put("GRADLE_PLUGIN_REPOSITORY_URL", pluginRepositoryUrl)
return envVars
}
def getJvmArgs() {
List<String> jvmArgs = [
"-Dgradle-enterprise.injection-enabled=true",
"-Dgradle-enterprise.url=$serverUrl",
"-Dgradle-enterprise.allow-untrusted-server=true",
"-Dgradle-enterprise.plugin.version=$GE_PLUGIN_VERSION",
"-Dgradle-enterprise.build-scan.upload-in-background=true"
"-Ddevelocity.injection-enabled=true",
"-Ddevelocity.url=$serverUrl",
"-Ddevelocity.allow-untrusted-server=true",
"-Ddevelocity.plugin.version=$DEVELOCITY_PLUGIN_VERSION",
"-Ddevelocity.build-scan.upload-in-background=true"
]
if (enforceUrl) jvmArgs.add("-Dgradle-enterprise.enforce-url=true")
if (ccudPluginVersion != null) jvmArgs.add("-Dgradle-enterprise.ccud-plugin.version=$ccudPluginVersion")
if (pluginRepositoryUrl != null) jvmArgs.add("-Dgradle-enterprise.plugin-repository.url=$pluginRepositoryUrl")
if (enforceUrl) jvmArgs.add("-Ddevelocity.enforce-url=true")
if (ccudPluginVersion != null) jvmArgs.add("-Ddevelocity.ccud-plugin.version=$ccudPluginVersion")
if (pluginRepositoryUrl != null) jvmArgs.add("-Dgradle.plugin-repository.url=$pluginRepositoryUrl")
return jvmArgs.collect { it.toString() } // Convert from GStrings
}

View File

@@ -17,10 +17,4 @@ describe('cacheUtils-utils', () => {
expect(posixHash).toBe(windowsHash)
})
})
describe('sanitizes workflow name in cache key', () => {
it('with comma', () => {
const cacheKey = cacheUtils.getCacheKeyForJob("Workflow, with,commas", "JOB_ID")
expect(cacheKey).toBe('workflow withcommas-JOB_ID')
})
})
})