Merge pull request #83 from gradle/dd/v2

Adapt paths to differing Gradle User Home
This commit is contained in:
Daz DeBoer 2021-09-27 22:08:40 -06:00 committed by GitHub
commit 5576baa56b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 163 additions and 83 deletions

View File

@ -1,5 +1,4 @@
# make sure the build works and doesn't produce spurious changes name: Verify generated outputs
name: dev
on: on:
pull_request: pull_request:

View File

@ -1,10 +1,6 @@
# Run builds under certain failure conditions to allow the output to be manually inspected. name: Execute failure cases
# These build invocations are informational only, and are expected to fail
name: failure-cases
on: on:
pull_request:
push:
workflow_dispatch: workflow_dispatch:
env: env:

View File

@ -1,5 +1,4 @@
# Verify the functionality works as expected name: Test caching
name: integration-testing
on: on:
pull_request: pull_request:
@ -31,42 +30,6 @@ jobs:
build-root-directory: __tests__/samples/basic build-root-directory: __tests__/samples/basic
arguments: test --configuration-cache arguments: test --configuration-cache
# Tests for executing with different Gradle versions.
# Each build verifies that it is executed with the expected Gradle version.
gradle-execution:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use defined Gradle version
uses: ./
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=6.9
cache-read-only: true
- name: Test use Gradle version alias
uses: ./
with:
gradle-version: release-candidate
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.2
cache-read-only: true
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: __tests__/samples/basic/gradlew${{ matrix.script-suffix }}
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.1.1
cache-read-only: true
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline # Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache: dependencies-cache:
needs: seed-build needs: seed-build

View File

@ -0,0 +1,42 @@
name: Test Gradle execution
on:
pull_request:
push:
workflow_dispatch:
env:
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.
gradle-execution:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use defined Gradle version
uses: ./
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=6.9
- name: Test use Gradle version alias
uses: ./
with:
gradle-version: release-candidate
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.2
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: __tests__/samples/basic/gradlew${{ matrix.script-suffix }}
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.1.1

View File

@ -0,0 +1,52 @@
name: Test caching with a custom GRADLE_USER_HOME
on:
pull_request:
push:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_USER_HOME: custom/gradle/home
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:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache:
needs: seed-build
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --offline
cache-read-only: true
# Test that the gradle-user-home cache will cache and restore local build-cache
build-cache:
needs: seed-build
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify tasks from cache
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test -DverifyCachedBuild=true
cache-read-only: true

View File

@ -1,5 +1,4 @@
# Make sure the action works on a clean machine without building name: Test caching with Kotlin DSL
name: integration-testing-kotlin-dsl
on: on:
pull_request: pull_request:

View File

@ -11,5 +11,11 @@ describe('cacheUtils-utils', () => {
const hash = cacheUtils.hashStrings(['foo', 'bar', 'baz']) const hash = cacheUtils.hashStrings(['foo', 'bar', 'baz'])
expect(hash).toBe('6df23dc03f9b54cc38a0fc1483df6e21') expect(hash).toBe('6df23dc03f9b54cc38a0fc1483df6e21')
}) })
it('normalized filenames', async () => {
const fileNames = ['/foo/bar/baz.zip', '../boo.html']
const posixHash = cacheUtils.hashFileNames(fileNames)
const windowsHash = cacheUtils.hashFileNames(fileNames)
expect(posixHash).toBe(windowsHash)
})
}) })
}) })

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

2
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

View File

