mirror of
				https://github.com/gradle/gradle-build-action.git
				synced 2025-11-04 01:28:56 +08:00 
			
		
		
		
	Merge pull request #128 from gradle/configuration-caching
Restore/save configuration-cache data in first action step. This will enable the use of the action for caching without managing all gradle invocations.
This commit is contained in:
		@@ -26,8 +26,13 @@ jobs:
 | 
				
			|||||||
      with:
 | 
					      with:
 | 
				
			||||||
        build-root-directory: __tests__/samples/groovy-dsl
 | 
					        build-root-directory: __tests__/samples/groovy-dsl
 | 
				
			||||||
        arguments: test --configuration-cache
 | 
					        arguments: test --configuration-cache
 | 
				
			||||||
 | 
					    - name: Second build with configuration-cache enabled
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        build-root-directory: __tests__/samples/kotlin-dsl
 | 
				
			||||||
 | 
					        arguments: test --configuration-cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Test that the project-dot-gradle cache will cache and restore configuration-cache
 | 
					  # Test restore configuration-cache
 | 
				
			||||||
  configuration-cache:
 | 
					  configuration-cache:
 | 
				
			||||||
    needs: seed-build
 | 
					    needs: seed-build
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
@@ -46,6 +51,25 @@ jobs:
 | 
				
			|||||||
        arguments: test --configuration-cache
 | 
					        arguments: test --configuration-cache
 | 
				
			||||||
        cache-read-only: true
 | 
					        cache-read-only: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Test restore configuration-cache from second build invocation
 | 
				
			||||||
 | 
					  configuration-cache-2:
 | 
				
			||||||
 | 
					    needs: seed-build
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        os: [ubuntu-latest, windows-latest]
 | 
				
			||||||
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout sources
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Execute Gradle build and verify cached configuration
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      env: 
 | 
				
			||||||
 | 
					        VERIFY_CACHED_CONFIGURATION: true
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        build-root-directory: __tests__/samples/kotlin-dsl
 | 
				
			||||||
 | 
					        arguments: test --configuration-cache
 | 
				
			||||||
 | 
					        cache-read-only: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Check that the build can run when no extracted cache entries are restored
 | 
					  # Check that the build can run when no extracted cache entries are restored
 | 
				
			||||||
  no-extracted-cache-entries-restored:
 | 
					  no-extracted-cache-entries-restored:
 | 
				
			||||||
    needs: seed-build
 | 
					    needs: seed-build
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import {CacheEntryListener, CacheListener} from '../src/cache-base'
 | 
					import {CacheEntryListener, CacheListener} from '../src/cache-reporting'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('caching report', () => {
 | 
					describe('caching report', () => {
 | 
				
			||||||
    describe('reports not fully restored', () => {
 | 
					    describe('reports not fully restored', () => {
 | 
				
			||||||
@@ -16,3 +16,15 @@ dependencies {
 | 
				
			|||||||
tasks.test {
 | 
					tasks.test {
 | 
				
			||||||
    useJUnitPlatform()
 | 
					    useJUnitPlatform()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tasks.named("test").configure {
 | 
				
			||||||
 | 
					    // Use an environment variable to bypass config-cache checks
 | 
				
			||||||
 | 
					    if (System.getenv("VERIFY_CACHED_CONFIGURATION") != null) {
 | 
				
			||||||
 | 
					        throw RuntimeException("Configuration was not cached: unexpected configuration of test task")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    doLast {
 | 
				
			||||||
 | 
					        if (System.getProperties().containsKey("verifyCachedBuild")) {
 | 
				
			||||||
 | 
					            throw RuntimeException("Build was not cached: unexpected execution of test task")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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/main/index.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/main/index.js.map
									
									
									
									
										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
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/post/index.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/post/index.js.map
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,10 +1,15 @@
 | 
				
			|||||||
import * as core from '@actions/core'
 | 
					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 {isCacheDebuggingEnabled, getCacheKeyPrefix, hashStrings, handleCacheFailure} from './cache-utils'
 | 
					import path from 'path'
 | 
				
			||||||
 | 
					import fs from 'fs'
 | 
				
			||||||
 | 
					import {CacheListener} from './cache-reporting'
 | 
				
			||||||
 | 
					import {isCacheDebuggingEnabled, getCacheKeyPrefix, determineJobContext, handleCacheFailure} from './cache-utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CACHE_PROTOCOL_VERSION = 'v5-'
 | 
					const CACHE_PROTOCOL_VERSION = 'v5-'
 | 
				
			||||||
const JOB_CONTEXT_PARAMETER = 'workflow-job-context'
 | 
					
 | 
				
			||||||
 | 
					export const META_FILE_DIR = '.gradle-build-action'
 | 
				
			||||||
 | 
					export const PROJECT_ROOTS_FILE = 'project-roots.txt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a key used to restore a cache entry.
 | 
					 * Represents a key used to restore a cache entry.
 | 
				
			||||||
@@ -57,100 +62,17 @@ function generateCacheKey(cacheName: string): CacheKey {
 | 
				
			|||||||
    return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs])
 | 
					    return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function determineJobContext(): string {
 | 
					 | 
				
			||||||
    // By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation
 | 
					 | 
				
			||||||
    // The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml.
 | 
					 | 
				
			||||||
    const workflowJobContext = core.getInput(JOB_CONTEXT_PARAMETER)
 | 
					 | 
				
			||||||
    return hashStrings([workflowJobContext])
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Collects information on what entries were saved and restored during the action.
 | 
					 | 
				
			||||||
 * This information is used to generate a summary of the cache usage.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class CacheListener {
 | 
					 | 
				
			||||||
    cacheEntries: CacheEntryListener[] = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    get fullyRestored(): boolean {
 | 
					 | 
				
			||||||
        return this.cacheEntries.every(x => !x.wasRequestedButNotRestored())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    entry(name: string): CacheEntryListener {
 | 
					 | 
				
			||||||
        for (const entry of this.cacheEntries) {
 | 
					 | 
				
			||||||
            if (entry.entryName === name) {
 | 
					 | 
				
			||||||
                return entry
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const newEntry = new CacheEntryListener(name)
 | 
					 | 
				
			||||||
        this.cacheEntries.push(newEntry)
 | 
					 | 
				
			||||||
        return newEntry
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    stringify(): string {
 | 
					 | 
				
			||||||
        return JSON.stringify(this)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static rehydrate(stringRep: string): CacheListener {
 | 
					 | 
				
			||||||
        const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep))
 | 
					 | 
				
			||||||
        const entries = rehydrated.cacheEntries
 | 
					 | 
				
			||||||
        for (let index = 0; index < entries.length; index++) {
 | 
					 | 
				
			||||||
            const rawEntry = entries[index]
 | 
					 | 
				
			||||||
            entries[index] = Object.assign(new CacheEntryListener(rawEntry.entryName), rawEntry)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return rehydrated
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Collects information on the state of a single cache entry.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class CacheEntryListener {
 | 
					 | 
				
			||||||
    entryName: string
 | 
					 | 
				
			||||||
    requestedKey: string | undefined
 | 
					 | 
				
			||||||
    requestedRestoreKeys: string[] | undefined
 | 
					 | 
				
			||||||
    restoredKey: string | undefined
 | 
					 | 
				
			||||||
    restoredSize: number | undefined
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    savedKey: string | undefined
 | 
					 | 
				
			||||||
    savedSize: number | undefined
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(entryName: string) {
 | 
					 | 
				
			||||||
        this.entryName = entryName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    wasRequestedButNotRestored(): boolean {
 | 
					 | 
				
			||||||
        return this.requestedKey !== undefined && this.restoredKey === undefined
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    markRequested(key: string, restoreKeys: string[] = []): CacheEntryListener {
 | 
					 | 
				
			||||||
        this.requestedKey = key
 | 
					 | 
				
			||||||
        this.requestedRestoreKeys = restoreKeys
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    markRestored(key: string, size: number | undefined): CacheEntryListener {
 | 
					 | 
				
			||||||
        this.restoredKey = key
 | 
					 | 
				
			||||||
        this.restoredSize = size
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    markSaved(key: string, size: number | undefined): CacheEntryListener {
 | 
					 | 
				
			||||||
        this.savedKey = key
 | 
					 | 
				
			||||||
        this.savedSize = size
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export abstract class AbstractCache {
 | 
					export abstract class AbstractCache {
 | 
				
			||||||
    private cacheName: string
 | 
					    private cacheName: string
 | 
				
			||||||
    private cacheDescription: string
 | 
					    private cacheDescription: string
 | 
				
			||||||
    private cacheKeyStateKey: string
 | 
					    private cacheKeyStateKey: string
 | 
				
			||||||
    private cacheResultStateKey: string
 | 
					    private cacheResultStateKey: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected readonly gradleUserHome: string
 | 
				
			||||||
    protected readonly cacheDebuggingEnabled: boolean
 | 
					    protected readonly cacheDebuggingEnabled: boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(cacheName: string, cacheDescription: string) {
 | 
					    constructor(gradleUserHome: string, cacheName: string, cacheDescription: string) {
 | 
				
			||||||
 | 
					        this.gradleUserHome = gradleUserHome
 | 
				
			||||||
        this.cacheName = cacheName
 | 
					        this.cacheName = cacheName
 | 
				
			||||||
        this.cacheDescription = cacheDescription
 | 
					        this.cacheDescription = cacheDescription
 | 
				
			||||||
        this.cacheKeyStateKey = `CACHE_KEY_${cacheName}`
 | 
					        this.cacheKeyStateKey = `CACHE_KEY_${cacheName}`
 | 
				
			||||||
@@ -158,23 +80,28 @@ export abstract class AbstractCache {
 | 
				
			|||||||
        this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
 | 
					        this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init(): void {
 | 
				
			||||||
 | 
					        const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action')
 | 
				
			||||||
 | 
					        fs.mkdirSync(actionCacheDir, {recursive: true})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d')
 | 
				
			||||||
 | 
					        fs.mkdirSync(initScriptsDir, {recursive: true})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Restores the cache entry, finding the closest match to the currently running job.
 | 
					     * Restores the cache entry, finding the closest match to the currently running job.
 | 
				
			||||||
     * If the target output already exists, caching will be skipped.
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async restore(listener: CacheListener): Promise<void> {
 | 
					    async restore(listener: CacheListener): Promise<void> {
 | 
				
			||||||
        if (this.cacheOutputExists()) {
 | 
					 | 
				
			||||||
            core.info(`${this.cacheDescription} already exists. Not restoring from cache.`)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const entryListener = listener.entry(this.cacheDescription)
 | 
					        const entryListener = listener.entry(this.cacheDescription)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const cacheKey = this.prepareCacheKey()
 | 
					        const cacheKey = this.prepareCacheKey()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.debug(
 | 
					        this.debug(
 | 
				
			||||||
            `Requesting ${this.cacheDescription} with
 | 
					            `Requesting ${this.cacheDescription} with
 | 
				
			||||||
                key:${cacheKey.key}
 | 
					    key:${cacheKey.key}
 | 
				
			||||||
                restoreKeys:[${cacheKey.restoreKeys}]`
 | 
					    restoreKeys:[${cacheKey.restoreKeys}]`
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const cacheResult = await this.restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys)
 | 
					        const cacheResult = await this.restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys)
 | 
				
			||||||
@@ -219,28 +146,17 @@ export abstract class AbstractCache {
 | 
				
			|||||||
    protected async afterRestore(_listener: CacheListener): Promise<void> {}
 | 
					    protected async afterRestore(_listener: CacheListener): Promise<void> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Saves the cache entry based on the current cache key, unless:
 | 
					     * Saves the cache entry based on the current cache key unless the cache was restored with the exact key,
 | 
				
			||||||
     * - If the cache output existed before restore, then it is not saved.
 | 
					     * in which case we cannot overwrite it.
 | 
				
			||||||
     * - If the cache was restored with the exact key, we cannot overwrite it.
 | 
					 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * If the cache entry was restored with a partial match on a restore key, then
 | 
					     * If the cache entry was restored with a partial match on a restore key, then
 | 
				
			||||||
     * it is saved with the exact key.
 | 
					     * it is saved with the exact key.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async save(listener: CacheListener): Promise<void> {
 | 
					    async save(listener: CacheListener): Promise<void> {
 | 
				
			||||||
        if (!this.cacheOutputExists()) {
 | 
					 | 
				
			||||||
            core.info(`No ${this.cacheDescription} to cache.`)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Retrieve the state set in the previous 'restore' step.
 | 
					        // Retrieve the state set in the previous 'restore' step.
 | 
				
			||||||
        const cacheKeyFromRestore = core.getState(this.cacheKeyStateKey)
 | 
					        const cacheKeyFromRestore = core.getState(this.cacheKeyStateKey)
 | 
				
			||||||
        const cacheResultFromRestore = core.getState(this.cacheResultStateKey)
 | 
					        const cacheResultFromRestore = core.getState(this.cacheResultStateKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!cacheKeyFromRestore) {
 | 
					 | 
				
			||||||
            core.info(`${this.cacheDescription} existed prior to cache restore. Not saving.`)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (cacheResultFromRestore && cacheKeyFromRestore === cacheResultFromRestore) {
 | 
					        if (cacheResultFromRestore && cacheKeyFromRestore === cacheResultFromRestore) {
 | 
				
			||||||
            core.info(`Cache hit occurred on the cache key ${cacheKeyFromRestore}, not saving cache.`)
 | 
					            core.info(`Cache hit occurred on the cache key ${cacheKeyFromRestore}, not saving cache.`)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
@@ -283,6 +199,6 @@ export abstract class AbstractCache {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected abstract cacheOutputExists(): boolean
 | 
					 | 
				
			||||||
    protected abstract getCachePath(): string[]
 | 
					    protected abstract getCachePath(): string[]
 | 
				
			||||||
 | 
					    protected abstract initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,13 @@
 | 
				
			|||||||
import path from 'path'
 | 
					import path from 'path'
 | 
				
			||||||
import fs from 'fs'
 | 
					import fs from 'fs'
 | 
				
			||||||
import os from 'os'
 | 
					 | 
				
			||||||
import * as core from '@actions/core'
 | 
					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, CacheEntryListener, CacheListener} from './cache-base'
 | 
					import {AbstractCache, META_FILE_DIR} from './cache-base'
 | 
				
			||||||
 | 
					import {CacheEntryListener, CacheListener} from './cache-reporting'
 | 
				
			||||||
import {getCacheKeyPrefix, hashFileNames, tryDelete} from './cache-utils'
 | 
					import {getCacheKeyPrefix, hashFileNames, tryDelete} from './cache-utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const META_FILE_DIR = '.gradle-build-action'
 | 
					 | 
				
			||||||
const META_FILE = 'cache-metadata.json'
 | 
					const META_FILE = 'cache-metadata.json'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes'
 | 
					const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes'
 | 
				
			||||||
@@ -46,16 +45,8 @@ class ExtractedCacheEntryMetadata {
 | 
				
			|||||||
 * for more efficient storage.
 | 
					 * for more efficient storage.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class GradleUserHomeCache extends AbstractCache {
 | 
					export class GradleUserHomeCache extends AbstractCache {
 | 
				
			||||||
    private gradleUserHome: string
 | 
					    constructor(gradleUserHome: string) {
 | 
				
			||||||
 | 
					        super(gradleUserHome, 'gradle', 'Gradle User Home')
 | 
				
			||||||
    constructor(rootDir: string) {
 | 
					 | 
				
			||||||
        super('gradle', 'Gradle User Home')
 | 
					 | 
				
			||||||
        this.gradleUserHome = this.determineGradleUserHome(rootDir)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    init(): void {
 | 
					 | 
				
			||||||
        this.debug(`Initializing Gradle User Home with properties and init script: ${this.gradleUserHome}`)
 | 
					 | 
				
			||||||
        initializeGradleUserHome(this.gradleUserHome)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -284,21 +275,6 @@ export class GradleUserHomeCache extends AbstractCache {
 | 
				
			|||||||
        fs.writeFileSync(cacheMetadataFile, filedata, 'utf-8')
 | 
					        fs.writeFileSync(cacheMetadataFile, filedata, 'utf-8')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected cacheOutputExists(): boolean {
 | 
					 | 
				
			||||||
        // Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
 | 
					 | 
				
			||||||
        const dir = path.resolve(this.gradleUserHome, 'caches')
 | 
					 | 
				
			||||||
        return fs.existsSync(dir)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Determines the paths within Gradle User Home to cache.
 | 
					     * Determines the paths within Gradle User Home to cache.
 | 
				
			||||||
     * By default, this is the 'caches' and 'notifications' directories,
 | 
					     * By default, this is the 'caches' and 'notifications' directories,
 | 
				
			||||||
@@ -363,21 +339,17 @@ export class GradleUserHomeCache extends AbstractCache {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        core.info('-----------------------')
 | 
					        core.info('-----------------------')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initializeGradleUserHome(gradleUserHome: string): void {
 | 
					    protected initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
 | 
				
			||||||
    fs.mkdirSync(gradleUserHome, {recursive: true})
 | 
					        const propertiesFile = path.resolve(gradleUserHome, 'gradle.properties')
 | 
				
			||||||
 | 
					        fs.writeFileSync(propertiesFile, 'org.gradle.daemon=false')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const propertiesFile = path.resolve(gradleUserHome, 'gradle.properties')
 | 
					        const buildScanCapture = path.resolve(initScriptsDir, 'build-scan-capture.init.gradle')
 | 
				
			||||||
    fs.writeFileSync(propertiesFile, 'org.gradle.daemon=false')
 | 
					        fs.writeFileSync(
 | 
				
			||||||
 | 
					            buildScanCapture,
 | 
				
			||||||
 | 
					            `import org.gradle.util.GradleVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const initScript = path.resolve(gradleUserHome, 'init.gradle')
 | 
					// Only run again root build. Do not run against included builds.
 | 
				
			||||||
    fs.writeFileSync(
 | 
					 | 
				
			||||||
        initScript,
 | 
					 | 
				
			||||||
        `
 | 
					 | 
				
			||||||
import org.gradle.util.GradleVersion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Don't run against the included builds (if the main build has any).
 | 
					 | 
				
			||||||
def isTopLevelBuild = gradle.getParent() == null
 | 
					def isTopLevelBuild = gradle.getParent() == null
 | 
				
			||||||
if (isTopLevelBuild) {
 | 
					if (isTopLevelBuild) {
 | 
				
			||||||
    def version = GradleVersion.current().baseVersion
 | 
					    def version = GradleVersion.current().baseVersion
 | 
				
			||||||
@@ -417,7 +389,7 @@ def registerCallbacks(buildScanExtension, rootProjectName) {
 | 
				
			|||||||
            println("::set-output name=build-scan-url::\${buildScan.buildScanUri}")
 | 
					            println("::set-output name=build-scan-url::\${buildScan.buildScanUri}")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}`
 | 
				
			||||||
`
 | 
					        )
 | 
				
			||||||
    )
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +1,52 @@
 | 
				
			|||||||
 | 
					import * as core from '@actions/core'
 | 
				
			||||||
import path from 'path'
 | 
					import path from 'path'
 | 
				
			||||||
import fs from 'fs'
 | 
					import fs from 'fs'
 | 
				
			||||||
import {AbstractCache} from './cache-base'
 | 
					import {AbstractCache, META_FILE_DIR, PROJECT_ROOTS_FILE} from './cache-base'
 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: Maybe allow the user to override / tweak this set
 | 
					 | 
				
			||||||
const PATHS_TO_CACHE = [
 | 
					 | 
				
			||||||
    'configuration-cache' // Only configuration-cache is stored at present
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A simple cache that saves and restores the '.gradle/configuration-cache' directory in the project root.
 | 
					 * A simple cache that saves and restores the '.gradle/configuration-cache' directory in the project root.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class ProjectDotGradleCache extends AbstractCache {
 | 
					export class ProjectDotGradleCache extends AbstractCache {
 | 
				
			||||||
    private rootDir: string
 | 
					    constructor(gradleUserHome: string) {
 | 
				
			||||||
    constructor(rootDir: string) {
 | 
					        super(gradleUserHome, 'project', 'Project configuration cache')
 | 
				
			||||||
        super('project', 'Project configuration cache')
 | 
					 | 
				
			||||||
        this.rootDir = rootDir
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected cacheOutputExists(): boolean {
 | 
					 | 
				
			||||||
        const dir = this.getProjectDotGradleDir()
 | 
					 | 
				
			||||||
        return fs.existsSync(dir)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected getCachePath(): string[] {
 | 
					    protected getCachePath(): string[] {
 | 
				
			||||||
        const dir = this.getProjectDotGradleDir()
 | 
					        return this.getProjectRoots().map(x => path.resolve(x, '.gradle/configuration-cache'))
 | 
				
			||||||
        return PATHS_TO_CACHE.map(x => path.resolve(dir, x))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private getProjectDotGradleDir(): string {
 | 
					    protected initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
 | 
				
			||||||
        return path.resolve(this.rootDir, '.gradle')
 | 
					        const projectRootCapture = path.resolve(initScriptsDir, 'project-root-capture.init.gradle')
 | 
				
			||||||
 | 
					        fs.writeFileSync(
 | 
				
			||||||
 | 
					            projectRootCapture,
 | 
				
			||||||
 | 
					            `
 | 
				
			||||||
 | 
					    // Only run again root build. Do not run against included builds.
 | 
				
			||||||
 | 
					    def isTopLevelBuild = gradle.getParent() == null
 | 
				
			||||||
 | 
					    if (isTopLevelBuild) {
 | 
				
			||||||
 | 
					        settingsEvaluated { settings ->
 | 
				
			||||||
 | 
					            def projectRootEntry = settings.rootDir.absolutePath + "\\n"
 | 
				
			||||||
 | 
					            def projectRootList = new File(settings.gradle.gradleUserHomeDir, "${META_FILE_DIR}/${PROJECT_ROOTS_FILE}")
 | 
				
			||||||
 | 
					            println "Adding " + projectRootEntry + " to " + projectRootList
 | 
				
			||||||
 | 
					            if (!projectRootList.exists() || !projectRootList.text.contains(projectRootEntry)) {
 | 
				
			||||||
 | 
					                projectRootList << projectRootEntry
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }`
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * For every Gradle invocation, we record the project root directory. This method returns the entire
 | 
				
			||||||
 | 
					     * set of project roots, to allow saving of configuration-cache entries for each.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private getProjectRoots(): string[] {
 | 
				
			||||||
 | 
					        const projectList = path.resolve(this.gradleUserHome, META_FILE_DIR, PROJECT_ROOTS_FILE)
 | 
				
			||||||
 | 
					        if (!fs.existsSync(projectList)) {
 | 
				
			||||||
 | 
					            core.info(`Missing project list file ${projectList}`)
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const projectRoots = fs.readFileSync(projectList, 'utf-8')
 | 
				
			||||||
 | 
					        core.info(`Found project roots '${projectRoots}' in ${projectList}`)
 | 
				
			||||||
 | 
					        return projectRoots.trim().split('\n')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										123
									
								
								src/cache-reporting.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/cache-reporting.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import * as core from '@actions/core'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Collects information on what entries were saved and restored during the action.
 | 
				
			||||||
 | 
					 * This information is used to generate a summary of the cache usage.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class CacheListener {
 | 
				
			||||||
 | 
					    cacheEntries: CacheEntryListener[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get fullyRestored(): boolean {
 | 
				
			||||||
 | 
					        return this.cacheEntries.every(x => !x.wasRequestedButNotRestored())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    entry(name: string): CacheEntryListener {
 | 
				
			||||||
 | 
					        for (const entry of this.cacheEntries) {
 | 
				
			||||||
 | 
					            if (entry.entryName === name) {
 | 
				
			||||||
 | 
					                return entry
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const newEntry = new CacheEntryListener(name)
 | 
				
			||||||
 | 
					        this.cacheEntries.push(newEntry)
 | 
				
			||||||
 | 
					        return newEntry
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stringify(): string {
 | 
				
			||||||
 | 
					        return JSON.stringify(this)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static rehydrate(stringRep: string): CacheListener {
 | 
				
			||||||
 | 
					        const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep))
 | 
				
			||||||
 | 
					        const entries = rehydrated.cacheEntries
 | 
				
			||||||
 | 
					        for (let index = 0; index < entries.length; index++) {
 | 
				
			||||||
 | 
					            const rawEntry = entries[index]
 | 
				
			||||||
 | 
					            entries[index] = Object.assign(new CacheEntryListener(rawEntry.entryName), rawEntry)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return rehydrated
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Collects information on the state of a single cache entry.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class CacheEntryListener {
 | 
				
			||||||
 | 
					    entryName: string
 | 
				
			||||||
 | 
					    requestedKey: string | undefined
 | 
				
			||||||
 | 
					    requestedRestoreKeys: string[] | undefined
 | 
				
			||||||
 | 
					    restoredKey: string | undefined
 | 
				
			||||||
 | 
					    restoredSize: number | undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    savedKey: string | undefined
 | 
				
			||||||
 | 
					    savedSize: number | undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(entryName: string) {
 | 
				
			||||||
 | 
					        this.entryName = entryName
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    wasRequestedButNotRestored(): boolean {
 | 
				
			||||||
 | 
					        return this.requestedKey !== undefined && this.restoredKey === undefined
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    markRequested(key: string, restoreKeys: string[] = []): CacheEntryListener {
 | 
				
			||||||
 | 
					        this.requestedKey = key
 | 
				
			||||||
 | 
					        this.requestedRestoreKeys = restoreKeys
 | 
				
			||||||
 | 
					        return this
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    markRestored(key: string, size: number | undefined): CacheEntryListener {
 | 
				
			||||||
 | 
					        this.restoredKey = key
 | 
				
			||||||
 | 
					        this.restoredSize = size
 | 
				
			||||||
 | 
					        return this
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    markSaved(key: string, size: number | undefined): CacheEntryListener {
 | 
				
			||||||
 | 
					        this.savedKey = key
 | 
				
			||||||
 | 
					        this.savedSize = size
 | 
				
			||||||
 | 
					        return this
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function logCachingReport(listener: CacheListener): void {
 | 
				
			||||||
 | 
					    if (listener.cacheEntries.length === 0) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    core.info(`---------- Caching Summary -------------
 | 
				
			||||||
 | 
					Restored Entries Count: ${getCount(listener.cacheEntries, e => e.restoredSize)}
 | 
				
			||||||
 | 
					                  Size: ${getSum(listener.cacheEntries, e => e.restoredSize)}
 | 
				
			||||||
 | 
					Saved Entries    Count: ${getCount(listener.cacheEntries, e => e.savedSize)}
 | 
				
			||||||
 | 
					                  Size: ${getSum(listener.cacheEntries, e => e.savedSize)}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    core.startGroup('Cache Entry details')
 | 
				
			||||||
 | 
					    for (const entry of listener.cacheEntries) {
 | 
				
			||||||
 | 
					        core.info(`Entry: ${entry.entryName}
 | 
				
			||||||
 | 
					    Requested Key : ${entry.requestedKey ?? ''}
 | 
				
			||||||
 | 
					    Restored  Key : ${entry.restoredKey ?? ''}
 | 
				
			||||||
 | 
					              Size: ${formatSize(entry.restoredSize)}
 | 
				
			||||||
 | 
					    Saved     Key : ${entry.savedKey ?? ''}
 | 
				
			||||||
 | 
					              Size: ${formatSize(entry.savedSize)}`)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    core.endGroup()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getCount(
 | 
				
			||||||
 | 
					    cacheEntries: CacheEntryListener[],
 | 
				
			||||||
 | 
					    predicate: (value: CacheEntryListener) => number | undefined
 | 
				
			||||||
 | 
					): number {
 | 
				
			||||||
 | 
					    return cacheEntries.filter(e => predicate(e) !== undefined).length
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getSum(
 | 
				
			||||||
 | 
					    cacheEntries: CacheEntryListener[],
 | 
				
			||||||
 | 
					    predicate: (value: CacheEntryListener) => number | undefined
 | 
				
			||||||
 | 
					): string {
 | 
				
			||||||
 | 
					    return formatSize(cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function formatSize(bytes: number | undefined): string {
 | 
				
			||||||
 | 
					    if (bytes === undefined || bytes === 0) {
 | 
				
			||||||
 | 
					        return ''
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,7 @@ import * as crypto from 'crypto'
 | 
				
			|||||||
import * as path from 'path'
 | 
					import * as path from 'path'
 | 
				
			||||||
import * as fs from 'fs'
 | 
					import * as fs from 'fs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const JOB_CONTEXT_PARAMETER = 'workflow-job-context'
 | 
				
			||||||
const CACHE_DISABLED_PARAMETER = 'cache-disabled'
 | 
					const CACHE_DISABLED_PARAMETER = 'cache-disabled'
 | 
				
			||||||
const CACHE_READONLY_PARAMETER = 'cache-read-only'
 | 
					const CACHE_READONLY_PARAMETER = 'cache-read-only'
 | 
				
			||||||
const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'
 | 
					const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'
 | 
				
			||||||
@@ -26,6 +27,17 @@ export function getCacheKeyPrefix(): string {
 | 
				
			|||||||
    return process.env[CACHE_PREFIX_VAR] || ''
 | 
					    return process.env[CACHE_PREFIX_VAR] || ''
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function determineJobContext(): string {
 | 
				
			||||||
 | 
					    // By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation
 | 
				
			||||||
 | 
					    // The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml.
 | 
				
			||||||
 | 
					    const workflowJobContext = core.getInput(JOB_CONTEXT_PARAMETER)
 | 
				
			||||||
 | 
					    return hashStrings([workflowJobContext])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function hashFileNames(fileNames: string[]): string {
 | 
				
			||||||
 | 
					    return hashStrings(fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/')))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function hashStrings(values: string[]): string {
 | 
					export function hashStrings(values: string[]): string {
 | 
				
			||||||
    const hash = crypto.createHash('md5')
 | 
					    const hash = crypto.createHash('md5')
 | 
				
			||||||
    for (const value of values) {
 | 
					    for (const value of values) {
 | 
				
			||||||
@@ -34,10 +46,6 @@ 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'), '/')))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function handleCacheFailure(error: unknown, message: string): void {
 | 
					export function handleCacheFailure(error: unknown, message: string): void {
 | 
				
			||||||
    if (error instanceof cache.ValidationError) {
 | 
					    if (error instanceof cache.ValidationError) {
 | 
				
			||||||
        // Fail on cache validation errors
 | 
					        // Fail on cache validation errors
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,26 @@
 | 
				
			|||||||
 | 
					import * as core from '@actions/core'
 | 
				
			||||||
import {GradleUserHomeCache} from './cache-gradle-user-home'
 | 
					import {GradleUserHomeCache} from './cache-gradle-user-home'
 | 
				
			||||||
import {ProjectDotGradleCache} from './cache-project-dot-gradle'
 | 
					import {ProjectDotGradleCache} from './cache-project-dot-gradle'
 | 
				
			||||||
import * as core from '@actions/core'
 | 
					 | 
				
			||||||
import {isCacheDisabled, isCacheReadOnly} from './cache-utils'
 | 
					import {isCacheDisabled, isCacheReadOnly} from './cache-utils'
 | 
				
			||||||
import {CacheEntryListener, CacheListener} from './cache-base'
 | 
					import {logCachingReport, CacheListener} from './cache-reporting'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR'
 | 
					const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED'
 | 
				
			||||||
 | 
					const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
 | 
				
			||||||
const CACHE_LISTENER = 'CACHE_LISTENER'
 | 
					const CACHE_LISTENER = 'CACHE_LISTENER'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function restore(buildRootDirectory: string): Promise<void> {
 | 
					export async function restore(gradleUserHome: string): Promise<void> {
 | 
				
			||||||
    const gradleUserHomeCache = new GradleUserHomeCache(buildRootDirectory)
 | 
					    if (!shouldRestoreCaches()) {
 | 
				
			||||||
    const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    gradleUserHomeCache.init()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isCacheDisabled()) {
 | 
					 | 
				
			||||||
        core.info('Cache is disabled: will not restore state from previous builds.')
 | 
					 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const gradleUserHomeCache = new GradleUserHomeCache(gradleUserHome)
 | 
				
			||||||
 | 
					    gradleUserHomeCache.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const projectDotGradleCache = new ProjectDotGradleCache(gradleUserHome)
 | 
				
			||||||
 | 
					    projectDotGradleCache.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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(GRADLE_USER_HOME, gradleUserHome)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const cacheListener = new CacheListener()
 | 
					        const cacheListener = new CacheListener()
 | 
				
			||||||
        await gradleUserHomeCache.restore(cacheListener)
 | 
					        await gradleUserHomeCache.restore(cacheListener)
 | 
				
			||||||
@@ -38,6 +39,10 @@ export async function restore(buildRootDirectory: string): Promise<void> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function save(): Promise<void> {
 | 
					export async function save(): Promise<void> {
 | 
				
			||||||
 | 
					    if (!shouldSaveCaches()) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
 | 
					    const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (isCacheReadOnly()) {
 | 
					    if (isCacheReadOnly()) {
 | 
				
			||||||
@@ -47,56 +52,44 @@ 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 gradleUserHome = core.getState(GRADLE_USER_HOME)
 | 
				
			||||||
        return Promise.all([
 | 
					        return Promise.all([
 | 
				
			||||||
            new GradleUserHomeCache(buildRootDirectory).save(cacheListener),
 | 
					            new GradleUserHomeCache(gradleUserHome).save(cacheListener),
 | 
				
			||||||
            new ProjectDotGradleCache(buildRootDirectory).save(cacheListener)
 | 
					            new ProjectDotGradleCache(gradleUserHome).save(cacheListener)
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logCachingReport(cacheListener)
 | 
					    logCachingReport(cacheListener)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function logCachingReport(listener: CacheListener): void {
 | 
					function shouldRestoreCaches(): boolean {
 | 
				
			||||||
    if (listener.cacheEntries.length === 0) {
 | 
					    if (isCacheDisabled()) {
 | 
				
			||||||
        return
 | 
					        core.info('Cache is disabled: will not restore state from previous builds.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    core.info(`---------- Caching Summary -------------
 | 
					    if (process.env[CACHE_RESTORED_VAR]) {
 | 
				
			||||||
Restored Entries Count: ${getCount(listener.cacheEntries, e => e.restoredSize)}
 | 
					        core.info('Cache only restored on first action step.')
 | 
				
			||||||
                  Size: ${getSum(listener.cacheEntries, e => e.restoredSize)}
 | 
					        return false
 | 
				
			||||||
Saved Entries    Count: ${getCount(listener.cacheEntries, e => e.savedSize)}
 | 
					 | 
				
			||||||
                  Size: ${getSum(listener.cacheEntries, e => e.savedSize)}`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    core.startGroup('Cache Entry details')
 | 
					 | 
				
			||||||
    for (const entry of listener.cacheEntries) {
 | 
					 | 
				
			||||||
        core.info(`Entry: ${entry.entryName}
 | 
					 | 
				
			||||||
    Requested Key : ${entry.requestedKey ?? ''}
 | 
					 | 
				
			||||||
    Restored  Key : ${entry.restoredKey ?? ''}
 | 
					 | 
				
			||||||
              Size: ${formatSize(entry.restoredSize)}
 | 
					 | 
				
			||||||
    Saved     Key : ${entry.savedKey ?? ''}
 | 
					 | 
				
			||||||
              Size: ${formatSize(entry.savedSize)}`)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    core.endGroup()
 | 
					
 | 
				
			||||||
 | 
					    // Export var that is detected in all later restore steps
 | 
				
			||||||
 | 
					    core.exportVariable(CACHE_RESTORED_VAR, true)
 | 
				
			||||||
 | 
					    // Export state that is detected in corresponding post-action step
 | 
				
			||||||
 | 
					    core.saveState(CACHE_RESTORED_VAR, true)
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getCount(
 | 
					function shouldSaveCaches(): boolean {
 | 
				
			||||||
    cacheEntries: CacheEntryListener[],
 | 
					    if (isCacheDisabled()) {
 | 
				
			||||||
    predicate: (value: CacheEntryListener) => number | undefined
 | 
					        core.info('Cache is disabled: will not save state for later builds.')
 | 
				
			||||||
): number {
 | 
					        return false
 | 
				
			||||||
    return cacheEntries.filter(e => predicate(e) !== undefined).length
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getSum(
 | 
					 | 
				
			||||||
    cacheEntries: CacheEntryListener[],
 | 
					 | 
				
			||||||
    predicate: (value: CacheEntryListener) => number | undefined
 | 
					 | 
				
			||||||
): string {
 | 
					 | 
				
			||||||
    return formatSize(cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function formatSize(bytes: number | undefined): string {
 | 
					 | 
				
			||||||
    if (bytes === undefined || bytes === 0) {
 | 
					 | 
				
			||||||
        return ''
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)`
 | 
					
 | 
				
			||||||
 | 
					    if (!core.getState(CACHE_RESTORED_VAR)) {
 | 
				
			||||||
 | 
					        core.info('Cache will only be saved in final post-action step.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/main.ts
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
				
			|||||||
import * as core from '@actions/core'
 | 
					import * as core from '@actions/core'
 | 
				
			||||||
import * as path from 'path'
 | 
					import * as path from 'path'
 | 
				
			||||||
 | 
					import * as os from 'os'
 | 
				
			||||||
import {parseArgsStringToArgv} from 'string-argv'
 | 
					import {parseArgsStringToArgv} from 'string-argv'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as caches from './caches'
 | 
					import * as caches from './caches'
 | 
				
			||||||
@@ -14,8 +15,9 @@ export async function run(): Promise<void> {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const workspaceDirectory = process.env[`GITHUB_WORKSPACE`] || ''
 | 
					        const workspaceDirectory = process.env[`GITHUB_WORKSPACE`] || ''
 | 
				
			||||||
        const buildRootDirectory = resolveBuildRootDirectory(workspaceDirectory)
 | 
					        const buildRootDirectory = resolveBuildRootDirectory(workspaceDirectory)
 | 
				
			||||||
 | 
					        const gradleUserHome = determineGradleUserHome(buildRootDirectory)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await caches.restore(buildRootDirectory)
 | 
					        await caches.restore(gradleUserHome)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const args: string[] = parseCommandLineArguments()
 | 
					        const args: string[] = parseCommandLineArguments()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,6 +65,15 @@ function resolveBuildRootDirectory(baseDirectory: string): string {
 | 
				
			|||||||
    return resolvedBuildRootDirectory
 | 
					    return resolvedBuildRootDirectory
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function determineGradleUserHome(rootDir: string): string {
 | 
				
			||||||
 | 
					    const customGradleUserHome = process.env['GRADLE_USER_HOME']
 | 
				
			||||||
 | 
					    if (customGradleUserHome) {
 | 
				
			||||||
 | 
					        return path.resolve(rootDir, customGradleUserHome)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return path.resolve(os.homedir(), '.gradle')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function parseCommandLineArguments(): string[] {
 | 
					function parseCommandLineArguments(): string[] {
 | 
				
			||||||
    const input = core.getInput('arguments')
 | 
					    const input = core.getInput('arguments')
 | 
				
			||||||
    return parseArgsStringToArgv(input)
 | 
					    return parseArgsStringToArgv(input)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user