Compare commits

..

4 Commits

Author SHA1 Message Date
daz
a8f75513ea Build outputs 2024-01-23 16:12:23 -07:00
daz
9283312acb Add new option to clear dependency-graph
When changing workflow names or when changing to the new 'dependency-submission'
action, it can be useful to clear existing dependency graph snapshots from previous
submissions. While the old graphs will eventually "age out", the 'clear' option will
submit an empty dependency graph for an existing Job correlator, ensuring that old
dependency graphs don't linger.
2024-01-23 16:11:35 -07:00
daz
7c8a278ea0 Remove old clear-dependency-graph action 2024-01-23 15:07:13 -07:00
daz
d8ca9b7d2e Do full checks on release branches 2024-01-23 15:07:13 -07:00
53 changed files with 17114 additions and 109497 deletions

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "com.gradle.enterprise" version "3.16.1" id "com.gradle.enterprise" version "3.15.1"
id "com.gradle.common-custom-user-data-gradle-plugin" version "1.12.1" id "com.gradle.common-custom-user-data-gradle-plugin" version "1.12"
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -8,7 +8,7 @@ repositories {
dependencies { dependencies {
api("org.apache.commons:commons-math3:3.6.1") api("org.apache.commons:commons-math3:3.6.1")
implementation("com.google.guava:guava:33.0.0-jre") implementation("com.google.guava:guava:32.1.3-jre")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1") testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
} }

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id("com.gradle.enterprise") version "3.16.1" id("com.gradle.enterprise") version "3.15.1"
id("com.gradle.common-custom-user-data-gradle-plugin") version "1.12.1" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.12"
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.build-scan" version "3.16.1" id "com.gradle.build-scan" version "3.15.1"
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.enterprise" version "3.16.1" id "com.gradle.enterprise" version "3.15.1"
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -1,10 +1,22 @@
# 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 name: CI-codeql
on: on:
push: push:
branches: [ "main" ] branches: [ main ]
pull_request: pull_request:
branches: [ "main" ] # The branches below must be a subset of the branches above
branches: [ main ]
schedule: schedule:
- cron: '25 23 * * 2' - cron: '25 23 * * 2'
@@ -20,7 +32,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript-typescript' ] language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -28,12 +42,29 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
config: | # If you wish to specify custom queries, you can do so here or in a config file.
paths: # By default, queries listed here will override any specified in a config file.
- src # 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@v2
# 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
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

View File

@@ -9,6 +9,7 @@ on:
push: push:
branches: branches:
- main - main
- release/**
paths: paths:
- '.github/**' - '.github/**'
- 'dist/**' - 'dist/**'
@@ -25,19 +26,12 @@ jobs:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
caching-config: caching-config:
uses: ./.github/workflows/integ-test-caching-config.yml uses: ./.github/workflows/integ-test-action-inputs-caching.yml
with: with:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
dependency-graph: dependency-graph:
uses: ./.github/workflows/integ-test-dependency-graph.yml 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: with:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
@@ -51,24 +45,22 @@ jobs:
with: with:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
develocity-injection: gradle-enterprise-injection:
uses: ./.github/workflows/integ-test-inject-develocity.yml uses: ./.github/workflows/integ-test-inject-gradle-enterprise.yml
with: with:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
secrets: secrets:
DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
provision-gradle-versions: provision-gradle-versions:
uses: ./.github/workflows/integ-test-provision-gradle-versions.yml uses: ./.github/workflows/integ-test-provision-gradle-versions.yml
with: with:
cache-key-prefix: ${{github.run_number}}- cache-key-prefix: ${{github.run_number}}-
restore-configuration-cache: # restore-configuration-cache:
uses: ./.github/workflows/integ-test-restore-configuration-cache.yml # uses: ./.github/workflows/integ-test-restore-configuration-cache.yml
with: # with:
cache-key-prefix: ${{github.run_number}}- # cache-key-prefix: ${{github.run_number}}-
secrets:
GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
restore-custom-gradle-home: restore-custom-gradle-home:
uses: ./.github/workflows/integ-test-restore-custom-gradle-home.yml uses: ./.github/workflows/integ-test-restore-custom-gradle-home.yml

View File

@@ -20,7 +20,7 @@ jobs:
distribution: temurin distribution: temurin
java-version: 8 java-version: 8
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2.11.0 # Use a released version to avoid breakages uses: gradle/gradle-build-action@v2.10.0 # Use a released version to avoid breakages
- name: Run integration tests - name: Run integration tests
working-directory: test/init-scripts working-directory: test/init-scripts
run: ./gradlew check run: ./gradlew check

View File

@@ -3,7 +3,9 @@ name: CI-quick-check
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches-ignore: main branches-ignore:
- main
- release/**
jobs: jobs:
build-distribution: build-distribution:
@@ -45,7 +47,7 @@ jobs:
caching-config: caching-config:
needs: build-distribution needs: build-distribution
uses: ./.github/workflows/integ-test-caching-config.yml uses: ./.github/workflows/integ-test-action-inputs-caching.yml
with: with:
runner-os: '["ubuntu-latest"]' runner-os: '["ubuntu-latest"]'
download-dist: true download-dist: true
@@ -53,15 +55,6 @@ jobs:
dependency-graph: dependency-graph:
needs: build-distribution needs: build-distribution
uses: ./.github/workflows/integ-test-dependency-graph.yml 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: with:
runner-os: '["ubuntu-latest"]' runner-os: '["ubuntu-latest"]'
download-dist: true download-dist: true
@@ -80,14 +73,14 @@ jobs:
runner-os: '["ubuntu-latest"]' runner-os: '["ubuntu-latest"]'
download-dist: true download-dist: true
develocity-injection: gradle-enterprise-injection:
needs: build-distribution needs: build-distribution
uses: ./.github/workflows/integ-test-inject-develocity.yml uses: ./.github/workflows/integ-test-inject-gradle-enterprise.yml
with: with:
runner-os: '["ubuntu-latest"]' runner-os: '["ubuntu-latest"]'
download-dist: true download-dist: true
secrets: secrets:
DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }}
provision-gradle-versions: provision-gradle-versions:
needs: build-distribution needs: build-distribution
@@ -96,14 +89,12 @@ jobs:
runner-os: '["ubuntu-latest"]' runner-os: '["ubuntu-latest"]'
download-dist: true download-dist: true
restore-configuration-cache: # restore-configuration-cache:
needs: build-distribution # needs: build-distribution
uses: ./.github/workflows/integ-test-restore-configuration-cache.yml # uses: ./.github/workflows/integ-test-restore-configuration-cache.yml
with: # with:
runner-os: '["ubuntu-latest"]' # runner-os: '["ubuntu-latest"]'
download-dist: true # download-dist: true
secrets:
GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
restore-containerized-gradle-home: restore-containerized-gradle-home:
needs: build-distribution needs: build-distribution

View File

@@ -8,6 +8,7 @@ on:
push: push:
branches: branches:
- main - main
- release/**
- dependabot/** - dependabot/**
jobs: jobs:
@@ -33,7 +34,7 @@ jobs:
id: diff id: diff
# If index.js was different than expected, upload the expected version as an artifact # If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: ${{ failure() && steps.diff.conclusion == 'failure' }} if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with: with:
name: dist name: dist

View File

@@ -8,7 +8,7 @@ env:
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs: jobs:
many-gradle-builds: run-gradle-builds:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -42,27 +42,6 @@ jobs:
continue-on-error: true continue-on-error: true
run: ./gradlew not-a-real-task 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: pre-existing-gradle-home:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

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

@@ -1,4 +1,4 @@
name: Test caching configuration name: Test action inputs for caching
on: on:
workflow_call: workflow_call:
@@ -38,8 +38,7 @@ jobs:
enterprise enterprise
# Exclude build-cache from main cache entry # Exclude build-cache from main cache entry
gradle-home-cache-excludes: | gradle-home-cache-excludes: |
caches/build-cache-* caches/build-cache-1
caches/*/executionHistory
- name: Build using Gradle wrapper - name: Build using Gradle wrapper
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew test run: ./gradlew test
@@ -64,8 +63,7 @@ jobs:
caches caches
enterprise enterprise
gradle-home-cache-excludes: | gradle-home-cache-excludes: |
caches/build-cache-* caches/build-cache-1
caches/*/executionHistory
cache-read-only: true cache-read-only: true
- name: Execute Gradle build with --offline - name: Execute Gradle build with --offline
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl

View File

@@ -1,103 +0,0 @@
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,9 +12,6 @@ on:
type: boolean type: boolean
default: false default: false
permissions:
contents: write
env: env:
DOWNLOAD_DIST: ${{ inputs.download-dist }} DOWNLOAD_DIST: ${{ inputs.download-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }} GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }}
@@ -34,7 +31,7 @@ jobs:
- name: Setup Gradle for dependency-graph generate - name: Setup Gradle for dependency-graph generate
uses: ./ uses: ./
with: with:
dependency-graph: generate-and-upload dependency-graph: generate
- name: Run gradle build - name: Run gradle build
run: ./gradlew build run: ./gradlew build
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
@@ -58,7 +55,7 @@ jobs:
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
submit: submit:
needs: [groovy-generate] needs: [groovy-generate, kotlin-generate]
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -83,22 +80,18 @@ jobs:
- name: Setup Gradle for dependency-graph generate - name: Setup Gradle for dependency-graph generate
uses: ./ uses: ./
with: with:
dependency-graph: generate-and-submit dependency-graph: generate
- id: gradle-assemble - id: gradle-assemble
run: ./gradlew assemble run: ./gradlew assemble
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
- id: gradle-build - id: gradle-build
run: ./gradlew build run: ./gradlew build
working-directory: .github/workflow-samples/groovy-dsl 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 - name: Check generated dependency graphs
shell: bash shell: bash
run: | run: |
echo "gradle-assemble report file: ${{ steps.gradle-assemble.outputs.dependency-graph-file }}" 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 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 ls -l dependency-graph-reports
if [ ! -e "${{ steps.gradle-assemble.outputs.dependency-graph-file }}" ]; then if [ ! -e "${{ steps.gradle-assemble.outputs.dependency-graph-file }}" ]; then
echo "Did not find gradle-assemble dependency graph file" echo "Did not find gradle-assemble dependency graph file"
@@ -108,41 +101,3 @@ jobs:
echo "Did not find gradle-build dependency graph files" echo "Did not find gradle-build dependency graph files"
exit 1 exit 1
fi 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,7 +21,6 @@ jobs:
# Test that pre-installed runner JDKs are detected # Test that pre-installed runner JDKs are detected
pre-installed-toolchains: pre-installed-toolchains:
strategy: strategy:
fail-fast: false
matrix: matrix:
os: ${{fromJSON(inputs.runner-os)}} os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -36,7 +35,7 @@ jobs:
shell: bash shell: bash
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: | run: |
gradle --info javaToolchains > output.txt gradle -q javaToolchains > output.txt
cat output.txt cat output.txt
- name: Verify detected toolchains - name: Verify detected toolchains
shell: bash shell: bash
@@ -45,12 +44,10 @@ 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 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 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 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 # Test that JDKs provisioned by setup-java are detected
setup-java-installed-toolchain: setup-java-installed-toolchain:
strategy: strategy:
fail-fast: false
matrix: matrix:
os: ${{fromJSON(inputs.runner-os)}} os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -75,19 +72,42 @@ jobs:
shell: bash shell: bash
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: | run: |
gradle --info javaToolchains > output.txt gradle -q javaToolchains > output.txt
cat output.txt cat output.txt
- name: Verify setup JDKs are detected - name: Verify detected toolchains
shell: bash shell: bash
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: | 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 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) grep -q 'Eclipse Temurin JDK 20' output.txt || (echo "::error::Did not detect setup-java installed JDK 20" && exit 1)
- name: Verify pre-installed toolchains are detected
# 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
shell: bash shell: bash
working-directory: .github/workflow-samples/groovy-dsl
run: | run: |
grep -q 'Eclipse Temurin JDK 1.8' output.txt || (echo "::error::Did not detect preinstalled JDK 1.8" && exit 1) mkdir -p ~/.gradle
grep -q 'Eclipse Temurin JDK 11' output.txt || (echo "::error::Did not detect preinstalled JDK 11" && exit 1) echo "org.gradle.java.installations.fromEnv=XXXXX" > ~/.gradle/gradle.properties
grep -q 'Eclipse Temurin JDK 17' output.txt || (echo "::error::Did not detect preinstalled JDK 17" && exit 1) - name: Setup Gradle
grep -q 'Eclipse Temurin JDK 21' output.txt || (echo "::error::Did not detect preinstalled JDK 21" && exit 1) 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

View File

@@ -46,6 +46,12 @@ jobs:
gradle-version: release-candidate gradle-version: release-candidate
build-root-directory: .github/workflow-samples/no-wrapper build-root-directory: .github/workflow-samples/no-wrapper
arguments: help 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: gradle-versions:
strategy: strategy:

View File

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

View File

@@ -11,9 +11,6 @@ on:
download-dist: download-dist:
type: boolean type: boolean
default: false default: false
secrets:
GRADLE_ENCRYPTION_KEY:
required: true
env: env:
DOWNLOAD_DIST: ${{ inputs.download-dist }} DOWNLOAD_DIST: ${{ inputs.download-dist }}
@@ -37,11 +34,9 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Groovy build with configuration-cache enabled - name: Groovy build with configuration-cache enabled
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache run: ./gradlew test --configuration-cache
verify-build-groovy: verify-build-groovy:
env: env:
@@ -60,12 +55,10 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: true cache-read-only: true
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Groovy build with configuration-cache enabled - name: Groovy build with configuration-cache enabled
id: execute id: execute
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache run: ./gradlew test --configuration-cache
- name: Check that configuration-cache was used - name: Check that configuration-cache was used
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
@@ -95,11 +88,9 @@ jobs:
GRADLE_BUILD_ACTION_SKIP_RESTORE: "generated-gradle-jars|wrapper-zips|java-toolchains|instrumented-jars|dependencies|kotlin-dsl" GRADLE_BUILD_ACTION_SKIP_RESTORE: "generated-gradle-jars|wrapper-zips|java-toolchains|instrumented-jars|dependencies|kotlin-dsl"
with: with:
cache-read-only: true cache-read-only: true
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) - name: Check execute Gradle build with configuration cache enabled (but not restored)
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache run: ./gradlew test --configuration-cache
seed-build-kotlin: seed-build-kotlin:
env: env:
@@ -117,11 +108,9 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'help' with configuration-cache enabled - name: Execute 'help' with configuration-cache enabled
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: gradle help --configuration-cache run: ./gradlew help --configuration-cache
modify-build-kotlin: modify-build-kotlin:
env: env:
@@ -140,11 +129,9 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'test' with configuration-cache enabled - name: Execute 'test' with configuration-cache enabled
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: gradle test --configuration-cache run: ./gradlew test --configuration-cache
# Test restore configuration-cache from the third build invocation # Test restore configuration-cache from the third build invocation
verify-build-kotlin: verify-build-kotlin:
@@ -164,12 +151,10 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: true cache-read-only: true
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6-rc-1
- name: Execute 'test' again with configuration-cache enabled - name: Execute 'test' again with configuration-cache enabled
id: execute id: execute
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: gradle test --configuration-cache run: ./gradlew test --configuration-cache
- name: Check that configuration-cache was used - name: Check that configuration-cache was used
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:

View File

@@ -1,3 +1,3 @@
# Configuration file for asdf version manager # Configuration file for asdf version manager
nodejs 20.10.0 nodejs 16.18.1
gradle 8.5 gradle 8.5

316
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`? ## Why use the `gradle-build-action`?
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. 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.
However, the `gradle-build-action` offers a number of advantages over this approach: However, the `gradle-build-action` offers a number of advantages over this approach:
@@ -36,7 +36,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-java@v4 - uses: actions/setup-java@v3
with: with:
distribution: temurin distribution: temurin
java-version: 11 java-version: 11
@@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-java@v4 - uses: actions/setup-java@v3
with: with:
distribution: temurin distribution: temurin
java-version: 11 java-version: 11
@@ -97,6 +97,7 @@ jobs:
- run: echo "The release-candidate version was ${{ steps.setup-gradle.outputs.gradle-version }}" - run: echo "The release-candidate version was ${{ steps.setup-gradle.outputs.gradle-version }}"
``` ```
## Caching build state between Jobs ## Caching build state between Jobs
The `gradle-build-action` will use the GitHub Actions cache to save and restore reusable state that may be speed up a subsequent build invocation. This includes most content that is downloaded from the internet as part of a build, as well as expensive to create content like compiled build scripts, transformed Jar files, etc. The `gradle-build-action` will use the GitHub Actions cache to save and restore reusable state that may be speed up a subsequent build invocation. This includes most content that is downloaded from the internet as part of a build, as well as expensive to create content like compiled build scripts, transformed Jar files, etc.
@@ -120,7 +121,7 @@ cache-disabled: true
By default, the `gradle-build-action` will only write to the cache from Jobs on the default (`main`/`master`) branch. 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. Jobs on other branches will read entries from the cache but will not write updated entries.
See [Optimizing cache effectiveness](#select-which-branches-should-write-to-the-cache) for a more detailed explanation. See [Optimizing cache effectiveness](#optimizing-cache-effectiveness) 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. 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.
@@ -160,31 +161,6 @@ If you want override the default and have the `gradle-build-action` caches overw
cache-overwrite-existing: true cache-overwrite-existing: true
``` ```
### Saving configuration-cache data
When Gradle is executed with the [configuration-cache](https://docs.gradle.org/current/userguide/configuration_cache.html) enabled, the configuration-cache data is stored
in the project directory, at `<project-dir>/.gradle/configuration-cache`. Due to the way the configuration-cache works, [this file may contain stored credentials and other
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:
- Execute your build with Gradle 8.6 or newer. This can be achieved directly, or via the Gradle Wrapper.
- 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:
gradle-with-configuration-cache:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/gradle-build-action@v3
with:
gradle-version: 8.6-rc-1
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
- run: gradle build --configuration-cache
```
### Incompatibility with other caching mechanisms ### Incompatibility with other caching mechanisms
When using `gradle-build-action` we recommend that you avoid using other mechanisms to save and restore the Gradle User Home. When using `gradle-build-action` we recommend that you avoid using other mechanisms to save and restore the Gradle User Home.
@@ -247,11 +223,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 Gradle User Home cache key is composed of:
- The current operating system (`RUNNER_OS`) - The current operating system (`RUNNER_OS`)
- The Job id - The workflow name and Job ID
- A hash of the Job matrix parameters and the workflow name - A hash of the Job matrix parameters
- The git SHA for the latest commit - The git SHA for the latest commit
Specifically, the cache key is: `${cache-protocol}-gradle|${runner-os}|${job-id}[${hash-of-job-matrix-and-workflow-name}]-${git-sha}` Specifically, the cache key is: `${cache-protocol}-gradle|${runner-os}|${workflow-name}-${job-id}[${hash-of-job-matrix}]-${git-sha}`
As such, the cache key is likely to change on each subsequent run of GitHub actions. 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. This allows the most recent state to always be available in the GitHub actions cache.
@@ -259,9 +235,9 @@ This allows the most recent state to always be available in the GitHub actions c
### Finding a matching cache entry ### 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: 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, job id, workflow name, matrix and Git SHA - An exact match on OS, workflow, job, 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, workflow, job and matrix values
- The most recent entry saved for the same OS and job id - The most recent entry saved for the same OS, workflow and job
- The most recent entry saved for the same OS - 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. 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.
@@ -314,23 +290,20 @@ There are some techniques that can be used to avoid/mitigate this issue:
### Select which branches should write to the cache ### Select which branches should write to the cache
GitHub cache entries are not shared between builds on different branches. GitHub cache entries are not shared between builds on different branches.
Workflow runs can restore caches created in either the current branch or the default branch (usually main). 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.
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. 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.
By default, the `gradle-build-action` will only _write_ to the cache for builds run on the default (`master`/`main`) branch. 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 behavior. Jobs run on other branches will only read from the cache. In most cases, this is the desired behaviour,
This is because Jobs run on other branches will benefit from the cache Gradle User Home from `main`, because Jobs run against 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. without writing private cache entries that could lead to evicting shared entries.
If you have other long-lived development branches that would benefit from writing to the cache, If you have other long-lived development branches that would benefit from writing to the cache,
you can configure this by disabling the `cache-read-only` action parameter for these branches. you can configure these by overriding the `cache-read-only` action parameter.
See [Using the cache read-only](#using-the-cache-read-only) for more details. See [Using the cache read-only](#using-the-cache-read-only) for more details.
Note there are some cases where writing cache entries is typically unhelpful (these are disabled by default): 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.
- 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 ### 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. As well as any wrapper distributions, the action will attempt to save and restore the `caches` and `notifications` directories from Gradle User Home.
@@ -371,57 +344,62 @@ gradle-home-cache-cleanup: true
## Build reporting ## Build reporting
The `gradle-build-action` collects information about any Gradle executions that occur in a workflow, including the root project, The `gradle-build-action` collects information about any Gradle executions that occur in a workflow, and reports these via
requested tasks, build outcome and any Build Scan link generated. Details of cache entries read and written are also collected. a Job Summary, visible in the GitHub Actions UI. For each Gradle execution, details about the invocation are listed, together with
These details are compiled into a Job Summary, which is visible in the GitHub Actions UI. a link to any Build Scan® published.
Generation of a Job Summary is enabled by default for all Jobs using the `gradle-build-action`. This feature can be configured Generation of a Job Summary is enabled by default. If this is not desired, it can be disable as follows:
so that a Job Summary is never generated, or so that a Job Summary is only generated on build failure:
```yaml ```yaml
add-job-summary: 'on-failure' # Valid values are 'always' (default), 'never', and 'on-failure' generate-job-summary: false
``` ```
### Adding Job Summary as a Pull Request comment 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.
It is sometimes more convenient to view the results of a GitHub Actions Job directly from the Pull Request that triggered ### Build Scan® link as Step output
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 ```yaml
name: CI # .github/workflows/gradle-build-pr.yml
on: name: Run Gradle on PRs
pull_request: on: pull_request
permissions:
pull-requests: write
jobs: jobs:
run-gradle-build: gradle:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout project sources - name: Checkout project sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v3 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()
with: with:
add-job-summary-as-pr-comment: on-failure # Valid values are 'never' (default), 'always', and 'on-failure' github-token: ${{secrets.GITHUB_TOKEN}}
- run: ./gradlew build --scan 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 }}'
})
``` ```
Note that in order to add a Pull Request comment, the workflow must be configured with the `pull-requests: write` permission. ### Saving 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,
### Build Scan® link as Step output but any output files generated by the build will not be saved.
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. To save selected files from your build execution, you can use the core [Upload-Artifact](https://github.com/actions/upload-artifact) action.
For example: For example:
@@ -445,13 +423,102 @@ jobs:
path: build/reports/ path: build/reports/
``` ```
### Use of custom init-scripts in Gradle User Home ## Use the action to invoke Gradle
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) If the `gradle-build-action` is configured with an `arguments` input, then Gradle will execute a Gradle build with the arguments provided. NOTE: We recommend using the `gradle-build-action` as a "Setup Gradle" step as described above, with Gradle being invoked via a regular `run` command.
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`. If no `arguments` are provided, the action will not execute Gradle, but will still cache Gradle state and configure build-scan capture for all subsequent Gradle executions.
Since Gradle applies init scripts in alphabetical order, one way to ensure this is via file naming.
```yaml
name: Run Gradle on PRs
on: pull_request
jobs:
gradle:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- name: Setup and execute Gradle 'test' task
uses: gradle/gradle-build-action@v2
with:
arguments: test
```
### Multiple Gradle executions in the same Job
It is possible to configure multiple Gradle executions to run sequentially in the same job.
The initial Action step will perform the Gradle setup.
```yaml
- uses: gradle/gradle-build-action@v2
with:
arguments: assemble
- uses: gradle/gradle-build-action@v2
with:
arguments: check
```
### Gradle command-line arguments
The `arguments` input can be used to pass arbitrary arguments to the `gradle` command line.
Arguments can be supplied in a single line, or as a multi-line input.
Here are some valid examples:
```yaml
arguments: build
arguments: check --scan
arguments: some arbitrary tasks
arguments: build -PgradleProperty=foo
arguments: |
build
--scan
-PgradleProperty=foo
-DsystemProperty=bar
```
If you need to pass environment variables, use the GitHub Actions workflow syntax:
```yaml
- uses: gradle/gradle-build-action@v2
env:
CI: true
with:
arguments: build
```
### Gradle build located in a subdirectory
By default, the action will execute Gradle in the root directory of your project.
Use the `build-root-directory` input to target a Gradle build in a subdirectory.
```yaml
- uses: gradle/gradle-build-action@v2
with:
arguments: build
build-root-directory: some/subdirectory
```
### Using a specific Gradle executable
The action will first look for a Gradle wrapper script in the root directory of your project.
If not found, `gradle` will be executed from the PATH.
Use the `gradle-executable` input to execute using a specific Gradle installation.
```yaml
- uses: gradle/gradle-build-action@v2
with:
arguments: build
gradle-executable: /path/to/installed/gradle
```
This mechanism can also be used to target a Gradle wrapper script that is located in a non-default location.
## Support for GitHub Enterprise Server (GHES) ## Support for GitHub Enterprise Server (GHES)
@@ -476,10 +543,9 @@ You enable GitHub Dependency Graph support by setting the `dependency-graph` act
| Option | Behaviour | | Option | Behaviour |
| --- | --- | | --- | --- |
| `disabled` | Do not generate a dependency graph for any build invocations.<p>This is the default. | | `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. | | `generate` | Generate a dependency graph snapshot for each build invocation, saving as a workflow artifact. |
| `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-submit` | As per `generate`, but any generated dependency graph snapshots will be submitted at the end 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, 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. |
| `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: Example of a CI workflow that generates and submits a dependency graph:
```yaml ```yaml
@@ -513,22 +579,6 @@ Depending on [repository settings](https://docs.github.com/en/actions/security-g
> for a PR submitted from a forked repository. > 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). > 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 ### 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. 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.
@@ -705,9 +755,6 @@ name: run-build-and-generate-dependency-snapshot
on: on:
pull_request: pull_request:
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -716,7 +763,7 @@ jobs:
- name: Setup Gradle to generate and submit dependency graphs - name: Setup Gradle to generate and submit dependency graphs
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
with: with:
dependency-graph: generate-and-upload # Generate graphs and save as workflow artifacts dependency-graph: generate # Only generate in this job
- name: Run a build, generating the dependency graph snapshot which will be submitted - name: Run a build, generating the dependency graph snapshot which will be submitted
run: ./gradlew build run: ./gradlew build
``` ```
@@ -730,9 +777,6 @@ on:
workflows: ['run-build-and-generate-dependency-snapshot'] workflows: ['run-build-and-generate-dependency-snapshot']
types: [completed] types: [completed]
permissions:
contents: write
jobs: jobs:
submit-dependency-graph: submit-dependency-graph:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -740,7 +784,7 @@ jobs:
- name: Retrieve dependency graph artifact and submit - name: Retrieve dependency graph artifact and submit
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
with: with:
dependency-graph: download-and-submit # Download saved workflow artifacts and submit dependency-graph: download-and-submit
``` ```
### Integrating `dependency-review-action` for pull request workflows ### Integrating `dependency-review-action` for pull request workflows
@@ -806,26 +850,27 @@ To reduce storage costs for these artifacts, you can set the `artifact-retention
# Develocity plugin injection # Gradle Enterprise plugin injection
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. 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.
This is achieved via an init-script installed into Gradle User Home, which is enabled and parameterized via environment variables. 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. 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 Develocity injection ## Enabling Gradle Enterprise injection
In order to enable Develocity injection for your build, you must provide the required configuration via environment variables. In order to enable Gradle Enterprise for your build, you must provide the required configuration via environment variables.
Here's a minimal example: Here's a minimal example:
```yaml ```yaml
name: Run build with Develocity injection name: Run build with Gradle Enterprise injection
env: env:
DEVELOCITY_INJECTION_ENABLED: true GRADLE_ENTERPRISE_INJECTION_ENABLED: true
DEVELOCITY_URL: https://develocity.your-server.com GRADLE_ENTERPRISE_URL: https://ge.gradle.org
DEVELOCITY_PLUGIN_VERSION: 3.16.1 GRADLE_ENTERPRISE_PLUGIN_VERSION: 3.15.1
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_KEY }} # Required to publish scans to ge.gradle.org
jobs: jobs:
build: build:
@@ -834,37 +879,36 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
- name: Run a Gradle build with Develocity injection enabled - name: Run a Gradle build with Gradle Enterprise injection enabled
run: ./gradlew build run: ./gradlew build
``` ```
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. This configuration will automatically apply `v3.15.1` of the [Gradle Enterprise Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/), and publish build scans to https://ge.gradle.org.
This example assumes that the `develocity.your-server.com` server allows anonymous publishing of build scans. 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,
In the likely scenario that your Develocity server requires authentication, you will also need to configure an addition environment variable but will be used by the GE plugin in order to authenticate with the server.
with a valid [Develocity access key](https://docs.gradle.com/enterprise/gradle-plugin/#via_environment_variable).
## Configuring Develocity injection ## Configuring Gradle Enterprise 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: 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 | | Variable | Required | Description |
|-----------------------------------| --- | --- | | --- | --- | --- |
| DEVELOCITY_INJECTION_ENABLED | :white_check_mark: | enables Develocity injection | | GRADLE_ENTERPRISE_INJECTION_ENABLED | :white_check_mark: | enables Gradle Enterprise injection |
| DEVELOCITY_URL | :white_check_mark: | the URL of the Develocity server | | GRADLE_ENTERPRISE_URL | :white_check_mark: | the URL of the Gradle Enterprise server |
| DEVELOCITY_ALLOW_UNTRUSTED_SERVER | | allow communication with an untrusted server; set to _true_ if your Develocity instance is using a self-signed certificate | | 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 |
| 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 | | 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 |
| DEVELOCITY_PLUGIN_VERSION | :white_check_mark: | the version of the [Develocity Gradle plugin](https://docs.gradle.com/enterprise/gradle-plugin/) to apply | | GRADLE_ENTERPRISE_PLUGIN_VERSION | :white_check_mark: | the version of the [Gradle Enterprise 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_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_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 | | 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 |
## Publishing to scans.gradle.com ## Publishing to scans.gradle.com
Develocity injection is designed to enable publishing of build scans to a Develocity instance, Gradle Enterprise injection is designed to enable publishing of build scans to a Gradle Enterprise instance,
and is not suitable for publishing to the public Build Scans instance (https://scans.gradle.com). 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: In order to publish Build Scans to scans.gradle.com, you need to:
- Apply the Develocity plugin to your build configuration ([see docs](https://docs.gradle.com/enterprise/get-started/#applying_the_plugin)) - Apply the Gradle Enterprise 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)) - 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)) - 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

@@ -8,6 +8,14 @@ inputs:
description: Gradle version to use. If specified, this Gradle version will be downloaded, added to the PATH and used for invoking Gradle. description: Gradle version to use. If specified, this Gradle version will be downloaded, added to the PATH and used for invoking Gradle.
required: false required: false
gradle-executable:
description: Path to the Gradle executable. If specified, this executable will be added to the PATH and used for invoking Gradle.
required: false
build-root-directory:
description: Path to the root directory of the build. Default is the root of the GitHub workspace.
required: false
cache-disabled: cache-disabled:
description: When 'true', all caching is disabled. No entries will be written to or read from the cache. description: When 'true', all caching is disabled. No entries will be written to or read from the cache.
required: false required: false
@@ -32,13 +40,6 @@ inputs:
required: false required: false
default: false default: false
cache-encryption-key:
description: |
A base64 encoded AES key used to encrypt the configuration-cache data. The key is exported as 'GRADLE_ENCRYPTION_KEY' for later steps.
A suitable key can be generated with `openssl rand -base64 16`.
Configuration-cache data will not be saved/restored without an encryption key being provided.
required: false
gradle-home-cache-includes: gradle-home-cache-includes:
description: Paths within Gradle User Home to cache. description: Paths within Gradle User Home to cache.
required: false required: false
@@ -58,46 +59,23 @@ inputs:
required: false required: false
default: false default: false
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: '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
# DEPRECATED ACTION INPUTS
arguments: arguments:
description: Gradle command line arguments (supports multi-line input) description: Gradle command line arguments (supports multi-line input)
required: false 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.
build-root-directory:
description: Path to the root directory of the build. Default is the root of the GitHub workspace.
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.
generate-job-summary: generate-job-summary:
description: When 'false', no Job Summary will be generated for the Job. description: When 'false', no Job Summary will be generated for the Job.
required: false required: false
default: true default: true
deprecation-message: Superceded by the new 'add-job-summary' and 'add-job-summary-as-pr-comment' parameters.
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', 'download-and-submit' and 'clear'.
required: false
default: 'disabled'
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
# EXPERIMENTAL & INTERNAL ACTION INPUTS # EXPERIMENTAL & INTERNAL ACTION INPUTS
# The following action properties allow fine-grained tweaking of the action caching behaviour. # The following action properties allow fine-grained tweaking of the action caching behaviour.
@@ -107,7 +85,6 @@ inputs:
description: When 'true', the action will not attempt to restore the Gradle User Home entries from other Jobs. description: When 'true', the action will not attempt to restore the Gradle User Home entries from other Jobs.
required: false required: false
default: false default: false
workflow-job-context: workflow-job-context:
description: Used to uniquely identify the current job invocation. Defaults to the matrix values for this job; this should not be overridden by users (INTERNAL). description: Used to uniquely identify the current job invocation. Defaults to the matrix values for this job; this should not be overridden by users (INTERNAL).
required: false required: false
@@ -127,7 +104,7 @@ outputs:
description: Version of Gradle that was setup by the action description: Version of Gradle that was setup by the action
runs: runs:
using: 'node20' using: 'node16'
main: 'dist/main/index.js' main: 'dist/main/index.js'
post: 'dist/post/index.js' post: 'dist/post/index.js'

View File

@@ -1,24 +0,0 @@
name: 'Clear dependency graph for a correlator'
inputs:
job-correlator:
required: true
runs:
using: "composite"
steps:
- name: Set current timestamp as env variable
shell: bash
run: echo "NOW=$(date -Iseconds)" >> $GITHUB_ENV
- name: Submit empty dependency graph
shell: bash
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/dependency-graph/snapshots \
-d '{ "version" : 0, "job" : { "id" : "${{ github.run_id }}", "correlator" : "${{ inputs.job-correlator }} " }, "sha" : "${{ github.sha }}", "ref" : "${{ github.ref }}", "detector" : { "name" : "GitHub Dependency Graph Gradle Plugin", "version" : "0.0.3", "url" : "https://github.com/gradle/github-dependency-graph-gradle-plugin" }, "manifests" : {}, "scanned" : "${{ env.NOW }}" }'
- run: echo "::notice ::Cleared dependency graph for job correlator '${{ inputs.job-correlator }}'"
shell: bash

60519
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

59674
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4518
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,13 @@
"format": "prettier --write **/*.ts", "format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts", "format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"compile-main": "ncc build src/main.ts --out dist/main --source-map --no-source-map-register", "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-post": "ncc build src/post.ts --out dist/post --source-map --no-source-map-register",
"compile": "npm-run-all --parallel compile-*", "compile": "npm run compile-main && npm run compile-post",
"check": "npm-run-all --parallel format lint",
"test": "jest", "test": "jest",
"check": "npm run format && npm run lint",
"build": "npm run check && npm run compile", "build": "npm run check && npm run compile",
"all": "npm run build && npm test" "all": "npm run build && npm test"
}, },
@@ -28,32 +30,30 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/artifact": "2.0.0", "@actions/artifact": "1.1.2",
"@actions/cache": "3.2.2", "@actions/cache": "3.2.2",
"@actions/core": "1.10.1", "@actions/core": "1.10.1",
"@actions/exec": "1.1.1", "@actions/exec": "1.1.1",
"@actions/github": "6.0.0", "@actions/github": "5.1.1",
"@actions/glob": "0.4.0", "@actions/glob": "0.4.0",
"@actions/http-client": "2.2.0", "@actions/http-client": "2.2.0",
"@actions/tool-cache": "2.0.1", "@actions/tool-cache": "2.0.1",
"@octokit/rest": "19.0.13", "@octokit/rest": "19.0.13",
"@octokit/webhooks-types": "7.3.1", "@octokit/webhooks-types": "7.3.1",
"semver": "7.5.4",
"string-argv": "0.3.2" "string-argv": "0.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "16.18.38",
"@types/jest": "29.5.11", "@types/jest": "29.5.11",
"@types/node": "20.10.0",
"@types/unzipper": "0.10.9", "@types/unzipper": "0.10.9",
"@typescript-eslint/parser": "6.17.0", "@typescript-eslint/parser": "6.14.0",
"@vercel/ncc": "0.38.1", "@vercel/ncc": "0.38.1",
"eslint": "8.56.0", "eslint": "8.55.0",
"eslint-plugin-github": "4.10.1", "eslint-plugin-github": "4.10.1",
"eslint-plugin-jest": "27.6.1", "eslint-plugin-jest": "27.6.0",
"eslint-plugin-prettier": "5.1.2", "eslint-plugin-prettier": "5.0.1",
"jest": "29.7.0", "jest": "29.7.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"npm-run-all": "4.1.5",
"patch-package": "8.0.0", "patch-package": "8.0.0",
"prettier": "3.1.1", "prettier": "3.1.1",
"ts-jest": "29.1.1", "ts-jest": "29.1.1",

