diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0ed23..0448b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Removed the job id from the cache key. This allows the same cache to be used + accross multiple jobs if the installation arguments are the same. +- Improved the cache key generation logic. + ## [3.2.0] - 2024-12-26 ### Changed diff --git a/dist/index.js b/dist/index.js index b4151b0..d298059 100644 --- a/dist/index.js +++ b/dist/index.js @@ -19414,7 +19414,7 @@ var require_exec = __commonJS({ exports2.getExecOutput = exports2.exec = void 0; var string_decoder_1 = require("string_decoder"); var tr = __importStar2(require_toolrunner()); - function exec5(commandLine, args, options) { + function exec6(commandLine, args, options) { return __awaiter2(this, void 0, void 0, function* () { const commandArgs = tr.argStringToArray(commandLine); if (commandArgs.length === 0) { @@ -19426,7 +19426,7 @@ var require_exec = __commonJS({ return runner.exec(); }); } - exports2.exec = exec5; + exports2.exec = exec6; function getExecOutput2(commandLine, args, options) { var _a, _b; return __awaiter2(this, void 0, void 0, function* () { @@ -19449,7 +19449,7 @@ var require_exec = __commonJS({ } }; const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener }); - const exitCode = yield exec5(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + const exitCode = yield exec6(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); stdout += stdoutDecoder.end(); stderr += stderrDecoder.end(); return { @@ -19527,12 +19527,12 @@ var require_platform = __commonJS({ Object.defineProperty(exports2, "__esModule", { value: true }); exports2.getDetails = exports2.isLinux = exports2.isMacOS = exports2.isWindows = exports2.arch = exports2.platform = void 0; var os_1 = __importDefault2(require("os")); - var exec5 = __importStar2(require_exec()); + var exec6 = __importStar2(require_exec()); var getWindowsInfo = () => __awaiter2(void 0, void 0, void 0, function* () { - const { stdout: version } = yield exec5.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', void 0, { + const { stdout: version } = yield exec6.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', void 0, { silent: true }); - const { stdout: name } = yield exec5.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', void 0, { + const { stdout: name } = yield exec6.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', void 0, { silent: true }); return { @@ -19542,7 +19542,7 @@ var require_platform = __commonJS({ }); var getMacOsInfo = () => __awaiter2(void 0, void 0, void 0, function* () { var _a, _b, _c, _d; - const { stdout } = yield exec5.getExecOutput("sw_vers", void 0, { + const { stdout } = yield exec6.getExecOutput("sw_vers", void 0, { silent: true }); const version = (_b = (_a = stdout.match(/ProductVersion:\s*(.+)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : ""; @@ -19553,7 +19553,7 @@ var require_platform = __commonJS({ }; }); var getLinuxInfo = () => __awaiter2(void 0, void 0, void 0, function* () { - const { stdout } = yield exec5.getExecOutput("lsb_release", ["-i", "-r", "-s"], { + const { stdout } = yield exec6.getExecOutput("lsb_release", ["-i", "-r", "-s"], { silent: true }); const [name, version] = stdout.trim().split("\n"); @@ -22779,7 +22779,7 @@ var require_cacheUtils = __commonJS({ Object.defineProperty(exports2, "__esModule", { value: true }); exports2.getRuntimeToken = exports2.getCacheVersion = exports2.assertDefined = exports2.getGnuTarPathOnWindows = exports2.getCacheFileName = exports2.getCompressionMethod = exports2.unlinkFile = exports2.resolvePaths = exports2.getArchiveFileSizeInBytes = exports2.createTempDirectory = void 0; var core6 = __importStar2(require_core()); - var exec5 = __importStar2(require_exec()); + var exec6 = __importStar2(require_exec()); var glob = __importStar2(require_glob()); var io2 = __importStar2(require_io()); var crypto2 = __importStar2(require("crypto")); @@ -22863,7 +22863,7 @@ var require_cacheUtils = __commonJS({ additionalArgs.push("--version"); core6.debug(`Checking ${app} ${additionalArgs.join(" ")}`); try { - yield exec5.exec(`${app}`, additionalArgs, { + yield exec6.exec(`${app}`, additionalArgs, { ignoreReturnCode: true, silent: true, listeners: { @@ -68818,10 +68818,10 @@ var require_semver3 = __commonJS({ }); // src/index.ts +var cache = __toESM(require_cache3()); var core5 = __toESM(require_core()); +var exec4 = __toESM(require_exec()); var io = __toESM(require_io()); -var cache = __toESM(require_cache3()); -var import_node_path2 = __toESM(require("node:path")); // node_modules/.pnpm/chalk@5.4.1/node_modules/chalk/source/vendor/ansi-styles/index.js var ANSI_BACKGROUND_OFFSET = 10; @@ -69316,26 +69316,63 @@ Object.defineProperties(createChalk.prototype, styles2); var chalk = createChalk(); var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 }); +// src/index.ts +var import_node_path2 = __toESM(require("node:path")); + // src/install.ts var core = __toESM(require_core()); var exec = __toESM(require_exec()); -var import_node_path = __toESM(require("node:path")); var import_node_crypto = __toESM(require("node:crypto")); +var import_node_path = __toESM(require("node:path")); async function getInstallSettings(input, version) { const homePath = process.env.HOME ?? process.env.USERPROFILE; if (homePath === void 0 || homePath === "") { - core.setFailed( - "Could not determine home directory (missing HOME and USERPROFILE environement variables)" - ); + core.setFailed("Could not determine home directory"); process.exit(1); } const installPath = import_node_path.default.join(homePath, ".cargo-install", input.crate); - const cacheKey = await getCacheKey(input, version); + const args = getInstallArgs(input, version, installPath); + const cacheKey = await getCacheKey(input, version, args); return { path: installPath, + args, cacheKey }; } +function getInstallArgs(input, version, installPath) { + let args = ["install", input.crate, "--force", "--root", installPath]; + if ("version" in version) { + args.push("--version", version.version); + } else { + args.push("--git", version.repository, "--rev", version.commit); + } + if (input.source.type === "registry" && input.source.registry) { + args.push("--registry", input.source.registry); + } + if (input.source.type === "registry" && input.source.index) { + args.push("--index", input.source.index); + } + if (input.features.length > 0) { + args.push("--features", input.features.join(",")); + } + if (input.args.length > 0) { + args = args.concat(input.args); + } + return args; +} +async function getCacheKey(input, version, args) { + const runnerOs = process.env.RUNNER_OS; + const runnerArch = process.env.RUNNER_ARCH; + const osVersion = await getOsVersion(); + if (runnerOs === void 0 || runnerArch === void 0) { + core.setFailed("Could not determine runner OS or runner arch"); + process.exit(1); + } + const hashKey = runnerOs + runnerArch + (osVersion ?? "") + args.join(" ") + (input.cacheKey ?? ""); + const hash = import_node_crypto.default.createHash("sha256").update(hashKey).digest("hex").slice(0, 24); + const versionKey = "version" in version ? version.version : version.commit.slice(0, 7); + return `cargo-install-${input.crate}-${versionKey}-${hash}`; +} async function getOsVersion() { const runnerOs = process.env.RUNNER_OS; if (runnerOs === "Linux") { @@ -69365,60 +69402,6 @@ async function getOsVersion() { return `${major.stdout.trim()}.${minor.stdout.trim()}`; } } -async function getCacheKey(input, version) { - const runnerOs = process.env.RUNNER_OS; - const runnerArch = process.env.RUNNER_ARCH; - const jobId = process.env.GITHUB_JOB; - const osVersion = await getOsVersion(); - if (runnerOs === void 0 || runnerArch === void 0 || jobId === void 0) { - core.setFailed("Could not determine runner OS, runner arch or job ID"); - process.exit(1); - } - let hashKey = jobId + runnerOs + runnerArch + (osVersion ?? ""); - hashKey += input.source.type; - if (input.source.type === "registry") { - hashKey += input.source.registry ?? ""; - hashKey += input.source.index ?? ""; - } else { - hashKey += input.source.repository; - hashKey += input.source.branch ?? ""; - hashKey += input.source.tag ?? ""; - hashKey += input.source.commit ?? ""; - } - for (const feature of input.features) { - hashKey += feature; - } - for (const arg of input.args) { - hashKey += arg; - } - if (input.cacheKey?.length > 0) { - hashKey += input.cacheKey; - } - const hash = import_node_crypto.default.createHash("sha256").update(hashKey).digest("hex").slice(0, 20); - const versionKey = "version" in version ? version.version : version.commit.slice(0, 7); - return `cargo-install-${input.crate}-${versionKey}-${hash}`; -} -async function runCargoInstall(input, version, install) { - let commandArgs = ["install", input.crate, "--force", "--root", install.path]; - if ("version" in version) { - commandArgs.push("--version", version.version); - } else { - commandArgs.push("--git", version.repository, "--rev", version.commit); - } - if (input.source.type === "registry" && input.source.registry !== void 0) { - commandArgs.push("--registry", input.source.registry); - } - if (input.source.type === "registry" && input.source.index !== void 0) { - commandArgs.push("--index", input.source.index); - } - if (input.features.length > 0) { - commandArgs.push("--features", input.features.join(",")); - } - if (input.args.length > 0) { - commandArgs = commandArgs.concat(input.args); - } - await exec.exec("cargo", commandArgs); -} // src/parse.ts var core2 = __toESM(require_core()); @@ -69513,9 +69496,76 @@ function parseInput() { }; } +// src/resolve/git.ts +var core3 = __toESM(require_core()); +var exec2 = __toESM(require_exec()); +async function resolveGitCommit(git) { + core3.info(`Fetching git commits for ${git.repository}...`); + const commits = await fetchGitRemote(git.repository); + if (git.commit !== void 0) { + core3.info(`Using explicit commit ${git.commit} for ${git.repository}`); + return { repository: git.repository, commit: git.commit }; + } + if (git.tag !== void 0) { + const commit = commits.tags[git.tag]; + if (commit === void 0) { + core3.setFailed(`Failed to resolve tag ${git.tag} for ${git.repository}`); + process.exit(1); + } + core3.info(`Resolved tag ${git.tag} to commit ${commit}`); + return { repository: git.repository, commit }; + } + if (git.branch !== void 0) { + const commit = commits.branches[git.branch]; + if (commit === void 0) { + core3.setFailed( + `Failed to resolve branch ${git.branch} for ${git.repository}` + ); + process.exit(1); + } + core3.info(`Resolved branch ${git.branch} to commit ${commit}`); + return { repository: git.repository, commit }; + } + core3.info(`Resolved HEAD to commit ${commits.head}`); + return { repository: git.repository, commit: commits.head }; +} +async function fetchGitRemote(repository) { + const chunks = []; + await exec2.exec("git", ["ls-remote", repository], { + listeners: { stdout: (data) => chunks.push(data) }, + silent: true + }); + const output = Buffer.concat(chunks).toString("utf-8"); + const commits = { head: "", tags: {}, branches: {} }; + for (const line of output.split("\n")) { + const [commit, ref] = line.split(" "); + if (commit === "" || ref === "" || ref === void 0) { + continue; + } + if (ref === "HEAD") { + commits.head = commit; + } + const tagMatch = "refs/tags/"; + if (ref.startsWith(tagMatch)) { + const tag = ref.slice(tagMatch.length); + commits.tags[tag] = commit; + } + const branchMatch = "refs/heads/"; + if (ref.startsWith(branchMatch)) { + const branch = ref.slice(branchMatch.length); + commits.branches[branch] = commit; + } + } + if (commits.head === "") { + core3.setFailed(`Failed to fetch HEAD commit for ${repository}`); + process.exit(1); + } + return commits; +} + // src/resolve/registry.ts +var core4 = __toESM(require_core()); var http = __toESM(require_lib()); -var core3 = __toESM(require_core()); var import_semver = __toESM(require_semver3()); // node_modules/.pnpm/valibot@0.33.3/node_modules/valibot/dist/index.js @@ -69750,11 +69800,11 @@ async function resolveRegistryVersion(crate, { version, registry, index }) { const registryIndex = index !== void 0 ? parseRegistryIndex(index) : { sparse: true, url: "https://index.crates.io/" }; const nonSparseIndex = registry !== void 0 || !registryIndex.sparse; if (isVersionRange && nonSparseIndex) { - core3.error("Version ranges can only be used with sparse indexes"); + core4.error("Version ranges can only be used with sparse indexes"); process.exit(1); } if (!isVersionRange && nonSparseIndex) { - core3.info("Using non-sparse index, skipping version resolution"); + core4.info("Using non-sparse index, skipping version resolution"); return { version }; } return await resolveCrateVersion(crate, version, registryIndex); @@ -69766,7 +69816,7 @@ function parseRegistryIndex(input) { return { sparse, url }; } async function resolveCrateVersion(crate, version, index) { - core3.info(`Fetching information for ${crate} from index ...`); + core4.info(`Fetching information for ${crate} from index ...`); const versions = await fetchIndex(crate, index.url); const sortedVersions = versions.sort((a, b) => import_semver.default.compare(a.vers, b.vers)).reverse(); const latest = sortedVersions.find((ver) => !ver.yanked && !import_semver.default.prerelease(ver.vers)) ?? sortedVersions[0]; @@ -69777,17 +69827,17 @@ async function resolveCrateVersion(crate, version, index) { (ver) => import_semver.default.satisfies(ver.vers, version) ); if (resolved.length === 0) { - core3.setFailed(`No version found for ${crate} that satisfies ${version}`); - core3.info( + core4.setFailed(`No version found for ${crate} that satisfies ${version}`); + core4.info( `Available versions: ${versions.map((ver) => ver.vers).join(", ")}` ); process.exit(1); } const resolvedVersion = resolved.find((ver) => !ver.yanked) ?? resolved[0]; if (resolvedVersion.yanked) { - core3.warning(`Using yanked version ${resolvedVersion.vers} for ${crate}`); + core4.warning(`Using yanked version ${resolvedVersion.vers} for ${crate}`); } else if (resolvedVersion.vers !== latest.vers) { - core3.warning( + core4.warning( `New version for ${crate} available: ${sortedVersions[0].vers}` ); } @@ -69798,11 +69848,11 @@ async function fetchIndex(crate, indexUrl) { const client = new http.HttpClient("cargo-install-action"); const response = await client.get(url.toString()); if (response.message.statusCode === 404) { - core3.setFailed(`Crate ${crate} not found on crates.io index`); + core4.setFailed(`Crate ${crate} not found on crates.io index`); process.exit(1); } else if (response.message.statusCode !== 200) { - core3.setFailed(`Failed to fetch crate ${crate} on crates.io index`); - core3.info( + core4.setFailed(`Failed to fetch crate ${crate} on crates.io index`); + core4.info( `Error: ${response.message.statusMessage ?? ""} (${response.message.statusCode ?? ""})` ); process.exit(1); @@ -69824,73 +69874,6 @@ function getIndexPath(crate) { return `${name.slice(0, 2)}/${name.slice(2, 4)}/${name}`; } -// src/resolve/git.ts -var exec3 = __toESM(require_exec()); -var core4 = __toESM(require_core()); -async function resolveGitCommit(git) { - core4.info(`Fetching git commits for ${git.repository}...`); - const commits = await fetchGitRemote(git.repository); - if (git.commit !== void 0) { - core4.info(`Using explicit commit ${git.commit} for ${git.repository}`); - return { repository: git.repository, commit: git.commit }; - } - if (git.tag !== void 0) { - const commit = commits.tags[git.tag]; - if (commit === void 0) { - core4.setFailed(`Failed to resolve tag ${git.tag} for ${git.repository}`); - process.exit(1); - } - core4.info(`Resolved tag ${git.tag} to commit ${commit}`); - return { repository: git.repository, commit }; - } - if (git.branch !== void 0) { - const commit = commits.branches[git.branch]; - if (commit === void 0) { - core4.setFailed( - `Failed to resolve branch ${git.branch} for ${git.repository}` - ); - process.exit(1); - } - core4.info(`Resolved branch ${git.branch} to commit ${commit}`); - return { repository: git.repository, commit }; - } - core4.info(`Resolved HEAD to commit ${commits.head}`); - return { repository: git.repository, commit: commits.head }; -} -async function fetchGitRemote(repository) { - const chunks = []; - await exec3.exec("git", ["ls-remote", repository], { - listeners: { stdout: (data) => chunks.push(data) }, - silent: true - }); - const output = Buffer.concat(chunks).toString("utf-8"); - const commits = { head: "", tags: {}, branches: {} }; - for (const line of output.split("\n")) { - const [commit, ref] = line.split(" "); - if (commit === "" || ref === "" || ref === void 0) { - continue; - } - if (ref === "HEAD") { - commits.head = commit; - } - const tagMatch = "refs/tags/"; - if (ref.startsWith(tagMatch)) { - const tag = ref.slice(tagMatch.length); - commits.tags[tag] = commit; - } - const branchMatch = "refs/heads/"; - if (ref.startsWith(branchMatch)) { - const branch = ref.slice(branchMatch.length); - commits.branches[branch] = commit; - } - } - if (commits.head === "") { - core4.setFailed(`Failed to fetch HEAD commit for ${repository}`); - process.exit(1); - } - return commits; -} - // src/index.ts var chalk2 = new Chalk({ level: 3 }); async function run() { @@ -69907,6 +69890,7 @@ async function run() { } core5.info(` path: ${install.path}`); core5.info(` key: ${install.cacheKey}`); + core5.info(` command: cargo ${install.args.join(" ")}`); await io.mkdirP(install.path); const restored = await cache.restoreCache([install.path], install.cacheKey); core5.endGroup(); @@ -69918,14 +69902,14 @@ async function run() { core5.startGroup( `No cached version found, installing ${input.crate} using cargo...` ); - await runCargoInstall(input, version, install); + await exec4.exec("cargo", install.args); try { await cache.saveCache([install.path], install.cacheKey); } catch (error2) { if (error2 instanceof Error) { core5.warning(error2.message); } else { - core5.warning("An error occurred while saving the cache."); + core5.warning("An unknown error occurred while saving the cache."); } } core5.endGroup(); diff --git a/package.json b/package.json index b62a6b4..5f6ba3b 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,15 @@ "esbuild": "^0.24.2", "eslint": "^9.17.0", "prettier": "^3.4.2", + "prettier-plugin-organize-imports": "^4.1.0", "typescript": "^5.7.2", "typescript-eslint": "^8.18.2" }, "prettier": { "arrowParens": "avoid", - "singleQuote": true + "singleQuote": true, + "plugins": [ + "prettier-plugin-organize-imports" + ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a971f77..e858f55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: prettier: specifier: ^3.4.2 version: 3.4.2 + prettier-plugin-organize-imports: + specifier: ^4.1.0 + version: 4.1.0(prettier@3.4.2)(typescript@5.7.2) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -819,6 +822,16 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-plugin-organize-imports@4.1.0: + resolution: {integrity: sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==} + peerDependencies: + prettier: '>=2.0' + typescript: '>=2.9' + vue-tsc: ^2.1.0 + peerDependenciesMeta: + vue-tsc: + optional: true + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -1804,6 +1817,11 @@ snapshots: prelude-ls@1.2.1: {} + prettier-plugin-organize-imports@4.1.0(prettier@3.4.2)(typescript@5.7.2): + dependencies: + prettier: 3.4.2 + typescript: 5.7.2 + prettier@2.8.8: {} prettier@3.4.2: {} diff --git a/src/index.ts b/src/index.ts index ddfd470..369770d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,15 @@ +import * as cache from '@actions/cache'; import * as core from '@actions/core'; +import * as exec from '@actions/exec'; import * as io from '@actions/io'; -import * as cache from '@actions/cache'; -import path from 'node:path'; + import { Chalk } from 'chalk'; +import path from 'node:path'; -import { - type ResolvedVersion, - getInstallSettings, - runCargoInstall, -} from './install'; +import { type ResolvedVersion, getInstallSettings } from './install'; import { parseInput } from './parse'; -import { resolveRegistryVersion } from './resolve/registry'; import { resolveGitCommit } from './resolve/git'; +import { resolveRegistryVersion } from './resolve/registry'; const chalk = new Chalk({ level: 3 }); @@ -35,6 +33,7 @@ async function run(): Promise { } core.info(` path: ${install.path}`); core.info(` key: ${install.cacheKey}`); + core.info(` command: cargo ${install.args.join(' ')}`); await io.mkdirP(install.path); const restored = await cache.restoreCache([install.path], install.cacheKey); @@ -50,7 +49,7 @@ async function run(): Promise { core.startGroup( `No cached version found, installing ${input.crate} using cargo...`, ); - await runCargoInstall(input, version, install); + await exec.exec('cargo', install.args); try { await cache.saveCache([install.path], install.cacheKey); @@ -58,7 +57,7 @@ async function run(): Promise { if (error instanceof Error) { core.warning(error.message); } else { - core.warning('An error occurred while saving the cache.'); + core.warning('An unknown error occurred while saving the cache.'); } } diff --git a/src/install.ts b/src/install.ts index 78606be..530061e 100644 --- a/src/install.ts +++ b/src/install.ts @@ -1,156 +1,150 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; -import path from 'node:path'; import crypto from 'node:crypto'; +import path from 'node:path'; import type { ActionInput } from './parse'; -// Resolved version information for the crate export type ResolvedVersion = | { version: string } | { repository: string; commit: string }; -// Installation settings for the crate (path and cache key) export interface InstallSettings { path: string; + args: string[]; cacheKey: string; } -// Get the installation settings for the crate (path and cache key) +// Get the installation settings for the crate (path, arguments and cache key) export async function getInstallSettings( input: ActionInput, version: ResolvedVersion, ): Promise { const homePath = process.env.HOME ?? process.env.USERPROFILE; + if (homePath === undefined || homePath === '') { - core.setFailed( - 'Could not determine home directory (missing HOME and USERPROFILE environement variables)', - ); + core.setFailed('Could not determine home directory'); process.exit(1); } const installPath = path.join(homePath, '.cargo-install', input.crate); - const cacheKey = await getCacheKey(input, version); + const args = getInstallArgs(input, version, installPath); + const cacheKey = await getCacheKey(input, version, args); return { path: installPath, + args, cacheKey, }; } -// Get the os version of the runner, used for the cache key -async function getOsVersion(): Promise { - const runnerOs = process.env.RUNNER_OS; +// Generate the arguments that will be passed to `cargo` to install the crate. +function getInstallArgs( + input: ActionInput, + version: ResolvedVersion, + installPath: string, +): string[] { + let args = ['install', input.crate, '--force', '--root', installPath]; - if (runnerOs === 'Linux') { - const output = await exec.getExecOutput('cat', ['/etc/os-release'], { - silent: true, - }); - const match = output.stdout.match(/VERSION_ID="(.*)"/); - return match?.[1]; + if ('version' in version) { + args.push('--version', version.version); + } else { + args.push('--git', version.repository, '--rev', version.commit); } - if (runnerOs === 'macOS') { - const output = await exec.getExecOutput('sw_vers', ['-productVersion'], { - silent: true, - }); - return output.stdout.trim(); + if (input.source.type === 'registry' && input.source.registry) { + args.push('--registry', input.source.registry); + } + if (input.source.type === 'registry' && input.source.index) { + args.push('--index', input.source.index); } - if (runnerOs === 'Windows') { - const major = await exec.getExecOutput( - 'pwsh', - ['-Command', '[System.Environment]::OSVersion.Version.Major'], - { silent: true }, - ); - const minor = await exec.getExecOutput( - 'pwsh', - ['-Command', '[System.Environment]::OSVersion.Version.Minor'], - { silent: true }, - ); - return `${major.stdout.trim()}.${minor.stdout.trim()}`; + if (input.features.length > 0) { + args.push('--features', input.features.join(',')); + } + + if (input.args.length > 0) { + args = args.concat(input.args); } + + return args; } async function getCacheKey( input: ActionInput, version: ResolvedVersion, + args: string[], ): Promise { const runnerOs = process.env.RUNNER_OS; const runnerArch = process.env.RUNNER_ARCH; - const jobId = process.env.GITHUB_JOB; const osVersion = await getOsVersion(); - if ( - runnerOs === undefined || - runnerArch === undefined || - jobId === undefined - ) { - core.setFailed('Could not determine runner OS, runner arch or job ID'); + if (runnerOs === undefined || runnerArch === undefined) { + core.setFailed('Could not determine runner OS or runner arch'); process.exit(1); } - let hashKey = jobId + runnerOs + runnerArch + (osVersion ?? ''); - - hashKey += input.source.type; - if (input.source.type === 'registry') { - hashKey += input.source.registry ?? ''; - hashKey += input.source.index ?? ''; - } else { - hashKey += input.source.repository; - hashKey += input.source.branch ?? ''; - hashKey += input.source.tag ?? ''; - hashKey += input.source.commit ?? ''; - } - - for (const feature of input.features) { - hashKey += feature; - } - for (const arg of input.args) { - hashKey += arg; - } - if (input.cacheKey?.length > 0) { - hashKey += input.cacheKey; - } + /** + * Most of the cache key is a hash of the parameters that may affect the build + * output. We take only the first 24 characters to improve readability. + * + * The key is composed of: + * - the runner os information (os name, architecture and version) + * - the arguments passed to cargo install (which contain the exact version + * installed, features enabled, ...) + * - additionally, the cache key provided by the user + */ + + const hashKey = + runnerOs + + runnerArch + + (osVersion ?? '') + + args.join(' ') + + (input.cacheKey ?? ''); const hash = crypto .createHash('sha256') .update(hashKey) .digest('hex') - .slice(0, 20); + .slice(0, 24); + + // We include the installed crate and version in the cache key to make it + // easier to identify if a manual invalidation is needed. const versionKey = 'version' in version ? version.version : version.commit.slice(0, 7); return `cargo-install-${input.crate}-${versionKey}-${hash}`; } -export async function runCargoInstall( - input: ActionInput, - version: ResolvedVersion, - install: InstallSettings, -): Promise { - let commandArgs = ['install', input.crate, '--force', '--root', install.path]; - - if ('version' in version) { - commandArgs.push('--version', version.version); - } else { - commandArgs.push('--git', version.repository, '--rev', version.commit); - } +async function getOsVersion(): Promise { + const runnerOs = process.env.RUNNER_OS; - if (input.source.type === 'registry' && input.source.registry !== undefined) { - commandArgs.push('--registry', input.source.registry); - } - if (input.source.type === 'registry' && input.source.index !== undefined) { - commandArgs.push('--index', input.source.index); + if (runnerOs === 'Linux') { + const output = await exec.getExecOutput('cat', ['/etc/os-release'], { + silent: true, + }); + const match = output.stdout.match(/VERSION_ID="(.*)"/); + return match?.[1]; } - if (input.features.length > 0) { - commandArgs.push('--features', input.features.join(',')); + if (runnerOs === 'macOS') { + const output = await exec.getExecOutput('sw_vers', ['-productVersion'], { + silent: true, + }); + return output.stdout.trim(); } - if (input.args.length > 0) { - commandArgs = commandArgs.concat(input.args); + if (runnerOs === 'Windows') { + const major = await exec.getExecOutput( + 'pwsh', + ['-Command', '[System.Environment]::OSVersion.Version.Major'], + { silent: true }, + ); + const minor = await exec.getExecOutput( + 'pwsh', + ['-Command', '[System.Environment]::OSVersion.Version.Minor'], + { silent: true }, + ); + return `${major.stdout.trim()}.${minor.stdout.trim()}`; } - - await exec.exec('cargo', commandArgs); } diff --git a/src/resolve/git.ts b/src/resolve/git.ts index 73b6c2f..5db0521 100644 --- a/src/resolve/git.ts +++ b/src/resolve/git.ts @@ -1,8 +1,8 @@ -import * as exec from '@actions/exec'; import * as core from '@actions/core'; +import * as exec from '@actions/exec'; -import type { GitSource } from '../parse'; import type { ResolvedVersion } from '../install'; +import type { GitSource } from '../parse'; interface GitRemoteCommits { head: string; diff --git a/src/resolve/registry.ts b/src/resolve/registry.ts index 9aeae67..d09049a 100644 --- a/src/resolve/registry.ts +++ b/src/resolve/registry.ts @@ -1,17 +1,17 @@ -import * as http from '@actions/http-client'; import * as core from '@actions/core'; +import * as http from '@actions/http-client'; import semver from 'semver'; -import type { ResolvedVersion } from '../install'; import { InferOutput, boolean, check, object, parse, - string, pipe, + string, } from 'valibot'; +import type { ResolvedVersion } from '../install'; import { RegistrySource } from '../parse'; const CrateVersionSchema = object({