@ -5,23 +5,24 @@ import * as core from '@actions/core'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import {AbstractCache, hashStrings} from './cache-utils' import {AbstractCache, hashFileNames} from './cache-utils'
// Which paths under Gradle User Home should be cached // Which paths under Gradle User Home should be cached
// TODO: This should adapt for the `GRADLE_USER_HOME` environment variable const CACHE_PATH = ['caches', 'notifications']
// TODO: Allow the user to override / tweak this set
const CACHE_PATH = ['~/.gradle/caches', '~/.gradle/notifications']
const COMMON_ARTIFACT_CACHES = new Map([ const COMMON_ARTIFACT_CACHES = new Map([
['generated-gradle-jars', '~/.gradle/caches/*/generated-gradle-jars/*.jar'], ['generated-gradle-jars', 'caches/*/generated-gradle-jars/*.jar'],
['wrapper-zips', '~/.gradle/wrapper/dists/*/*/*.zip'], ['wrapper-zips', 'wrapper/dists/*/*/*.zip'],
['dependency-jars', '~/.gradle/caches/modules-*/files-*/**/*.jar'], ['dependency-jars', 'caches/modules-*/files-*/**/*.jar'],
['instrumented-jars', '~/.gradle/caches/jars-*/*/*.jar'] ['instrumented-jars', 'caches/jars-*/*/*.jar']
]) ])
export class GradleUserHomeCache extends AbstractCache { export class GradleUserHomeCache extends AbstractCache {
constructor() { private gradleUserHome: string
constructor(rootDir: string) {
super('gradle', 'Gradle User Home') super('gradle', 'Gradle User Home')
this.gradleUserHome = this.determineGradleUserHome(rootDir)
} }
async afterRestore(): Promise<void> { async afterRestore(): Promise<void> {
@ -32,7 +33,7 @@ export class GradleUserHomeCache extends AbstractCache {
private async restoreCommonArtifacts(): Promise<void> { private async restoreCommonArtifacts(): Promise<void> {
const processes: Promise<void>[] = [] const processes: Promise<void>[] = []
for (const [bundle, pattern] of COMMON_ARTIFACT_CACHES) { for (const [bundle, pattern] of this.getCommonArtifactPaths()) {
const p = this.restoreCommonArtifactBundle(bundle, pattern) const p = this.restoreCommonArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled // Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) { if (this.cacheDebuggingEnabled) {
@ -46,19 +47,19 @@ export class GradleUserHomeCache extends AbstractCache {
private async restoreCommonArtifactBundle( private async restoreCommonArtifactBundle(
bundle: string, bundle: string,
pattern: string artifactPath: string
): Promise<void> { ): Promise<void> {
const cacheMetaFile = this.getCacheMetaFile(bundle) const cacheMetaFile = this.getCacheMetaFile(bundle)
if (fs.existsSync(cacheMetaFile)) { if (fs.existsSync(cacheMetaFile)) {
const cacheKey = fs.readFileSync(cacheMetaFile, 'utf-8').trim() const cacheKey = fs.readFileSync(cacheMetaFile, 'utf-8').trim()
const restoreKey = await this.restoreCache([pattern], cacheKey) const restoreKey = await this.restoreCache([artifactPath], cacheKey)
if (restoreKey) { if (restoreKey) {
core.info( core.info(
`Restored ${bundle} with key ${cacheKey} to ${pattern}` `Restored ${bundle} with key ${cacheKey} to ${artifactPath}`
) )
} else { } else {
this.debug( this.debug(
`Failed to restore ${bundle} with key ${cacheKey} to ${pattern}` `Failed to restore ${bundle} with key ${cacheKey} to ${artifactPath}`
) )
} }
} else { } else {
@ -70,7 +71,7 @@ export class GradleUserHomeCache extends AbstractCache {
private getCacheMetaFile(name: string): string { private getCacheMetaFile(name: string): string {
return path.resolve( return path.resolve(
this.getGradleUserHome(), this.gradleUserHome,
'caches', 'caches',
`.gradle-build-action.${name}.cache` `.gradle-build-action.${name}.cache`
) )
@ -80,15 +81,14 @@ export class GradleUserHomeCache extends AbstractCache {
if (!this.cacheDebuggingEnabled) { if (!this.cacheDebuggingEnabled) {
return return
} }
const gradleUserHome = path.resolve(os.homedir(), '.gradle') if (!fs.existsSync(this.gradleUserHome)) {
if (!fs.existsSync(gradleUserHome)) {
return return
} }
const result = await exec.getExecOutput( const result = await exec.getExecOutput(
'du', 'du',
['-h', '-c', '-t', '5M'], ['-h', '-c', '-t', '5M'],
{ {
cwd: gradleUserHome, cwd: this.gradleUserHome,
silent: true, silent: true,
ignoreReturnCode: true ignoreReturnCode: true
} }
@ -116,7 +116,7 @@ export class GradleUserHomeCache extends AbstractCache {
private async saveCommonArtifacts(): Promise<void> { private async saveCommonArtifacts(): Promise<void> {
const processes: Promise<void>[] = [] const processes: Promise<void>[] = []
for (const [bundle, pattern] of COMMON_ARTIFACT_CACHES) { for (const [bundle, pattern] of this.getCommonArtifactPaths()) {
const p = this.saveCommonArtifactBundle(bundle, pattern) const p = this.saveCommonArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled // Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) { if (this.cacheDebuggingEnabled) {
@ -130,11 +130,11 @@ export class GradleUserHomeCache extends AbstractCache {
private async saveCommonArtifactBundle( private async saveCommonArtifactBundle(
bundle: string, bundle: string,
pattern: string artifactPath: string
): Promise<void> { ): Promise<void> {
const cacheMetaFile = this.getCacheMetaFile(bundle) const cacheMetaFile = this.getCacheMetaFile(bundle)
const globber = await glob.create(pattern) const globber = await glob.create(artifactPath)
const commonArtifactFiles = await globber.glob() const commonArtifactFiles = await globber.glob()
// Handle no matching files // Handle no matching files
@ -149,10 +149,7 @@ export class GradleUserHomeCache extends AbstractCache {
const previouslyRestoredKey = fs.existsSync(cacheMetaFile) const previouslyRestoredKey = fs.existsSync(cacheMetaFile)
? fs.readFileSync(cacheMetaFile, 'utf-8').trim() ? fs.readFileSync(cacheMetaFile, 'utf-8').trim()
: '' : ''
const cacheKey = this.createCacheKey( const cacheKey = this.createCacheKey(bundle, commonArtifactFiles)
bundle,
hashStrings(commonArtifactFiles)
)
if (previouslyRestoredKey === cacheKey) { if (previouslyRestoredKey === cacheKey) {
this.debug( this.debug(
@ -160,7 +157,7 @@ export class GradleUserHomeCache extends AbstractCache {
) )
} else { } else {
core.info(`Caching ${bundle} with cache key: ${cacheKey}`) core.info(`Caching ${bundle} with cache key: ${cacheKey}`)
await this.saveCache([pattern], cacheKey) await this.saveCache([artifactPath], cacheKey)
this.debug(`Writing cache metafile: ${cacheMetaFile}`) this.debug(`Writing cache metafile: ${cacheMetaFile}`)
fs.writeFileSync(cacheMetaFile, cacheKey) fs.writeFileSync(cacheMetaFile, cacheKey)
@ -171,22 +168,41 @@ export class GradleUserHomeCache extends AbstractCache {
} }
} }
protected createCacheKey(bundle: string, key: string): string { protected createCacheKey(bundle: string, files: string[]): string {
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || '' const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
const relativeFiles = files.map(x =>
path.relative(this.gradleUserHome, x)
)
const key = hashFileNames(relativeFiles)
return `${cacheKeyPrefix}${bundle}-${key}` return `${cacheKeyPrefix}${bundle}-${key}`
} }
protected getGradleUserHome(): string { protected determineGradleUserHome(rootDir: string): string {
const customGradleUserHome = process.env['GRADLE_USER_HOME']
if (customGradleUserHome) {
return path.resolve(rootDir, customGradleUserHome)
}
return path.resolve(os.homedir(), '.gradle') return path.resolve(os.homedir(), '.gradle')
} }
protected cacheOutputExists(): boolean { protected cacheOutputExists(): boolean {
// Need to check for 'caches' directory to avoid incorrect detection on MacOS agents // Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
const dir = path.resolve(this.getGradleUserHome(), 'caches') const dir = path.resolve(this.gradleUserHome, 'caches')
return fs.existsSync(dir) return fs.existsSync(dir)
} }
protected getCachePath(): string[] { protected getCachePath(): string[] {
return CACHE_PATH return CACHE_PATH.map(x => path.resolve(this.gradleUserHome, x))
}
private getCommonArtifactPaths(): Map<string, string> {
return new Map(
Array.from(COMMON_ARTIFACT_CACHES, ([key, value]) => [
key,
path.resolve(this.gradleUserHome, value)
])
)
} }
} }

View File

@ -2,6 +2,7 @@ import * as core from '@actions/core'
import * as cache from '@actions/cache' import * as cache from '@actions/cache'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as path from 'path'
export function isCacheDisabled(): boolean { export function isCacheDisabled(): boolean {
return core.getBooleanInput('cache-disabled') return core.getBooleanInput('cache-disabled')
@ -53,6 +54,12 @@ export function hashStrings(values: string[]): string {
return hash.digest('hex') return hash.digest('hex')
} }
export function hashFileNames(fileNames: string[]): string {
return hashStrings(
fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/'))
)
}
class CacheKey { class CacheKey {
key: string key: string
restoreKeys: string[] restoreKeys: string[]

View File

@ -14,7 +14,7 @@ export async function restore(buildRootDirectory: string): Promise<void> {
await core.group('Restore Gradle state from cache', async () => { await core.group('Restore Gradle state from cache', async () => {
core.saveState(BUILD_ROOT_DIR, buildRootDirectory) core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
return Promise.all([ return Promise.all([
new GradleUserHomeCache().restore(), new GradleUserHomeCache(buildRootDirectory).restore(),
new ProjectDotGradleCache(buildRootDirectory).restore() new ProjectDotGradleCache(buildRootDirectory).restore()
]) ])
}) })
@ -29,7 +29,7 @@ export async function save(): Promise<void> {
await core.group('Caching Gradle state', async () => { await core.group('Caching Gradle state', async () => {
const buildRootDirectory = core.getState(BUILD_ROOT_DIR) const buildRootDirectory = core.getState(BUILD_ROOT_DIR)
return Promise.all([ return Promise.all([
new GradleUserHomeCache().save(), new GradleUserHomeCache(buildRootDirectory).save(),
new ProjectDotGradleCache(buildRootDirectory).save() new ProjectDotGradleCache(buildRootDirectory).save()
]) ])
}) })