2021-08-19 17:19:35 +00:00
/ * *
* @fileoverview this file provides methods handling dependency cache
* /
2023-03-09 12:49:35 +00:00
import { join } from 'path' ;
2021-08-19 17:19:35 +00:00
import os from 'os' ;
import * as cache from '@actions/cache' ;
import * as core from '@actions/core' ;
import * as glob from '@actions/glob' ;
const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key' ;
const CACHE_MATCHED_KEY = 'cache-matched-key' ;
const CACHE_KEY_PREFIX = 'setup-java' ;
interface PackageManager {
2022-04-20 14:26:27 +00:00
id : 'maven' | 'gradle' | 'sbt' ;
2021-08-19 17:19:35 +00:00
/ * *
* Paths of the file that specify the files to cache .
* /
path : string [ ] ;
pattern : string [ ] ;
}
const supportedPackageManager : PackageManager [ ] = [
{
id : 'maven' ,
path : [ join ( os . homedir ( ) , '.m2' , 'repository' ) ] ,
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern : [ '**/pom.xml' ]
} ,
{
id : 'gradle' ,
2023-03-09 12:49:35 +00:00
path : [
join ( os . homedir ( ) , '.gradle' , 'caches' ) ,
join ( os . homedir ( ) , '.gradle' , 'wrapper' )
] ,
2021-08-19 17:19:35 +00:00
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
2022-04-25 11:40:12 +00:00
pattern : [
'**/*.gradle*' ,
'**/gradle-wrapper.properties' ,
'buildSrc/**/Versions.kt' ,
2022-10-17 16:34:41 +00:00
'buildSrc/**/Dependencies.kt' ,
2023-07-21 11:38:46 +00:00
'gradle/*.versions.toml' ,
'**/versions.properties'
2022-04-25 11:40:12 +00:00
]
2022-04-20 14:26:27 +00:00
} ,
{
id : 'sbt' ,
path : [
join ( os . homedir ( ) , '.ivy2' , 'cache' ) ,
join ( os . homedir ( ) , '.sbt' ) ,
2022-05-25 09:39:10 +00:00
getCoursierCachePath ( ) ,
2022-06-15 06:10:31 +00:00
// Some files should not be cached to avoid resolution problems.
// In particular the resolution of snapshots (ideological gap between maven/ivy).
2022-05-25 09:39:10 +00:00
'!' + join ( os . homedir ( ) , '.sbt' , '*.lock' ) ,
'!' + join ( os . homedir ( ) , '**' , 'ivydata-*.properties' )
2022-04-20 14:26:27 +00:00
] ,
2023-03-09 12:49:35 +00:00
pattern : [
'**/*.sbt' ,
'**/project/build.properties' ,
2023-04-10 07:56:26 +00:00
'**/project/**.scala' ,
'**/project/**.sbt'
2023-03-09 12:49:35 +00:00
]
2021-08-19 17:19:35 +00:00
}
] ;
2022-04-20 14:26:27 +00:00
function getCoursierCachePath ( ) : string {
if ( os . type ( ) === 'Linux' ) return join ( os . homedir ( ) , '.cache' , 'coursier' ) ;
2023-03-09 12:49:35 +00:00
if ( os . type ( ) === 'Darwin' )
return join ( os . homedir ( ) , 'Library' , 'Caches' , 'Coursier' ) ;
2022-04-20 14:26:27 +00:00
return join ( os . homedir ( ) , 'AppData' , 'Local' , 'Coursier' , 'Cache' ) ;
}
2021-08-19 17:19:35 +00:00
function findPackageManager ( id : string ) : PackageManager {
2023-03-09 12:49:35 +00:00
const packageManager = supportedPackageManager . find (
packageManager = > packageManager . id === id
) ;
2021-08-19 17:19:35 +00:00
if ( packageManager === undefined ) {
throw new Error ( ` unknown package manager specified: ${ id } ` ) ;
}
return packageManager ;
}
/ * *
* A function that generates a cache key to use .
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}" " .
* @see { @link https : //docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key|spec of cache key}
* /
2023-11-22 15:43:14 +00:00
async function computeCacheKey (
packageManager : PackageManager ,
cacheDependencyPath : string
) {
const pattern = cacheDependencyPath
? cacheDependencyPath . trim ( ) . split ( '\n' )
: packageManager . pattern ;
const fileHash = await glob . hashFiles ( pattern . join ( '\n' ) ) ;
if ( ! fileHash ) {
throw new Error (
` No file in ${ process . cwd ( ) } matched to [ ${ pattern } ], make sure you have checked out the target repository `
) ;
}
return ` ${ CACHE_KEY_PREFIX } - ${ process . env [ 'RUNNER_OS' ] } - ${ packageManager . id } - ${ fileHash } ` ;
2021-08-19 17:19:35 +00:00
}
/ * *
* Restore the dependency cache
* @param id ID of the package manager , should be "maven" or "gradle"
2023-11-22 15:43:14 +00:00
* @param cacheDependencyPath The path to a dependency file
2021-08-19 17:19:35 +00:00
* /
2023-11-22 15:43:14 +00:00
export async function restore ( id : string , cacheDependencyPath : string ) {
2021-08-19 17:19:35 +00:00
const packageManager = findPackageManager ( id ) ;
2023-11-22 15:43:14 +00:00
const primaryKey = await computeCacheKey ( packageManager , cacheDependencyPath ) ;
2021-08-19 17:19:35 +00:00
core . debug ( ` primary key is ${ primaryKey } ` ) ;
core . saveState ( STATE_CACHE_PRIMARY_KEY , primaryKey ) ;
2022-01-13 15:39:37 +00:00
// No "restoreKeys" is set, to start with a clear cache after dependency update (see https://github.com/actions/setup-java/issues/269)
const matchedKey = await cache . restoreCache ( packageManager . path , primaryKey ) ;
2021-08-19 17:19:35 +00:00
if ( matchedKey ) {
core . saveState ( CACHE_MATCHED_KEY , matchedKey ) ;
2022-04-07 08:58:22 +00:00
core . setOutput ( 'cache-hit' , matchedKey === primaryKey ) ;
2021-08-19 17:19:35 +00:00
core . info ( ` Cache restored from key: ${ matchedKey } ` ) ;
} else {
2022-04-07 08:58:22 +00:00
core . setOutput ( 'cache-hit' , false ) ;
2021-08-19 17:19:35 +00:00
core . info ( ` ${ packageManager . id } cache is not found ` ) ;
}
}
/ * *
* Save the dependency cache
* @param id ID of the package manager , should be "maven" or "gradle"
* /
export async function save ( id : string ) {
const packageManager = findPackageManager ( id ) ;
const matchedKey = core . getState ( CACHE_MATCHED_KEY ) ;
2022-09-09 11:35:58 +00:00
// Inputs are re-evaluated before the post action, so we want the original key used for restore
2021-08-19 17:19:35 +00:00
const primaryKey = core . getState ( STATE_CACHE_PRIMARY_KEY ) ;
if ( ! primaryKey ) {
core . warning ( 'Error retrieving key from state.' ) ;
return ;
} else if ( matchedKey === primaryKey ) {
// no change in target directories
2023-03-09 12:49:35 +00:00
core . info (
` Cache hit occurred on the primary key ${ primaryKey } , not saving cache. `
) ;
2021-08-19 17:19:35 +00:00
return ;
}
try {
await cache . saveCache ( packageManager . path , primaryKey ) ;
core . info ( ` Cache saved with the key: ${ primaryKey } ` ) ;
} catch ( error ) {
2023-11-29 14:11:46 +00:00
const err = error as Error ;
if ( err . name === cache . ReserveCacheError . name ) {
core . info ( err . message ) ;
2021-08-19 17:19:35 +00:00
} else {
2023-11-29 14:11:46 +00:00
if ( isProbablyGradleDaemonProblem ( packageManager , err ) ) {
2021-08-19 17:19:35 +00:00
core . warning (
'Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.'
) ;
}
throw error ;
}
}
}
/ * *
* @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache
* @returns true if the given error seems related to the { @link https : //github.com/actions/cache/issues/454|running Gradle Daemon issue}.
* @see { @link https : //github.com/actions/cache/issues/454#issuecomment-840493935|why --no-daemon is necessary}
* /
2023-03-09 12:49:35 +00:00
function isProbablyGradleDaemonProblem (
packageManager : PackageManager ,
error : Error
) {
if (
packageManager . id !== 'gradle' ||
process . env [ 'RUNNER_OS' ] !== 'Windows'
) {
2021-08-19 17:19:35 +00:00
return false ;
}
const message = error . message || '' ;
return message . startsWith ( 'Tar failed with error: ' ) ;
}