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));