Skip to content

Commit

Permalink
🚀 Add HTTP caching proxy that implements Gradle HTTP cache API
Browse files Browse the repository at this point in the history
  • Loading branch information
vlsi committed Aug 18, 2020
1 parent 597d5ea commit effb04a
Show file tree
Hide file tree
Showing 50 changed files with 1,976 additions and 386 deletions.
167 changes: 157 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,68 @@
This ia GitHub Action for caching Gradle caches.
In other words, this is [@actions/cache](https://github.com/actions/cache) customized for Gradle.

Key improvements over `@actions/cache`
- Simplified configuration
- Less space usage (there's overall 5GiB limit, so cache space matters)
- Native support for caching Gradle's local build cache
Key improvements over [@actions/cache](https://github.com/actions/cache) and [gradle-command-action](https://github.com/eskatos/gradle-command-action)
- 🚀 Gradle remote build cache backend (pulls only the needed entries from GitHub cache)
- 🎉 Support multiple remote caches via [gradle-multi-cache](https://github.com/burrunan/gradle-multi-cache) (e.g. GitHub Actions + S3)
- 👋 Simplified configuration (action name + gradle command is enough for most case)
- 👾 Less space usage (GitHub imposes overall 5GiB limit by default, so cache space matters)
- 🔗 Link to Build Scan in build results
- 💡 Gradle build failure markers added to the diff view (e.g. `compileJava` or `compileKotlin` markers right in the commit diff)

## Usage

Add the following to `.github/workflows/...`

Note: like with [gradle-command-action](https://github.com/eskatos/gradle-command-action), you can
specify `gradle-version: release` to test with the current release version of Gradle, `gradle-version: nightly` for testing Gradle nightly builds,
an so on (see `gradle-version` below).

```yaml
- uses: burrunan/gradle-cache-action@v1
name: Cache .gradle
name: Build PROJECT_NAME
# Extra environment variables for Gradle execution (regular GitHub Actions feature)
# Note: env must be outside of "with"
env:
VARIABLE: VALUE
with:
# If you have multiple jobs, use distinct job-id in in case you want to split caches
# For instance, jobs with different JDK versions can't share caches
# RUNNER_OS is added to job-id automatically
job-id: jdk8
# Specifies arguments for Gradle execution
# If arguments is missing or empty, then Gradle is not executed
arguments: build
# arguments can be multi-line for better readability
# arguments: |
# --no-paralell
# build
# -x test
# Gradle version to use for execution:
# wrapper (default), current, rc, nightly, release-nightly, or
# versions like 6.6 (see https://services.gradle.org/versions/all)
gradle-version: wrapper
# Properties are passed as -Pname=value
properties: |
kotlin.js.compiler=ir
kotlin.parallel.tasks.in.project=true
```
You might want to enable [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)
For instance, add `--build-cache` option when running Gradle.
By default, the action enables `local` build cache, and it adds a remote build cache
that stores the data in GitHub Actions cache.
However, you might want to enable [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)
for your local builds to make them faster, or even add a remote cache instance, so your local
builds can reuse artifacts that are build on CI.

This is how you can enable local build cache (don't forget to add `--build-cache` option or
`org.gradle.caching=true` property).

```kotlin
// build.gradle.kts
// settings.gradle.kts
val isCiServer = System.getenv().containsKey("CI")
// Cache build artifacts, so expensive operations do not need to be re-computed
buildCache {
local {
isEnabled = !isCiServer || System.getenv().containsKey("GITHUB_ACTIONS")
isEnabled = !isCiServer
}
}
```
Expand All @@ -45,6 +78,9 @@ The default configuration should suit for most of the cases, however, there are
```yaml
- uses: burrunan/gradle-cache-action@v1
name: Cache .gradle
# Extra environment variables for Gradle execution (regular GitHub Actions feature)
env:
VARIABLE: VALUE
with:
# If you have multiple jobs, use distinct job-id in in case you want to split caches
# For instance, jobs with different JDK versions can't share caches
Expand All @@ -54,10 +90,34 @@ The default configuration should suit for most of the cases, however, there are
# Disable caching of $HOME/.gradle/caches/*.*/generated-gradle-jars
save-generated-gradle-jars: false
# Disable remote cache that proxies requests to GitHub Actions cache
remote-build-cache-proxy-enabled: false
# Set the cache key for Gradle version (e.g. in case multiple jobs use different versions)
# By default the value is `wrapper`, so the version is determined from the gradle-wrapper.properties
# Note: this argument specifies the version for Gradle execution (if `arguments` is present)
# Supported values:
# wrapper (default), current, rc, nightly, release-nightly, or
# versions like 6.6 (see https://services.gradle.org/versions/all)
gradle-version: 6.5.1-custom

# Arguments for Gradle execution
arguments: build jacocoReport

# Properties are passed as -Pname=value
properties: |
kotlin.js.compiler=ir
kotlin.parallel.tasks.in.project=true
# Relative path under $GITHUB_WORKSPACE where Git repository is placed
build-root-directory: sub/directory

# Activates only the caches that are relevant for executing gradle command.
# This is helpful when build job executes multiple gradle commands sequentially.
# Then the caching is implemented in the very first one, and the subsequent should be marked
# with execution-only-caches: true
execution-only-caches: true

# Disable caching of ~/.gradle/caches/build-cache-*
save-local-build-cache: false

Expand All @@ -72,12 +132,17 @@ The default configuration should suit for most of the cases, however, there are
# Disable caching of ~/.m2/repository/
save-maven-dependencies-cache: false

# Ignore some of the paths when caching Maven Local repository
maven-local-ignore-paths: |
org/example/
com/example/
# Enable concurrent cache save and restore
# Default is concurrent=false for better log readability
concurrent: true
```
## How does it work?
## How does dependency caching work?
The current GitHub Actions cache (both [actions/cache](https://github.com/actions/cache) action and
[@actions/cache](https://github.com/actions/toolkit/tree/main/packages/cache) npm package) is immutable.
Expand All @@ -89,6 +154,88 @@ If only a small fraction of files changes, then the action reuses the existing c
That enables to save cache space (GitHub has a default limit of 5GiB), and it reduces upload time as only
the cache receives only the updated files.

## How does GitHub Actions-based Gradle remote build cache work?

`gradle-cache-action` launches a small proxy server that listens for Gradle requests and
then it redirects the requests to `@actions/cache` API.

That makes Gradle believe it is talking to a regular remote cache, and the cache receives
only the relevant updates.
The increased granularity enables GitHub to evict entries better (it removes unused entries
automatically).

The action configures the URL to the cache proxy via `~/.gradle/init.gradle` script, and
[Gradle picks it up automatically](https://docs.gradle.org/current/userguide/init_scripts.html)

Note: saving GitHub Actions caches might take noticeable time (e.g. 100ms), so the cache uploads
in the background. In other words, build scan would show virtually zero response times for
cache save operations.

If your build already has a remote cache declared (e.g. you are using your own cache),
then `gradle-cache-action` would configure **both** remote caches.
It would read from GitHub cache first, and it would save data to both caches.

Multi-cache feature can be disabled via `multi-cache-enabled: false`.

## How to enable build scans?

1. Read and agree to the terms of service: https://gradle.com/terms-of-service
1. Add `--scan` to `arguments:`, and add the following to `settings.gradle.kts`

```kotlin
plugins {
`gradle-enterprise`
}

val isCiServer = System.getenv().containsKey("CI")

if (isCiServer) {
gradleEnterprise {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
tag("CI")
}
}
}
```

## Why another action instead of gradle-command-action?

`gradle-command-action` was started as a Kotlin/JS experiment for making a customized
[@actions/cache](https://github.com/actions/cache) that would make Gradle builds faster.

Then it turned out there's a possibility to use proxy remote cache requests to `@actions/cache` API
which is possible in case the caching action executes Gradle, so `gradle-cache-action`
got Gradle execution feature.

Of course, the same could have been made in [gradle-command-action](https://github.com/eskatos/gradle-command-action),
however:
- The author was not familiar with TypeScript ecosystem (stdlib, typical libraries, testing libraries, etc)
- Caching logic is collections-heavy, and Kotlin stdlib shines here.

For instance, in Kotlin `list + list` adds lists, and `array.associateWith { valueFor(it) }` converts array to map.
One writes that without StackOverflow, the code is readable, and it does not require
you [to fight with the compiler](https://blog.johnnyreilly.com/2016/06/create-es2015-map-from-array-in-typescript.html).

- A single language helps when building connected components.
`gradle-cache-action` integrates with [gradle-multi-cache](https://github.com/burrunan/gradle-multi-cache) and
[gradle-s3-build-cache](https://github.com/burrunan/gradle-s3-build-cache), and they all are Kotlin-based.

## Can I use the caching part of the action only?

Yes, you can. If you omit `arguments:`, then the action runs in `cache-only` mode.
It won't launch Gradle.

## Can I call multiple different Gradle builds in the same job?

This might be complicated, see https://github.com/burrunan/gradle-cache-action/issues/15.

Currently, the workaround is to configure `execution-only-caches: true` for all but one
`gradle-cache-action` executions.
Then one of the actions would do the cache save and restore, and the rest would use their own
caches only.

## Contributing

Contributions are always welcome! If you'd like to contribute (and we hope you do) please open a pull request.
Expand Down
53 changes: 52 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,85 @@
name: 'Gradle Cache'
description: 'Caches .gradle folder (dependencies, local build cache, ...)'
author: 'Vladimir Sitnikov'
outputs:
build-scan-url:
description: Link to the build scan if any
inputs:
job-id:
description: A job identifier to avoid cache pollution from different jobs
required: false
path:
build-root-directory:
description: Relative path under $GITHUB_WORKSPACE where Git repository is placed
required: false
gradle-version:
description: (wrapper | or explicit version) Caches often depend on the Gradle version, so this parameter sets the ID to use for cache keys. It does not affect the Gradle version used for build
required: false
default: wrapper
save-generated-gradle-jars:
description: Enables caching of $HOME/.gradle/caches/*.*/generated-gradle-jars
required: false
default: 'true'
save-local-build-cache:
description: Enables caching of $HOME/.gradle/caches/build-cache-1
required: false
default: 'true'
multi-cache-enabled:
description: Adds com.github.burrunan.multi-cache plugin to settings.gradle so GitHub Actions cache can be used in parallel with Gradle remote build cache
required: false
default: 'true'
multi-cache-version:
description: Configures com.github.burrunan.multi-cache version to use
required: false
default: '1.0'
multi-cache-repository:
description: Configures repository where com.github.burrunan.multi-cache can be located
required: false
default: ''
multi-cache-group-id-filter:
description: Configures group id for selecting only com.github.burrunan.multi-cache artifacts (it enables Gradle to use custom repository for multi-cache only)
required: false
default: 'com[.]github[.]burrunan[.]multi-?cache'
save-gradle-dependencies-cache:
description: Enables caching of ~/.gradle/caches/modules-*
required: false
default: 'true'
execution-only-caches:
description: |
Activates only the caches that are relevant for executing gradle command.
This is helpful when build job executes multiple gradle commands sequentially.
Then the caching is implemented in the very first one, and the subsequent should be marked
with execution-only-caches: true
required: false
default: 'false'
remote-build-cache-proxy-enabled:
description: Activates a remote cache that proxies requests to GitHub Actions cache
required: false
default: 'true'
gradle-dependencies-cache-key:
description: Extra files to take into account for ~/.gradle/caches dependencies
required: false
save-maven-dependencies-cache:
description: Enables caching of ~/.m2/repository/
required: false
default: 'true'
maven-local-ignore-paths:
description: Specifies ignored paths in the Maven Local repository (e.g. the artifacts of the current project)
required: false
default: ''
debug:
description: Shows extra logging to debug the action
required: false
default: 'true'
concurrent:
description: Enables concurent cache download and upload (disabled by default for better log output)
required: false
default: 'false'
arguments:
description: Gradle arguments to pass (optionally multiline)
required: false
properties:
description: Extra Gradle properties (multiline) which would be passed as -Pname=value arguments
required: false
runs:
using: node12
main: dist/cache-action-entrypoint.js
Expand Down
16 changes: 14 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,22 @@ allprojects {
configure<org.jetbrains.kotlin.gradle.dsl.KotlinJsProjectExtension> {
js {
if (project.name.endsWith("-entrypoint")) {
browser()
browser {
testTask {
useMocha {
timeout = "10000"
}
}
}
binaries.executable()
} else {
nodejs()
nodejs {
testTask {
useMocha {
timeout = "10000"
}
}
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions cache-action-entrypoint/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
*/

dependencies {
implementation(project(":cache-proxy"))
implementation(project(":gradle-launcher"))
implementation(project(":layered-cache"))
implementation(project(":wrappers:actions-core"))
implementation(project(":wrappers:actions-io"))
implementation(project(":wrappers:nodejs"))
implementation(project(":wrappers:octokit-webhooks"))
implementation(npm("string-argv", "0.3.1"))
}
Loading

0 comments on commit effb04a

Please sign in to comment.