mirror of
https://github.com/gradle/actions.git
synced 2025-02-03 15:26:42 -05:00
Isolate uses of GitHub cache API
- Introduced cache-api with Cache interfaces - Extracted github-actions-cache implementation - Use the cache-api consistently for all cache access
This commit is contained in:
parent
44db8041a5
commit
e03531acf1
6 changed files with 120 additions and 41 deletions
37
sources/src/caching/cache-api.ts
Normal file
37
sources/src/caching/cache-api.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {GitHubActionsCache} from './github-actions-cache'
|
||||
|
||||
export function getCache(): Cache {
|
||||
return new GitHubActionsCache()
|
||||
}
|
||||
|
||||
export interface Cache {
|
||||
/**
|
||||
* @returns boolean return true if cache service feature is available, otherwise false
|
||||
*/
|
||||
isAvailable(): boolean
|
||||
|
||||
/**
|
||||
* Restores cache from keys
|
||||
*
|
||||
* @param paths a list of file paths to restore from the cache
|
||||
* @param primaryKey an explicit key for restoring the cache. Lookup is done with prefix matching.
|
||||
* @param restoreKeys an optional ordered list of keys to use for restoring the cache if no cache hit occurred for primaryKey
|
||||
* @returns the restored entry details for the cache hit, otherwise returns undefined
|
||||
*/
|
||||
restore(paths: string[], primaryKey: string, restoreKeys?: string[]): Promise<CacheResult | undefined>
|
||||
|
||||
/**
|
||||
* Saves a list of files with the specified key
|
||||
*
|
||||
* @param paths a list of file paths to be cached
|
||||
* @param key an explicit key for restoring the cache
|
||||
* @returns the saved entry details if the cache was saved successfully and throws an error if save fails
|
||||
*/
|
||||
save(paths: string[], key: string): Promise<CacheResult>
|
||||
}
|
||||
|
||||
export declare class CacheResult {
|
||||
key: string
|
||||
size?: number
|
||||
constructor(key: string, size?: number)
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import * as cache from '@actions/cache'
|
||||
import * as core from '@actions/core'
|
||||
import {getCache} from './cache-api'
|
||||
|
||||
export const DEFAULT_CACHE_ENABLED_REASON = `[Cache was enabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#caching-build-state-between-jobs). Action attempted to both restore and save the Gradle User Home.`
|
||||
|
||||
|
@ -28,6 +29,7 @@ export const CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT =
|
|||
*/
|
||||
export class CacheListener {
|
||||
cacheEntries: CacheEntryListener[] = []
|
||||
cacheAvailable = getCache().isAvailable()
|
||||
cacheReadOnly = false
|
||||
cacheWriteOnly = false
|
||||
cacheDisabled = false
|
||||
|
@ -39,7 +41,7 @@ export class CacheListener {
|
|||
}
|
||||
|
||||
get cacheStatus(): string {
|
||||
if (!cache.isFeatureAvailable()) return 'not available'
|
||||
if (!this.cacheAvailable) return 'not available'
|
||||
if (this.cacheDisabled) return 'disabled'
|
||||
if (this.cacheWriteOnly) return 'write-only'
|
||||
if (this.cacheReadOnly) return 'read-only'
|
||||
|
@ -133,6 +135,8 @@ export class CacheEntryListener {
|
|||
}
|
||||
|
||||
markRestored(key: string, size: number | undefined, time: number): CacheEntryListener {
|
||||
core.info(`Restored cache entry with key ${key} in ${time}ms`)
|
||||
|
||||
this.restoredKey = key
|
||||
this.restoredSize = size
|
||||
this.restoredTime = time
|
||||
|
@ -145,6 +149,8 @@ export class CacheEntryListener {
|
|||
}
|
||||
|
||||
markSaved(key: string, size: number | undefined, time: number): CacheEntryListener {
|
||||
core.info(`Saved cache entry with key ${key} in ${time}ms`)
|
||||
|
||||
this.savedKey = key
|
||||
this.savedSize = size
|
||||
this.savedTime = time
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as cache from '@actions/cache'
|
||||
import * as exec from '@actions/exec'
|
||||
|
||||
import * as crypto from 'crypto'
|
||||
|
@ -7,9 +6,7 @@ import * as path from 'path'
|
|||
import * as fs from 'fs'
|
||||
|
||||
import {CacheEntryListener} from './cache-reporting'
|
||||
|
||||
const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS'
|
||||
const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes
|
||||
import {CacheResult, getCache} from './cache-api'
|
||||
|
||||
export function isCacheDebuggingEnabled(): boolean {
|
||||
if (core.isDebug()) {
|
||||
|
@ -31,23 +28,18 @@ export function hashStrings(values: string[]): string {
|
|||
}
|
||||
|
||||
export async function restoreCache(
|
||||
cachePath: string[],
|
||||
cachePaths: string[],
|
||||
cacheKey: string,
|
||||
cacheRestoreKeys: string[],
|
||||
listener: CacheEntryListener
|
||||
): Promise<cache.CacheEntry | undefined> {
|
||||
): Promise<CacheResult | undefined> {
|
||||
listener.markRequested(cacheKey, cacheRestoreKeys)
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
// Only override the read timeout if the SEGMENT_DOWNLOAD_TIMEOUT_MINS env var has NOT been set
|
||||
const cacheRestoreOptions = process.env[SEGMENT_DOWNLOAD_TIMEOUT_VAR]
|
||||
? {}
|
||||
: {segmentTimeoutInMs: SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT}
|
||||
const restoredEntry = await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys, cacheRestoreOptions)
|
||||
const restoredEntry = await getCache().restore(cachePaths, cacheKey, cacheRestoreKeys)
|
||||
if (restoredEntry !== undefined) {
|
||||
const restoreTime = Date.now() - startTime
|
||||
listener.markRestored(restoredEntry.key, restoredEntry.size, restoreTime)
|
||||
core.info(`Restored cache entry with key ${cacheKey} to ${cachePath.join()} in ${restoreTime}ms`)
|
||||
}
|
||||
return restoredEntry
|
||||
} catch (error) {
|
||||
|
@ -57,20 +49,19 @@ export async function restoreCache(
|
|||
}
|
||||
}
|
||||
|
||||
export async function saveCache(cachePath: string[], cacheKey: string, listener: CacheEntryListener): Promise<void> {
|
||||
export async function saveCache(cachePaths: string[], cacheKey: string, listener: CacheEntryListener): Promise<void> {
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
const savedEntry = await cache.saveCache(cachePath, cacheKey)
|
||||
const saveResult = await getCache().save(cachePaths, cacheKey)
|
||||
const saveTime = Date.now() - startTime
|
||||
listener.markSaved(savedEntry.key, savedEntry.size, saveTime)
|
||||
core.info(`Saved cache entry with key ${cacheKey} from ${cachePath.join()} in ${saveTime}ms`)
|
||||
} catch (error) {
|
||||
if (error instanceof cache.ReserveCacheError) {
|
||||
if (saveResult.size === 0) {
|
||||
listener.markAlreadyExists(cacheKey)
|
||||
} else {
|
||||
listener.markNotSaved((error as Error).message)
|
||||
listener.markSaved(saveResult.key, saveResult.size, saveTime)
|
||||
}
|
||||
handleCacheFailure(error, `Failed to save cache entry with path '${cachePath}' and key: ${cacheKey}`)
|
||||
} catch (error) {
|
||||
listener.markNotSaved((error as Error).message)
|
||||
handleCacheFailure(error, `Failed to save cache entry with path '${cachePaths}' and key: ${cacheKey}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,19 +74,10 @@ export function cacheDebug(message: string): void {
|
|||
}
|
||||
|
||||
export function handleCacheFailure(error: unknown, message: string): void {
|
||||
if (error instanceof cache.ValidationError) {
|
||||
// Fail on cache validation errors
|
||||
throw error
|
||||
}
|
||||
if (error instanceof cache.ReserveCacheError) {
|
||||
// Reserve cache errors are expected if the artifact has been previously cached
|
||||
core.info(`${message}: ${error}`)
|
||||
} else {
|
||||
// Warn on all other errors
|
||||
core.warning(`${message}: ${error}`)
|
||||
if (error instanceof Error && error.stack) {
|
||||
cacheDebug(error.stack)
|
||||
}
|
||||
// Warn on and continue on any cache error
|
||||
core.warning(`${message}: ${error}`)
|
||||
if (error instanceof Error && error.stack) {
|
||||
cacheDebug(error.stack)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
49
sources/src/caching/github-actions-cache.ts
Normal file
49
sources/src/caching/github-actions-cache.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import * as gitHubCache from '@actions/cache'
|
||||
import * as core from '@actions/core'
|
||||
import {Cache} from './cache-api'
|
||||
|
||||
const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS'
|
||||
const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes
|
||||
|
||||
export class GitHubActionsCache implements Cache {
|
||||
isAvailable(): boolean {
|
||||
return gitHubCache.isFeatureAvailable()
|
||||
}
|
||||
|
||||
async restore(paths: string[], primaryKey: string, restoreKeys?: string[]): Promise<CacheResult | undefined> {
|
||||
// Only override the read timeout if the SEGMENT_DOWNLOAD_TIMEOUT_MINS env var has NOT been set
|
||||
const cacheRestoreOptions = process.env[SEGMENT_DOWNLOAD_TIMEOUT_VAR]
|
||||
? {}
|
||||
: {segmentTimeoutInMs: SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT}
|
||||
|
||||
const restored = await gitHubCache.restoreCache(paths, primaryKey, restoreKeys, cacheRestoreOptions)
|
||||
return restored ? this.cacheResult(restored.key, restored.size) : undefined
|
||||
}
|
||||
|
||||
async save(paths: string[], key: string): Promise<CacheResult> {
|
||||
try {
|
||||
const cacheEntry = await gitHubCache.saveCache(paths, key)
|
||||
return this.cacheResult(cacheEntry.key, cacheEntry.size)
|
||||
} catch (error) {
|
||||
if (error instanceof gitHubCache.ReserveCacheError) {
|
||||
// Reserve cache errors are expected if the artifact has been previously cached
|
||||
core.info(`Cache entry ${key} already exists: ${error}`)
|
||||
return this.cacheResult(key, 0)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private cacheResult(key: string, size?: number): CacheResult {
|
||||
return new CacheResult(key, size)
|
||||
}
|
||||
}
|
||||
|
||||
class CacheResult {
|
||||
key: string
|
||||
size?: number
|
||||
constructor(key: string, size?: number) {
|
||||
this.key = key
|
||||
this.size = size
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as cache from '@actions/cache'
|
||||
import * as deprecator from './deprecation-collector'
|
||||
import {getCache} from './caching/cache-api'
|
||||
import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary'
|
||||
|
||||
import path from 'path'
|
||||
|
@ -104,8 +104,12 @@ export enum DependencyGraphOption {
|
|||
}
|
||||
|
||||
export class CacheConfig {
|
||||
isFeatureAvailable(): boolean {
|
||||
return getCache().isAvailable()
|
||||
}
|
||||
|
||||
isCacheDisabled(): boolean {
|
||||
if (!cache.isFeatureAvailable()) {
|
||||
if (!this.isFeatureAvailable()) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ import * as os from 'os'
|
|||
import * as path from 'path'
|
||||
import * as httpm from 'typed-rest-client/HttpClient'
|
||||
import * as core from '@actions/core'
|
||||
import * as cache from '@actions/cache'
|
||||
import * as toolCache from '@actions/tool-cache'
|
||||
|
||||
import {determineGradleVersion, findGradleExecutableOnPath, versionIsAtLeast} from './gradle'
|
||||
import * as gradlew from './gradlew'
|
||||
import {handleCacheFailure} from '../caching/cache-utils'
|
||||
import {handleCacheFailure, saveCache, restoreCache} from '../caching/cache-utils'
|
||||
import {CacheConfig} from '../configuration'
|
||||
import {CacheEntryListener} from '../caching/cache-reporting'
|
||||
|
||||
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
|
||||
|
||||
|
@ -167,8 +167,9 @@ async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo
|
|||
}
|
||||
|
||||
const cacheKey = `gradle-${versionInfo.version}`
|
||||
const listener = new CacheEntryListener(cacheKey)
|
||||
try {
|
||||
const restoreKey = await cache.restoreCache([downloadPath], cacheKey)
|
||||
const restoreKey = await restoreCache([downloadPath], cacheKey, [], listener)
|
||||
if (restoreKey) {
|
||||
core.info(`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`)
|
||||
return downloadPath
|
||||
|
@ -182,7 +183,7 @@ async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo
|
|||
|
||||
if (!cacheConfig.isCacheReadOnly()) {
|
||||
try {
|
||||
await cache.saveCache([downloadPath], cacheKey)
|
||||
await saveCache([downloadPath], cacheKey, listener)
|
||||
} catch (error) {
|
||||
handleCacheFailure(error, `Save Gradle distribution ${versionInfo.version} failed`)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue