3
0
Fork 0
mirror of https://github.com/Swatinem/rust-cache synced 2025-04-29 14:45:53 +00:00

initial commit

This commit is contained in:
Arpad Borsos 2020-09-25 16:42:39 +02:00
commit 99970e092c
No known key found for this signature in database
GPG key ID: 908EDF65263368B4
12 changed files with 111031 additions and 0 deletions

124
src/common.ts Normal file
View file

@ -0,0 +1,124 @@
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import * as glob from "@actions/glob";
import crypto from "crypto";
import fs from "fs";
import os from "os";
import path from "path";
const home = os.homedir();
export const paths = {
index: path.join(home, ".cargo/registry/index"),
cache: path.join(home, ".cargo/registry/cache"),
git: path.join(home, ".cargo/git/db"),
target: "target",
};
export interface CacheConfig {
path: string;
key: string;
restoreKeys?: Array<string>;
}
export interface Caches {
index: CacheConfig;
cache: CacheConfig;
git: CacheConfig;
target: CacheConfig;
}
const RefKey = "GITHUB_REF";
export function isValidEvent(): boolean {
return RefKey in process.env && Boolean(process.env[RefKey]);
}
export async function getCaches(): Promise<Caches> {
const rustKey = await getRustKey();
let lockHash = core.getState("lockHash");
if (!lockHash) {
lockHash = await getLockfileHash();
core.saveState("lockHash", lockHash);
}
let targetKey = core.getInput("key");
if (targetKey) {
targetKey = `${targetKey}-`;
}
return {
index: { path: paths.index, key: "registry-index-XXX", restoreKeys: ["registry-index"] },
cache: { path: paths.cache, key: `registry-cache-${lockHash}`, restoreKeys: ["registry-cache"] },
git: { path: paths.git, key: "git-db" },
target: {
path: paths.target,
key: `target-${targetKey}${rustKey}-${lockHash}`,
restoreKeys: [`target-${targetKey}${rustKey}`],
},
};
}
export async function getRustKey(): Promise<string> {
const rustc = await getRustVersion();
return `${rustc.release}-${rustc.host}-${rustc["commit-hash"]}`;
}
interface RustVersion {
host: string;
release: string;
"commit-hash": string;
}
export async function getRustVersion(): Promise<RustVersion> {
const stdout = await getCmdOutput("rustc", ["-vV"]);
let splits = stdout
.split(/[\n\r]+/)
.filter(Boolean)
.map((s) => s.split(":").map((s) => s.trim()))
.filter((s) => s.length === 2);
return Object.fromEntries(splits);
}
export async function getCmdOutput(
cmd: string,
args: Array<string> = [],
options: exec.ExecOptions = {},
): Promise<string> {
let stdout = "";
await exec.exec(cmd, args, {
silent: true,
listeners: {
stdout(data) {
stdout += data.toString();
},
},
...options,
});
return stdout;
}
export async function getRegistryName() {
const globber = await glob.create(`${paths.index}/**/.last-updated`, { followSymbolicLinks: false });
const files = await globber.glob();
if (files.length > 1) {
core.debug(`got multiple registries: "${files.join('", "')}"`);
}
const first = files.shift();
if (!first) {
return;
}
return path.basename(path.dirname(first));
}
export async function getLockfileHash() {
const globber = await glob.create("**/Cargo.toml\n**/Cargo.lock", { followSymbolicLinks: false });
const files = await globber.glob();
files.sort((a, b) => a.localeCompare(b));
const hasher = crypto.createHash("sha1");
for (const file of files) {
for await (const chunk of fs.createReadStream(file)) {
hasher.update(chunk);
}
}
return hasher.digest("hex");
}

35
src/restore.ts Normal file
View file

@ -0,0 +1,35 @@
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import { getCaches, isValidEvent } from "./common";
async function run() {
if (!isValidEvent()) {
return;
}
try {
core.exportVariable("CARGO_INCREMENTAL", 0);
const caches = await getCaches();
for (const [name, { path, key, restoreKeys }] of Object.entries(caches)) {
try {
core.startGroup(`Restoring "${path}" from "${key}"…`);
const restoreKey = await cache.restoreCache([path], key, restoreKeys);
if (restoreKey) {
core.info(`Restored "${path}" from cache key "${restoreKey}".`);
core.saveState(name, restoreKey);
} else {
core.info("No cache found.");
}
} catch (e) {
core.info(`[warning] ${e.message}`);
} finally {
core.endGroup();
}
}
} catch (e) {
core.info(`[warning] ${e.message}`);
}
}
run();

133
src/save.ts Normal file
View file

@ -0,0 +1,133 @@
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import * as io from "@actions/io";
import fs from "fs";
import path from "path";
import { getCaches, getCmdOutput, getRegistryName, isValidEvent, paths } from "./common";
async function run() {
if (!isValidEvent()) {
//return;
}
try {
const caches = await getCaches();
const registryName = await getRegistryName();
const packages = await getPackages();
await pruneTarget(packages);
if (registryName) {
// save the index based on its revision
const indexRef = await getIndexRef(registryName);
caches.index.key = `registry-index-${indexRef}`;
await io.rmRF(path.join(paths.index, registryName, ".cache"));
await pruneRegistryCache(registryName, packages);
} else {
delete (caches as any).index;
delete (caches as any).cache;
}
for (const [name, { path, key }] of Object.entries(caches)) {
if (core.getState(name) === key) {
core.info(`Cache for "${path}" up-to-date.`);
continue;
}
try {
core.startGroup(`Saving "${path}" to cache key "${key}"…`);
if (await cache.saveCache([path], key)) {
core.info(`Saved "${path}" to cache key "${key}".`);
}
} catch (e) {
core.info(`[warning] ${e.message}`);
} finally {
core.endGroup();
}
}
} catch (e) {
core.info(`[warning] ${e.message}`);
}
}
run();
async function getIndexRef(registryName: string) {
const cwd = path.join(paths.index, registryName);
return (await getCmdOutput("git", ["rev-parse", "--short", "origin/master"], { cwd })).trim();
}
interface PackageDefinition {
name: string;
version: string;
}
type Packages = Array<PackageDefinition>;
async function getPackages(): Promise<Packages> {
const meta = JSON.parse(await getCmdOutput("cargo", ["metadata", "--format-version", "1"]));
return meta.packages.map(({ name, version }: any) => ({ name, version }));
}
async function pruneRegistryCache(registryName: string, packages: Packages) {
const pkgSet = new Set(packages.map((p) => `${p.name}-${p.version}.crate`));
const dir = await fs.promises.opendir(path.join(paths.cache, registryName));
for await (const dirent of dir) {
if (dirent.isFile() && !pkgSet.has(dirent.name)) {
const fileName = path.join(dir.path, dirent.name);
await fs.promises.unlink(fileName);
core.debug(`deleting "${fileName}"`);
}
}
}
async function pruneTarget(packages: Packages) {
await fs.promises.unlink("./target/.rustc_info.json");
await io.rmRF("./target/debug/examples");
await io.rmRF("./target/debug/incremental");
let dir: fs.Dir;
// remove all *files* from debug
dir = await fs.promises.opendir("./target/debug");
for await (const dirent of dir) {
if (dirent.isFile()) {
const fileName = path.join(dir.path, dirent.name);
await fs.promises.unlink(fileName);
}
}
const keepPkg = new Set(packages.map((p) => p.name));
await rmExcept("./target/debug/build", keepPkg);
await rmExcept("./target/debug/.fingerprint", keepPkg);
const keepDeps = new Set(
packages.flatMap((p) => {
const name = p.name.replace(/-/g, "_");
return [name, `lib${name}`];
}),
);
await rmExcept("./target/debug/deps", keepDeps);
}
const twoWeeks = 14 * 24 * 3600 * 1000;
async function rmExcept(dirName: string, keepPrefix: Set<string>) {
const dir = await fs.promises.opendir(dirName);
for await (const dirent of dir) {
let name = dirent.name;
const idx = name.lastIndexOf("-");
if (idx !== -1) {
name = name.slice(0, idx);
}
const fileName = path.join(dir.path, dirent.name);
const { mtime } = await fs.promises.stat(fileName);
if (!keepPrefix.has(name) || Date.now() - mtime.getTime() > twoWeeks) {
core.debug(`deleting "${fileName}"`);
if (dirent.isFile()) {
await fs.promises.unlink(fileName);
} else if (dirent.isDirectory()) {
await io.rmRF(fileName);
}
}
}
}