View File

@@ -1,13 +1,11 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as glob from '@actions/glob'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import * as params from './input-params' import * as params from './input-params'
import {CacheListener} from './cache-reporting' import {CacheListener} from './cache-reporting'
import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete, generateCacheKey} from './cache-utils' import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete, generateCacheKey} from './cache-utils'
import {GradleHomeEntryExtractor, ConfigurationCacheEntryExtractor} from './cache-extract-entries' import {GradleHomeEntryExtractor} from './cache-extract-entries'
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key' const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'
@@ -17,24 +15,22 @@ export class GradleStateCache {
private cacheName: string private cacheName: string
private cacheDescription: string private cacheDescription: string
protected readonly userHome: string
protected readonly gradleUserHome: string protected readonly gradleUserHome: string
constructor(userHome: string, gradleUserHome: string) { constructor(gradleUserHome: string) {
this.userHome = userHome
this.gradleUserHome = gradleUserHome this.gradleUserHome = gradleUserHome
this.cacheName = 'gradle' this.cacheName = 'gradle'
this.cacheDescription = 'Gradle User Home' this.cacheDescription = 'Gradle User Home'
} }
init(): void { init(): void {
this.initializeGradleUserHome() const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action')
fs.mkdirSync(actionCacheDir, {recursive: true})
// Export the GRADLE_ENCRYPTION_KEY variable if provided const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d')
const encryptionKey = params.getCacheEncryptionKey() fs.mkdirSync(initScriptsDir, {recursive: true})
if (encryptionKey) {
core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey) this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir)
}
} }
cacheOutputExists(): boolean { cacheOutputExists(): boolean {
@@ -83,7 +79,7 @@ export class GradleStateCache {
async afterRestore(listener: CacheListener): Promise<void> { async afterRestore(listener: CacheListener): Promise<void> {
await this.debugReportGradleUserHomeSize('as restored from cache') await this.debugReportGradleUserHomeSize('as restored from cache')
await new GradleHomeEntryExtractor(this.gradleUserHome).restore(listener) await new GradleHomeEntryExtractor(this.gradleUserHome).restore(listener)
await new ConfigurationCacheEntryExtractor(this.gradleUserHome).restore(listener) // await new ConfigurationCacheEntryExtractor(this.gradleUserHome).restore(listener)
await this.debugReportGradleUserHomeSize('after restoring common artifacts') await this.debugReportGradleUserHomeSize('after restoring common artifacts')
} }
@@ -131,10 +127,10 @@ export class GradleStateCache {
*/ */
async beforeSave(listener: CacheListener): Promise<void> { async beforeSave(listener: CacheListener): Promise<void> {
await this.debugReportGradleUserHomeSize('before saving common artifacts') await this.debugReportGradleUserHomeSize('before saving common artifacts')
await this.deleteExcludedPaths() this.deleteExcludedPaths()
await Promise.all([ await Promise.all([
new GradleHomeEntryExtractor(this.gradleUserHome).extract(listener), new GradleHomeEntryExtractor(this.gradleUserHome).extract(listener)
new ConfigurationCacheEntryExtractor(this.gradleUserHome).extract(listener) // new ConfigurationCacheEntryExtractor(this.gradleUserHome).extract(listener)
]) ])
await this.debugReportGradleUserHomeSize( await this.debugReportGradleUserHomeSize(
"after extracting common artifacts (only 'caches' and 'notifications' will be stored)" "after extracting common artifacts (only 'caches' and 'notifications' will be stored)"
@@ -144,21 +140,13 @@ export class GradleStateCache {
/** /**
* Delete any file paths that are excluded by the `gradle-home-cache-excludes` parameter. * Delete any file paths that are excluded by the `gradle-home-cache-excludes` parameter.
*/ */
private async deleteExcludedPaths(): Promise<void> { private deleteExcludedPaths(): void {
const rawPaths: string[] = params.getCacheExcludes() const rawPaths: string[] = params.getCacheExcludes()
rawPaths.push('caches/*/cc-keystore')
const resolvedPaths = rawPaths.map(x => path.resolve(this.gradleUserHome, x)) const resolvedPaths = rawPaths.map(x => path.resolve(this.gradleUserHome, x))
for (const p of resolvedPaths) { for (const p of resolvedPaths) {
cacheDebug(`Removing excluded path: ${p}`) cacheDebug(`Deleting excluded path: ${p}`)
const globber = await glob.create(p, { tryDelete(p)
implicitDescendants: false
})
for (const toDelete of await globber.glob()) {
cacheDebug(`Removing excluded file: ${toDelete}`)
await tryDelete(toDelete)
}
} }
} }
@@ -183,59 +171,40 @@ export class GradleStateCache {
return path.resolve(this.gradleUserHome, rawPath) return path.resolve(this.gradleUserHome, rawPath)
} }
private initializeGradleUserHome(): void { private initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
// Create a directory for storing action metadata // Ensure that pre-installed java versions are detected. Only add property if it isn't already defined.
const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action') const gradleProperties = path.resolve(gradleUserHome, 'gradle.properties')
fs.mkdirSync(actionCacheDir, {recursive: true}) 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
`
)
}
this.copyInitScripts() // Copy init scripts from src/resources
// 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 = [ const initScriptFilenames = [
'gradle-build-action.build-result-capture.init.gradle', 'gradle-build-action.build-result-capture.init.gradle',
'gradle-build-action.build-result-capture-service.plugin.groovy', 'gradle-build-action.build-result-capture-service.plugin.groovy',
'gradle-build-action.github-dependency-graph.init.gradle', 'gradle-build-action.github-dependency-graph.init.gradle',
'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy', 'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy',
'gradle-build-action.inject-develocity.init.gradle' 'gradle-build-action.inject-gradle-enterprise.init.gradle'
] ]
for (const initScriptFilename of initScriptFilenames) { for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readResourceFileAsString('init-scripts', initScriptFilename) const initScriptContent = this.readInitScriptAsString(initScriptFilename)
const initScriptPath = path.resolve(initScriptsDir, initScriptFilename) const initScriptPath = path.resolve(initScriptsDir, initScriptFilename)
fs.writeFileSync(initScriptPath, initScriptContent) fs.writeFileSync(initScriptPath, initScriptContent)
} }
} }
private registerToolchains(): void { private readInitScriptAsString(resource: string): string {
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 // Resolving relative to __dirname will allow node to find the resource at runtime
const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', ...paths) const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource)
return fs.readFileSync(absolutePath, 'utf8') return fs.readFileSync(absolutePath, 'utf8')
} }

View File

@@ -2,14 +2,13 @@ import path from 'path'
import fs from 'fs' import fs from 'fs'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as semver from 'semver'
import * as params from './input-params' import * as params from './input-params'
import {META_FILE_DIR} from './cache-base' import {META_FILE_DIR} from './cache-base'
import {CacheEntryListener, CacheListener} from './cache-reporting' import {CacheEntryListener, CacheListener} from './cache-reporting'
import {cacheDebug, getCacheKeyPrefix, hashFileNames, restoreCache, saveCache, tryDelete} from './cache-utils' import {cacheDebug, getCacheKeyPrefix, hashFileNames, restoreCache, saveCache, tryDelete} from './cache-utils'
import {BuildResult, loadBuildResults} from './build-results' import {loadBuildResults} from './build-results'
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE' const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE'
@@ -47,7 +46,6 @@ class ExtractedCacheEntryDefinition {
pattern: string pattern: string
bundle: boolean bundle: boolean
uniqueFileNames = true uniqueFileNames = true
notCacheableReason: string | undefined
constructor(artifactType: string, pattern: string, bundle: boolean) { constructor(artifactType: string, pattern: string, bundle: boolean) {
this.artifactType = artifactType this.artifactType = artifactType
@@ -55,24 +53,10 @@ class ExtractedCacheEntryDefinition {
this.bundle = bundle this.bundle = bundle
} }
/**
* Indicate that the file names matching the cache entry pattern are NOT sufficient to uniquely identify the contents.
* If the file names are sufficient, then we use a hash of the file names to identify the entry.
* With non-unique-file-names, we hash the file contents to identify the cache entry.
*/
withNonUniqueFileNames(): ExtractedCacheEntryDefinition { withNonUniqueFileNames(): ExtractedCacheEntryDefinition {
this.uniqueFileNames = false this.uniqueFileNames = false
return this return this
} }
/**
* Specify that the cache entry, should not be saved for some reason, even though the contents exist.
* This is used to prevent configuration-cache entries being cached when they were generated by Gradle < 8.6,
*/
notCacheableBecause(reason: string): ExtractedCacheEntryDefinition {
this.notCacheableReason = reason
return this
}
} }
/** /**
@@ -158,11 +142,6 @@ abstract class AbstractEntryExtractor {
const artifactType = cacheEntryDefinition.artifactType const artifactType = cacheEntryDefinition.artifactType
const pattern = cacheEntryDefinition.pattern const pattern = cacheEntryDefinition.pattern
if (cacheEntryDefinition.notCacheableReason) {
listener.entry(pattern).markNotSaved(cacheEntryDefinition.notCacheableReason)
continue
}
// Find all matching files for this cache entry definition // Find all matching files for this cache entry definition
const globber = await glob.create(pattern, { const globber = await glob.create(pattern, {
implicitDescendants: false implicitDescendants: false
@@ -277,7 +256,7 @@ abstract class AbstractEntryExtractor {
} }
const filedata = fs.readFileSync(cacheMetadataFile, 'utf-8') const filedata = fs.readFileSync(cacheMetadataFile, 'utf-8')
cacheDebug(`Loaded cache metadata for ${this.extractorName}: ${filedata}`) cacheDebug(`Loaded cache metadata: ${filedata}`)
const extractedCacheEntryMetadata = JSON.parse(filedata) as ExtractedCacheEntryMetadata const extractedCacheEntryMetadata = JSON.parse(filedata) as ExtractedCacheEntryMetadata
return extractedCacheEntryMetadata.entries return extractedCacheEntryMetadata.entries
} }
@@ -285,12 +264,12 @@ abstract class AbstractEntryExtractor {
/** /**
* Saves information about the extracted cache entries into the 'cache-metadata.json' file. * Saves information about the extracted cache entries into the 'cache-metadata.json' file.
*/ */
protected saveMetadataForCacheResults(results: ExtractedCacheEntry[]): void { private saveMetadataForCacheResults(results: ExtractedCacheEntry[]): void {
const extractedCacheEntryMetadata = new ExtractedCacheEntryMetadata() const extractedCacheEntryMetadata = new ExtractedCacheEntryMetadata()
extractedCacheEntryMetadata.entries = results.filter(x => x.cacheKey !== undefined) extractedCacheEntryMetadata.entries = results.filter(x => x.cacheKey !== undefined)
const filedata = JSON.stringify(extractedCacheEntryMetadata) const filedata = JSON.stringify(extractedCacheEntryMetadata)
cacheDebug(`Saving cache metadata for ${this.extractorName}: ${filedata}`) cacheDebug(`Saving cache metadata: ${filedata}`)
fs.writeFileSync(this.getCacheMetadataFile(), filedata, 'utf-8') fs.writeFileSync(this.getCacheMetadataFile(), filedata, 'utf-8')
} }
@@ -372,96 +351,37 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
* entry is not reusable. * entry is not reusable.
*/ */
async restore(listener: CacheListener): Promise<void> { async restore(listener: CacheListener): Promise<void> {
if (!listener.fullyRestored) { if (listener.fullyRestored) {
this.markNotRestored(listener, 'Gradle User Home was not fully restored') return super.restore(listener)
return
} }
if (!params.getCacheEncryptionKey()) { core.info('Not restoring configuration-cache state, as Gradle User Home was not fully restored')
this.markNotRestored(listener, 'Encryption Key was not provided') for (const cacheEntry of this.loadExtractedCacheEntries()) {
return listener.entry(cacheEntry.pattern).markRequested('NOT_RESTORED')
} }
return await super.restore(listener)
}
private markNotRestored(listener: CacheListener, reason: string): void {
const cacheEntries = this.loadExtractedCacheEntries()
if (cacheEntries.length > 0) {
core.info(`Not restoring configuration-cache state, as ${reason}`)
for (const cacheEntry of cacheEntries) {
listener.entry(cacheEntry.pattern).markNotRestored(reason)
}
// Update the results file based on no entries restored
this.saveMetadataForCacheResults([])
}
}
async extract(listener: CacheListener): Promise<void> {
if (!params.getCacheEncryptionKey()) {
const cacheEntryDefinitions = this.getExtractedCacheEntryDefinitions()
if (cacheEntryDefinitions.length > 0) {
core.info('Not saving configuration-cache state, as no encryption key was provided')
for (const cacheEntry of cacheEntryDefinitions) {
listener.entry(cacheEntry.pattern).markNotSaved('No encryption key provided')
}
}
return
}
await super.extract(listener)
} }
/** /**
* Extract cache entries for the configuration cache in each project. * Extract cache entries for the configuration cache in each project.
*/ */
protected getExtractedCacheEntryDefinitions(): ExtractedCacheEntryDefinition[] { protected getExtractedCacheEntryDefinitions(): ExtractedCacheEntryDefinition[] {
// Group BuildResult by existing configCacheDir return this.getProjectRoots().map(projectRoot => {
const groupedResults = this.getConfigCacheDirectoriesWithAssociatedBuildResults() const configCachePath = path.resolve(projectRoot, '.gradle/configuration-cache')
return new ExtractedCacheEntryDefinition(
return Object.entries(groupedResults).map(([configCachePath, pathResults]) => {
// Create a entry definition for each unique configuration cache directory
const definition = new ExtractedCacheEntryDefinition(
'configuration-cache', 'configuration-cache',
configCachePath, configCachePath,
true true
).withNonUniqueFileNames() ).withNonUniqueFileNames()
// If any associated build result used Gradle < 8.6, then mark it as not cacheable
if (
pathResults.find(result => {
const gradleVersion = semver.coerce(result.gradleVersion)
return gradleVersion && semver.lt(gradleVersion, '8.6.0')
})
) {
core.info(
`Not saving config-cache data for ${configCachePath}. Configuration cache data is only saved for Gradle 8.6+`
)
definition.notCacheableBecause('Configuration cache data only saved for Gradle 8.6+')
}
return definition
}) })
} }
private getConfigCacheDirectoriesWithAssociatedBuildResults(): Record<string, BuildResult[]> { /**
return loadBuildResults().reduce( * For every Gradle invocation, we record the project root directory. This method returns the entire
(acc, buildResult) => { * set of project roots, to allow saving of configuration-cache entries for each.
// For each build result, find the config-cache dir */
const configCachePath = path.resolve(buildResult.rootProjectDir, '.gradle/configuration-cache') private getProjectRoots(): string[] {
// Ignore case where config-cache dir doesn't exist const buildResults = loadBuildResults()
if (!fs.existsSync(configCachePath)) { const projectRootDirs = buildResults.map(x => x.rootProjectDir)
return acc return [...new Set(projectRootDirs)] // Remove duplicates
}
// Group by unique config cache directories and collect associated build results
if (!acc[configCachePath]) {
acc[configCachePath] = []
}
acc[configCachePath].push(buildResult)
return acc
},
{} as Record<string, BuildResult[]>
)
} }
} }

View File

@@ -1,3 +1,4 @@
import * as core from '@actions/core'
import * as cache from '@actions/cache' import * as cache from '@actions/cache'
/** /**
@@ -111,36 +112,47 @@ export class CacheEntryListener {
} }
} }
export function generateCachingReport(listener: CacheListener): string { export function writeCachingReport(listener: CacheListener): void {
const entries = listener.cacheEntries const entries = listener.cacheEntries
return ` core.summary.addRaw(
<details> `\n<details><summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary>\n`
<summary><h4>Caching for gradle-build-action was ${listener.cacheStatus} - expand for details</h4></summary> )
${renderEntryTable(entries)}
<h5>Cache Entry Details</h5> core.summary.addTable([
<pre> [
${renderEntryDetails(listener)} {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}
</pre> </pre>
</details> </details>
` `)
} }
function renderEntryTable(entries: CacheEntryListener[]): string { export function logCachingReport(listener: CacheListener): void {
return ` const entries = listener.cacheEntries
<table>
<tr><td></td><th>Count</th><th>Total Size (Mb)</th></tr> core.startGroup(`Caching for gradle-build-action was ${listener.cacheStatus} - expand for details`)
<tr><td>Entries Restored</td>
<td>${getCount(entries, e => e.restoredSize)}</td> core.info(
<td>${getSize(entries, e => e.restoredSize)}</td> `Entries Restored: ${getCount(entries, e => e.restoredSize)} (${getSize(entries, e => e.restoredSize)} Mb)`
</tr> )
<tr><td>Entries Saved</td> core.info(`Entries Saved : ${getCount(entries, e => e.savedSize)} (${getSize(entries, e => e.savedSize)} Mb)`)
<td>${getCount(entries, e => e.savedSize)}</td>
<td>${getSize(entries, e => e.savedSize)}</td> core.info(`Cache Entry Details`)
</tr> core.info(renderEntryDetails(listener))
</table>
` core.endGroup()
} }
function renderEntryDetails(listener: CacheListener): string { function renderEntryDetails(listener: CacheListener): string {
@@ -186,9 +198,6 @@ function getSavedMessage(entry: CacheEntryListener, cacheReadOnly: boolean): str
if (cacheReadOnly) { if (cacheReadOnly) {
return '(Entry not saved: cache is read-only)' return '(Entry not saved: cache is read-only)'
} }
if (entry.notRestored) {
return '(Entry not saved: not restored)'
}
return '(Entry not saved: reason unknown)' return '(Entry not saved: reason unknown)'
} }
if (entry.savedSize === 0) { if (entry.savedSize === 0) {

View File

@@ -11,7 +11,7 @@ import * as params from './input-params'
import {CacheEntryListener} from './cache-reporting' import {CacheEntryListener} from './cache-reporting'
const CACHE_PROTOCOL_VERSION = 'v9-' const CACHE_PROTOCOL_VERSION = 'v8-'
const CACHE_KEY_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' const CACHE_KEY_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX'
const CACHE_KEY_OS_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_ENVIRONMENT' const CACHE_KEY_OS_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_ENVIRONMENT'
@@ -86,10 +86,10 @@ export function generateCacheKey(cacheName: string): CacheKey {
// At the most general level, share caches for all executions on the same OS // At the most general level, share caches for all executions on the same OS
const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}` const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}`
// Then prefer caches that run job with the same ID // Prefer caches that run this job
const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}` const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}`
// Prefer (even more) jobs that run this job in the same workflow with the same context (matrix) // Prefer (even more) jobs that run this job with the same context (matrix)
const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]` const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]`
// Exact match on Git SHA // Exact match on Git SHA
@@ -113,7 +113,12 @@ function getCacheKeyEnvironment(): string {
} }
function getCacheKeyJob(): string { function getCacheKeyJob(): string {
return process.env[CACHE_KEY_JOB_VAR] || github.context.job 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}`
} }
function getCacheKeyJobInstance(): string { function getCacheKeyJobInstance(): string {
@@ -122,11 +127,25 @@ function getCacheKeyJobInstance(): string {
return override return override
} }
// By default, we hash the workflow name and the full `matrix` data for the run, to uniquely identify this job invocation // By default, we hash 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. // 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() const workflowJobContext = params.getJobMatrix()
return hashStrings([workflowName, workflowJobContext]) 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()
} }
function getCacheKeyJobExecution(): string { function getCacheKeyJobExecution(): string {

View File

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

View File

@@ -1,7 +1,8 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as artifact from '@actions/artifact'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import {DefaultArtifactClient} from '@actions/artifact' import * as toolCache from '@actions/tool-cache'
import {GitHub} from '@actions/github/lib/utils' import {GitHub} from '@actions/github/lib/utils'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import type {PullRequestEvent} from '@octokit/webhooks-types' import type {PullRequestEvent} from '@octokit/webhooks-types'
@@ -10,15 +11,9 @@ import * as path from 'path'
import fs from 'fs' import fs from 'fs'
import * as layout from './repository-layout' import * as layout from './repository-layout'
import {PostActionJobFailure} from './errors' import {DependencyGraphOption, getJobMatrix, getArtifactRetentionDays} from './input-params'
import {
DependencyGraphOption,
getDependencyGraphContinueOnFailure,
getJobMatrix,
getArtifactRetentionDays
} from './input-params'
const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_' const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph'
export async function setup(option: DependencyGraphOption): Promise<void> { export async function setup(option: DependencyGraphOption): Promise<void> {
if (option === DependencyGraphOption.Disabled) { if (option === DependencyGraphOption.Disabled) {
@@ -31,68 +26,57 @@ export async function setup(option: DependencyGraphOption): Promise<void> {
} }
core.info('Enabling dependency graph generation') core.info('Enabling dependency graph generation')
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true') core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', getDependencyGraphContinueOnFailure()) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator()) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext()) core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory()) core.exportVariable(
maybeExportVariable(
'DEPENDENCY_GRAPH_REPORT_DIR', 'DEPENDENCY_GRAPH_REPORT_DIR',
path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports') path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports')
) )
}
function maybeExportVariable(variableName: string, value: unknown): void { // To clear the dependency graph, we generate an empty graph by excluding all projects and configurations
if (!process.env[variableName]) { if (option === DependencyGraphOption.Clear) {
core.exportVariable(variableName, value) core.exportVariable('DEPENDENCY_GRAPH_INCLUDE_PROJECTS', '')
core.exportVariable('DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS', '')
} }
} }
export async function complete(option: DependencyGraphOption): Promise<void> { export async function complete(option: DependencyGraphOption): Promise<void> {
try { switch (option) {
switch (option) { case DependencyGraphOption.Disabled:
case DependencyGraphOption.Disabled: case DependencyGraphOption.DownloadAndSubmit: // Performed in setup
case DependencyGraphOption.Generate: // Performed via init-script: nothing to do here return
case DependencyGraphOption.DownloadAndSubmit: // Performed in setup case DependencyGraphOption.Generate:
return await uploadDependencyGraphs()
case DependencyGraphOption.GenerateAndSubmit: return
await submitDependencyGraphs(await findGeneratedDependencyGraphFiles()) case DependencyGraphOption.GenerateAndSubmit:
return case DependencyGraphOption.Clear: // Submit the empty dependency graph
case DependencyGraphOption.GenerateAndUpload: await submitDependencyGraphs(await uploadDependencyGraphs())
await uploadDependencyGraphs(await findGeneratedDependencyGraphFiles()) return
}
} catch (e) {
warnOrFail(option, e)
} }
} }
async function findGeneratedDependencyGraphFiles(): Promise<string[]> { async function uploadDependencyGraphs(): Promise<string[]> {
const workspaceDirectory = layout.workspaceDirectory() const workspaceDirectory = layout.workspaceDirectory()
return await findDependencyGraphFiles(workspaceDirectory) const graphFiles = await findDependencyGraphFiles(workspaceDirectory)
}
async function uploadDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> { const relativeGraphFiles = graphFiles.map(x => getRelativePathFromWorkspace(x))
const workspaceDirectory = layout.workspaceDirectory() core.info(`Uploading dependency graph files: ${relativeGraphFiles}`)
const artifactClient = new DefaultArtifactClient() const artifactClient = artifact.create()
for (const dependencyGraphFile of dependencyGraphFiles) { artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, graphFiles, workspaceDirectory, {
const relativePath = getRelativePathFromWorkspace(dependencyGraphFile) retentionDays: getArtifactRetentionDays()
core.info(`Uploading dependency graph file: ${relativePath}`) })
const artifactName = `${DEPENDENCY_GRAPH_PREFIX}${path.basename(dependencyGraphFile)}`
await artifactClient.uploadArtifact(artifactName, [dependencyGraphFile], workspaceDirectory, { return graphFiles
retentionDays: getArtifactRetentionDays()
})
}
} }
async function downloadAndSubmitDependencyGraphs(): Promise<void> { async function downloadAndSubmitDependencyGraphs(): Promise<void> {
try { const workspaceDirectory = layout.workspaceDirectory()
await submitDependencyGraphs(await downloadDependencyGraphs()) submitDependencyGraphs(await retrieveDependencyGraphs(workspaceDirectory))
} catch (e) {
warnOrFail(DependencyGraphOption.DownloadAndSubmit, e)
}
} }
async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> { async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
@@ -101,7 +85,7 @@ async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<v
await submitDependencyGraphFile(jsonFile) await submitDependencyGraphFile(jsonFile)
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {
throw new Error(translateErrorMessage(jsonFile, error)) core.warning(buildWarningMessage(jsonFile, error))
} else { } else {
throw error throw error
} }
@@ -109,9 +93,9 @@ async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<v
} }
} }
function translateErrorMessage(jsonFile: string, error: RequestError): string { function buildWarningMessage(jsonFile: string, error: RequestError): string {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile) const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${String(error)}` const mainWarning = `Failed to submit dependency graph ${relativeJsonFile}.\n${String(error)}`
if (error.message === 'Resource not accessible by integration') { if (error.message === 'Resource not accessible by integration') {
return `${mainWarning} return `${mainWarning}
Please ensure that the 'contents: write' permission is available for the workflow job. Please ensure that the 'contents: write' permission is available for the workflow job.
@@ -134,37 +118,56 @@ async function submitDependencyGraphFile(jsonFile: string): Promise<void> {
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`) core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
} }
async function downloadDependencyGraphs(): Promise<string[]> { async function retrieveDependencyGraphs(workspaceDirectory: string): Promise<string[]> {
const workspaceDirectory = layout.workspaceDirectory() if (github.context.payload.workflow_run) {
return await retrieveDependencyGraphsForWorkflowRun(github.context.payload.workflow_run.id, workspaceDirectory)
}
return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory)
}
const findBy = github.context.payload.workflow_run async function retrieveDependencyGraphsForWorkflowRun(runId: number, workspaceDirectory: string): Promise<string[]> {
? { const octokit = getOctokit()
token: getGithubToken(),
workflowRunId: github.context.payload.workflow_run.id,
repositoryName: github.context.repo.repo,
repositoryOwner: github.context.repo.owner
}
: undefined
const artifactClient = new DefaultArtifactClient() // Find the workflow run artifacts named "dependency-graph"
const downloadPath = path.resolve(workspaceDirectory, 'dependency-graph') const artifacts = await octokit.rest.actions.listWorkflowRunArtifacts({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
run_id: runId
})
const dependencyGraphArtifacts = ( const matchArtifact = artifacts.data.artifacts.find(candidate => {
await artifactClient.listArtifacts({ return candidate.name === DEPENDENCY_GRAPH_ARTIFACT
latest: true, })
findBy
})
).artifacts.filter(candidate => candidate.name.startsWith(DEPENDENCY_GRAPH_PREFIX))
for (const artifact of dependencyGraphArtifacts) { if (matchArtifact === undefined) {
const downloadedArtifact = await artifactClient.downloadArtifact(artifact.id, { throw new Error(`Dependency graph artifact not found. Has it been generated by workflow run '${runId}'?`)
path: downloadPath,
findBy
})
core.info(`Downloading dependency-graph artifact ${artifact.name} to ${downloadedArtifact.downloadPath}`)
} }
return findDependencyGraphFiles(downloadPath) // Download the dependency-graph artifact
const download = await octokit.rest.actions.downloadArtifact({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip'
})
const downloadBuffer = download.data as ArrayBuffer
const downloadZip = path.resolve(workspaceDirectory, 'dependency-graph.zip')
fs.writeFileSync(downloadZip, Buffer.from(downloadBuffer))
// Expance the dependency-graph zip and locate each dependency-graph JSON file
const extractDir = path.resolve(workspaceDirectory, 'dependency-graph')
const extracted = await toolCache.extractZip(downloadZip, extractDir)
core.info(`Extracted dependency graph artifacts to ${extracted}: ${fs.readdirSync(extracted)}`)
return findDependencyGraphFiles(extracted)
}
async function retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory: string): Promise<string[]> {
const artifactClient = artifact.create()
const downloadPath = path.resolve(workspaceDirectory, 'dependency-graph')
await artifactClient.downloadArtifact(DEPENDENCY_GRAPH_ARTIFACT, downloadPath)
return await findDependencyGraphFiles(downloadPath)
} }
async function findDependencyGraphFiles(dir: string): Promise<string[]> { async function findDependencyGraphFiles(dir: string): Promise<string[]> {
@@ -173,14 +176,6 @@ async function findDependencyGraphFiles(dir: string): Promise<string[]> {
return graphFiles 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> { function getOctokit(): InstanceType<typeof GitHub> {
return github.getOctokit(getGithubToken()) return github.getOctokit(getGithubToken())
} }

View File

@@ -1,11 +0,0 @@
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 { function verifyExists(file: string, description: string): void {
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
throw new Error( throw new Error(
`Cannot locate ${description} at '${file}'. Specify 'gradle-version' for projects without Gradle wrapper configured.` `Cannot locate ${description} at '${file}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.`
) )
} }
} }

View File

@@ -29,10 +29,6 @@ export function isCacheCleanupEnabled(): boolean {
return getBooleanInput('gradle-home-cache-cleanup') return getBooleanInput('gradle-home-cache-cleanup')
} }
export function getCacheEncryptionKey(): string {
return core.getInput('cache-encryption-key')
}
export function getCacheIncludes(): string[] { export function getCacheIncludes(): string[] {
return core.getMultilineInput('gradle-home-cache-includes') return core.getMultilineInput('gradle-home-cache-includes')
} }
@@ -49,6 +45,10 @@ export function getGradleVersion(): string {
return core.getInput('gradle-version') return core.getInput('gradle-version')
} }
export function getGradleExecutable(): string {
return core.getInput('gradle-executable')
}
export function getArguments(): string[] { export function getArguments(): string[] {
const input = core.getInput('arguments') const input = core.getInput('arguments')
return parseArgsStringToArgv(input) return parseArgsStringToArgv(input)
@@ -67,25 +67,8 @@ export function isJobSummaryEnabled(): boolean {
return getBooleanInput('generate-job-summary', true) return getBooleanInput('generate-job-summary', true)
} }
export function getJobSummaryOption(): JobSummaryOption { export function isDependencyGraphEnabled(): boolean {
return parseJobSummaryOption('add-job-summary') return getBooleanInput('generate-dependency-graph', true)
}
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 { export function getDependencyGraphOption(): DependencyGraphOption {
@@ -97,20 +80,16 @@ export function getDependencyGraphOption(): DependencyGraphOption {
return DependencyGraphOption.Generate return DependencyGraphOption.Generate
case 'generate-and-submit': case 'generate-and-submit':
return DependencyGraphOption.GenerateAndSubmit return DependencyGraphOption.GenerateAndSubmit
case 'generate-and-upload':
return DependencyGraphOption.GenerateAndUpload
case 'download-and-submit': case 'download-and-submit':
return DependencyGraphOption.DownloadAndSubmit return DependencyGraphOption.DownloadAndSubmit
case 'clear':
return DependencyGraphOption.Clear
} }
throw TypeError( 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-and-upload, generate-and-submit, download-and-submit]. The default value is 'disabled'.`
) )
} }
export function getDependencyGraphContinueOnFailure(): boolean {
return getBooleanInput('dependency-graph-continue-on-failure', true)
}
export function getArtifactRetentionDays(): number { export function getArtifactRetentionDays(): number {
const val = core.getInput('artifact-retention-days') const val = core.getInput('artifact-retention-days')
return parseNumericInput('artifact-retention-days', val, 0) return parseNumericInput('artifact-retention-days', val, 0)
@@ -142,15 +121,9 @@ function getBooleanInput(paramName: string, paramDefault = false): boolean {
} }
export enum DependencyGraphOption { export enum DependencyGraphOption {
Disabled = 'disabled', Disabled,
Generate = 'generate', Generate,
GenerateAndSubmit = 'generate-and-submit', GenerateAndSubmit,
GenerateAndUpload = 'generate-and-upload', DownloadAndSubmit,
DownloadAndSubmit = 'download-and-submit' Clear
}
export enum JobSummaryOption {
Never = 'never',
Always = 'always',
OnFailure = 'on-failure'
} }

View File

@@ -1,94 +1,45 @@
import * as core from '@actions/core' 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 {BuildResult} from './build-results'
import {CacheListener, generateCachingReport} from './cache-reporting' import {writeCachingReport, CacheListener, logCachingReport} from './cache-reporting'
export async function generateJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> { export async function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
const summaryTable = renderSummaryTable(buildResults) core.info('Writing job summary')
const cachingReport = generateCachingReport(cacheListener)
if (shouldGenerateJobSummary(buildResults)) { if (buildResults.length === 0) {
core.info('Generating Job Summary') core.debug('No Gradle build results found. Summary table will not be generated.')
core.summary.addRaw(summaryTable)
core.summary.addRaw(cachingReport)
await core.summary.write()
} else { } else {
core.info('============================') writeSummaryTable(buildResults)
core.info(summaryTable)
core.info('============================')
core.info(cachingReport)
core.info('============================')
} }
if (shouldAddPRComment(buildResults)) { writeCachingReport(cacheListener)
await addPRComment(summaryTable)
} await core.summary.write()
} }
async function addPRComment(jobSummary: string): Promise<void> { export async function logJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
const context = github.context if (buildResults.length === 0) {
if (context.payload.pull_request == null) { core.debug('No Gradle build results found. Summary table will not be logged.')
core.info('No pull_request trigger: not adding PR comment') } else {
return logSummaryTable(buildResults)
} }
const pull_request_number = context.payload.pull_request.number logCachingReport(cacheListener)
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 buildWarningMessage(error: RequestError): string { function writeSummaryTable(results: BuildResult[]): void {
const mainWarning = `Failed to generate PR comment.\n${String(error)}` core.summary.addHeading('Gradle Builds', 3)
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: BuildResult[]): string { core.summary.addRaw(`
if (results.length === 0) {
return 'No Gradle build results detected.'
}
return `
<table> <table>
<tr> <tr>
<th>Gradle Root Project</th> <th>Root Project</th>
<th>Requested Tasks</th> <th>Requested Tasks</th>
<th>Gradle Version</th> <th>Gradle Version</th>
<th>Build Outcome</th> <th>Build Outcome</th>
<th>Build Scan®</th> <th>Build Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')} </tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table> </table>
` `)
} }
function renderBuildResultRow(result: BuildResult): string { function renderBuildResultRow(result: BuildResult): string {
@@ -126,32 +77,18 @@ function renderBuildScanBadge(outcomeText: string, outcomeColor: string, targetU
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>` return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`
} }
function shouldGenerateJobSummary(buildResults: BuildResult[]): boolean { function logSummaryTable(results: BuildResult[]): void {
// Check if Job Summary is supported on this platform core.info('============================')
if (!process.env[SUMMARY_ENV_VAR]) { core.info('Gradle Builds')
return false core.info('----------------------------')
} core.info('Root Project | Requested Tasks | Gradle Version | Build Outcome | Build Scan®')
core.info('----------------------------')
// Check if Job Summary is disabled using the deprecated input for (const result of results) {
if (!params.isJobSummaryEnabled()) { core.info(
return false `${result.rootProjectName} | ${result.requestedTasks} | ${result.gradleVersion} | ${
} result.buildFailed ? 'FAILED' : 'SUCCESS'
} | ${result.buildScanFailed ? 'Publish failed' : result.buildScanUri}`
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,9 +29,6 @@ export async function run(): Promise<void> {
core.info(error.stack) core.info(error.stack)
} }
} }
// Explicit process.exit() to prevent waiting for hanging promises.
process.exit()
} }
run() run()

View File

@@ -1,6 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as setupGradle from './setup-gradle' 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 // 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 // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
@@ -14,15 +13,8 @@ export async function run(): Promise<void> {
try { try {
await setupGradle.complete() await setupGradle.complete()
} catch (error) { } catch (error) {
if (error instanceof PostActionJobFailure) { handleFailure(error)
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 { function handleFailure(error: unknown): void {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,44 +0,0 @@
<?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,24 +1,23 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary'
import * as path from 'path' import * as path from 'path'
import * as os from 'os' import * as os from 'os'
import * as caches from './caches' import * as caches from './caches'
import * as layout from './repository-layout' import * as layout from './repository-layout'
import * as params from './input-params' import * as params from './input-params'
import * as dependencyGraph from './dependency-graph' import * as dependencyGraph from './dependency-graph'
import * as jobSummary from './job-summary'
import {logJobSummary, writeJobSummary} from './job-summary'
import {loadBuildResults} from './build-results' import {loadBuildResults} from './build-results'
import {CacheListener} from './cache-reporting' import {CacheListener} from './cache-reporting'
import {DaemonController} from './daemon-controller' import {DaemonController} from './daemon-controller'
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED' const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
const USER_HOME = 'USER_HOME'
const GRADLE_USER_HOME = 'GRADLE_USER_HOME' const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
const CACHE_LISTENER = 'CACHE_LISTENER' const CACHE_LISTENER = 'CACHE_LISTENER'
export async function setup(): Promise<void> { export async function setup(): Promise<void> {
const userHome = await determineUserHome()
const gradleUserHome = await determineGradleUserHome() const gradleUserHome = await determineGradleUserHome()
// Bypass setup on all but first action step in workflow. // Bypass setup on all but first action step in workflow.
@@ -31,12 +30,11 @@ export async function setup(): Promise<void> {
// Record setup complete: visible in post-action, to control action completion // Record setup complete: visible in post-action, to control action completion
core.saveState(GRADLE_SETUP_VAR, true) core.saveState(GRADLE_SETUP_VAR, true)
// Save the User Home and Gradle User Home for use in the post-action step. // Save the Gradle User Home for use in the post-action step.
core.saveState(USER_HOME, userHome)
core.saveState(GRADLE_USER_HOME, gradleUserHome) core.saveState(GRADLE_USER_HOME, gradleUserHome)
const cacheListener = new CacheListener() const cacheListener = new CacheListener()
await caches.restore(userHome, gradleUserHome, cacheListener) await caches.restore(gradleUserHome, cacheListener)
core.saveState(CACHE_LISTENER, cacheListener.stringify()) core.saveState(CACHE_LISTENER, cacheListener.stringify())
@@ -48,22 +46,23 @@ export async function complete(): Promise<void> {
core.info('Gradle setup post-action only performed for first gradle-build-action step in workflow.') core.info('Gradle setup post-action only performed for first gradle-build-action step in workflow.')
return return
} }
core.info('In post-action step') core.info('In final post-action step, saving state and writing summary')
const buildResults = loadBuildResults() const buildResults = loadBuildResults()
const userHome = core.getState(USER_HOME)
const gradleUserHome = core.getState(GRADLE_USER_HOME) const gradleUserHome = core.getState(GRADLE_USER_HOME)
const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER)) const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
const daemonController = new DaemonController(buildResults) const daemonController = new DaemonController(buildResults)
await caches.save(userHome, gradleUserHome, cacheListener, daemonController) await caches.save(gradleUserHome, cacheListener, daemonController)
await jobSummary.generateJobSummary(buildResults, cacheListener) if (shouldGenerateJobSummary()) {
await writeJobSummary(buildResults, cacheListener)
} else {
logJobSummary(buildResults, cacheListener)
}
await dependencyGraph.complete(params.getDependencyGraphOption()) await dependencyGraph.complete(params.getDependencyGraphOption())
core.info('Completed post-action step')
} }
async function determineGradleUserHome(): Promise<string> { async function determineGradleUserHome(): Promise<string> {
@@ -92,3 +91,12 @@ async function determineUserHome(): Promise<string> {
core.debug(`Determined user.home from java -version output: '${userHome}'`) core.debug(`Determined user.home from java -version output: '${userHome}'`)
return 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

@@ -1,6 +1,6 @@
plugins { plugins {
id "com.gradle.enterprise" version "3.16.1" id "com.gradle.enterprise" version "3.15.1"
id "com.gradle.common-custom-user-data-gradle-plugin" version "1.12.1" id "com.gradle.common-custom-user-data-gradle-plugin" version "1.12"
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -16,15 +16,14 @@ import java.nio.file.Files
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
class BaseInitScriptTest extends Specification { class BaseInitScriptTest extends Specification {
static final String DEVELOCITY_PLUGIN_VERSION = '3.16.1' static final String GE_PLUGIN_VERSION = '3.15.1'
static final String CCUD_PLUGIN_VERSION = '1.12.1' static final String CCUD_PLUGIN_VERSION = '1.12'
static final TestGradleVersion GRADLE_3_X = new TestGradleVersion(GradleVersion.version('3.5.1'), 7, 9) static final TestGradleVersion GRADLE_3_X = new TestGradleVersion(GradleVersion.version('3.5.1'), 7, 9)
static final TestGradleVersion GRADLE_4_X = new TestGradleVersion(GradleVersion.version('4.10.3'), 7, 10) static final TestGradleVersion GRADLE_4_X = new TestGradleVersion(GradleVersion.version('4.10.3'), 7, 10)
static final TestGradleVersion GRADLE_5_X = new TestGradleVersion(GradleVersion.version('5.6.4'), 8, 12) 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_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_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_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_0 = new TestGradleVersion(GradleVersion.version('8.0.2'), 8, 19)
static final TestGradleVersion GRADLE_8_X = new TestGradleVersion(GradleVersion.version('8.5'), 8, 19) static final TestGradleVersion GRADLE_8_X = new TestGradleVersion(GradleVersion.version('8.5'), 8, 19)
@@ -147,7 +146,7 @@ class BaseInitScriptTest extends Specification {
} else { } else {
""" """
plugins { plugins {
id 'com.gradle.enterprise' version '${DEVELOCITY_PLUGIN_VERSION}' id 'com.gradle.enterprise' version '${GE_PLUGIN_VERSION}'
${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""} ${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""}
} }
gradleEnterprise { gradleEnterprise {
@@ -175,7 +174,7 @@ class BaseInitScriptTest extends Specification {
} else if (gradleVersion < GradleVersion.version('6.0')) { } else if (gradleVersion < GradleVersion.version('6.0')) {
""" """
plugins { plugins {
id 'com.gradle.build-scan' version '${DEVELOCITY_PLUGIN_VERSION}' id 'com.gradle.build-scan' version '${GE_PLUGIN_VERSION}'
${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""} ${ccudPluginVersion ? "id 'com.gradle.common-custom-user-data-gradle-plugin' version '$ccudPluginVersion'" : ""}
} }
gradleEnterprise { gradleEnterprise {

View File

@@ -171,7 +171,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
when: when:
settingsFile.text = """ settingsFile.text = """
plugins { plugins {
id 'com.gradle.enterprise' version '3.16.1' apply(false) id 'com.gradle.enterprise' version '3.15.1' apply(false)
} }
gradle.settingsEvaluated { gradle.settingsEvaluated {
apply plugin: 'com.gradle.enterprise' apply plugin: 'com.gradle.enterprise'

View File

@@ -1,16 +1,11 @@
package com.gradle.gradlebuildaction package com.gradle.gradlebuildaction
import org.gradle.util.GradleVersion
import static org.junit.Assume.assumeTrue import static org.junit.Assume.assumeTrue
class TestDependencyGraph extends BaseInitScriptTest { class TestDependencyGraph extends BaseInitScriptTest {
def initScript = 'gradle-build-action.github-dependency-graph.init.gradle' def initScript = 'gradle-build-action.github-dependency-graph.init.gradle'
static final TestGradleVersion GRADLE_5_1 = new TestGradleVersion(GradleVersion.version('5.1.1'), 8, 12) static final List<TestGradleVersion> NO_DEPENDENCY_GRAPH_VERSIONS = [GRADLE_3_X, GRADLE_4_X]
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 static final List<TestGradleVersion> DEPENDENCY_GRAPH_VERSIONS = ALL_VERSIONS - NO_DEPENDENCY_GRAPH_VERSIONS
def "does not produce dependency graph when not enabled"() { def "does not produce dependency graph when not enabled"() {
@@ -34,6 +29,7 @@ class TestDependencyGraph extends BaseInitScriptTest {
then: then:
assert reportFile.exists() assert reportFile.exists()
assert gitHubOutputFile.text == "dependency-graph-file=${reportFile.absolutePath}\n"
where: where:
testGradleVersion << [GRADLE_8_X] testGradleVersion << [GRADLE_8_X]
@@ -61,23 +57,7 @@ class TestDependencyGraph extends BaseInitScriptTest {
then: then:
assert !reportsDir.exists() assert !reportsDir.exists()
assert result.output.contains("::warning::Dependency Graph is not supported for ${testGradleVersion}") assert result.output.contains("::warning::Dependency Graph is not supported")
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: where:
testGradleVersion << NO_DEPENDENCY_GRAPH_VERSIONS testGradleVersion << NO_DEPENDENCY_GRAPH_VERSIONS
@@ -130,13 +110,13 @@ class TestDependencyGraph extends BaseInitScriptTest {
def getEnvVars() { def getEnvVars() {
return [ return [
GITHUB_DEPENDENCY_GRAPH_ENABLED: "true", GITHUB_DEPENDENCY_GRAPH_ENABLED: "true",
GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE: "true",
GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR: "CORRELATOR", GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR: "CORRELATOR",
GITHUB_DEPENDENCY_GRAPH_JOB_ID: "1", GITHUB_DEPENDENCY_GRAPH_JOB_ID: "1",
GITHUB_DEPENDENCY_GRAPH_REF: "main", GITHUB_DEPENDENCY_GRAPH_REF: "main",
GITHUB_DEPENDENCY_GRAPH_SHA: "123456", GITHUB_DEPENDENCY_GRAPH_SHA: "123456",
GITHUB_DEPENDENCY_GRAPH_WORKSPACE: testProjectDir.absolutePath, GITHUB_DEPENDENCY_GRAPH_WORKSPACE: testProjectDir.absolutePath,
DEPENDENCY_GRAPH_REPORT_DIR: reportsDir.absolutePath, DEPENDENCY_GRAPH_REPORT_DIR: reportsDir.absolutePath,
GITHUB_OUTPUT: gitHubOutputFile.absolutePath
] ]
} }
@@ -147,4 +127,8 @@ class TestDependencyGraph extends BaseInitScriptTest {
def getReportFile() { def getReportFile() {
return new File(reportsDir, "CORRELATOR.json") 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 import static org.junit.Assume.assumeTrue
class TestDevelocityInjection extends BaseInitScriptTest { class TestGradleEnterpriseInjection extends BaseInitScriptTest {
static final List<TestGradleVersion> CCUD_COMPATIBLE_VERSIONS = ALL_VERSIONS - [GRADLE_3_X] static final List<TestGradleVersion> CCUD_COMPATIBLE_VERSIONS = ALL_VERSIONS - [GRADLE_3_X]
def initScript = 'gradle-build-action.inject-develocity.init.gradle' def initScript = 'gradle-build-action.inject-gradle-enterprise.init.gradle'
private static final GradleVersion GRADLE_6 = GradleVersion.version('6.0') private static final GradleVersion GRADLE_6 = GradleVersion.version('6.0')
def "does not apply Develocity plugins when not requested"() { def "does not apply GE plugins when not requested"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
def result = run([], initScript, testGradleVersion.gradleVersion) def result = run([], initScript, testGradleVersion.gradleVersion)
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "does not override Develocity plugin when already defined in project"() { def "does not override GE plugin when already defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
given: given:
@@ -36,7 +36,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig()) def result = run(testGradleVersion, testConfig())
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
@@ -46,14 +46,14 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "applies Develocity plugin via init script when not defined in project"() { def "applies GE plugin via init script when not defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
def result = run(testGradleVersion, testConfig()) def result = run(testGradleVersion, testConfig())
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
@@ -63,14 +63,14 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "applies Develocity and CCUD plugins via init script when not defined in project"() { def "applies GE and CCUD plugins via init script when not defined in project"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
def result = run(testGradleVersion, testConfig().withCCUDPlugin()) def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result) outputContainsCcudPluginApplicationViaInitScript(result)
and: and:
@@ -80,7 +80,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << CCUD_COMPATIBLE_VERSIONS testGradleVersion << CCUD_COMPATIBLE_VERSIONS
} }
def "applies CCUD plugin via init script where Develocity plugin already applied"() { def "applies CCUD plugin via init script where GE plugin already applied"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
given: given:
@@ -90,7 +90,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig().withCCUDPlugin()) def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputContainsCcudPluginApplicationViaInitScript(result) outputContainsCcudPluginApplicationViaInitScript(result)
and: and:
@@ -110,7 +110,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, testConfig().withCCUDPlugin()) def result = run(testGradleVersion, testConfig().withCCUDPlugin())
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
@@ -120,18 +120,18 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << CCUD_COMPATIBLE_VERSIONS testGradleVersion << CCUD_COMPATIBLE_VERSIONS
} }
def "ignores Develocity URL and allowUntrustedServer when Develocity plugin is not applied by the init script"() { def "ignores GE URL and allowUntrustedServer when GE plugin is not applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
given: given:
declareGePluginApplication(testGradleVersion.gradleVersion) declareGePluginApplication(testGradleVersion.gradleVersion)
when: when:
def config = testConfig().withServer(URI.create('https://develocity-server.invalid')) def config = testConfig().withServer(URI.create('https://ge-server.invalid'))
def result = run(testGradleVersion, config) def result = run(testGradleVersion, config)
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
@@ -141,7 +141,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "configures Develocity URL and allowUntrustedServer when Develocity plugin is applied by the init script"() { def "configures GE URL and allowUntrustedServer when GE plugin is applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
@@ -149,8 +149,8 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config) def result = run(testGradleVersion, config)
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityConnectionInfo(result, mockScansServer.address.toString(), true) outputContainsGeConnectionInfo(result, mockScansServer.address.toString(), true)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
outputContainsPluginRepositoryInfo(result, 'https://plugins.gradle.org/m2') outputContainsPluginRepositoryInfo(result, 'https://plugins.gradle.org/m2')
@@ -161,22 +161,22 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "enforces Develocity URL and allowUntrustedServer in project if enforce url parameter is enabled"() { def "enforces GE URL and allowUntrustedServer in project if enforce url parameter is enabled"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
given: given:
declareGePluginApplication(testGradleVersion.gradleVersion, URI.create('https://develocity-server.invalid')) declareGePluginApplication(testGradleVersion.gradleVersion, URI.create('https://ge-server.invalid'))
when: when:
def config = testConfig().withServer(mockScansServer.address, true) def config = testConfig().withServer(mockScansServer.address, true)
def result = run(testGradleVersion, config) def result = run(testGradleVersion, config)
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
outputEnforcesDevelocityUrl(result, mockScansServer.address.toString(), true) outputEnforcesGeUrl(result, mockScansServer.address.toString(), true)
and: and:
outputContainsBuildScanUrl(result) outputContainsBuildScanUrl(result)
@@ -185,7 +185,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "can configure alternative repository for plugins when Develocity plugin is applied by the init script"() { def "can configure alternative repository for plugins when GE plugin is applied by the init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
@@ -193,8 +193,8 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config) def result = run(testGradleVersion, config)
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsDevelocityConnectionInfo(result, mockScansServer.address.toString(), true) outputContainsGeConnectionInfo(result, mockScansServer.address.toString(), true)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
outputContainsPluginRepositoryInfo(result, 'https://plugins.grdev.net/m2') outputContainsPluginRepositoryInfo(result, 'https://plugins.grdev.net/m2')
@@ -213,7 +213,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config) def result = run(testGradleVersion, config)
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
result.output.contains('Common Custom User Data Gradle plugin must be at least 1.7. Configured version is 1.6.6.') 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 TestDevelocityInjection extends BaseInitScriptTest {
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
} }
def "can configure Develocity via CCUD system property overrides when CCUD plugin is inject via init script"() { def "can configure GE via CCUD system property overrides when CCUD plugin is inject via init script"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm assumeTrue testGradleVersion.compatibleWithCurrentJvm
when: when:
def config = testConfig().withCCUDPlugin().withServer(URI.create('https://develocity-server.invalid')) def config = testConfig().withCCUDPlugin().withServer(URI.create('https://ge-server.invalid'))
def result = run(testGradleVersion, config, ["help", "-Dgradle.enterprise.url=${mockScansServer.address}".toString()]) def result = run(testGradleVersion, config, ["help", "-Dgradle.enterprise.url=${mockScansServer.address}".toString()])
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result) outputContainsCcudPluginApplicationViaInitScript(result)
and: and:
@@ -247,7 +247,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def result = run(testGradleVersion, config, ["help", "--configuration-cache"]) def result = run(testGradleVersion, config, ["help", "--configuration-cache"])
then: then:
outputContainsDevelocityPluginApplicationViaInitScript(result, testGradleVersion.gradleVersion) outputContainsGePluginApplicationViaInitScript(result, testGradleVersion.gradleVersion)
outputContainsCcudPluginApplicationViaInitScript(result) outputContainsCcudPluginApplicationViaInitScript(result)
and: and:
@@ -257,7 +257,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
result = run(testGradleVersion, config, ["help", "--configuration-cache"]) result = run(testGradleVersion, config, ["help", "--configuration-cache"])
then: then:
outputMissesDevelocityPluginApplicationViaInitScript(result) outputMissesGePluginApplicationViaInitScript(result)
outputMissesCcudPluginApplicationViaInitScript(result) outputMissesCcudPluginApplicationViaInitScript(result)
and: and:
@@ -273,7 +273,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
assert 1 == result.output.count(message) assert 1 == result.output.count(message)
} }
void outputContainsDevelocityPluginApplicationViaInitScript(BuildResult result, GradleVersion gradleVersion) { void outputContainsGePluginApplicationViaInitScript(BuildResult result, GradleVersion gradleVersion) {
def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script" def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script"
def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script" def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script"
if (gradleVersion < GRADLE_6) { if (gradleVersion < GRADLE_6) {
@@ -287,7 +287,7 @@ class TestDevelocityInjection extends BaseInitScriptTest {
} }
} }
void outputMissesDevelocityPluginApplicationViaInitScript(BuildResult result) { void outputMissesGePluginApplicationViaInitScript(BuildResult result) {
def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script" def pluginApplicationLogMsgGradle4And5 = "Applying com.gradle.scan.plugin.BuildScanPlugin via init script"
def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script" def pluginApplicationLogMsgGradle6AndHigher = "Applying com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin via init script"
assert !result.output.contains(pluginApplicationLogMsgGradle4And5) assert !result.output.contains(pluginApplicationLogMsgGradle4And5)
@@ -305,20 +305,20 @@ class TestDevelocityInjection extends BaseInitScriptTest {
assert !result.output.contains(pluginApplicationLogMsg) assert !result.output.contains(pluginApplicationLogMsg)
} }
void outputContainsDevelocityConnectionInfo(BuildResult result, String geUrl, boolean geAllowUntrustedServer) { void outputContainsGeConnectionInfo(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def geConnectionInfo = "Connection to Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer" def geConnectionInfo = "Connection to Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
assert result.output.contains(geConnectionInfo) assert result.output.contains(geConnectionInfo)
assert 1 == result.output.count(geConnectionInfo) assert 1 == result.output.count(geConnectionInfo)
} }
void outputContainsPluginRepositoryInfo(BuildResult result, String gradlePluginRepositoryUrl) { void outputContainsPluginRepositoryInfo(BuildResult result, String gradlePluginRepositoryUrl) {
def repositoryInfo = "Develocity plugins resolution: ${gradlePluginRepositoryUrl}" def repositoryInfo = "Gradle Enterprise plugins resolution: ${gradlePluginRepositoryUrl}"
assert result.output.contains(repositoryInfo) assert result.output.contains(repositoryInfo)
assert 1 == result.output.count(repositoryInfo) assert 1 == result.output.count(repositoryInfo)
} }
void outputEnforcesDevelocityUrl(BuildResult result, String geUrl, boolean geAllowUntrustedServer) { void outputEnforcesGeUrl(BuildResult result, String geUrl, boolean geAllowUntrustedServer) {
def enforceUrl = "Enforcing Develocity: $geUrl, allowUntrustedServer: $geAllowUntrustedServer" def enforceUrl = "Enforcing Gradle Enterprise: $geUrl, allowUntrustedServer: $geAllowUntrustedServer"
assert result.output.contains(enforceUrl) assert result.output.contains(enforceUrl)
assert 1 == result.output.count(enforceUrl) assert 1 == result.output.count(enforceUrl)
} }
@@ -369,31 +369,31 @@ class TestDevelocityInjection extends BaseInitScriptTest {
def getEnvVars() { def getEnvVars() {
Map<String, String> envVars = [ Map<String, String> envVars = [
DEVELOCITY_INJECTION_ENABLED: "true", GRADLE_ENTERPRISE_INJECTION_ENABLED: "true",
DEVELOCITY_URL: serverUrl, GRADLE_ENTERPRISE_URL: serverUrl,
DEVELOCITY_ALLOW_UNTRUSTED_SERVER: "true", GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER: "true",
DEVELOCITY_PLUGIN_VERSION: DEVELOCITY_PLUGIN_VERSION, GRADLE_ENTERPRISE_PLUGIN_VERSION: GE_PLUGIN_VERSION,
DEVELOCITY_BUILD_SCAN_UPLOAD_IN_BACKGROUND: "true" // Need to upload in background since our Mock server doesn't cope with foreground upload GRADLE_ENTERPRISE_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("DEVELOCITY_ENFORCE_URL", "true") if (enforceUrl) envVars.put("GRADLE_ENTERPRISE_ENFORCE_URL", "true")
if (ccudPluginVersion != null) envVars.put("DEVELOCITY_CCUD_PLUGIN_VERSION", ccudPluginVersion) if (ccudPluginVersion != null) envVars.put("GRADLE_ENTERPRISE_CCUD_PLUGIN_VERSION", ccudPluginVersion)
if (pluginRepositoryUrl != null) envVars.put("GRADLE_PLUGIN_REPOSITORY_URL", pluginRepositoryUrl) if (pluginRepositoryUrl != null) envVars.put("GRADLE_ENTERPRISE_PLUGIN_REPOSITORY_URL", pluginRepositoryUrl)
return envVars return envVars
} }
def getJvmArgs() { def getJvmArgs() {
List<String> jvmArgs = [ List<String> jvmArgs = [
"-Ddevelocity.injection-enabled=true", "-Dgradle-enterprise.injection-enabled=true",
"-Ddevelocity.url=$serverUrl", "-Dgradle-enterprise.url=$serverUrl",
"-Ddevelocity.allow-untrusted-server=true", "-Dgradle-enterprise.allow-untrusted-server=true",
"-Ddevelocity.plugin.version=$DEVELOCITY_PLUGIN_VERSION", "-Dgradle-enterprise.plugin.version=$GE_PLUGIN_VERSION",
"-Ddevelocity.build-scan.upload-in-background=true" "-Dgradle-enterprise.build-scan.upload-in-background=true"
] ]
if (enforceUrl) jvmArgs.add("-Ddevelocity.enforce-url=true") if (enforceUrl) jvmArgs.add("-Dgradle-enterprise.enforce-url=true")
if (ccudPluginVersion != null) jvmArgs.add("-Ddevelocity.ccud-plugin.version=$ccudPluginVersion") if (ccudPluginVersion != null) jvmArgs.add("-Dgradle-enterprise.ccud-plugin.version=$ccudPluginVersion")
if (pluginRepositoryUrl != null) jvmArgs.add("-Dgradle.plugin-repository.url=$pluginRepositoryUrl") if (pluginRepositoryUrl != null) jvmArgs.add("-Dgradle-enterprise.plugin-repository.url=$pluginRepositoryUrl")
return jvmArgs.collect { it.toString() } // Convert from GStrings return jvmArgs.collect { it.toString() } // Convert from GStrings
} }

View File

@@ -17,4 +17,10 @@ describe('cacheUtils-utils', () => {
expect(posixHash).toBe(windowsHash) 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')
})
})
}) })