Compare commits

...

10 Commits

Author SHA1 Message Date
Daz DeBoer
b3afdc78a7 Truncate Gradle args when constructing cache key (#71)
Cache keys have a hard limit of 512 characters, so we need to ensure that we don't generate a key longer than this.

- Remove excess whitespace
- Truncate to 400 characters

Fixes #70
2021-08-24 12:46:48 -06:00
Daz DeBoer
e0c2736e35 Include Gradle invocation arguments in cache keys (#69)
This permits a new cache entry to be persisted when a subsequent Gradle invocation does more work that an earlier invocation.

Fixes #68
2021-08-22 17:11:05 -06:00
Daz DeBoer
a63892c289 Log cache entry on save 2021-08-22 14:58:36 -06:00
Daz DeBoer
d432f2086c Provide a better description for the action 2021-08-22 14:48:38 -06:00
Daz DeBoer
eaad2cd2bb Merge pull request #67 from gradle/dd/v15
Allow caching to be enabled read-only
2021-08-22 14:45:57 -06:00
Daz DeBoer
a148b21183 Improve prod workflow
- Use a separate job to test read-only cache
- Use dependency jobs to avoid cache race conditions
2021-08-22 14:42:01 -06:00
Daz DeBoer
e7422f245c Fix typo in README 2021-08-22 14:42:01 -06:00
Daz DeBoer
c86093d76a Build distribution 2021-08-22 14:28:35 -06:00
Daz DeBoer
a693ccda4b Allow use of caches 'read-only'
To avoid evicting useful entries, some pipeline may benefit from using existing
cache entries without writing any changes back to the cache.

Fixes #62
2021-08-22 14:28:34 -06:00
Daz DeBoer
543cacb256 Allow manual trigger of prod workflow 2021-08-20 14:00:30 -06:00
10 changed files with 127 additions and 28 deletions

View File

@@ -4,9 +4,25 @@ name: prod
on:
pull_request:
push:
workflow_dispatch:
jobs:
basic-build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test
gradle-execution:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
@@ -17,11 +33,6 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test
- name: Test use defined Gradle version
uses: ./
with:
@@ -48,6 +59,7 @@ jobs:
arguments: help
dependencies-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
@@ -63,6 +75,7 @@ jobs:
dependencies-cache-enabled: true
configuration-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
@@ -79,8 +92,26 @@ jobs:
dependencies-cache-enabled: true
# Configuration cache requires dependencies cache, since it assumes dependencies are already downloaded.
cache-read-only:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test cache-read-only
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --no-daemon
dependencies-cache-enabled: true
configuration-cache-enabled: true
cache-read-only: true
failures: # These build invocations are informational only, and are expected to fail
needs: basic-build
runs-on: ubuntu-latest
steps:
- name: Checkout sources

View File

@@ -144,7 +144,7 @@ The distributions cache is simple and can't be configured further.
The dependencies and configuration cache will compute a cache key in a best effort manner.
Keep reading to learn how to better control how they work.
Note that enabling configuration cache without thee dependencies cache is not permitted, since a hit in the configuration cache assumes that dependencies are already present in the local dependencies cache.
Note that enabling configuration cache without the dependencies cache is not permitted, since a hit in the configuration cache assumes that dependencies are already present in the local dependencies cache.
### Configuring the dependencies and configuration caches
@@ -187,6 +187,18 @@ dependencies-cache-key: gradle/dependency-locks/**
dependencies-cache-exact: true
```
### Using the caches read-only
Cache storage space is limited for GitHub actions, and writing new cache entries can trigger the deletion of exising entries.
In some circumstances, it makes sense for a Gradle invocation to use any existing cache entries but not to write and changes back.
For example, you may want to write cache entries for builds on your `main` branch, but not for any PR build invocations.
Use the following configuration to avoid writing cache entries for the action invocation:
```yaml
cache-read-only: true
```
## Build scans
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-build-action` action will emit the link to the published build scan as an output named `build-scan-url`.

View File

@@ -1,10 +1,10 @@
import * as cryptoUtils from '../src/crypto-utils'
import * as cacheUtils from '../src/cache-utils'
import * as path from 'path'
describe('crypto-utils', () => {
describe('cacheUtils-utils', () => {
describe('can hash', () => {
it('a directory', async () => {
const hash = await cryptoUtils.hashFiles(
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/gradle')
)
expect(hash).toBe(
@@ -14,7 +14,7 @@ describe('crypto-utils', () => {
)
})
it('a directory with a glob', async () => {
const hash = await cryptoUtils.hashFiles(
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['gradle/**']
)
@@ -25,7 +25,7 @@ describe('crypto-utils', () => {
)
})
it('a directory with globs', async () => {
const hash = await cryptoUtils.hashFiles(
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['**/*.gradle', 'gradle/**']
)
@@ -36,4 +36,30 @@ describe('crypto-utils', () => {
)
})
})
describe('can truncate args', () => {
test('handles zero-length string', () => {
expect(cacheUtils.truncateArgs('')).toBe('')
})
test('leaves short string untouched', () => {
expect(
cacheUtils.truncateArgs('short string that-should-be-untouched')
).toBe('short string that-should-be-untouched')
})
test('truncates long string', () => {
const longString = 'a'.repeat(500)
expect(cacheUtils.truncateArgs(longString)).toBe('a'.repeat(400))
})
test('trims leading and trailing whitespace', () => {
expect(cacheUtils.truncateArgs(' this is an arg ')).toBe(
'this is an arg'
)
})
test('removes repeated whitespace', () => {
expect(
cacheUtils.truncateArgs(
' this one has long \t\n\t\r spaces '
)
).toBe('this one has long spaces')
})
})
})

View File

@@ -1,5 +1,5 @@
name: "Gradle Build Action"
description: 'Execute Gradle Build'
description: 'Executes a Gradle build, caching useful state in the GitHub actions cache'
# https://help.github.com/en/articles/metadata-syntax-for-github-actions
@@ -51,6 +51,10 @@ inputs:
description: Whether to restore only if exact match, default to 'false'
required: false
default: false
cache-read-only:
description: When 'true', existing entries will be read from the cache but no entries will be written
required: false
default: false
outputs:
build-scan-url:

2
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,10 @@
import path from 'path'
import fs from 'fs'
import * as path from 'path'
import * as fs from 'fs'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as crypto from './crypto-utils'
import * as cacheUtils from './cache-utils'
import {
inputCacheKeyGlobs,
@@ -32,17 +32,22 @@ export async function restoreCachedConfiguration(
core.saveState(CONFIGURATION_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('configuration-cache-exact')
const cacheKeyGlobs = inputCacheKeyGlobs('configuration-cache-key')
const cacheKeyPrefix = 'configuration|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('configuration-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
const hash = await crypto.hashFiles(rootDir, cacheKeyGlobs)
const cacheKeyPrefix = 'configuration-'
const cacheKey = `${cacheKeyPrefix}${hash}`
core.saveState(CONFIGURATION_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyPrefix]
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
@@ -86,6 +91,8 @@ export async function cacheConfiguration(): Promise<void> {
return
}
core.info(`Will cache configuration with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {

View File

@@ -5,7 +5,7 @@ import * as os from 'os'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as crypto from './crypto-utils'
import * as cacheUtils from './cache-utils'
const DEPENDENCIES_CACHE_PATH = 'DEPENDENCIES_CACHE_PATH'
const DEPENDENCIES_CACHE_KEY = 'DEPENDENCIES_CACHE_KEY'
@@ -21,17 +21,22 @@ export async function restoreCachedDependencies(
core.saveState(DEPENDENCIES_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('dependencies-cache-exact')
const cacheKeyGlobs = inputCacheKeyGlobs('dependencies-cache-key')
const cacheKeyPrefix = 'dependencies|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('dependencies-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
const hash = await crypto.hashFiles(rootDir, cacheKeyGlobs)
const cacheKeyPrefix = 'dependencies-'
const cacheKey = `${cacheKeyPrefix}${hash}`
core.saveState(DEPENDENCIES_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyPrefix]
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
@@ -73,6 +78,8 @@ export async function cacheDependencies(): Promise<void> {
return
}
core.info(`Will cache dependencies with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {

View File

@@ -11,3 +11,7 @@ export async function hashFiles(
.join('\n')
return glob.hashFiles(combinedPatterns, {followSymbolicLinks})
}
export function truncateArgs(args: string): string {
return args.trim().replace(/\s+/g, ' ').substr(0, 400)
}

View File

@@ -1,12 +1,20 @@
import * as core from '@actions/core'
import * as cacheWrapper from './cache-wrapper'
import * as cacheDependencies from './cache-dependencies'
import * as cacheConfiguration from './cache-configuration'
// Invoked by GitHub Actions
export async function run(): Promise<void> {
if (isCacheReadOnly()) return
await cacheWrapper.cacheWrapperDist()
await cacheDependencies.cacheDependencies()
await cacheConfiguration.cacheConfiguration()
}
function isCacheReadOnly(): boolean {
return core.getBooleanInput('cache-read-only')
}
run()