From 7a423049e54308c3d61b2468db3c543d239549d7 Mon Sep 17 00:00:00 2001 From: Andrew Marshall Date: Sat, 28 Dec 2024 20:02:54 +0000 Subject: [PATCH] make evict-by-age parameter more general --- .github/workflows/tests.yml | 2 +- __tests__/common.test.ts | 20 +++++++++++++++++++ __tests__/save.test.ts | 13 ++++++++++-- action.yml | 7 ++++--- dist/restore/index.js | 21 ++++++++++++++++++- dist/save/index.js | 40 +++++++++++++++++++++++++++++++------ src/common.ts | 20 +++++++++++++++++++ src/restore.ts | 2 +- src/save.ts | 22 ++++++++++++++------ 9 files changed, 127 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a675f748..d2155dd1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -245,7 +245,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - evict: [true, false] + evict: ['job', '30s', ''] steps: - uses: actions/checkout@v4 - name: Run ccache-action diff --git a/__tests__/common.test.ts b/__tests__/common.test.ts index 24620569..06a6047d 100644 --- a/__tests__/common.test.ts +++ b/__tests__/common.test.ts @@ -2,6 +2,26 @@ import * as common from '../src/common'; import * as core from "@actions/core"; describe('ccache common', () => { + test('parse evict age parameter in seconds', () => { + const age = '42s'; + const [time, unit] = common.parseEvictAgeParameter(age); + expect(time).toEqual(42); + expect(unit).toEqual(common.AgeUnit.Seconds); + }); + + test('parse evict age parameter in days', () => { + const age = '28d'; + const [time, unit] = common.parseEvictAgeParameter(age); + expect(time).toEqual(28); + expect(unit).toEqual(common.AgeUnit.Days); + }); + + test('parse evict age parameter - job', () => { + const age = 'job'; + const [, unit] = common.parseEvictAgeParameter(age); + expect(unit).toEqual(common.AgeUnit.Job); + }); + test('get duration of job in seconds', () => { const stateMock = jest.spyOn(core, "getState"); const expectedAgeInSeconds = 1234; diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index 9522f0e6..8551a91a 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -1,14 +1,23 @@ +import {AgeUnit} from "../src/common"; import * as save from '../src/save'; import * as exec from "@actions/exec"; jest.mock("@actions/exec"); describe('ccache save', () => { - test('evict old files from the cache', async () => { + test('evict old files from the cache by age in seconds', async () => { const proc = jest.spyOn(exec, "exec"); const ageInSeconds = 42; - await save.evictOldFiles(ageInSeconds); + await save.evictOldFiles(ageInSeconds, AgeUnit.Seconds); expect(proc).toHaveBeenCalledWith(`ccache --evict-older-than ${ageInSeconds}s`); }); + + test('evict old files from the cache by age in days', async () => { + const proc = jest.spyOn(exec, "exec"); + + const ageInDays = 3; + await save.evictOldFiles(ageInDays, AgeUnit.Days); + expect(proc).toHaveBeenCalledWith(`ccache --evict-older-than ${ageInDays}d`); + }); }); diff --git a/action.yml b/action.yml index 3fd87629..8a9c65f2 100644 --- a/action.yml +++ b/action.yml @@ -37,9 +37,10 @@ inputs: empty string to disable this feature. Requires CCache 4.10+" default: 'CCache Statistics' evict-old-files: - description: "Evict any files not used in the current job run from the cache. Corresponds to the ccache - --evict-older-than AGE option, where AGE is the number of seconds since the job started." - default: true + description: "Corresponds to the ccache --evict-older-than AGE option, where AGE is the number of seconds or days + followed by the 's' or 'd' suffix respectively. Also supports the special value 'job' which represents the time + since the job started, which evicts all cache files that were not touched during the job run." + default: '' runs: using: "node20" main: "dist/restore/index.js" diff --git a/dist/restore/index.js b/dist/restore/index.js index 0597622d..bd3412db 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -58831,10 +58831,29 @@ var cache = __nccwpck_require__(5116); ;// CONCATENATED MODULE: ./src/common.ts +var AgeUnit; +(function (AgeUnit) { + AgeUnit["Seconds"] = "s"; + AgeUnit["Days"] = "d"; + AgeUnit["Job"] = "job"; +})(AgeUnit || (AgeUnit = {})); function getJobDurationInSeconds() { const startTime = Number.parseInt(core.getState("startTimestamp")); return Math.floor((Date.now() - startTime) * 0.001); } +function parseEvictAgeParameter(age) { + const expr = /([0-9]+)([sd])|job/; + const result = age.match(expr); + if (result) { + if (result[0] !== "job") { + return [Number.parseInt(result[1]), result[2]]; + } + else { + return [null, AgeUnit.Job]; + } + } + throw new Error(`age parameter ${age} was not valid`); +} /** * Parse the output of ccache --version to extract the semantic version components * @param ccacheOutput @@ -59030,7 +59049,7 @@ async function runInner() { const ccacheVariant = lib_core.getInput("variant"); lib_core.saveState("startTimestamp", Date.now()); lib_core.saveState("ccacheVariant", ccacheVariant); - lib_core.saveState("evictOldFiles", lib_core.getBooleanInput("evict-old-files")); + lib_core.saveState("evictOldFiles", lib_core.getInput("evict-old-files")); lib_core.saveState("shouldSave", lib_core.getBooleanInput("save")); lib_core.saveState("appendTimestamp", lib_core.getBooleanInput("append-timestamp")); let ccachePath = await io.which(ccacheVariant); diff --git a/dist/save/index.js b/dist/save/index.js index d49450f5..97b3ee9a 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -58819,10 +58819,29 @@ var external_path_default = /*#__PURE__*/__nccwpck_require__.n(external_path_); ;// CONCATENATED MODULE: ./src/common.ts +var AgeUnit; +(function (AgeUnit) { + AgeUnit["Seconds"] = "s"; + AgeUnit["Days"] = "d"; + AgeUnit["Job"] = "job"; +})(AgeUnit || (AgeUnit = {})); function getJobDurationInSeconds() { const startTime = Number.parseInt(core.getState("startTimestamp")); return Math.floor((Date.now() - startTime) * 0.001); } +function parseEvictAgeParameter(age) { + const expr = /([0-9]+)([sd])|job/; + const result = age.match(expr); + if (result) { + if (result[0] !== "job") { + return [Number.parseInt(result[1]), result[2]]; + } + else { + return [null, AgeUnit.Job]; + } + } + throw new Error(`age parameter ${age} was not valid`); +} /** * Parse the output of ccache --version to extract the semantic version components * @param ccacheOutput @@ -58869,6 +58888,7 @@ function cacheDir(ccacheVariant) { + async function ccacheIsEmpty(ccacheVariant, ccacheKnowsVerbosityFlag) { if (ccacheVariant === "ccache") { if (ccacheKnowsVerbosityFlag) { @@ -58913,9 +58933,9 @@ async function hasJsonStats(ccacheVariant) { const version = parseCCacheVersion(result.stdout); return version != null && version[0] >= 4 && version[1] >= 10; } -async function evictOldFiles(seconds) { +async function evictOldFiles(age, unit) { try { - await exec.exec(`ccache --evict-older-than ${seconds}s`); + await exec.exec(`ccache --evict-older-than ${age}${unit}`); } catch (error) { core.warning(`Error occurred evicting old cache files: ${error}`); @@ -58953,10 +58973,18 @@ async function run(earlyExit) { core.info("Not saving cache because 'save' is set to 'false'."); return; } - if (core.getState("evictOldFiles") === "true" && ccacheVariant === "ccache") { - const jobDuration = getJobDurationInSeconds(); - core.debug(`Evicting cache files older than ${jobDuration} seconds`); - await evictOldFiles(jobDuration); + const evictByAge = core.getState("evictOldFiles"); + if (evictByAge && ccacheVariant === "ccache") { + const [time, unit] = parseEvictAgeParameter(evictByAge); + if (unit === AgeUnit.Job) { + const duration = getJobDurationInSeconds(); + core.debug(`Evicting cache files older than ${duration} seconds`); + await evictOldFiles(duration, AgeUnit.Seconds); + } + else { + core.debug(`Evicting cache files older than ${time}${unit}`); + await evictOldFiles(time, unit); + } } if (await ccacheIsEmpty(ccacheVariant, ccacheKnowsVerbosityFlag)) { core.info("Not saving cache because no objects are cached."); diff --git a/src/common.ts b/src/common.ts index 3a0a92b2..95993b99 100644 --- a/src/common.ts +++ b/src/common.ts @@ -4,11 +4,31 @@ import * as core from "@actions/core"; type Version = [number,number,number]; +export enum AgeUnit { + Seconds = "s", + Days = "d", + Job = "job" +} + export function getJobDurationInSeconds() : number { const startTime = Number.parseInt(core.getState("startTimestamp")); return Math.floor((Date.now() - startTime) * 0.001); } +export function parseEvictAgeParameter(age: string): [number | null, AgeUnit] { + const expr = /([0-9]+)([sd])|job/ + const result = age.match(expr); + if (result) { + if (result[0] !== "job") { + return [Number.parseInt(result[1]), result[2] as AgeUnit]; + } else { + return [null, AgeUnit.Job]; + } + } + + throw new Error(`age parameter ${age} was not valid`); +} + /** * Parse the output of ccache --version to extract the semantic version components * @param ccacheOutput diff --git a/src/restore.ts b/src/restore.ts index e8482887..a35e981a 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -184,7 +184,7 @@ async function runInner() : Promise { const ccacheVariant = core.getInput("variant"); core.saveState("startTimestamp", Date.now()); core.saveState("ccacheVariant", ccacheVariant); - core.saveState("evictOldFiles", core.getBooleanInput("evict-old-files")); + core.saveState("evictOldFiles", core.getInput("evict-old-files")); core.saveState("shouldSave", core.getBooleanInput("save")); core.saveState("appendTimestamp", core.getBooleanInput("append-timestamp")); let ccachePath = await io.which(ccacheVariant); diff --git a/src/save.ts b/src/save.ts index 444edf33..82e604e6 100644 --- a/src/save.ts +++ b/src/save.ts @@ -2,6 +2,8 @@ import * as core from "@actions/core"; import * as cache from "@actions/cache"; import * as exec from "@actions/exec"; import * as common from "./common"; +import {AgeUnit} from "./common"; + async function ccacheIsEmpty(ccacheVariant : string, ccacheKnowsVerbosityFlag : boolean) : Promise { if (ccacheVariant === "ccache") { @@ -53,9 +55,9 @@ async function hasJsonStats(ccacheVariant: string) : Promise { return version != null && version[0] >= 4 && version[1] >= 10; } -export async function evictOldFiles(seconds : number) : Promise { +export async function evictOldFiles(age : number, unit : common.AgeUnit) : Promise { try { - await exec.exec(`ccache --evict-older-than ${seconds}s`); + await exec.exec(`ccache --evict-older-than ${age}${unit}`); } catch (error) { core.warning(`Error occurred evicting old cache files: ${error}`); @@ -100,10 +102,18 @@ async function run(earlyExit : boolean | undefined) : Promise { return; } - if (core.getState("evictOldFiles") === "true" && ccacheVariant === "ccache") { - const jobDuration = common.getJobDurationInSeconds(); - core.debug(`Evicting cache files older than ${jobDuration} seconds`); - await evictOldFiles(jobDuration); + const evictByAge = core.getState("evictOldFiles"); + if (evictByAge && ccacheVariant === "ccache") { + const [time, unit] = common.parseEvictAgeParameter(evictByAge) + if (unit === AgeUnit.Job) { + const duration = common.getJobDurationInSeconds(); + core.debug(`Evicting cache files older than ${duration} seconds`); + await evictOldFiles(duration, common.AgeUnit.Seconds); + } + else { + core.debug(`Evicting cache files older than ${time}${unit}`); + await evictOldFiles(time as number, unit); + } } if (await ccacheIsEmpty(ccacheVariant, ccacheKnowsVerbosityFlag)) {