mirror of
				https://github.com/gradle/gradle-build-action.git
				synced 2025-11-04 09:58:56 +08: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:
		
							
								
								
									
										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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user