Merge pull request #130 from gradle/setup-gradle

Allow action to be used without Gradle execution
This commit is contained in:
Daz DeBoer 2021-12-17 13:08:36 -07:00 committed by GitHub
commit b84b650c31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 328 additions and 174 deletions

View file

@ -11,8 +11,6 @@ env:
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
# Run initial Gradle builds to push initial cache entries
# These builds should start fresh without cache hits, due to the seed injected into the cache key above.
seed-build:
strategy:
matrix:
@ -21,11 +19,9 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
# Add "wrapper" to main cache entry and remove 'wrapper-zips' cache entry
# Exclude build-cache from main cache entry
gradle-home-cache-includes: |
@ -41,6 +37,9 @@ jobs:
["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
]
- name: Build using Gradle wrapper
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
verify-build:
@ -52,13 +51,10 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline
cache-read-only: true
# Need the same configuration when restoring state from cache
# Use the same configuration when restoring state from cache
gradle-home-cache-includes: |
caches
notifications
@ -72,5 +68,9 @@ jobs:
["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
]
cache-read-only: true
- name: Execute Gradle build with --offline
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --offline

View file

@ -21,19 +21,16 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build with configuration-cache enabled
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
- name: Second build with configuration-cache enabled
uses: ./
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test --configuration-cache
- name: Groovy build with configuration-cache enabled
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --configuration-cache
- name: Kotlin build with configuration-cache enabled
working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew test --configuration-cache
# Test restore configuration-cache
configuration-cache:
configuration-cache-groovy:
needs: seed-build
strategy:
matrix:
@ -42,17 +39,18 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify cached configuration
- name: Setup Gradle
uses: ./
with:
cache-read-only: true
- name: Execute Gradle build and verify cached configuration
env:
VERIFY_CACHED_CONFIGURATION: true
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
cache-read-only: true
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --configuration-cache
# Test restore configuration-cache from second build invocation
configuration-cache-2:
# Test restore configuration-cache from the second build invocation
configuration-cache-kotlin:
needs: seed-build
strategy:
matrix:
@ -61,14 +59,15 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify cached configuration
- name: Setup Gradle
uses: ./
with:
cache-read-only: true
- name: Execute Gradle build and verify cached configuration
env:
VERIFY_CACHED_CONFIGURATION: true
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test --configuration-cache
cache-read-only: true
working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew test --configuration-cache
# Check that the build can run when no extracted cache entries are restored
no-extracted-cache-entries-restored:
@ -80,11 +79,11 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with no cache extracted cache entries restored
- name: Setup Gradle with no cache extracted cache entries restored
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
cache-read-only: true
gradle-home-extracted-cache-entries: '[]'
- name: Check execute Gradle build with configuration cache enabled (but not restored)
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --configuration-cache

View file

@ -10,8 +10,6 @@ env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
# Run initial Gradle builds to push initial cache entries
# These builds should start fresh without cache hits, due to the seed injected into the cache key above.
seed-build:
strategy:
matrix:
@ -20,11 +18,11 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
- name: Build using Gradle wrapper
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache:
@ -36,12 +34,13 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline
cache-read-only: true
- name: Execute Gradle build with --offline
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --offline
# Test that the gradle-user-home cache will cache and restore local build-cache
build-cache:
@ -53,12 +52,13 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify tasks from cache
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test -DverifyCachedBuild=true
cache-read-only: true
- name: Execute Gradle build and verify tasks from cache
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test -DverifyCachedBuild=true
# Check that the build can run when Gradle User Home is not fully restored
no-extracted-cache-entries-restored:
@ -70,11 +70,12 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with no extracted cache entries restored
- name: Setup Gradle with no extracted cache entries restored
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
cache-read-only: true
gradle-home-extracted-cache-entries: '[]'
- name: Check executee Gradle build
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test

View file

@ -0,0 +1,43 @@
name: Test save/restore Gradle state with direct execution
on:
pull_request:
types: [assigned, review_requested]
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
seed-build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Exucute Gradle build
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
# Test that the gradle-user-home is restored
verify-build:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build
uses: ./
with:
cache-read-only: true
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline -DverifyCachedBuild=true

View file

@ -8,7 +8,7 @@ on:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_USER_HOME: custom/gradle/home
GRADLE_USER_HOME: ${{github.workspace}}/custom/gradle/home
jobs:
# Run initial Gradle builds to push initial cache entries
@ -21,11 +21,11 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
- name: Build using Gradle wrapper
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --info
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache:
@ -37,12 +37,13 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline
cache-read-only: true
- name: Execute Gradle build with --offline
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --offline --info
# Test that the gradle-user-home cache will cache and restore local build-cache
build-cache:
@ -54,9 +55,10 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify tasks from cache
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test -DverifyCachedBuild=true
cache-read-only: true
- name: Execute Gradle build and verify tasks from cache
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test -DverifyCachedBuild=true --info

View file

@ -0,0 +1,77 @@
name: Test provision different Gradle versions
on:
pull_request:
types: [assigned, review_requested]
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
# Tests for executing with different Gradle versions.
# Each build verifies that it is executed with the expected Gradle version.
provision-gradle:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Setup Gradle with v6.9
uses: ./
with:
gradle-version: 6.9
- name: Test uses Gradle v6.9
working-directory: __tests__/samples/no-wrapper
run: gradle help "-DgradleVersionCheck=6.9"
- name: Setup Gradle with v7.1.1
uses: ./
with:
gradle-version: 7.1.1
- name: Test uses Gradle v7.1.1
working-directory: __tests__/samples/no-wrapper
run: gradle help "-DgradleVersionCheck=7.1.1"
- name: Setup Gradle with release-candidate
uses: ./
with:
gradle-version: release-candidate
- name: Test use release-candidate
working-directory: __tests__/samples/no-wrapper
run: gradle help
gradle-versions:
strategy:
matrix:
gradle: [7.3, 6.9, 5.6.4, 4.10.3]
os: [ubuntu-latest, windows-latest]
include:
- gradle: 5.6.4
build-root-suffix: -gradle-5
- gradle: 4.10.3
build-root-suffix: -gradle-4
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Setup Gradle
uses: ./
with:
gradle-version: ${{ matrix.gradle }}
- name: Run Gradle build
id: gradle
working-directory: __tests__/samples/no-wrapper${{ matrix.build-root-suffix }}
run: gradle help "-DgradleVersionCheck=${{matrix.gradle}}"
- name: Check build scan url
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')

View file

@ -20,11 +20,11 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build kotlin-dsl project
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test
- name: Build kotlin-dsl project
working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew test
# Check that the build can run --offline
verify-build:
@ -36,8 +36,8 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build kotlin-dsl project
- name: Setup Gradle
uses: ./
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test --offline
- name: Build kotlin-dsl project
working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew test --offline

View file

@ -1,5 +1,7 @@
name: Add a build scan comment to PR
on: pull_request
on:
pull_request:
types: [assigned, review_requested]
jobs:
gradle:
runs-on: ubuntu-latest
@ -8,11 +10,10 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: gradle/gradle-build-action@v2
id: gradle
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: help
- uses: ./
- id: gradle
working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew help
- name: "Comment build scan url"
uses: actions/github-script@v3
with:
@ -22,5 +23,5 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Dummy comment for PR: ${{ steps.gradle.outputs.build-scan-url }}'
body: 'PR ready for review: ${{ steps.gradle.outputs.build-scan-url }}'
})

167
README.md
View file

@ -1,6 +1,6 @@
# Execute Gradle builds in GitHub Actions workflows
This GitHub Action can be used to execute a Gradle build on any platform supported by GitHub Actions.
This GitHub Action can be used to configure Gradle and optionally execute a Gradle build on any platform supported by GitHub Actions.
## Usage
@ -27,6 +27,28 @@ jobs:
arguments: build
```
The `gradle-build-action` can also be used for caching Gradle state without owning the actual Gradle execution.
The following workflow is effectively the same as the one above, but supports full scripting of the Gradle invocation.
```yaml
# .github/workflows/gradle-build-pr.yml
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@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: gradle/gradle-build-action@v2
- run: ./gradlew build
```
It is possible to configure multiple Gradle executions to run sequentially in the same job.
Each invocation will start its run with the filesystem state remaining from the previous execution.
@ -39,6 +61,14 @@ Each invocation will start its run with the filesystem state remaining from the
arguments: check
```
The same can be achieved with a single `gradle-build-action` step and multiple `run` steps.
```yaml
- uses: gradle/gradle-build-action@v2
- run: ./gradlew assemble
- run: ./gradlew check
```
### Why is this better than running Gradle directly?
It is possible to directly invoke Gradle in your workflow, and the `setup-java` action provides a simple way to cache Gradle dependencies.
@ -51,67 +81,12 @@ However, the `gradle-build-action` offers a number of advantages over this appro
- [Automatic capture of build scan links](#build-scans) from the build, making these easier to locate for workflow run.
The `gradle-build-action` is designed to provide these benefits with minimal configuration.
These features work both when Gradle is executed via the `gradle-build-action` and for any Gradle execution in subsequent steps.
## Gradle Execution
## Gradle Installation
### Command-line arguments
The `arguments` input can 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
```
See `gradle --help` for more information.
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:
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:
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.
### Download, install and use a specific Gradle version
The `gradle-build-action` is able to download and install a specific Gradle version to execute.
The `gradle-build-action` will download and install a specified Gradle version, adding this installed version to the PATH.
Downloaded Gradle versions are stored in the GitHub Actions cache, to avoid requiring downloading again later.
```yaml
- uses: gradle/gradle-build-action@v2
@ -119,7 +94,7 @@ The `gradle-build-action` is able to download and install a specific Gradle vers
gradle-version: 6.5
```
`gradle-version` can be set to any valid Gradle version.
The `gradle-version` parameter can be set to any valid Gradle version.
Moreover, you can use the following aliases:
@ -150,9 +125,70 @@ jobs:
- uses: gradle/gradle-build-action@v2
with:
gradle-version: release-candidate
arguments: build --dry-run # just test build configuration
- run: gradle build --dry-run # just test build configuration
```
## Gradle Execution
If the action is configured with an `arguments` input, then Gradle will execute a Gradle build with the arguments provided.
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.
### Gradle command-line arguments
The `arguments` input can 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.
## Caching
By default, this action aims to cache any and all reusable state that may be speed up a subsequent build invocation.
@ -257,7 +293,7 @@ and you can selectively [exclude content using `gradle-home-cache-exclude`](#gra
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-build-action` action will:
- Add a notice with the link to the GitHub Actions user interface
- Emit the link to the published build scan as an output named `build-scan-url`.
- For each step that executes Gradle, adds the link to the published build scan as a Step output named `build-scan-url`.
You can then use that link in subsequent actions of your workflow. For example:
@ -274,9 +310,8 @@ jobs:
with:
java-version: 11
- uses: gradle/gradle-build-action@v2
id: gradle
with:
arguments: build
- id: gradle
run: ./gradlew build
- name: "Comment build scan url"
uses: actions/github-script@v3
if: github.event_name == 'pull_request' && failure()

2
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

View file

@ -1,8 +1,10 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import fs from 'fs'
import path from 'path'
import * as gradlew from './gradlew'
export async function execute(executable: string, root: string, args: string[]): Promise<BuildResult> {
export async function executeGradleBuild(executable: string | undefined, root: string, args: string[]): Promise<void> {
let buildScanUrl: string | undefined
const buildScanFile = path.resolve(root, 'gradle-build-scan.txt')
@ -10,7 +12,9 @@ export async function execute(executable: string, root: string, args: string[]):
fs.unlinkSync(buildScanFile)
}
const status: number = await exec.exec(executable, args, {
// Use the provided executable, or look for a Gradle wrapper script to run
const toExecute = executable ?? gradlew.locateGradleWrapperScript(root)
const status: number = await exec.exec(toExecute, args, {
cwd: root,
ignoreReturnCode: true
})
@ -19,14 +23,11 @@ export async function execute(executable: string, root: string, args: string[]):
buildScanUrl = fs.readFileSync(buildScanFile, 'utf-8')
}
return new BuildResultImpl(status, buildScanUrl)
if (status !== 0) {
if (buildScanUrl) {
core.setFailed(`Gradle build failed: ${buildScanUrl}`)
} else {
core.setFailed(`Gradle build failed: process exited with status ${status}`)
}
export interface BuildResult {
readonly status: number
readonly buildScanUrl?: string
}
class BuildResultImpl implements BuildResult {
constructor(readonly status: number, readonly buildScanUrl?: string) {}
}

View file

@ -5,7 +5,6 @@ import {parseArgsStringToArgv} from 'string-argv'
import * as caches from './caches'
import * as execution from './execution'
import * as gradlew from './gradlew'
import * as provision from './provision'
/**
@ -19,20 +18,16 @@ export async function run(): Promise<void> {
await caches.restore(gradleUserHome)
const args: string[] = parseCommandLineArguments()
const result = await execution.execute(
await resolveGradleExecutable(workspaceDirectory, buildRootDirectory),
buildRootDirectory,
args
)
if (result.status !== 0) {
if (result.buildScanUrl) {
core.setFailed(`Gradle build failed: ${result.buildScanUrl}`)
} else {
core.setFailed(`Gradle build failed: process exited with status ${result.status}`)
const executable = await provisionGradle(workspaceDirectory)
// executable will be undefined if using Gradle wrapper
if (executable !== undefined) {
core.addPath(path.dirname(executable))
}
// Only execute if arguments have been provided
const args: string[] = parseCommandLineArguments()
if (args.length > 0) {
await execution.executeGradleBuild(executable, buildRootDirectory, args)
}
} catch (error) {
core.setFailed(String(error))
@ -44,7 +39,7 @@ export async function run(): Promise<void> {
run()
async function resolveGradleExecutable(workspaceDirectory: string, buildRootDirectory: string): Promise<string> {
async function provisionGradle(workspaceDirectory: string): Promise<string | undefined> {
const gradleVersion = core.getInput('gradle-version')
if (gradleVersion !== '' && gradleVersion !== 'wrapper') {
return path.resolve(await provision.gradleVersion(gradleVersion))
@ -55,7 +50,7 @@ async function resolveGradleExecutable(workspaceDirectory: string, buildRootDire
return path.resolve(workspaceDirectory, gradleExecutable)
}
return gradlew.locateGradleWrapperScript(buildRootDirectory)
return undefined
}
function resolveBuildRootDirectory(baseDirectory: string): string {