From 9d93ed2c803b6ef458525d258db00765e4c96913 Mon Sep 17 00:00:00 2001
From: Miles Johnson <milesj@users.noreply.github.com>
Date: Mon, 17 Apr 2023 15:20:40 -0700
Subject: [PATCH] new: Add .cargo/target caching. (#2)

---
 .github/workflows/publish.yml |   4 +-
 README.md                     |  33 +++-
 action.yml                    |  14 +-
 index.ts                      |  25 +--
 package.json                  |   4 +-
 pnpm-lock.yaml                | 314 +++++++++++++++++++++++++++++++++-
 post.ts                       |  12 ++
 src/cargo.ts                  | 120 +++++++++++++
 src/rust.ts                   |  31 ++++
 9 files changed, 528 insertions(+), 29 deletions(-)
 create mode 100644 post.ts
 create mode 100644 src/cargo.ts
 create mode 100644 src/rust.ts

diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e13350d..775fb59 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -13,6 +13,8 @@ jobs:
       - run: npm install -g pnpm
       - run: pnpm install
       - run: pnpm run build
-      - uses: JasonEtco/build-and-tag-action@v2
+      - uses: aboutte/build-and-tag-action@v2
+        with:
+          additional_files: 'dist/post/index.js'
         env:
           GITHUB_TOKEN: ${{ github.token }}
diff --git a/README.md b/README.md
index 36d5633..3adf639 100644
--- a/README.md
+++ b/README.md
@@ -13,13 +13,6 @@ jobs:
       - run: cargo test
 ```
 
-## To Do
-
-- [x] Install Rust toolchain with `rustup`
-- [x] Install Cargo bins with `cargo-binstall`
-- [ ] Cache `~/.cargo` directory (not everything)
-- [ ] Cache `target` directory?
-
 ## Configuring the Rust toolchain
 
 This action will automatically install the appropriate toolchain with `rustup` by inspecting the
@@ -80,6 +73,30 @@ names (`cargo-` prefix optional).
 > Binaries are installed with [`cargo-binstall`](https://crates.io/crates/cargo-binstall) under the
 > hood.
 
+## Caching in CI
+
+By default this action will cache the `~/.cargo/registry` and `/target/debug` directories to improve
+CI times. To disable caching, set the `cache` input to `false`.
+
+```yaml
+- uses: moonrepo/setup-rust@v0
+  with:
+    cache: false
+```
+
+The following optimizations and considerations are taken into account when caching:
+
+- The `~/.cargo/bin` directory is not cached as we manage binary installation in this action via the
+  `bins` input.
+- The `~/.cargo/git` directory is not cached as it's not necessary for CI. When required by Cargo or
+  a crate, a checkout will be performed on-demand.
+- The `~/.cargo/registry` directory is _cleaned_ before saving the cache. This includes removing
+  `src`, `.cache`, and any other unnecessary files.
+- Only the `/target/debug` profile is cached, as this profile is typically used for formatting,
+  linting, and testing.
+- The following sources are hashed for the generated cache key: `Cargo.lock`, Rust version, Rust
+  commit hash, and operating system.
+
 ## Compared to
 
 ### `actions-rs/*`
@@ -89,6 +106,7 @@ maintenance, and being full of deprecation warnings, it was time to create somet
 
 Outside of being evergreen, this action also supports the following features:
 
+- Automatically caches.
 - Installs Cargo bins.
 - Assumes `rustup`, `cargo`, and other commands are available globally. This allows you to use them
   directly in a `run` command, without having to use `actions-rs/cargo`.
@@ -105,4 +123,5 @@ but this action also supports the following features:
 
 - Extracts the toolchain/channel from `rust-toolchain.toml` and `rust-toolchain` configuration
   files.
+- Automatically caches.
 - Installs Cargo bins.
diff --git a/action.yml b/action.yml
index a7d85b1..8a52c54 100644
--- a/action.yml
+++ b/action.yml
@@ -6,6 +6,9 @@ description:
 inputs:
   bins:
     description: 'Comma-separated list of global binaries to install into Cargo.'
+  cache:
+    description: 'Toggle caching of ~/.cargo/registry and /target/debug directories.'
+    default: true
   channel:
     description: 'Toolchain specification/channel to install.'
   components:
@@ -14,10 +17,19 @@ inputs:
     description: 'Comma-separated list of additional targets to install.'
   profile:
     description: 'Profile to install. Defaults to "minimal".'
-outputs: {}
+outputs:
+  cache-key:
+    description: 'The generated cache key used.'
+  cache-hit:
+    description: 'Indicates an exact match was found for the cache key.'
+  rust-version:
+    description: 'Version of the installed rustc.'
+  rust-hash:
+    description: 'Commit hash of the installed rustc.'
 runs:
   using: 'node16'
   main: 'dist/index.js'
+  post: 'dist/post/index.js'
 branding:
   icon: 'settings'
   color: 'orange'
diff --git a/index.ts b/index.ts
index effd379..16160fb 100644
--- a/index.ts
+++ b/index.ts
@@ -1,10 +1,11 @@
 import fs from 'fs';
-import os from 'os';
 import path from 'path';
 import * as core from '@actions/core';
 import * as exec from '@actions/exec';
 import * as tc from '@actions/tool-cache';
 import TOML from '@ltd/j-toml';
+import { CACHE_ENABLED, CARGO_HOME, restoreCache } from './src/cargo';
+import { extractRustVersion } from './src/rust';
 
 interface Toolchain {
 	channel: string;
@@ -24,14 +25,6 @@ const DEFAULT_TOOLCHAIN: Toolchain = {
 	targets: [],
 };
 
-function getCargoHome(): string {
-	if (process.env.CARGO_HOME) {
-		return process.env.CARGO_HOME;
-	}
-
-	return path.join(os.homedir(), '.cargo');
-}
-
 function parseConfig(configPath: string): Partial<Toolchain> {
 	const contents = fs.readFileSync(configPath, 'utf8').trim();
 
@@ -126,7 +119,7 @@ async function installToolchain(toolchain: Toolchain) {
 
 	core.info('Logging installed toolchain versions');
 
-	await exec.exec('rustc', [`+${toolchain.channel}`, '--version', '--verbose']);
+	await extractRustVersion(toolchain.channel);
 }
 
 async function downloadAndInstallBinstall(binDir: string) {
@@ -178,13 +171,17 @@ async function installBins() {
 		.filter(Boolean)
 		.map((bin) => (bin.startsWith('cargo-') ? bin : `cargo-${bin}`));
 
+	if (CACHE_ENABLED) {
+		bins.push('cargo-cache');
+	}
+
 	if (bins.length === 0) {
 		return;
 	}
 
 	core.info('Installing additional binaries');
 
-	const binDir = path.join(getCargoHome(), 'bin');
+	const binDir = path.join(CARGO_HOME, 'bin');
 
 	if (!fs.existsSync(path.join(binDir, 'cargo-binstall'))) {
 		await downloadAndInstallBinstall(binDir);
@@ -205,6 +202,10 @@ async function run() {
 	try {
 		await installToolchain(detectToolchain());
 		await installBins();
+
+		// Restore cache after the toolchain has been installed,
+		// as we use the rust version and commit hashes in the cache key!
+		await restoreCache();
 	} catch (error: unknown) {
 		core.setFailed((error as Error).message);
 
@@ -213,7 +214,7 @@ async function run() {
 
 	core.info('Adding ~/.cargo/bin to PATH');
 
-	core.addPath(path.join(getCargoHome(), 'bin'));
+	core.addPath(path.join(CARGO_HOME, 'bin'));
 }
 
 void run();
diff --git a/package.json b/package.json
index 17c6cde..f639aa5 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "description": "A GitHub action for setting up Rust and Cargo.",
   "main": "dist/index.js",
   "scripts": {
-    "build": "ncc build ./index.ts",
+    "build": "ncc build ./index.ts && ncc build ./post.ts --out ./dist/post",
     "check": "npm run lint && npm run typecheck",
     "lint": "eslint --ext .ts,.js --fix .",
     "typecheck": "tsc --noEmit"
@@ -16,8 +16,10 @@
   "author": "Miles Johnson",
   "license": "MIT",
   "dependencies": {
+    "@actions/cache": "^3.2.1",
     "@actions/core": "^1.10.0",
     "@actions/exec": "^1.1.1",
+    "@actions/glob": "^0.4.0",
     "@actions/tool-cache": "^2.0.1",
     "@ltd/j-toml": "^1.38.0"
   },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d35ecbb..d56c81f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,12 +1,18 @@
 lockfileVersion: '6.0'
 
 dependencies:
+  '@actions/cache':
+    specifier: ^3.2.1
+    version: 3.2.1
   '@actions/core':
     specifier: ^1.10.0
     version: 1.10.0
   '@actions/exec':
     specifier: ^1.1.1
     version: 1.1.1
+  '@actions/glob':
+    specifier: ^0.4.0
+    version: 0.4.0
   '@actions/tool-cache':
     specifier: ^2.0.1
     version: 2.0.1
@@ -42,6 +48,23 @@ devDependencies:
 
 packages:
 
+  /@actions/cache@3.2.1:
+    resolution: {integrity: sha512-QurbMiY//02+0kN1adJkMHN44RcZ5kAXfhSnKUZmtSmhMTNqLitGArG1xOkt93NNyByTlLGAc5wIOF/dZ2ENOQ==}
+    dependencies:
+      '@actions/core': 1.10.0
+      '@actions/exec': 1.1.1
+      '@actions/glob': 0.1.2
+      '@actions/http-client': 2.1.0
+      '@actions/io': 1.1.3
+      '@azure/abort-controller': 1.1.0
+      '@azure/ms-rest-js': 2.6.6
+      '@azure/storage-blob': 12.14.0
+      semver: 6.3.0
+      uuid: 3.4.0
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /@actions/core@1.10.0:
     resolution: {integrity: sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==}
     dependencies:
@@ -55,6 +78,20 @@ packages:
       '@actions/io': 1.1.3
     dev: false
 
+  /@actions/glob@0.1.2:
+    resolution: {integrity: sha512-SclLR7Ia5sEqjkJTPs7Sd86maMDw43p769YxBOxvPvEWuPEhpAnBsQfENOpXjFYMmhCqd127bmf+YdvJqVqR4A==}
+    dependencies:
+      '@actions/core': 1.10.0
+      minimatch: 3.1.2
+    dev: false
+
+  /@actions/glob@0.4.0:
+    resolution: {integrity: sha512-+eKIGFhsFa4EBwaf/GMyzCdWrXWymGXfFmZU3FHQvYS8mPcHtTtZONbkcqqUMzw9mJ/pImEBFET1JNifhqGsAQ==}
+    dependencies:
+      '@actions/core': 1.10.0
+      minimatch: 3.1.2
+    dev: false
+
   /@actions/http-client@2.1.0:
     resolution: {integrity: sha512-BonhODnXr3amchh4qkmjPMUO8mFi/zLaaCeCAJZqch8iQqyDnVIkySjB38VHAC8IJ+bnlgfOqlhpyCUZHlQsqw==}
     dependencies:
@@ -76,6 +113,115 @@ packages:
       uuid: 3.4.0
     dev: false
 
+  /@azure/abort-controller@1.1.0:
+    resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
+    engines: {node: '>=12.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/core-auth@1.4.0:
+    resolution: {integrity: sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==}
+    engines: {node: '>=12.0.0'}
+    dependencies:
+      '@azure/abort-controller': 1.1.0
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/core-http@3.0.1:
+    resolution: {integrity: sha512-A3x+um3cAPgQe42Lu7Iv/x8/fNjhL/nIoEfqFxfn30EyxK6zC13n+OUxzZBRC0IzQqssqIbt4INf5YG7lYYFtw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@azure/abort-controller': 1.1.0
+      '@azure/core-auth': 1.4.0
+      '@azure/core-tracing': 1.0.0-preview.13
+      '@azure/core-util': 1.3.1
+      '@azure/logger': 1.0.4
+      '@types/node-fetch': 2.6.3
+      '@types/tunnel': 0.0.3
+      form-data: 4.0.0
+      node-fetch: 2.6.9
+      process: 0.11.10
+      tslib: 2.5.0
+      tunnel: 0.0.6
+      uuid: 8.3.2
+      xml2js: 0.5.0
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
+  /@azure/core-lro@2.5.2:
+    resolution: {integrity: sha512-tucUutPhBwCPu6v16KEFYML81npEL6gnT+iwewXvK5ZD55sr0/Vw2jfQETMiKVeARRrXHB2QQ3SpxxGi1zAUWg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@azure/abort-controller': 1.1.0
+      '@azure/core-util': 1.3.1
+      '@azure/logger': 1.0.4
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/core-paging@1.5.0:
+    resolution: {integrity: sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/core-tracing@1.0.0-preview.13:
+    resolution: {integrity: sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==}
+    engines: {node: '>=12.0.0'}
+    dependencies:
+      '@opentelemetry/api': 1.4.1
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/core-util@1.3.1:
+    resolution: {integrity: sha512-pjfOUAb+MPLODhGuXot/Hy8wUgPD0UTqYkY3BiYcwEETrLcUCVM1t0roIvlQMgvn1lc48TGy5bsonsFpF862Jw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@azure/abort-controller': 1.1.0
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/logger@1.0.4:
+    resolution: {integrity: sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@azure/ms-rest-js@2.6.6:
+    resolution: {integrity: sha512-WYIda8VvrkZE68xHgOxUXvjThxNf1nnGPPe0rAljqK5HJHIZ12Pi3YhEDOn3Ge7UnwaaM3eFO0VtAy4nGVI27Q==}
+    dependencies:
+      '@azure/core-auth': 1.4.0
+      abort-controller: 3.0.0
+      form-data: 2.5.1
+      node-fetch: 2.6.9
+      tough-cookie: 3.0.1
+      tslib: 1.14.1
+      tunnel: 0.0.6
+      uuid: 8.3.2
+      xml2js: 0.5.0
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
+  /@azure/storage-blob@12.14.0:
+    resolution: {integrity: sha512-g8GNUDpMisGXzBeD+sKphhH5yLwesB4JkHr1U6be/X3F+cAMcyGLPD1P89g2M7wbEtUJWoikry1rlr83nNRBzg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@azure/abort-controller': 1.1.0
+      '@azure/core-http': 3.0.1
+      '@azure/core-lro': 2.5.2
+      '@azure/core-paging': 1.5.0
+      '@azure/core-tracing': 1.0.0-preview.13
+      '@azure/logger': 1.0.4
+      events: 3.3.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /@babel/code-frame@7.21.4:
     resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
     engines: {node: '>=6.9.0'}
@@ -199,6 +345,11 @@ packages:
       fastq: 1.15.0
     dev: true
 
+  /@opentelemetry/api@1.4.1:
+    resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==}
+    engines: {node: '>=8.0.0'}
+    dev: false
+
   /@tsconfig/node14@1.0.3:
     resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
     dev: true
@@ -211,9 +362,15 @@ packages:
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
     dev: true
 
+  /@types/node-fetch@2.6.3:
+    resolution: {integrity: sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==}
+    dependencies:
+      '@types/node': 18.15.10
+      form-data: 3.0.1
+    dev: false
+
   /@types/node@18.15.10:
     resolution: {integrity: sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==}
-    dev: true
 
   /@types/normalize-package-data@2.4.1:
     resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -223,6 +380,12 @@ packages:
     resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
     dev: true
 
+  /@types/tunnel@0.0.3:
+    resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==}
+    dependencies:
+      '@types/node': 18.15.10
+    dev: false
+
   /@typescript-eslint/eslint-plugin@5.58.0(@typescript-eslint/parser@5.58.0)(eslint@8.36.0)(typescript@5.0.2):
     resolution: {integrity: sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -358,6 +521,13 @@ packages:
     hasBin: true
     dev: true
 
+  /abort-controller@3.0.0:
+    resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+    engines: {node: '>=6.5'}
+    dependencies:
+      event-target-shim: 5.0.1
+    dev: false
+
   /acorn-jsx@5.3.2(acorn@8.8.2):
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
@@ -473,6 +643,10 @@ packages:
     resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
     dev: true
 
+  /asynckit@0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+    dev: false
+
   /available-typed-arrays@1.0.5:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
@@ -491,14 +665,12 @@ packages:
 
   /balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-    dev: true
 
   /brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
       balanced-match: 1.0.2
       concat-map: 0.0.1
-    dev: true
 
   /braces@3.0.2:
     resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
@@ -589,9 +761,15 @@ packages:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
     dev: true
 
+  /combined-stream@1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+    dependencies:
+      delayed-stream: 1.0.0
+    dev: false
+
   /concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-    dev: true
 
   /confusing-browser-globals@1.0.11:
     resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==}
@@ -672,6 +850,11 @@ packages:
       object-keys: 1.1.1
     dev: true
 
+  /delayed-stream@1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+    dev: false
+
   /dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -1239,6 +1422,16 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /event-target-shim@5.0.1:
+    resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+    engines: {node: '>=6'}
+    dev: false
+
+  /events@3.3.0:
+    resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+    engines: {node: '>=0.8.x'}
+    dev: false
+
   /execa@5.1.1:
     resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
     engines: {node: '>=10'}
@@ -1331,6 +1524,33 @@ packages:
       is-callable: 1.2.7
     dev: true
 
+  /form-data@2.5.1:
+    resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==}
+    engines: {node: '>= 0.12'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
+  /form-data@3.0.1:
+    resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
+    engines: {node: '>= 6'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
+  /form-data@4.0.0:
+    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+    engines: {node: '>= 6'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
   /fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
     dev: true
@@ -1540,6 +1760,11 @@ packages:
       side-channel: 1.0.4
     dev: true
 
+  /ip-regex@2.1.0:
+    resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==}
+    engines: {node: '>=4'}
+    dev: false
+
   /is-arguments@1.1.1:
     resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
     engines: {node: '>= 0.4'}
@@ -1857,6 +2082,18 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+    dev: false
+
+  /mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-db: 1.52.0
+    dev: false
+
   /mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     engines: {node: '>=6'}
@@ -1871,7 +2108,6 @@ packages:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
     dependencies:
       brace-expansion: 1.1.11
-    dev: true
 
   /minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@@ -1893,6 +2129,18 @@ packages:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
 
+  /node-fetch@2.6.9:
+    resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
+    engines: {node: 4.x || >=6.0.0}
+    peerDependencies:
+      encoding: ^0.1.0
+    peerDependenciesMeta:
+      encoding:
+        optional: true
+    dependencies:
+      whatwg-url: 5.0.0
+    dev: false
+
   /node-releases@2.0.10:
     resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
     dev: true
@@ -2107,6 +2355,11 @@ packages:
     hasBin: true
     dev: true
 
+  /process@0.11.10:
+    resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+    engines: {node: '>= 0.6.0'}
+    dev: false
+
   /prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
     dependencies:
@@ -2115,10 +2368,13 @@ packages:
       react-is: 16.13.1
     dev: true
 
+  /psl@1.9.0:
+    resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
+    dev: false
+
   /punycode@2.3.0:
     resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
     engines: {node: '>=6'}
-    dev: true
 
   /queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -2232,6 +2488,10 @@ packages:
       regexp-tree: 0.1.24
     dev: true
 
+  /sax@1.2.4:
+    resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
+    dev: false
+
   /semver@5.7.1:
     resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
     hasBin: true
@@ -2418,6 +2678,19 @@ packages:
       is-number: 7.0.0
     dev: true
 
+  /tough-cookie@3.0.1:
+    resolution: {integrity: sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==}
+    engines: {node: '>=6'}
+    dependencies:
+      ip-regex: 2.1.0
+      psl: 1.9.0
+      punycode: 2.3.0
+    dev: false
+
+  /tr46@0.0.3:
+    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+    dev: false
+
   /tsconfig-moon@1.3.0:
     resolution: {integrity: sha512-OVa+cjaKIsXIQqEWVqvF3xH6xmFOjKnbwiCmAKx2J8uq7M1KTX/NFFi/YXZdKb6YjHJhq8tvg2JsGSBTxTANcQ==}
     dev: true
@@ -2433,7 +2706,10 @@ packages:
 
   /tslib@1.14.1:
     resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-    dev: true
+
+  /tslib@2.5.0:
+    resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
+    dev: false
 
   /tsutils@3.21.0(typescript@5.0.2):
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@@ -2530,6 +2806,17 @@ packages:
       spdx-expression-parse: 3.0.1
     dev: true
 
+  /webidl-conversions@3.0.1:
+    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+    dev: false
+
+  /whatwg-url@5.0.0:
+    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+    dependencies:
+      tr46: 0.0.3
+      webidl-conversions: 3.0.1
+    dev: false
+
   /which-boxed-primitive@1.0.2:
     resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
     dependencies:
@@ -2578,6 +2865,19 @@ packages:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
     dev: true
 
+  /xml2js@0.5.0:
+    resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
+    engines: {node: '>=4.0.0'}
+    dependencies:
+      sax: 1.2.4
+      xmlbuilder: 11.0.1
+    dev: false
+
+  /xmlbuilder@11.0.1:
+    resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
+    engines: {node: '>=4.0'}
+    dev: false
+
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
     dev: true
diff --git a/post.ts b/post.ts
new file mode 100644
index 0000000..077c816
--- /dev/null
+++ b/post.ts
@@ -0,0 +1,12 @@
+import * as core from '@actions/core';
+import { saveCache } from './src/cargo';
+
+async function run() {
+	try {
+		await saveCache();
+	} catch (error: unknown) {
+		core.setFailed((error as Error).message);
+	}
+}
+
+void run();
diff --git a/src/cargo.ts b/src/cargo.ts
new file mode 100644
index 0000000..db480e5
--- /dev/null
+++ b/src/cargo.ts
@@ -0,0 +1,120 @@
+/* eslint-disable node/no-unsupported-features/node-builtins */
+
+import crypto from 'crypto';
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import * as cache from '@actions/cache';
+import * as core from '@actions/core';
+import * as exec from '@actions/exec';
+import * as glob from '@actions/glob';
+
+// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+export const CARGO_HOME = process.env.CARGO_HOME || path.join(os.homedir(), '.cargo');
+
+export const CACHE_ENABLED = core.getBooleanInput('cache') || cache.isFeatureAvailable();
+
+let CACHE_KEY = '';
+
+export async function getPrimaryCacheKey() {
+	if (CACHE_KEY) {
+		return CACHE_KEY;
+	}
+
+	core.info('Generating cache key');
+
+	const rustVersion = core.getState('rust-version');
+
+	core.debug(`Hashing Rust version = ${rustVersion}`);
+
+	const rustHash = core.getState('rust-hash');
+
+	core.debug(`Hashing Rust commit hash = ${rustHash}`);
+
+	const lockHash = await glob.hashFiles('Cargo.lock');
+
+	core.debug(`Hashing Cargo.lock = ${lockHash}`);
+
+	const hasher = crypto.createHash('sha1');
+	hasher.update(rustVersion);
+	hasher.update(rustHash);
+	hasher.update(lockHash);
+
+	// eslint-disable-next-line require-atomic-updates
+	CACHE_KEY = `setup-rustcargo-${process.platform}-${hasher.digest('hex')}`;
+
+	return CACHE_KEY;
+}
+
+export function getPathsToCache(): string[] {
+	return [
+		// ~/.cargo/registry
+		path.join(CARGO_HOME, 'registry'),
+		// /workspace/target/debug
+		path.join(process.cwd(), 'target/debug'),
+	];
+}
+
+export async function cleanCargoRegistry() {
+	core.info('Cleaning cache before saving');
+
+	const registryDir = path.join(CARGO_HOME, 'registry');
+
+	// .cargo/registry/src - Delete entirely
+	await exec.exec('cargo', ['cache', '--autoclean']);
+
+	// .cargo/registry/index - Delete .cache directories
+	const globber = await glob.create(path.join(registryDir, 'index/**/.cache'));
+
+	for await (const file of globber.globGenerator()) {
+		core.debug(`Deleting ${file}`);
+		await fs.promises.unlink(file);
+	}
+
+	// .cargo/registry/cache - Do nothing?
+}
+
+export async function saveCache() {
+	if (!CACHE_ENABLED) {
+		return;
+	}
+
+	const primaryKey = await getPrimaryCacheKey();
+	const cacheHitKey = core.getState('cache-hit-key');
+
+	if (cacheHitKey === primaryKey) {
+		core.info(`Cache hit occured on the key ${cacheHitKey}, not saving cache`);
+		return;
+	}
+
+	await cleanCargoRegistry();
+
+	core.info(`Saving cache with key ${primaryKey}`);
+
+	await cache.saveCache(getPathsToCache(), primaryKey);
+}
+
+export async function restoreCache() {
+	if (!CACHE_ENABLED) {
+		return;
+	}
+
+	core.info('Attempting to restore cache');
+
+	const primaryKey = await getPrimaryCacheKey();
+
+	const cacheKey = await cache.restoreCache(getPathsToCache(), primaryKey, [
+		`setup-rustcargo-${process.platform}`,
+		'setup-rustcargo',
+	]);
+
+	if (cacheKey) {
+		core.saveState('cache-hit-key', cacheKey);
+		core.info(`Cache restored using key ${primaryKey}`);
+	} else {
+		core.warning(`Cache does not exist using key ${primaryKey}`);
+	}
+
+	core.setOutput('cache-key', cacheKey ?? primaryKey);
+	core.setOutput('cache-hit', !!cacheKey);
+}
diff --git a/src/rust.ts b/src/rust.ts
new file mode 100644
index 0000000..f2333b8
--- /dev/null
+++ b/src/rust.ts
@@ -0,0 +1,31 @@
+import * as core from '@actions/core';
+import * as exec from '@actions/exec';
+
+export async function extractRustVersion(toolchain: string) {
+	let out = '';
+
+	await exec.exec('rustc', [`+${toolchain}`, '--version', '--verbose'], {
+		listeners: {
+			stdout(data: Buffer) {
+				out += data.toString();
+			},
+		},
+	});
+
+	out.split('\n').forEach((line) => {
+		let key = '';
+
+		if (line.startsWith('commit-hash')) {
+			key = 'rust-hash';
+		} else if (line.startsWith('release')) {
+			key = 'rust-version';
+		} else {
+			return;
+		}
+
+		const value = line.split(':')[1].trim();
+
+		core.saveState(key, value);
+		core.setOutput(key, value);
+	});
+}