From 622616010ebf8bbac89355b6932bc1a627eafb78 Mon Sep 17 00:00:00 2001 From: Arpad Borsos <swatinem@swatinem.de> Date: Sat, 16 Jul 2022 12:38:38 +0200 Subject: [PATCH] prepare v2 --- .github/workflows/coverage.yml | 2 +- .github/workflows/install.yml | 2 +- .github/workflows/simple.yml | 2 +- .github/workflows/sparse-registry.yml | 2 +- .github/workflows/target-dir.yml | 2 +- .github/workflows/workspaces.yml | 2 +- CHANGELOG.md | 9 +++ README.md | 108 ++++++++++++++++---------- TODO.md | 2 +- action.yml | 10 +-- src/cleanup.ts | 18 +++-- src/config.ts | 12 +-- 12 files changed, 106 insertions(+), 65 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b610560..3d58428 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,7 +16,7 @@ jobs: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal --component llvm-tools-preview diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index ed0aec6..ba23c86 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -16,7 +16,7 @@ jobs: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal diff --git a/.github/workflows/simple.yml b/.github/workflows/simple.yml index 13d58fc..a31115f 100644 --- a/.github/workflows/simple.yml +++ b/.github/workflows/simple.yml @@ -16,7 +16,7 @@ jobs: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal diff --git a/.github/workflows/sparse-registry.yml b/.github/workflows/sparse-registry.yml index 4669e5b..e543371 100644 --- a/.github/workflows/sparse-registry.yml +++ b/.github/workflows/sparse-registry.yml @@ -17,7 +17,7 @@ jobs: CARGO_UNSTABLE_SPARSE_REGISTRY: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: | rustup toolchain install nightly --profile minimal diff --git a/.github/workflows/target-dir.yml b/.github/workflows/target-dir.yml index 799df3a..cebd222 100644 --- a/.github/workflows/target-dir.yml +++ b/.github/workflows/target-dir.yml @@ -16,7 +16,7 @@ jobs: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal diff --git a/.github/workflows/workspaces.yml b/.github/workflows/workspaces.yml index f55af51..925f815 100644 --- a/.github/workflows/workspaces.yml +++ b/.github/workflows/workspaces.yml @@ -16,7 +16,7 @@ jobs: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal --target wasm32-unknown-unknown diff --git a/CHANGELOG.md b/CHANGELOG.md index e13870d..15a55e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.0.0 + +- The action code was refactored to allow for caching multiple workspaces and + different `target` directory layouts. +- The `working-directory` and `target-dir` input options were replaced by a + single `workspaces` option that has the form of `$workspace -> $target`. +- Support for considering `env-vars` as part of the cache key. +- The `sharedKey` input option was renamed to `shared-key` for consistency. + ## 1.4.0 - Clean both `debug` and `release` target directories. diff --git a/README.md b/README.md index a3f3cd6..ce57be4 100644 --- a/README.md +++ b/README.md @@ -6,43 +6,48 @@ sensible defaults. ## Example usage ```yaml -- uses: actions/checkout@v2 +- uses: actions/checkout@v3 # selecting a toolchain either by action or manual `rustup` calls should happen -# before the plugin, as it uses the current rustc version as its cache key -- uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable +# before the plugin, as the cache uses the current rustc version as its cache key +- run: rustup toolchain install stable --profile minimal -- uses: Swatinem/rust-cache@v1 +- uses: Swatinem/rust-cache@v2 + with: + # An explicit cache key that is used instead of the automatic `job`-based + # cache key and is thus stable across jobs. + # Default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # Default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # Default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # Default: ". -> target" + workspaces: "" + + # Determines if the cache should be saved even when the workflow has failed. + # Default: "false" + cache-on-failure: "" ``` -## Inputs - -: `key` -An optional key that is added to the automatic cache key. - -: `sharedKey` -An additional key that is stable over multiple jobs. - -: `envVars` -A space-separated list of regular expressions that define additional environment variable filters. -These are added to an additional cache key that's generated from environment variable contents. - -: `working-directory` -The working directory the action operates in, is case the cargo project is not -located in the repo root. - -: `target-dir` -The target directory that should be cleaned and persisted, defaults to `./target`. - -: `cache-on-failure` -Cache even if the build fails, defaults to false +Further examples are available in the [.github/workflows][] directory. ## Outputs -: `cache-hit` +**`cache-hit`** This is a boolean flag that will be set to `true` when there was an exact cache hit. @@ -67,24 +72,21 @@ a more stable experience, please use a fixed revision or tag. This action currently caches the following files/directories: -- `~/.cargo/bin` -- `~/.cargo/registry/index` -- `~/.cargo/registry/cache` -- `~/.cargo/git` -- `~/.cargo/.crates.toml` -- `~/.cargo/.crates2.json` -- `./target` +- `~/.cargo` (installed binaries, the cargo registry, cache, and git dependencies) +- `./target` (build artifacts of dependencies) This cache is automatically keyed by: - the github [`job_id`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_id), -- the rustc release / host / hash, and +- the rustc release / host / hash, +- the value of some compiler-specific environment variables (eg. RUSTFLAGS, etc), and - a hash of all `Cargo.lock` / `Cargo.toml` files found anywhere in the repository (if present). - a hash of all `rust-toolchain` / `rust-toolchain.toml` files in the root of the repository (if present). An additional input `key` can be provided if the builtin keys are not sufficient. Before being persisted, the cache is cleaned of: + - Any files in `~/.cargo/bin` that were present before the action ran (for example `rustc`). - Dependencies that are no longer used. - Anything that is not a dependency. @@ -103,13 +105,39 @@ 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. +The action invokes `cargo metadata` to determine the current set of dependencies. + Additionally, the action automatically works around [cargo#8603](https://github.com/rust-lang/cargo/issues/8603) / [actions/cache#403](https://github.com/actions/cache/issues/403) which would otherwise corrupt the cache on macOS builds. +## Cache Limits and Control + +This specialized cache action is built on top of the upstream cache action +maintained by GitHub. The same restrictions and limits apply, which are +documented here: +https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows + +In particular, caches are currently limited to 10 GB in total and exceeding that +limit will cause eviction of older caches. + +Caches from base branches are available to PRs, but not across unrelated +branches. + +The caches can be controlled using the [Cache API](https://docs.github.com/en/rest/actions/cache) +which allows listing existing caches and manually removing entries. + +## Debugging + +The action prints detailed information about which information it considers +for its cache key, and it outputs more debug-only information about which +cleanup steps it performs before persisting the cache. + +You can read up on how to [enable debug logging](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) +to see those details as well as further details related to caching operations. + ## Known issues -- The cache cleaning process currently only runs against the build artifacts under - `./target/debug/`, so projects using release or cross-compiled builds will experience - larger cache sizes. +- The cache cleaning process currently removes all the files from `~/.cargo/bin` + that were present before the action ran (for example `rustc`). diff --git a/TODO.md b/TODO.md index b321a10..b15aefb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -- Update readme with better docs ;-) - better .cargo/bin handling: - get a list of all the files on "pre"/"restore" - move the files out of the way on "post"/"save" and move them back afterwards +- properly clean sparse registry diff --git a/action.yml b/action.yml index 320a5b6..4bc7f8e 100644 --- a/action.yml +++ b/action.yml @@ -2,14 +2,14 @@ name: "Rust Cache" description: "A GitHub Action that implements smart caching for rust/cargo projects with sensible defaults." author: "Arpad Borsos <swatinem@swatinem.de>" inputs: + shared-key: + description: "An additional cache key that is stable over multiple jobs" + required: false key: description: "An additional key for the cache" required: false - sharedKey: - description: "An additional cache key that is stable over multiple jobs" - required: false - envVars: - description: "Additional environment variables to include in the cache key, separeted by spaces" + env-vars: + description: "Additional environment variables to include in the cache key, separated by spaces" required: false workspaces: description: "Paths to multiple Cargo workspaces and their target directories, separated by newlines" diff --git a/src/cleanup.ts b/src/cleanup.ts index 597811c..99f31a0 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -31,8 +31,8 @@ export async function cleanTargetDir(targetDir: string, packages: Packages) { } async function cleanProfileTarget(profileDir: string, packages: Packages) { - await io.rmRF(path.join(profileDir, "examples")); - await io.rmRF(path.join(profileDir, "incremental")); + await rmRF(path.join(profileDir, "examples")); + await rmRF(path.join(profileDir, "incremental")); let dir: fs.Dir; // remove all *files* from the profile directory @@ -94,8 +94,7 @@ export async function cleanBin() { export async function cleanRegistry(packages: Packages) { // `.cargo/registry/src` // we can remove this completely, as cargo will recreate this from `cache` - const srcDir = path.join(CARGO_HOME, "registry", "src"); - await io.rmRF(srcDir); + await rmRF(path.join(CARGO_HOME, "registry", "src")); // `.cargo/registry/index` const indexDir = await fs.promises.opendir(path.join(CARGO_HOME, "registry", "index")); @@ -107,7 +106,7 @@ export async function cleanRegistry(packages: Packages) { // for a git registry, we can remove `.cache`, as cargo will recreate it from git if (await exists(path.join(dir.path, ".git"))) { - await io.rmRF(path.join(dir.path, ".cache")); + await rmRF(path.join(dir.path, ".cache")); } // TODO: else, clean `.cache` based on the `packages` } @@ -183,7 +182,7 @@ export async function cleanGit(packages: Packages) { const ONE_WEEK = 7 * 24 * 3600 * 1000; -export async function rmExcept(dirName: string, keepPrefix: Set<string>) { +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; @@ -200,7 +199,7 @@ export async function rmExcept(dirName: string, keepPrefix: Set<string>) { } } -export async function rm(parent: string, dirent: fs.Dirent) { +async function rm(parent: string, dirent: fs.Dirent) { try { const fileName = path.join(parent, dirent.name); core.debug(`deleting "${fileName}"`); @@ -212,6 +211,11 @@ export async function rm(parent: string, dirent: fs.Dirent) { } catch {} } +async function rmRF(dirName: string) { + core.debug(`deleting "${dirName}"`); + await io.rmRF(dirName); +} + async function exists(path: string) { try { await fs.promises.access(path); diff --git a/src/config.ts b/src/config.ts index 22a5ea8..5ff8ef8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,12 +47,12 @@ export class CacheConfig { const self = new CacheConfig(); // Construct key prefix: - // This uses either the `sharedKey` input, + // This uses either the `shared-key` input, // or the `key` input combined with the `job` key. let key = `v0-rust`; - const sharedKey = core.getInput("sharedKey"); + const sharedKey = core.getInput("shared-key"); if (sharedKey) { key += `-${sharedKey}`; } else { @@ -72,7 +72,7 @@ export class CacheConfig { // Construct environment portion of the key: // This consists of a hash that considers the rust version // as well as all the environment variables as given by a default list - // and the `envVars` input. + // and the `env-vars` input. // The env vars are sorted, matched by prefix and hashed into the // resulting environment hash. @@ -87,8 +87,8 @@ export class CacheConfig { self.keyRust = keyRust; // these prefixes should cover most of the compiler / rust / cargo keys - const envPrefixes = ["CARGO", "CC", "CXX", "CMAKE", "RUST"]; - envPrefixes.push(...core.getInput("envVars").split(/\s+/).filter(Boolean)); + const envPrefixes = ["CARGO", "CC", "CFLAGS", "CXX", "CMAKE", "RUST"]; + envPrefixes.push(...core.getInput("env-vars").split(/\s+/).filter(Boolean)); // sort the available env vars so we have a more stable hash const keyEnvs = []; @@ -147,7 +147,7 @@ export class CacheConfig { const workspaces: Array<Workspace> = []; const workspacesInput = core.getInput("workspaces") || "."; for (const workspace of workspacesInput.trim().split("\n")) { - let [root, target = "target"] = workspace.split(" -> "); + let [root, target = "target"] = workspace.split("->").map((s) => s.trim()); root = path.resolve(root); target = path.join(root, target); workspaces.push(new Workspace(root, target));