From c211be411ec5239607533cda7f755a2ccc42f0e5 Mon Sep 17 00:00:00 2001 From: Daz DeBoer Date: Sun, 22 Aug 2021 20:14:47 -0600 Subject: [PATCH] Use monolithic cache for Gradle User Home - Do not restore cache when GUH exists - Include RUNNER_OS in the cache key - Do not save cache on exact hit - Only save cache in the final post action - Log before saving cache --- action.yml | 4 + package-lock.json | 139 +++++++++++++++++++++++++++++++++- package.json | 1 + src/cache-gradle-user-home.ts | 101 ++++++++++++++++++++++++ src/execution.ts | 5 -- src/main.ts | 11 +-- src/post.ts | 8 +- 7 files changed, 247 insertions(+), 22 deletions(-) create mode 100644 src/cache-gradle-user-home.ts diff --git a/action.yml b/action.yml index aa03d39..548e89e 100644 --- a/action.yml +++ b/action.yml @@ -24,6 +24,10 @@ inputs: description: Whether caching downloaded Gradle distributions is enabled or not, default to 'true' required: false default: true + gradle-user-home-cache-enabled: + description: Whether caching of state in Gradle User Home is enabled, default to 'true' + required: false + default: true wrapper-cache-enabled: description: Whether caching wrapper installation is enabled or not, default to 'true' required: false diff --git a/package-lock.json b/package-lock.json index 0285aaa..ef283cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,17 @@ "@actions/io": "^1.0.1" } }, + "@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "requires": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, "@actions/glob": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz", @@ -1094,6 +1105,115 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", + "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, + "@octokit/graphql": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.4.tgz", + "integrity": "sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-9.7.0.tgz", + "integrity": "sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.1.tgz", + "integrity": "sha512-47r52KkhQDkmvUKZqXzA1lKvcyJEfYh3TKAIe5+EzMeyDM3d+/s5v11i2gTk8/n6No6DPi3k5Ind6wtDbo/AEg==", + "requires": { + "@octokit/types": "^6.24.0" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.8.0.tgz", + "integrity": "sha512-qeLZZLotNkoq+it6F+xahydkkbnvSK0iDjlXFo3jNTB+Ss0qIbYQb9V/soKLMkgGw8Q2sHjY5YEXiA47IVPp4A==", + "requires": { + "@octokit/types": "^6.25.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", + "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.25.0.tgz", + "integrity": "sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q==", + "requires": { + "@octokit/openapi-types": "^9.5.0" + } + }, "@opencensus/web-types": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz", @@ -1807,6 +1927,11 @@ "tweetnacl": "^0.14.3" } }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2202,6 +2327,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5128,7 +5258,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -6554,6 +6683,11 @@ "set-value": "^2.0.1" } }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6778,8 +6912,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index 384db67..3ecb2ed 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@actions/cache": "1.0.7", "@actions/core": "1.4.0", "@actions/exec": "1.1.0", + "@actions/github": "5.0.0", "@actions/glob": "0.2.0", "@actions/http-client": "1.0.11", "@actions/tool-cache": "1.7.1", diff --git a/src/cache-gradle-user-home.ts b/src/cache-gradle-user-home.ts new file mode 100644 index 0000000..d2381fd --- /dev/null +++ b/src/cache-gradle-user-home.ts @@ -0,0 +1,101 @@ +import path from 'path' +import fs from 'fs' +import os from 'os' + +import * as core from '@actions/core' +import * as cache from '@actions/cache' +import * as github from '@actions/github' +import {truncateArgs} from './cache-utils' + +const CACHE_PATH = [ + '~/.gradle/caches/*', // All directories in 'caches' + '~/.gradle/notifications/*', // Prevent the re-rendering of first-use message for version + '~/.gradle/wrapper/dists/*/*/*.zip' // Only wrapper zips are required : Gradle will expand these on demand +] +const GUH_CACHE_KEY = 'GUH_CACHE_KEY' +const GUH_CACHE_RESULT = 'GUH_CACHE_RESULT' + +export async function restore(): Promise { + if (!isGradleUserHomeCacheEnabled()) return + + if (gradleUserHomeExists()) { + core.debug('Gradle User Home already exists. Not restoring from cache.') + return + } + + const runnerOs = process.env[`RUNNER_OS`] || '' + const cacheKeyPrefix = `${runnerOs}-gradle|` + + const args = truncateArgs(core.getInput('arguments')) + const cacheKeyWithArgs = `${cacheKeyPrefix}${args}|` + + const cacheKey = `${cacheKeyWithArgs}${github.context.sha}` + + core.saveState(GUH_CACHE_KEY, cacheKey) + + const cacheResult = await cache.restoreCache(CACHE_PATH, cacheKey, [ + cacheKeyWithArgs, + cacheKeyPrefix + ]) + + if (!cacheResult) { + core.info( + 'Gradle User Home cache not found. Will start with empty home.' + ) + return + } + + core.info(`Gradle User Home restored from cache key: ${cacheResult}`) + return +} + +export async function save(): Promise { + if (!isGradleUserHomeCacheEnabled()) return + + if (!gradleUserHomeExists()) { + core.debug('No Gradle User Home to cache.') + return + } + + const cacheKey = core.getState(GUH_CACHE_KEY) + const cacheResult = core.getState(GUH_CACHE_RESULT) + + if (!cacheKey) { + core.info( + 'Gradle User Home existed prior to cache restore. Not saving.' + ) + return + } + + if (cacheResult && cacheKey === cacheResult) { + core.info( + `Cache hit occurred on the cache key ${cacheKey}, not saving cache.` + ) + return + } + + core.info(`Caching Gradle User Home with cache key: ${cacheKey}`) + try { + await cache.saveCache(CACHE_PATH, 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 +} + +export function isGradleUserHomeCacheEnabled(): boolean { + return core.getBooleanInput('gradle-user-home-cache-enabled') +} + +function gradleUserHomeExists(): boolean { + // Need to check for 'caches' directory to avoid incorrect detection on MacOS agents + const dir = path.resolve(os.homedir(), '.gradle/caches') + return fs.existsSync(dir) +} diff --git a/src/execution.ts b/src/execution.ts index e6570a1..8e3b59b 100644 --- a/src/execution.ts +++ b/src/execution.ts @@ -1,15 +1,10 @@ import * as exec from '@actions/exec' -import * as cacheDependencies from './cache-dependencies' -import * as cacheConfiguration from './cache-configuration' export async function execute( executable: string, root: string, argv: string[] ): Promise { - await cacheDependencies.restoreCachedDependencies(root) - await cacheConfiguration.restoreCachedConfiguration(root) - let publishing = false let buildScanUrl: string | undefined diff --git a/src/main.ts b/src/main.ts index a62b5ec..b39ca59 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,13 +2,15 @@ import * as core from '@actions/core' import * as path from 'path' import {parseArgsStringToArgv} from 'string-argv' -import * as cacheWrapper from './cache-wrapper' +import * as cacheGradleUserHome from './cache-gradle-user-home' import * as execution from './execution' import * as gradlew from './gradlew' import * as provision from './provision' // Invoked by GitHub Actions export async function run(): Promise { + await cacheGradleUserHome.restore() + try { const workspaceDirectory = process.env[`GITHUB_WORKSPACE`] || '' const buildRootDirectory = resolveBuildRootDirectory(workspaceDirectory) @@ -47,11 +49,6 @@ async function resolveGradleExecutable( const gradleExecutable = core.getInput('gradle-executable') if (gradleExecutable !== '') { - if (gradleExecutable.endsWith(gradlew.wrapperFilename())) { - await cacheWrapper.restoreCachedWrapperDist( - path.resolve(gradleExecutable, '..') - ) - } return path.resolve(workspaceDirectory, gradleExecutable) } @@ -62,8 +59,6 @@ async function resolveGradleExecutable( : buildRootDirectory gradlew.validateGradleWrapper(gradlewDirectory) - await cacheWrapper.restoreCachedWrapperDist(gradlewDirectory) - return path.resolve(gradlewDirectory, gradlew.wrapperFilename()) } diff --git a/src/post.ts b/src/post.ts index d119ceb..31476cc 100644 --- a/src/post.ts +++ b/src/post.ts @@ -1,16 +1,12 @@ import * as core from '@actions/core' -import * as cacheWrapper from './cache-wrapper' -import * as cacheDependencies from './cache-dependencies' -import * as cacheConfiguration from './cache-configuration' +import * as cacheGradleUserHome from './cache-gradle-user-home' // Invoked by GitHub Actions export async function run(): Promise { if (isCacheReadOnly()) return - await cacheWrapper.cacheWrapperDist() - await cacheDependencies.cacheDependencies() - await cacheConfiguration.cacheConfiguration() + await cacheGradleUserHome.save() } function isCacheReadOnly(): boolean {