mirror of
https://github.com/Swatinem/rust-cache
synced 2026-06-24 09:10:31 +00:00
Add source-keyed target cache
This commit is contained in:
parent
c106961fee
commit
c020f6afef
12 changed files with 420 additions and 141 deletions
29
.github/workflows/target-key.yml
vendored
Normal file
29
.github/workflows/target-key.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: target-key
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
target-key:
|
||||
if: github.repository == 'Swatinem/rust-cache'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
|
||||
- uses: ./
|
||||
with:
|
||||
workspaces: tests
|
||||
cache-workspace-crates: "true"
|
||||
target-key: ${{ hashFiles('tests/**/*.rs', 'tests/Cargo.toml', 'tests/Cargo.lock') }}
|
||||
|
||||
- run: cargo build --release
|
||||
working-directory: tests
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add `target-key` for opt-in source-keyed workspace target caching.
|
||||
|
||||
## 2.9.1
|
||||
|
||||
- Fix regression in hash calculation
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -76,6 +76,14 @@ sensible defaults.
|
|||
# default: "false"
|
||||
cache-workspace-crates: ""
|
||||
|
||||
# An additional key for source-keyed target caching.
|
||||
# When set together with `cache-targets` and `cache-workspace-crates`,
|
||||
# workspace target directories are cached separately from CARGO_HOME using
|
||||
# this key. This allows rebuilt workspace artifacts to be saved under a new
|
||||
# source key while preserving the normal dependency cache behavior.
|
||||
# default: empty
|
||||
target-key: ""
|
||||
|
||||
# Determines whether the cache should be saved.
|
||||
# If `false`, the cache is only restored.
|
||||
# Useful for jobs where the matrix is additive e.g. additional Cargo features,
|
||||
|
|
@ -173,6 +181,14 @@ to recreate it from the compressed crate archives in `~/.cargo/registry/cache`.
|
|||
The action will try to restore from a previous `Cargo.lock` version as well, so
|
||||
lockfile updates should only re-build changed dependencies.
|
||||
|
||||
When `target-key` is set together with `cache-targets` and
|
||||
`cache-workspace-crates`, workspace target directories are restored after the
|
||||
normal CARGO_HOME cache and saved as a separate source-keyed cache. This is useful
|
||||
for workflows that deliberately cache workspace crates and can provide a stable
|
||||
source fingerprint, for example `${{ hashFiles('src/**', 'Cargo.toml') }}`.
|
||||
The target cache still restores from older target caches via restore prefixes,
|
||||
but exact source matches can become Cargo no-ops across repeated runs.
|
||||
|
||||
The action invokes `cargo metadata` to determine the current set of dependencies.
|
||||
|
||||
Additionally, the action automatically works around
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ inputs:
|
|||
description: "Similar to cache-all-crates. If `true` the workspace crates will be cached."
|
||||
required: false
|
||||
default: "false"
|
||||
target-key:
|
||||
description: "An additional key for source-keyed target caching. When set together with cache-targets and cache-workspace-crates, workspace target directories are cached separately using this key."
|
||||
required: false
|
||||
save-if:
|
||||
description: "Determiners whether the cache should be saved. If `false`, the cache is only restored."
|
||||
required: false
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { f as debug, m as getDefaultExportFromCjs, n as mkdirP, l as exec, w as which, o as warning, i as info, H as HttpCodes, p as HttpClientError, q as HttpClient, t as isDebug, u as setSecret, B as BearerCredentialHandler, e as error } from './cleanup-ChNUL7jL.js';
|
||||
import { f as debug, m as getDefaultExportFromCjs, n as mkdirP, l as exec, w as which, o as warning, i as info, H as HttpCodes, p as HttpClientError, q as HttpClient, t as isDebug, u as setSecret, B as BearerCredentialHandler, e as error } from './cleanup-ctNqmXyy.js';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { writeFileSync, existsSync } from 'fs';
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { v as commonjsGlobal, x as requireTunnel, m as getDefaultExportFromCjs, y as getAugmentedNamespace } from './cleanup-ChNUL7jL.js';
|
||||
import { v as commonjsGlobal, x as requireTunnel, m as getDefaultExportFromCjs, y as getAugmentedNamespace } from './cleanup-ctNqmXyy.js';
|
||||
import os__default from 'os';
|
||||
import crypto__default from 'crypto';
|
||||
import fs__default from 'fs';
|
||||
|
|
@ -34120,10 +34120,10 @@ async function getCacheProvider() {
|
|||
let cache;
|
||||
switch (cacheProvider) {
|
||||
case "github":
|
||||
cache = await import('./cache-Cb-Up9r2.js');
|
||||
cache = await import('./cache-BSoAyDaq.js');
|
||||
break;
|
||||
case "warpbuild":
|
||||
cache = await import('./cache-1jS6aShy.js').then(function (n) { return n.c; });
|
||||
cache = await import('./cache-D5WyUDMY.js').then(function (n) { return n.c; });
|
||||
break;
|
||||
default:
|
||||
throw new Error(`The \`cache-provider\` \`${cacheProvider}\` is not valid.`);
|
||||
|
|
@ -34192,6 +34192,18 @@ class CacheConfig {
|
|||
cacheKey = "";
|
||||
/** The secondary (restore) key that only contains the prefix and environment */
|
||||
restoreKey = "";
|
||||
/** Whether the primary cache needs saving in the post action */
|
||||
cacheNeedsSave = true;
|
||||
/** Workspace target paths cached separately when `target-key` is used */
|
||||
targetCachePaths = [];
|
||||
/** The source-keyed workspace target cache key */
|
||||
targetCacheKey = "";
|
||||
/** The source-keyed workspace target restore keys */
|
||||
targetRestoreKeys = [];
|
||||
/** Whether workspace targets are cached separately from CARGO_HOME */
|
||||
targetCacheEnabled = false;
|
||||
/** Whether the workspace target cache needs saving in the post action */
|
||||
targetCacheNeedsSave = false;
|
||||
/** Whether to cache CARGO_HOME/.bin */
|
||||
cacheBin = true;
|
||||
/** The workspace configurations */
|
||||
|
|
@ -34387,22 +34399,41 @@ class CacheConfig {
|
|||
let lockHash = digest(hasher);
|
||||
key += `-${lockHash}`;
|
||||
}
|
||||
self.cacheKey = key;
|
||||
self.cachePaths = [path__default.join(CARGO_HOME, "registry"), path__default.join(CARGO_HOME, "git")];
|
||||
const baseCacheKey = key;
|
||||
const baseRestoreKey = self.restoreKey;
|
||||
const cargoCachePaths = [path__default.join(CARGO_HOME, "registry"), path__default.join(CARGO_HOME, "git")];
|
||||
if (self.cacheBin) {
|
||||
self.cachePaths = [
|
||||
path__default.join(CARGO_HOME, "bin"),
|
||||
path__default.join(CARGO_HOME, ".crates.toml"),
|
||||
path__default.join(CARGO_HOME, ".crates2.json"),
|
||||
...self.cachePaths,
|
||||
];
|
||||
cargoCachePaths.unshift(path__default.join(CARGO_HOME, "bin"), path__default.join(CARGO_HOME, ".crates.toml"), path__default.join(CARGO_HOME, ".crates2.json"));
|
||||
}
|
||||
const cacheTargets = getInput("cache-targets").toLowerCase() || "true";
|
||||
if (cacheTargets === "true") {
|
||||
self.cachePaths.push(...workspaces.map((ws) => ws.target));
|
||||
const targetCachePaths = cacheTargets === "true" ? workspaces.map((ws) => ws.target) : [];
|
||||
const cacheDirectories = getInput("cache-directories").trim().split(/\s+/).filter(Boolean);
|
||||
const targetKey = getInput("target-key");
|
||||
const workspaceCrates = getInput("cache-workspace-crates").toLowerCase() || "false";
|
||||
if (targetKey && cacheTargets !== "true") {
|
||||
warning("`target-key` is ignored because `cache-targets` is not `true`.");
|
||||
}
|
||||
const cacheDirectories = getInput("cache-directories");
|
||||
for (const dir of cacheDirectories.trim().split(/\s+/).filter(Boolean)) {
|
||||
if (targetKey && workspaceCrates !== "true") {
|
||||
warning("`target-key` is ignored because `cache-workspace-crates` is not `true`.");
|
||||
}
|
||||
self.targetCacheEnabled = Boolean(targetKey) && cacheTargets === "true" && workspaceCrates === "true";
|
||||
if (self.targetCacheEnabled) {
|
||||
self.cacheKey = baseCacheKey;
|
||||
self.cachePaths = [...cargoCachePaths, ...cacheDirectories];
|
||||
const targetKeyPrefix = `${baseRestoreKey}-target`;
|
||||
const targetKeyEnvironment = baseCacheKey.slice(baseRestoreKey.length);
|
||||
self.targetCachePaths = targetCachePaths;
|
||||
self.targetCacheKey = `${targetKeyPrefix}${targetKeyEnvironment}-${targetKey}`;
|
||||
self.targetRestoreKeys = uniqInOrder([`${targetKeyPrefix}${targetKeyEnvironment}-`, `${targetKeyPrefix}-`]);
|
||||
}
|
||||
else {
|
||||
self.cacheKey = baseCacheKey;
|
||||
self.cachePaths = [...cargoCachePaths];
|
||||
}
|
||||
if (!self.targetCacheEnabled && cacheTargets === "true") {
|
||||
self.cachePaths.push(...targetCachePaths);
|
||||
}
|
||||
for (const dir of self.targetCacheEnabled ? [] : cacheDirectories) {
|
||||
self.cachePaths.push(dir);
|
||||
}
|
||||
const bins = await getCargoBins();
|
||||
|
|
@ -34438,14 +34469,26 @@ class CacheConfig {
|
|||
for (const workspace of this.workspaces) {
|
||||
info(` ${workspace.root}`);
|
||||
}
|
||||
info(`Cache Paths:`);
|
||||
info(`${this.targetCacheEnabled ? "Cargo Cache" : "Cache"} Paths:`);
|
||||
for (const path of this.cachePaths) {
|
||||
info(` ${path}`);
|
||||
}
|
||||
info(`Restore Key:`);
|
||||
info(`${this.targetCacheEnabled ? "Cargo Restore" : "Restore"} Key:`);
|
||||
info(` ${this.restoreKey}`);
|
||||
info(`Cache Key:`);
|
||||
info(`${this.targetCacheEnabled ? "Cargo Cache" : "Cache"} Key:`);
|
||||
info(` ${this.cacheKey}`);
|
||||
if (this.targetCacheEnabled) {
|
||||
info(`Target Cache Paths:`);
|
||||
for (const path of this.targetCachePaths) {
|
||||
info(` ${path}`);
|
||||
}
|
||||
info(`Target Restore Keys:`);
|
||||
for (const key of this.targetRestoreKeys) {
|
||||
info(` ${key}`);
|
||||
}
|
||||
info(`Target Cache Key:`);
|
||||
info(` ${this.targetCacheKey}`);
|
||||
}
|
||||
info(`.. Prefix:`);
|
||||
info(` - ${this.keyPrefix}`);
|
||||
info(`.. Environment considered:`);
|
||||
|
|
@ -34563,6 +34606,9 @@ function sort_and_uniq(a) {
|
|||
return accumulator;
|
||||
}, []);
|
||||
}
|
||||
function uniqInOrder(a) {
|
||||
return a.filter((value, index) => a.indexOf(value) === index);
|
||||
}
|
||||
|
||||
async function cleanTargetDir(targetDir, packages, checkTimestamp = false) {
|
||||
debug(`cleaning target directory "${targetDir}"`);
|
||||
77
dist/restore.js
vendored
77
dist/restore.js
vendored
|
|
@ -1,4 +1,4 @@
|
|||
import { e as error, g as getCacheProvider, a as getInput, b as exportVariable, C as CacheConfig, i as info, c as cleanTargetDir, r as reportError, s as setOutput } from './cleanup-ChNUL7jL.js';
|
||||
import { e as error, g as getCacheProvider, a as getInput, b as exportVariable, C as CacheConfig, i as info, r as reportError, s as setOutput, c as cleanTargetDir } from './cleanup-ctNqmXyy.js';
|
||||
import 'os';
|
||||
import 'crypto';
|
||||
import 'fs';
|
||||
|
|
@ -47,44 +47,43 @@ async function run() {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
var cacheOnFailure = getInput("cache-on-failure").toLowerCase();
|
||||
let cacheOnFailure = getInput("cache-on-failure").toLowerCase();
|
||||
if (cacheOnFailure !== "true") {
|
||||
cacheOnFailure = "false";
|
||||
}
|
||||
var lookupOnly = getInput("lookup-only").toLowerCase() === "true";
|
||||
const lookupOnly = getInput("lookup-only").toLowerCase() === "true";
|
||||
exportVariable("CACHE_ON_FAILURE", cacheOnFailure);
|
||||
exportVariable("CARGO_INCREMENTAL", 0);
|
||||
const config = await CacheConfig.new();
|
||||
config.printInfo(cacheProvider);
|
||||
info("");
|
||||
info(`... ${lookupOnly ? "Checking" : "Restoring"} cache ...`);
|
||||
const key = config.cacheKey;
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
const restoreKey = await cacheProvider.cache.restoreCache(config.cachePaths.slice(), key, [config.restoreKey], {
|
||||
lookupOnly,
|
||||
});
|
||||
if (restoreKey) {
|
||||
const match = restoreKey.localeCompare(key, undefined, {
|
||||
sensitivity: "accent",
|
||||
}) === 0;
|
||||
info(`${lookupOnly ? "Found" : "Restored from"} cache key "${restoreKey}" full match: ${match}.`);
|
||||
if (!match) {
|
||||
// pre-clean the target directory on cache mismatch
|
||||
for (const workspace of config.workspaces) {
|
||||
try {
|
||||
await cleanTargetDir(workspace.target, [], true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
// We restored the cache but it is not a full match.
|
||||
const cacheResult = await restoreCache(cacheProvider, config.cachePaths, config.cacheKey, [config.restoreKey], lookupOnly);
|
||||
config.cacheNeedsSave = !cacheResult.match;
|
||||
if (config.targetCacheEnabled) {
|
||||
if (cacheResult.found && !cacheResult.match) {
|
||||
// pre-clean the target directory on cargo cache mismatch before restoring target cache
|
||||
await cleanTargets(config);
|
||||
}
|
||||
const targetResult = await restoreCache(cacheProvider, config.targetCachePaths, config.targetCacheKey, config.targetRestoreKeys, lookupOnly, "target");
|
||||
config.targetCacheNeedsSave = !targetResult.match;
|
||||
if (targetResult.found && !targetResult.match) {
|
||||
// pre-clean the target directory on target cache mismatch
|
||||
await cleanTargets(config);
|
||||
}
|
||||
if (!cacheResult.match || !targetResult.match) {
|
||||
config.saveState();
|
||||
}
|
||||
setCacheHitOutput(match);
|
||||
setCacheHitOutput(cacheResult.match && targetResult.match);
|
||||
}
|
||||
else if (cacheResult.match) {
|
||||
setCacheHitOutput(true);
|
||||
}
|
||||
else {
|
||||
info("No cache found.");
|
||||
if (cacheResult.found) {
|
||||
// pre-clean the target directory on cache mismatch
|
||||
await cleanTargets(config);
|
||||
}
|
||||
config.saveState();
|
||||
setCacheHitOutput(false);
|
||||
}
|
||||
|
|
@ -98,4 +97,30 @@ async function run() {
|
|||
function setCacheHitOutput(cacheHit) {
|
||||
setOutput("cache-hit", cacheHit.toString());
|
||||
}
|
||||
async function restoreCache(cacheProvider, paths, key, restoreKeys, lookupOnly, name = "") {
|
||||
const label = name ? `${name} cache` : "cache";
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
const restoreKey = await cacheProvider.cache.restoreCache(paths.slice(), key, restoreKeys, {
|
||||
lookupOnly,
|
||||
});
|
||||
if (!restoreKey) {
|
||||
info(`No ${label} found.`);
|
||||
return { found: false, match: false };
|
||||
}
|
||||
const match = restoreKey.localeCompare(key, undefined, {
|
||||
sensitivity: "accent",
|
||||
}) === 0;
|
||||
info(`${lookupOnly ? "Found" : "Restored from"} ${label} key "${restoreKey}" full match: ${match}.`);
|
||||
return { found: true, match };
|
||||
}
|
||||
async function cleanTargets(config) {
|
||||
for (const workspace of config.workspaces) {
|
||||
try {
|
||||
await cleanTargetDir(workspace.target, [], true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
|
|
|||
83
dist/save.js
vendored
83
dist/save.js
vendored
|
|
@ -1,4 +1,4 @@
|
|||
import { e as error, g as getCacheProvider, a as getInput, d as isCacheUpToDate, i as info, C as CacheConfig, c as cleanTargetDir, f as debug, h as cleanRegistry, j as cleanBin, k as cleanGit, r as reportError, l as exec } from './cleanup-ChNUL7jL.js';
|
||||
import { e as error, g as getCacheProvider, a as getInput, d as isCacheUpToDate, i as info, C as CacheConfig, c as cleanTargetDir, f as debug, h as cleanRegistry, j as cleanBin, k as cleanGit, r as reportError, l as exec } from './cleanup-ctNqmXyy.js';
|
||||
import 'os';
|
||||
import 'crypto';
|
||||
import 'fs';
|
||||
|
|
@ -58,6 +58,8 @@ async function run() {
|
|||
if (process.env["RUNNER_OS"] == "macOS") {
|
||||
await macOsWorkaround();
|
||||
}
|
||||
const cleanCargo = !config.targetCacheEnabled || config.cacheNeedsSave;
|
||||
const cleanTargets = !config.targetCacheEnabled || config.targetCacheNeedsSave;
|
||||
const workspaceCrates = getInput("cache-workspace-crates").toLowerCase() || "false";
|
||||
const allPackages = [];
|
||||
for (const workspace of config.workspaces) {
|
||||
|
|
@ -67,43 +69,62 @@ async function run() {
|
|||
packages.push(...wsMembers);
|
||||
}
|
||||
allPackages.push(...packages);
|
||||
if (cleanTargets) {
|
||||
try {
|
||||
info(`... Cleaning ${workspace.target} ...`);
|
||||
await cleanTargetDir(workspace.target, packages);
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cleanCargo) {
|
||||
try {
|
||||
info(`... Cleaning ${workspace.target} ...`);
|
||||
await cleanTargetDir(workspace.target, packages);
|
||||
const crates = getInput("cache-all-crates").toLowerCase() || "false";
|
||||
info(`... Cleaning cargo registry (cache-all-crates: ${crates}) ...`);
|
||||
await cleanRegistry(allPackages, crates !== "true");
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
if (config.cacheBin) {
|
||||
try {
|
||||
info(`... Cleaning cargo/bin ...`);
|
||||
await cleanBin(config.cargoBins);
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
info(`... Cleaning cargo git cache ...`);
|
||||
await cleanGit(allPackages);
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const crates = getInput("cache-all-crates").toLowerCase() || "false";
|
||||
info(`... Cleaning cargo registry (cache-all-crates: ${crates}) ...`);
|
||||
await cleanRegistry(allPackages, crates !== "true");
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
if (config.cacheBin) {
|
||||
try {
|
||||
info(`... Cleaning cargo/bin ...`);
|
||||
await cleanBin(config.cargoBins);
|
||||
if (config.targetCacheEnabled) {
|
||||
if (config.cacheNeedsSave) {
|
||||
info(`... Saving cargo cache ...`);
|
||||
await saveCache(cacheProvider, config.cachePaths, config.cacheKey);
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
else {
|
||||
info(`Cargo cache up-to-date.`);
|
||||
}
|
||||
if (config.targetCacheNeedsSave) {
|
||||
info(`... Saving target cache ...`);
|
||||
await saveCache(cacheProvider, config.targetCachePaths, config.targetCacheKey);
|
||||
}
|
||||
else {
|
||||
info(`Target cache up-to-date.`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
info(`... Cleaning cargo git cache ...`);
|
||||
await cleanGit(allPackages);
|
||||
else {
|
||||
info(`... Saving cache ...`);
|
||||
await saveCache(cacheProvider, config.cachePaths, config.cacheKey);
|
||||
}
|
||||
catch (e) {
|
||||
debug(`${e.stack}`);
|
||||
}
|
||||
info(`... Saving cache ...`);
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
await cacheProvider.cache.saveCache(config.cachePaths.slice(), config.cacheKey);
|
||||
}
|
||||
catch (e) {
|
||||
reportError(e);
|
||||
|
|
@ -111,6 +132,12 @@ async function run() {
|
|||
process.exit();
|
||||
}
|
||||
run();
|
||||
async function saveCache(cacheProvider, paths, key) {
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
await cacheProvider.cache.saveCache(paths.slice(), key);
|
||||
}
|
||||
async function macOsWorkaround() {
|
||||
try {
|
||||
// Workaround for https://github.com/actions/cache/issues/403
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ export class CacheConfig {
|
|||
/** The secondary (restore) key that only contains the prefix and environment */
|
||||
public restoreKey = "";
|
||||
|
||||
/** Whether the primary cache needs saving in the post action */
|
||||
public cacheNeedsSave = true;
|
||||
|
||||
/** Workspace target paths cached separately when `target-key` is used */
|
||||
public targetCachePaths: Array<string> = [];
|
||||
/** The source-keyed workspace target cache key */
|
||||
public targetCacheKey = "";
|
||||
/** The source-keyed workspace target restore keys */
|
||||
public targetRestoreKeys: Array<string> = [];
|
||||
/** Whether workspace targets are cached separately from CARGO_HOME */
|
||||
public targetCacheEnabled = false;
|
||||
/** Whether the workspace target cache needs saving in the post action */
|
||||
public targetCacheNeedsSave = false;
|
||||
|
||||
/** Whether to cache CARGO_HOME/.bin */
|
||||
public cacheBin: boolean = true;
|
||||
|
||||
|
|
@ -266,24 +280,50 @@ export class CacheConfig {
|
|||
key += `-${lockHash}`;
|
||||
}
|
||||
|
||||
self.cacheKey = key;
|
||||
const baseCacheKey = key;
|
||||
const baseRestoreKey = self.restoreKey;
|
||||
|
||||
self.cachePaths = [path.join(CARGO_HOME, "registry"), path.join(CARGO_HOME, "git")];
|
||||
const cargoCachePaths = [path.join(CARGO_HOME, "registry"), path.join(CARGO_HOME, "git")];
|
||||
if (self.cacheBin) {
|
||||
self.cachePaths = [
|
||||
cargoCachePaths.unshift(
|
||||
path.join(CARGO_HOME, "bin"),
|
||||
path.join(CARGO_HOME, ".crates.toml"),
|
||||
path.join(CARGO_HOME, ".crates2.json"),
|
||||
...self.cachePaths,
|
||||
];
|
||||
);
|
||||
}
|
||||
const cacheTargets = core.getInput("cache-targets").toLowerCase() || "true";
|
||||
if (cacheTargets === "true") {
|
||||
self.cachePaths.push(...workspaces.map((ws) => ws.target));
|
||||
const targetCachePaths = cacheTargets === "true" ? workspaces.map((ws) => ws.target) : [];
|
||||
const cacheDirectories = core.getInput("cache-directories").trim().split(/\s+/).filter(Boolean);
|
||||
|
||||
const targetKey = core.getInput("target-key");
|
||||
const workspaceCrates = core.getInput("cache-workspace-crates").toLowerCase() || "false";
|
||||
if (targetKey && cacheTargets !== "true") {
|
||||
core.warning("`target-key` is ignored because `cache-targets` is not `true`.");
|
||||
}
|
||||
if (targetKey && workspaceCrates !== "true") {
|
||||
core.warning("`target-key` is ignored because `cache-workspace-crates` is not `true`.");
|
||||
}
|
||||
|
||||
const cacheDirectories = core.getInput("cache-directories");
|
||||
for (const dir of cacheDirectories.trim().split(/\s+/).filter(Boolean)) {
|
||||
self.targetCacheEnabled = Boolean(targetKey) && cacheTargets === "true" && workspaceCrates === "true";
|
||||
if (self.targetCacheEnabled) {
|
||||
self.cacheKey = baseCacheKey;
|
||||
self.cachePaths = [...cargoCachePaths, ...cacheDirectories];
|
||||
|
||||
const targetKeyPrefix = `${baseRestoreKey}-target`;
|
||||
const targetKeyEnvironment = baseCacheKey.slice(baseRestoreKey.length);
|
||||
self.targetCachePaths = targetCachePaths;
|
||||
self.targetCacheKey = `${targetKeyPrefix}${targetKeyEnvironment}-${targetKey}`;
|
||||
self.targetRestoreKeys = uniqInOrder([`${targetKeyPrefix}${targetKeyEnvironment}-`, `${targetKeyPrefix}-`]);
|
||||
} else {
|
||||
self.cacheKey = baseCacheKey;
|
||||
self.cachePaths = [...cargoCachePaths];
|
||||
}
|
||||
|
||||
if (!self.targetCacheEnabled && cacheTargets === "true") {
|
||||
self.cachePaths.push(...targetCachePaths);
|
||||
}
|
||||
|
||||
for (const dir of self.targetCacheEnabled ? [] : cacheDirectories) {
|
||||
self.cachePaths.push(dir);
|
||||
}
|
||||
|
||||
|
|
@ -325,14 +365,26 @@ export class CacheConfig {
|
|||
for (const workspace of this.workspaces) {
|
||||
core.info(` ${workspace.root}`);
|
||||
}
|
||||
core.info(`Cache Paths:`);
|
||||
core.info(`${this.targetCacheEnabled ? "Cargo Cache" : "Cache"} Paths:`);
|
||||
for (const path of this.cachePaths) {
|
||||
core.info(` ${path}`);
|
||||
}
|
||||
core.info(`Restore Key:`);
|
||||
core.info(`${this.targetCacheEnabled ? "Cargo Restore" : "Restore"} Key:`);
|
||||
core.info(` ${this.restoreKey}`);
|
||||
core.info(`Cache Key:`);
|
||||
core.info(`${this.targetCacheEnabled ? "Cargo Cache" : "Cache"} Key:`);
|
||||
core.info(` ${this.cacheKey}`);
|
||||
if (this.targetCacheEnabled) {
|
||||
core.info(`Target Cache Paths:`);
|
||||
for (const path of this.targetCachePaths) {
|
||||
core.info(` ${path}`);
|
||||
}
|
||||
core.info(`Target Restore Keys:`);
|
||||
for (const key of this.targetRestoreKeys) {
|
||||
core.info(` ${key}`);
|
||||
}
|
||||
core.info(`Target Cache Key:`);
|
||||
core.info(` ${this.targetCacheKey}`);
|
||||
}
|
||||
core.info(`.. Prefix:`);
|
||||
core.info(` - ${this.keyPrefix}`);
|
||||
core.info(`.. Environment considered:`);
|
||||
|
|
@ -465,3 +517,7 @@ function sort_and_uniq(a: string[]) {
|
|||
return accumulator;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function uniqInOrder(a: string[]) {
|
||||
return a.filter((value, index) => a.indexOf(value) === index);
|
||||
}
|
||||
|
|
|
|||
101
src/restore.ts
101
src/restore.ts
|
|
@ -2,7 +2,7 @@ import * as core from "@actions/core";
|
|||
|
||||
import { cleanTargetDir } from "./cleanup";
|
||||
import { CacheConfig } from "./config";
|
||||
import { getCacheProvider, reportError } from "./utils";
|
||||
import { CacheProvider, getCacheProvider, reportError } from "./utils";
|
||||
|
||||
process.on("uncaughtException", (e) => {
|
||||
core.error(e.message);
|
||||
|
|
@ -20,11 +20,11 @@ async function run() {
|
|||
}
|
||||
|
||||
try {
|
||||
var cacheOnFailure = core.getInput("cache-on-failure").toLowerCase();
|
||||
let cacheOnFailure = core.getInput("cache-on-failure").toLowerCase();
|
||||
if (cacheOnFailure !== "true") {
|
||||
cacheOnFailure = "false";
|
||||
}
|
||||
var lookupOnly = core.getInput("lookup-only").toLowerCase() === "true";
|
||||
const lookupOnly = core.getInput("lookup-only").toLowerCase() === "true";
|
||||
|
||||
core.exportVariable("CACHE_ON_FAILURE", cacheOnFailure);
|
||||
core.exportVariable("CARGO_INCREMENTAL", 0);
|
||||
|
|
@ -34,36 +34,42 @@ async function run() {
|
|||
core.info("");
|
||||
|
||||
core.info(`... ${lookupOnly ? "Checking" : "Restoring"} cache ...`);
|
||||
const key = config.cacheKey;
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
const restoreKey = await cacheProvider.cache.restoreCache(config.cachePaths.slice(), key, [config.restoreKey], {
|
||||
lookupOnly,
|
||||
});
|
||||
if (restoreKey) {
|
||||
const match =
|
||||
restoreKey.localeCompare(key, undefined, {
|
||||
sensitivity: "accent",
|
||||
}) === 0;
|
||||
core.info(`${lookupOnly ? "Found" : "Restored from"} cache key "${restoreKey}" full match: ${match}.`);
|
||||
if (!match) {
|
||||
// pre-clean the target directory on cache mismatch
|
||||
for (const workspace of config.workspaces) {
|
||||
try {
|
||||
await cleanTargetDir(workspace.target, [], true);
|
||||
} catch {}
|
||||
}
|
||||
const cacheResult = await restoreCache(cacheProvider, config.cachePaths, config.cacheKey, [config.restoreKey], lookupOnly);
|
||||
config.cacheNeedsSave = !cacheResult.match;
|
||||
if (config.targetCacheEnabled) {
|
||||
if (cacheResult.found && !cacheResult.match) {
|
||||
// pre-clean the target directory on cargo cache mismatch before restoring target cache
|
||||
await cleanTargets(config);
|
||||
}
|
||||
|
||||
// We restored the cache but it is not a full match.
|
||||
const targetResult = await restoreCache(
|
||||
cacheProvider,
|
||||
config.targetCachePaths,
|
||||
config.targetCacheKey,
|
||||
config.targetRestoreKeys,
|
||||
lookupOnly,
|
||||
"target",
|
||||
);
|
||||
config.targetCacheNeedsSave = !targetResult.match;
|
||||
if (targetResult.found && !targetResult.match) {
|
||||
// pre-clean the target directory on target cache mismatch
|
||||
await cleanTargets(config);
|
||||
}
|
||||
|
||||
if (!cacheResult.match || !targetResult.match) {
|
||||
config.saveState();
|
||||
}
|
||||
|
||||
setCacheHitOutput(match);
|
||||
setCacheHitOutput(cacheResult.match && targetResult.match);
|
||||
} else if (cacheResult.match) {
|
||||
setCacheHitOutput(true);
|
||||
} else {
|
||||
core.info("No cache found.");
|
||||
config.saveState();
|
||||
if (cacheResult.found) {
|
||||
// pre-clean the target directory on cache mismatch
|
||||
await cleanTargets(config);
|
||||
}
|
||||
|
||||
config.saveState();
|
||||
setCacheHitOutput(false);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -78,4 +84,45 @@ function setCacheHitOutput(cacheHit: boolean): void {
|
|||
core.setOutput("cache-hit", cacheHit.toString());
|
||||
}
|
||||
|
||||
async function restoreCache(
|
||||
cacheProvider: CacheProvider,
|
||||
paths: string[],
|
||||
key: string,
|
||||
restoreKeys: string[],
|
||||
lookupOnly: boolean,
|
||||
name = "",
|
||||
): Promise<RestoreResult> {
|
||||
const label = name ? `${name} cache` : "cache";
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
const restoreKey = await cacheProvider.cache.restoreCache(paths.slice(), key, restoreKeys, {
|
||||
lookupOnly,
|
||||
});
|
||||
if (!restoreKey) {
|
||||
core.info(`No ${label} found.`);
|
||||
return { found: false, match: false };
|
||||
}
|
||||
|
||||
const match =
|
||||
restoreKey.localeCompare(key, undefined, {
|
||||
sensitivity: "accent",
|
||||
}) === 0;
|
||||
core.info(`${lookupOnly ? "Found" : "Restored from"} ${label} key "${restoreKey}" full match: ${match}.`);
|
||||
return { found: true, match };
|
||||
}
|
||||
|
||||
interface RestoreResult {
|
||||
found: boolean;
|
||||
match: boolean;
|
||||
}
|
||||
|
||||
async function cleanTargets(config: CacheConfig) {
|
||||
for (const workspace of config.workspaces) {
|
||||
try {
|
||||
await cleanTargetDir(workspace.target, [], true);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
|
|||
84
src/save.ts
84
src/save.ts
|
|
@ -3,7 +3,7 @@ import * as exec from "@actions/exec";
|
|||
|
||||
import { cleanBin, cleanGit, cleanRegistry, cleanTargetDir } from "./cleanup";
|
||||
import { CacheConfig, isCacheUpToDate } from "./config";
|
||||
import { getCacheProvider, reportError } from "./utils";
|
||||
import { CacheProvider, getCacheProvider, reportError } from "./utils";
|
||||
|
||||
process.on("uncaughtException", (e) => {
|
||||
core.error(e.message);
|
||||
|
|
@ -36,6 +36,8 @@ async function run() {
|
|||
await macOsWorkaround();
|
||||
}
|
||||
|
||||
const cleanCargo = !config.targetCacheEnabled || config.cacheNeedsSave;
|
||||
const cleanTargets = !config.targetCacheEnabled || config.targetCacheNeedsSave;
|
||||
const workspaceCrates = core.getInput("cache-workspace-crates").toLowerCase() || "false";
|
||||
const allPackages = [];
|
||||
for (const workspace of config.workspaces) {
|
||||
|
|
@ -45,43 +47,60 @@ async function run() {
|
|||
packages.push(...wsMembers);
|
||||
}
|
||||
allPackages.push(...packages);
|
||||
if (cleanTargets) {
|
||||
try {
|
||||
core.info(`... Cleaning ${workspace.target} ...`);
|
||||
await cleanTargetDir(workspace.target, packages);
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanCargo) {
|
||||
try {
|
||||
core.info(`... Cleaning ${workspace.target} ...`);
|
||||
await cleanTargetDir(workspace.target, packages);
|
||||
const crates = core.getInput("cache-all-crates").toLowerCase() || "false";
|
||||
core.info(`... Cleaning cargo registry (cache-all-crates: ${crates}) ...`);
|
||||
await cleanRegistry(allPackages, crates !== "true");
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
}
|
||||
|
||||
if (config.cacheBin) {
|
||||
try {
|
||||
core.info(`... Cleaning cargo/bin ...`);
|
||||
await cleanBin(config.cargoBins);
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
core.info(`... Cleaning cargo git cache ...`);
|
||||
await cleanGit(allPackages);
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const crates = core.getInput("cache-all-crates").toLowerCase() || "false";
|
||||
core.info(`... Cleaning cargo registry (cache-all-crates: ${crates}) ...`);
|
||||
await cleanRegistry(allPackages, crates !== "true");
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
}
|
||||
|
||||
if (config.cacheBin) {
|
||||
try {
|
||||
core.info(`... Cleaning cargo/bin ...`);
|
||||
await cleanBin(config.cargoBins);
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
if (config.targetCacheEnabled) {
|
||||
if (config.cacheNeedsSave) {
|
||||
core.info(`... Saving cargo cache ...`);
|
||||
await saveCache(cacheProvider, config.cachePaths, config.cacheKey);
|
||||
} else {
|
||||
core.info(`Cargo cache up-to-date.`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
core.info(`... Cleaning cargo git cache ...`);
|
||||
await cleanGit(allPackages);
|
||||
} catch (e) {
|
||||
core.debug(`${(e as any).stack}`);
|
||||
if (config.targetCacheNeedsSave) {
|
||||
core.info(`... Saving target cache ...`);
|
||||
await saveCache(cacheProvider, config.targetCachePaths, config.targetCacheKey);
|
||||
} else {
|
||||
core.info(`Target cache up-to-date.`);
|
||||
}
|
||||
} else {
|
||||
core.info(`... Saving cache ...`);
|
||||
await saveCache(cacheProvider, config.cachePaths, config.cacheKey);
|
||||
}
|
||||
|
||||
core.info(`... Saving cache ...`);
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
await cacheProvider.cache.saveCache(config.cachePaths.slice(), config.cacheKey);
|
||||
} catch (e) {
|
||||
reportError(e);
|
||||
}
|
||||
|
|
@ -90,6 +109,13 @@ async function run() {
|
|||
|
||||
run();
|
||||
|
||||
async function saveCache(cacheProvider: CacheProvider, paths: string[], key: string) {
|
||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||
// https://github.com/actions/toolkit/pull/1378
|
||||
// TODO: remove this once the underlying bug is fixed.
|
||||
await cacheProvider.cache.saveCache(paths.slice(), key);
|
||||
}
|
||||
|
||||
async function macOsWorkaround() {
|
||||
try {
|
||||
// Workaround for https://github.com/actions/cache/issues/403
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue