mirror of
https://github.com/gradle/actions
synced 2024-11-27 11:52:24 +00:00
Automatic caching of dependencies
in a best effort manner by default allowing to specify files to hash for computing the cache key
This commit is contained in:
parent
fcc1683d01
commit
95e20daa83
15 changed files with 192 additions and 14 deletions
4
.github/workflows/dev.yml
vendored
4
.github/workflows/dev.yml
vendored
|
@ -22,10 +22,10 @@ jobs:
|
|||
with:
|
||||
wrapper-directory: __tests__/data/basic
|
||||
build-root-directory: __tests__/data/basic
|
||||
arguments: help
|
||||
arguments: test
|
||||
- name: Test dist download
|
||||
uses: ./
|
||||
with:
|
||||
gradle-version: 6.5
|
||||
build-root-directory: __tests__/data/basic
|
||||
arguments: help
|
||||
arguments: test
|
||||
|
|
4
.github/workflows/prod.yml
vendored
4
.github/workflows/prod.yml
vendored
|
@ -21,10 +21,10 @@ jobs:
|
|||
with:
|
||||
wrapper-directory: __tests__/data/basic
|
||||
build-root-directory: __tests__/data/basic
|
||||
arguments: help
|
||||
arguments: test
|
||||
- name: Test dist download
|
||||
uses: ./
|
||||
with:
|
||||
gradle-version: 6.5
|
||||
build-root-directory: __tests__/data/basic
|
||||
arguments: help
|
||||
arguments: test
|
||||
|
|
33
__tests__/crypto-utils.test.ts
Normal file
33
__tests__/crypto-utils.test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import * as cryptoUtils from '../src/crypto-utils'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('crypto-utils', () => {
|
||||
describe('can hash', () => {
|
||||
it('a directory', async () => {
|
||||
const hash = await cryptoUtils.hashFiles(
|
||||
path.resolve('__tests__/data/basic/gradle')
|
||||
)
|
||||
expect(hash).toBe(
|
||||
'4ebb65b45e6f6796d5ec6ace96e9471cc6573d294c54f99c4920fe5328e75bab'
|
||||
)
|
||||
})
|
||||
it('a directory with a glob', async () => {
|
||||
const hash = await cryptoUtils.hashFiles(
|
||||
path.resolve('__tests__/data/basic/'),
|
||||
['gradle/**']
|
||||
)
|
||||
expect(hash).toBe(
|
||||
'4ebb65b45e6f6796d5ec6ace96e9471cc6573d294c54f99c4920fe5328e75bab'
|
||||
)
|
||||
})
|
||||
it('a directory with globs', async () => {
|
||||
const hash = await cryptoUtils.hashFiles(
|
||||
path.resolve('__tests__/data/basic/'),
|
||||
['**/*.gradle', 'gradle/**']
|
||||
)
|
||||
expect(hash).toBe(
|
||||
'2db1d5291774949ab89e18e9d82ee24748ca0f6cc78de69ea9104357c50ad4a5'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,6 +1,11 @@
|
|||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* This is a general purpose Gradle build.
|
||||
* Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
|
||||
*/
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation('junit:junit:4.12')
|
||||
}
|
||||
|
|
10
__tests__/data/basic/src/test/java/basic/BasicTest.java
Normal file
10
__tests__/data/basic/src/test/java/basic/BasicTest.java
Normal file
|
@ -0,0 +1,10 @@
|
|||
package basic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BasicTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assert true;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,9 @@ inputs:
|
|||
arguments:
|
||||
description: Gradle command line arguments, see gradle --help
|
||||
required: false
|
||||
dependencies-cache-key:
|
||||
description: Globs of files to hash in the build root directory, separated by new lines, use best-effort if unset
|
||||
required: false
|
||||
|
||||
outputs:
|
||||
build-scan-url:
|
||||
|
|
2
dist/main/index.js
vendored
2
dist/main/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/post/index.js
vendored
2
dist/post/index.js
vendored
File diff suppressed because one or more lines are too long
|
@ -24,11 +24,12 @@
|
|||
"author": "Paul Merlin <paul@nosphere.org>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "0.2.1",
|
||||
"@actions/core": "1.2.4",
|
||||
"@actions/exec": "1.0.4",
|
||||
"@actions/glob": "0.1.0",
|
||||
"@actions/io": "1.0.2",
|
||||
"@actions/tool-cache": "1.5.5",
|
||||
"@actions/cache": "0.2.1",
|
||||
"string-argv": "0.3.1",
|
||||
"typed-rest-client": "1.7.3",
|
||||
"unzipper": "0.10.11"
|
||||
|
|
72
src/cache-dependencies.ts
Normal file
72
src/cache-dependencies.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
|
||||
import * as core from '@actions/core'
|
||||
import * as cache from '@actions/cache'
|
||||
|
||||
import * as github from './github-utils'
|
||||
import * as crypto from './crypto-utils'
|
||||
|
||||
const DEPENDENCIES_CACHE_PATH = 'DEPENDENCIES_CACHE_PATH'
|
||||
const DEPENDENCIES_CACHE_KEY = 'DEPENDENCIES_CACHE_KEY'
|
||||
const DEPENDENCIES_CACHE_RESULT = 'DEPENDENCIES_CACHE_RESULT'
|
||||
|
||||
export async function restoreCachedDependencies(
|
||||
rootDir: string
|
||||
): Promise<void> {
|
||||
const cachePath = path.resolve(os.homedir(), '.gradle/caches/modules-2')
|
||||
core.saveState(DEPENDENCIES_CACHE_PATH, cachePath)
|
||||
|
||||
const inputCacheKeyGlobs = github.inputArrayOrNull('dependencies-cache-key')
|
||||
const cacheKeyGlobs = inputCacheKeyGlobs
|
||||
? inputCacheKeyGlobs
|
||||
: [
|
||||
'**/*.gradle',
|
||||
'**/*.gradle.kts',
|
||||
'**/gradle.properties',
|
||||
'gradle/**'
|
||||
]
|
||||
|
||||
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, [
|
||||
cacheKeyPrefix
|
||||
])
|
||||
core.saveState(DEPENDENCIES_CACHE_RESULT, cacheResult)
|
||||
}
|
||||
|
||||
export async function cacheDependencies(): Promise<void> {
|
||||
const cachePath = core.getState(DEPENDENCIES_CACHE_PATH)
|
||||
const cacheKey = core.getState(DEPENDENCIES_CACHE_KEY)
|
||||
const cacheResult = core.getState(DEPENDENCIES_CACHE_RESULT)
|
||||
|
||||
if (!cachePath || !fs.existsSync(cachePath)) {
|
||||
core.debug('No dependencies to cache.')
|
||||
return
|
||||
}
|
||||
|
||||
if (cacheResult && cacheKey === cacheResult) {
|
||||
core.info(
|
||||
`Dependencies cache hit occurred on the cache key ${cacheKey}, not saving cache.`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await cache.saveCache([cachePath], cacheKey)
|
||||
} catch (error) {
|
||||
if (error.name === cache.ValidationError.name) {
|
||||
throw error
|
||||
} else if (error.name === cache.ReserveCacheError.name) {
|
||||
core.info(error.message)
|
||||
} else {
|
||||
core.info(`[warning] ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -52,7 +52,7 @@ export async function cacheWrapperDist(): Promise<void> {
|
|||
const cachePath = core.getState(WRAPPER_CACHE_PATH)
|
||||
const cacheResult = core.getState(WRAPPER_CACHE_RESULT)
|
||||
|
||||
if (!cachePath) {
|
||||
if (!cachePath || !fs.existsSync(cachePath)) {
|
||||
core.debug('No wrapper installation to cache.')
|
||||
return
|
||||
}
|
||||
|
|
40
src/crypto-utils.ts
Normal file
40
src/crypto-utils.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import * as crypto from 'crypto'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as stream from 'stream'
|
||||
import * as util from 'util'
|
||||
|
||||
import * as glob from '@actions/glob'
|
||||
|
||||
export async function hashFiles(
|
||||
baseDir: string,
|
||||
globs: string[] = ['**'],
|
||||
followSymbolicLinks = false
|
||||
): Promise<string | null> {
|
||||
let hasMatch = false
|
||||
const result = crypto.createHash('sha256')
|
||||
for await (const globPattern of globs) {
|
||||
const globMatch = `${baseDir}/${globPattern}`
|
||||
const globber = await glob.create(globMatch, {followSymbolicLinks})
|
||||
for await (const file of globber.globGenerator()) {
|
||||
// console.log(file)
|
||||
if (!file.startsWith(`${baseDir}${path.sep}`)) {
|
||||
// console.log(`Ignore '${file}' since it is not under '${baseDir}'.`)
|
||||
continue
|
||||
}
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
// console.log(`Skip directory '${file}'.`)
|
||||
continue
|
||||
}
|
||||
const hash = crypto.createHash('sha256')
|
||||
const pipeline = util.promisify(stream.pipeline)
|
||||
await pipeline(fs.createReadStream(file), hash)
|
||||
result.write(hash.digest())
|
||||
if (!hasMatch) {
|
||||
hasMatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
result.end()
|
||||
return hasMatch ? result.digest('hex') : null
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
import * as exec from '@actions/exec'
|
||||
import * as cacheDependencies from './cache-dependencies'
|
||||
|
||||
export async function execute(
|
||||
executable: string,
|
||||
root: string,
|
||||
argv: string[]
|
||||
): Promise<BuildResult> {
|
||||
await cacheDependencies.restoreCachedDependencies(root)
|
||||
|
||||
let publishing = false
|
||||
let buildScanUrl: string | undefined
|
||||
|
||||
|
|
|
@ -7,3 +7,12 @@ export function inputOrNull(name: string): string | null {
|
|||
}
|
||||
return inputString
|
||||
}
|
||||
|
||||
export function inputArrayOrNull(name: string): string[] | null {
|
||||
const string = inputOrNull(name)
|
||||
if (!string) return null
|
||||
return string
|
||||
.split('\n')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s !== '')
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import * as cacheWrapper from './cache-wrapper'
|
||||
import * as cacheDependencies from './cache-dependencies'
|
||||
|
||||
// Invoked by GitHub Actions
|
||||
export async function run(): Promise<void> {
|
||||
await cacheWrapper.cacheWrapperDist()
|
||||
await cacheDependencies.cacheDependencies()
|
||||
}
|
||||
|
||||
run()
|
||||
|
|
Loading…
Reference in a new issue