mirror of
https://github.com/Swatinem/rust-cache
synced 2025-06-23 08:43:42 +00:00
wip: add incremental mtime restore
This commit is contained in:
parent
f0deed1e0e
commit
3faf29c29b
6 changed files with 116 additions and 21 deletions
|
@ -48,6 +48,10 @@ inputs:
|
||||||
description: "Check if a cache entry exists without downloading the cache"
|
description: "Check if a cache entry exists without downloading the cache"
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: "false"
|
||||||
|
incremental:
|
||||||
|
description: "Determines whether to cache incremental builds - speeding up builds for more disk usage. Defaults to false."
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
outputs:
|
outputs:
|
||||||
cache-hit:
|
cache-hit:
|
||||||
description: "A boolean value that indicates an exact match was found."
|
description: "A boolean value that indicates an exact match was found."
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { CARGO_HOME } from "./config";
|
||||||
import { exists } from "./utils";
|
import { exists } from "./utils";
|
||||||
import { Packages } from "./workspace";
|
import { Packages } from "./workspace";
|
||||||
|
|
||||||
export async function cleanTargetDir(targetDir: string, packages: Packages, checkTimestamp = false) {
|
export async function cleanTargetDir(targetDir: string, packages: Packages, checkTimestamp: boolean, incremental: boolean) {
|
||||||
core.debug(`cleaning target directory "${targetDir}"`);
|
core.debug(`cleaning target directory "${targetDir}"`);
|
||||||
|
|
||||||
// remove all *files* from the profile directory
|
// remove all *files* from the profile directory
|
||||||
|
@ -21,9 +21,9 @@ export async function cleanTargetDir(targetDir: string, packages: Packages, chec
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isNestedTarget) {
|
if (isNestedTarget) {
|
||||||
await cleanTargetDir(dirName, packages, checkTimestamp);
|
await cleanTargetDir(dirName, packages, checkTimestamp, incremental);
|
||||||
} else {
|
} else {
|
||||||
await cleanProfileTarget(dirName, packages, checkTimestamp);
|
await cleanProfileTarget(dirName, packages, checkTimestamp, incremental);
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
} else if (dirent.name !== "CACHEDIR.TAG") {
|
} else if (dirent.name !== "CACHEDIR.TAG") {
|
||||||
|
@ -32,7 +32,7 @@ export async function cleanTargetDir(targetDir: string, packages: Packages, chec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanProfileTarget(profileDir: string, packages: Packages, checkTimestamp = false) {
|
async function cleanProfileTarget(profileDir: string, packages: Packages, checkTimestamp: boolean, incremental: boolean) {
|
||||||
core.debug(`cleaning profile directory "${profileDir}"`);
|
core.debug(`cleaning profile directory "${profileDir}"`);
|
||||||
|
|
||||||
// Quite a few testing utility crates store compilation artifacts as nested
|
// Quite a few testing utility crates store compilation artifacts as nested
|
||||||
|
@ -42,15 +42,44 @@ async function cleanProfileTarget(profileDir: string, packages: Packages, checkT
|
||||||
try {
|
try {
|
||||||
// https://github.com/vertexclique/kaos/blob/9876f6c890339741cc5be4b7cb9df72baa5a6d79/src/cargo.rs#L25
|
// https://github.com/vertexclique/kaos/blob/9876f6c890339741cc5be4b7cb9df72baa5a6d79/src/cargo.rs#L25
|
||||||
// https://github.com/eupn/macrotest/blob/c4151a5f9f545942f4971980b5d264ebcd0b1d11/src/cargo.rs#L27
|
// https://github.com/eupn/macrotest/blob/c4151a5f9f545942f4971980b5d264ebcd0b1d11/src/cargo.rs#L27
|
||||||
cleanTargetDir(path.join(profileDir, "target"), packages, checkTimestamp);
|
cleanTargetDir(path.join(profileDir, "target"), packages, checkTimestamp, incremental);
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// https://github.com/dtolnay/trybuild/blob/eec8ca6cb9b8f53d0caf1aa499d99df52cae8b40/src/cargo.rs#L50
|
// https://github.com/dtolnay/trybuild/blob/eec8ca6cb9b8f53d0caf1aa499d99df52cae8b40/src/cargo.rs#L50
|
||||||
cleanTargetDir(path.join(profileDir, "trybuild"), packages, checkTimestamp);
|
cleanTargetDir(path.join(profileDir, "trybuild"), packages, checkTimestamp, incremental);
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
// Delete everything else.
|
// Delete everything else.
|
||||||
await rmExcept(profileDir, new Set(["target", "trybuild"]), checkTimestamp);
|
let except = new Set(["target", "trybuild"]);
|
||||||
|
|
||||||
|
// Keep the incremental folder if incremental builds are enabled
|
||||||
|
if (incremental) {
|
||||||
|
except.add("incremental");
|
||||||
|
|
||||||
|
// Traverse the incremental folder recursively and collect the modified times in a map
|
||||||
|
const incrementalDir = path.join(profileDir, "incremental");
|
||||||
|
const modifiedTimes = new Map<string, number>();
|
||||||
|
const fillModifiedTimes = async (dir: string) => {
|
||||||
|
const dirEntries = await fs.promises.opendir(dir);
|
||||||
|
for await (const dirent of dirEntries) {
|
||||||
|
if (dirent.isDirectory()) {
|
||||||
|
await fillModifiedTimes(path.join(dir, dirent.name));
|
||||||
|
} else {
|
||||||
|
const fileName = path.join(dir, dirent.name);
|
||||||
|
const { mtime } = await fs.promises.stat(fileName);
|
||||||
|
modifiedTimes.set(fileName, mtime.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await fillModifiedTimes(incrementalDir);
|
||||||
|
|
||||||
|
// Write the modified times to the incremental folder
|
||||||
|
const contents = JSON.stringify({ modifiedTimes });
|
||||||
|
await fs.promises.writeFile(path.join(incrementalDir, "incremental-restore.json"), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
await rmExcept(profileDir, except, checkTimestamp);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,9 @@ export class CacheConfig {
|
||||||
/** The cargo binaries present during main step */
|
/** The cargo binaries present during main step */
|
||||||
public cargoBins: Array<string> = [];
|
public cargoBins: Array<string> = [];
|
||||||
|
|
||||||
|
/** Whether to cache incremental builds */
|
||||||
|
public incremental: boolean = false;
|
||||||
|
|
||||||
/** The prefix portion of the cache key */
|
/** The prefix portion of the cache key */
|
||||||
private keyPrefix = "";
|
private keyPrefix = "";
|
||||||
/** The rust version considered for the cache key */
|
/** The rust version considered for the cache key */
|
||||||
|
|
48
src/incremental.ts
Normal file
48
src/incremental.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import * as core from "@actions/core";
|
||||||
|
import * as io from "@actions/io";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
import { CARGO_HOME } from "./config";
|
||||||
|
import { exists } from "./utils";
|
||||||
|
import { Packages } from "./workspace";
|
||||||
|
|
||||||
|
|
||||||
|
export async function restoreIncremental(targetDir: string) {
|
||||||
|
core.debug(`restoring incremental directory "${targetDir}"`);
|
||||||
|
|
||||||
|
|
||||||
|
let dir = await fs.promises.opendir(targetDir);
|
||||||
|
for await (const dirent of dir) {
|
||||||
|
if (dirent.isDirectory()) {
|
||||||
|
let dirName = path.join(dir.path, dirent.name);
|
||||||
|
// is it a profile dir, or a nested target dir?
|
||||||
|
let isNestedTarget =
|
||||||
|
(await exists(path.join(dirName, "CACHEDIR.TAG"))) || (await exists(path.join(dirName, ".rustc_info.json")));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isNestedTarget) {
|
||||||
|
await restoreIncremental(dirName);
|
||||||
|
} else {
|
||||||
|
await restoreIncrementalProfile(dirName);
|
||||||
|
} restoreIncrementalProfile
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreIncrementalProfile(dirName: string) {
|
||||||
|
core.debug(`restoring incremental profile directory "${dirName}"`);
|
||||||
|
const incrementalJson = path.join(dirName, "incremental-restore.json");
|
||||||
|
if (await exists(incrementalJson)) {
|
||||||
|
const contents = await fs.promises.readFile(incrementalJson, "utf8");
|
||||||
|
const { modifiedTimes } = JSON.parse(contents);
|
||||||
|
|
||||||
|
// Write the mtimes to all the files in the profile directory
|
||||||
|
for (const fileName of Object.keys(modifiedTimes)) {
|
||||||
|
const mtime = modifiedTimes[fileName];
|
||||||
|
const filePath = path.join(dirName, fileName);
|
||||||
|
await fs.promises.utimes(filePath, new Date(mtime), new Date(mtime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import * as core from "@actions/core";
|
||||||
import { cleanTargetDir } from "./cleanup";
|
import { cleanTargetDir } from "./cleanup";
|
||||||
import { CacheConfig } from "./config";
|
import { CacheConfig } from "./config";
|
||||||
import { getCacheProvider, reportError } from "./utils";
|
import { getCacheProvider, reportError } from "./utils";
|
||||||
|
import { restoreIncremental } from "./incremental";
|
||||||
|
|
||||||
process.on("uncaughtException", (e) => {
|
process.on("uncaughtException", (e) => {
|
||||||
core.error(e.message);
|
core.error(e.message);
|
||||||
|
@ -27,12 +28,15 @@ async function run() {
|
||||||
var lookupOnly = core.getInput("lookup-only").toLowerCase() === "true";
|
var lookupOnly = core.getInput("lookup-only").toLowerCase() === "true";
|
||||||
|
|
||||||
core.exportVariable("CACHE_ON_FAILURE", cacheOnFailure);
|
core.exportVariable("CACHE_ON_FAILURE", cacheOnFailure);
|
||||||
core.exportVariable("CARGO_INCREMENTAL", 0);
|
|
||||||
|
|
||||||
const config = await CacheConfig.new();
|
const config = await CacheConfig.new();
|
||||||
config.printInfo(cacheProvider);
|
config.printInfo(cacheProvider);
|
||||||
core.info("");
|
core.info("");
|
||||||
|
|
||||||
|
if (!config.incremental) {
|
||||||
|
core.exportVariable("CARGO_INCREMENTAL", 0);
|
||||||
|
}
|
||||||
|
|
||||||
core.info(`... ${lookupOnly ? "Checking" : "Restoring"} cache ...`);
|
core.info(`... ${lookupOnly ? "Checking" : "Restoring"} cache ...`);
|
||||||
const key = config.cacheKey;
|
const key = config.cacheKey;
|
||||||
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
// Pass a copy of cachePaths to avoid mutating the original array as reported by:
|
||||||
|
@ -44,11 +48,18 @@ async function run() {
|
||||||
if (restoreKey) {
|
if (restoreKey) {
|
||||||
const match = restoreKey === key;
|
const match = restoreKey === key;
|
||||||
core.info(`${lookupOnly ? "Found" : "Restored from"} cache key "${restoreKey}" full match: ${match}.`);
|
core.info(`${lookupOnly ? "Found" : "Restored from"} cache key "${restoreKey}" full match: ${match}.`);
|
||||||
|
|
||||||
|
if (config.incremental) {
|
||||||
|
for (const workspace of config.workspaces) {
|
||||||
|
await restoreIncremental(workspace.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
// pre-clean the target directory on cache mismatch
|
// pre-clean the target directory on cache mismatch
|
||||||
for (const workspace of config.workspaces) {
|
for (const workspace of config.workspaces) {
|
||||||
try {
|
try {
|
||||||
await cleanTargetDir(workspace.target, [], true);
|
await cleanTargetDir(workspace.target, [], true, false);
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ async function run() {
|
||||||
allPackages.push(...packages);
|
allPackages.push(...packages);
|
||||||
try {
|
try {
|
||||||
core.info(`... Cleaning ${workspace.target} ...`);
|
core.info(`... Cleaning ${workspace.target} ...`);
|
||||||
await cleanTargetDir(workspace.target, packages);
|
await cleanTargetDir(workspace.target, packages, false, config.incremental);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
core.debug(`${(e as any).stack}`);
|
core.debug(`${(e as any).stack}`);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue