From 6abe6f43cb2892b509a01ddc8d5696021d850c62 Mon Sep 17 00:00:00 2001 From: Dominic Rubas <1042243+rubas@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:41:47 +0200 Subject: [PATCH 1/2] Find Erlang/OTP in the tool cache tc.find only matches semver versions, so it never finds Erlang/OTP, which we cache as e.g. ubuntu-24.04/OTP-27.2. Look up the path that tc.cacheDir writes instead, and keep downloading branch builds. --- dist/index.js | 65 ++++++++++++++++++++++++++--------------- src/setup-beam.js | 21 ++++++++++++- test/setup-beam.test.js | 24 +++++++++++++++ 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/dist/index.js b/dist/index.js index fb7a1043..8add111a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -34408,7 +34408,7 @@ function find(toolName, versionSpec, arch) { if (!versionSpec) { throw new Error('versionSpec parameter is required'); } - arch = arch || external_os_namespaceObject.arch(); + arch = arch || os.arch(); // attempt to resolve an explicit version if (!isExplicitVersion(versionSpec)) { const localVersions = findAllVersions(toolName, arch); @@ -34418,15 +34418,15 @@ function find(toolName, versionSpec, arch) { // check for the explicit version in the cache let toolPath = ''; if (versionSpec) { - versionSpec = node_modules_semver.clean(versionSpec) || ''; - const cachePath = external_path_namespaceObject.join(_getCacheDirectory(), toolName, versionSpec, arch); - core_debug(`checking cache: ${cachePath}`); - if (external_fs_namespaceObject.existsSync(cachePath) && external_fs_namespaceObject.existsSync(`${cachePath}.complete`)) { - core_debug(`Found tool in cache ${toolName} ${versionSpec} ${arch}`); + versionSpec = semver.clean(versionSpec) || ''; + const cachePath = path.join(_getCacheDirectory(), toolName, versionSpec, arch); + core.debug(`checking cache: ${cachePath}`); + if (fs.existsSync(cachePath) && fs.existsSync(`${cachePath}.complete`)) { + core.debug(`Found tool in cache ${toolName} ${versionSpec} ${arch}`); toolPath = cachePath; } else { - core_debug('not found'); + core.debug('not found'); } } return toolPath; @@ -34439,14 +34439,14 @@ function find(toolName, versionSpec, arch) { */ function findAllVersions(toolName, arch) { const versions = []; - arch = arch || external_os_namespaceObject.arch(); - const toolPath = external_path_namespaceObject.join(_getCacheDirectory(), toolName); - if (external_fs_namespaceObject.existsSync(toolPath)) { - const children = external_fs_namespaceObject.readdirSync(toolPath); + arch = arch || os.arch(); + const toolPath = path.join(_getCacheDirectory(), toolName); + if (fs.existsSync(toolPath)) { + const children = fs.readdirSync(toolPath); for (const child of children) { if (isExplicitVersion(child)) { - const fullPath = external_path_namespaceObject.join(toolPath, child, arch || ''); - if (external_fs_namespaceObject.existsSync(fullPath) && external_fs_namespaceObject.existsSync(`${fullPath}.complete`)) { + const fullPath = path.join(toolPath, child, arch || ''); + if (fs.existsSync(fullPath) && fs.existsSync(`${fullPath}.complete`)) { versions.push(child); } } @@ -34530,10 +34530,10 @@ function _completeToolPath(tool, version, arch) { * @param versionSpec version string to check */ function isExplicitVersion(versionSpec) { - const c = node_modules_semver.clean(versionSpec) || ''; - core_debug(`isExplicit: ${c}`); - const valid = node_modules_semver.valid(c) != null; - core_debug(`explicit? ${valid}`); + const c = semver.clean(versionSpec) || ''; + core.debug(`isExplicit: ${c}`); + const valid = semver.valid(c) != null; + core.debug(`explicit? ${valid}`); return valid; } /** @@ -34544,26 +34544,26 @@ function isExplicitVersion(versionSpec) { */ function evaluateVersions(versions, versionSpec) { let version = ''; - core_debug(`evaluating ${versions.length} versions`); + core.debug(`evaluating ${versions.length} versions`); versions = versions.sort((a, b) => { - if (node_modules_semver.gt(a, b)) { + if (semver.gt(a, b)) { return 1; } return -1; }); for (let i = versions.length - 1; i >= 0; i--) { const potential = versions[i]; - const satisfied = node_modules_semver.satisfies(potential, versionSpec); + const satisfied = semver.satisfies(potential, versionSpec); if (satisfied) { version = potential; break; } } if (version) { - core_debug(`matched: ${version}`); + core.debug(`matched: ${version}`); } else { - core_debug('match not found'); + core.debug('match not found'); } return version; } @@ -39070,7 +39070,7 @@ async function install(toolName, opts) { async function installTool(opts) { const { toolName, versionSpec, installOpts } = opts const platformOpts = installOpts[process.platform] || installOpts.all - let cachePath = find(toolName, versionSpec) + let cachePath = findInToolCache(toolName, versionSpec) core_debug(`Checking if ${installOpts.tool} is already cached...`) if (cachePath === '') { @@ -39113,6 +39113,24 @@ async function installTool(opts) { await exec_exec(cmd, args, { env: { ...process.env, ...env } }) } +// tc.find only finds semver versions, but tc.cacheDir also stores others as-is, +// e.g. Erlang/OTP as ubuntu-24.04/OTP-27.2. Branch builds (e.g. main, +// maint-27, nightly) change over time, so we always download them again. +function findInToolCache(toolName, versionSpec) { + if (isKnownVerBranch(versionSpec) || versionSpec === 'nightly') { + return '' + } + + const cachePath = external_node_path_namespaceObject.join( + process.env.RUNNER_TOOL_CACHE, + toolName, + node_modules_semver.clean(versionSpec) || versionSpec, + external_node_os_namespaceObject.arch(), + ) + + return external_node_fs_namespaceObject.existsSync(`${cachePath}.complete`) ? cachePath : '' +} + function checkOtpArchitecture() { const otpArch = otpArchitecture() @@ -39132,6 +39150,7 @@ function debugLoggingEnabled() { } /* harmony default export */ const setup_beam = ({ + findInToolCache, get, getElixirVersion, getGleamVersion, diff --git a/src/setup-beam.js b/src/setup-beam.js index fd6cc5a8..c0726e61 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -1239,7 +1239,7 @@ async function install(toolName, opts) { async function installTool(opts) { const { toolName, versionSpec, installOpts } = opts const platformOpts = installOpts[process.platform] || installOpts.all - let cachePath = tc.find(toolName, versionSpec) + let cachePath = findInToolCache(toolName, versionSpec) core.debug(`Checking if ${installOpts.tool} is already cached...`) if (cachePath === '') { @@ -1282,6 +1282,24 @@ async function installTool(opts) { await exec(cmd, args, { env: { ...process.env, ...env } }) } +// tc.find only finds semver versions, but tc.cacheDir also stores others as-is, +// e.g. Erlang/OTP as ubuntu-24.04/OTP-27.2. Branch builds (e.g. main, +// maint-27, nightly) change over time, so we always download them again. +function findInToolCache(toolName, versionSpec) { + if (isKnownVerBranch(versionSpec) || versionSpec === 'nightly') { + return '' + } + + const cachePath = path.join( + process.env.RUNNER_TOOL_CACHE, + toolName, + semver.clean(versionSpec) || versionSpec, + os.arch(), + ) + + return fs.existsSync(`${cachePath}.complete`) ? cachePath : '' +} + function checkOtpArchitecture() { const otpArch = otpArchitecture() @@ -1301,6 +1319,7 @@ function debugLoggingEnabled() { } export default { + findInToolCache, get, getElixirVersion, getGleamVersion, diff --git a/test/setup-beam.test.js b/test/setup-beam.test.js index 3ed43379..4c707014 100644 --- a/test/setup-beam.test.js +++ b/test/setup-beam.test.js @@ -4,6 +4,7 @@ import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { describe, it } from 'node:test' +import * as tc from '@actions/tool-cache' import * as csv from 'csv-parse/sync' import elixirMatchers from '../matchers/elixir-matchers.json' with { type: 'json' } import erlangMatchers from '../matchers/erlang-matchers.json' with { type: 'json' } @@ -167,6 +168,29 @@ describe('rebar3 install', () => { }) }) +describe('.findInToolCache(_)', () => { + it('finds what install caches, and skips branch builds', async () => { + const before = process.env.RUNNER_TOOL_CACHE + process.env.RUNNER_TOOL_CACHE = fs.mkdtempSync( + path.join(os.tmpdir(), 'tool-cache-'), + ) + const source = fs.mkdtempSync(path.join(os.tmpdir(), 'tool-')) + + const otp = await tc.cacheDir(source, 'otp', 'ubuntu-24.04/OTP-27.2') + assert.equal(setupBeam.findInToolCache('otp', 'ubuntu-24.04/OTP-27.2'), otp) + + const elixir = await tc.cacheDir(source, 'elixir', 'v1.18.4-otp-27') + assert.equal(setupBeam.findInToolCache('elixir', 'v1.18.4-otp-27'), elixir) + + await tc.cacheDir(source, 'otp', 'ubuntu-24.04/maint-27') + assert.equal(setupBeam.findInToolCache('otp', 'ubuntu-24.04/maint-27'), '') + + assert.equal(setupBeam.findInToolCache('otp', 'ubuntu-24.04/OTP-27.1'), '') + + process.env.RUNNER_TOOL_CACHE = before + }) +}) + describe('.getOTPVersion(_) - Erlang', () => { let got let expected From a4d75d68f708c8215e8f8d972d6fcc7b9e8c2efc Mon Sep 17 00:00:00 2001 From: Dominic Rubas <1042243+rubas@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:49 +0200 Subject: [PATCH 2/2] Check the cached directory as well as its marker, as tc.find does --- dist/index.js | 4 +++- src/setup-beam.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 8add111a..c8ae8353 100644 --- a/dist/index.js +++ b/dist/index.js @@ -39128,7 +39128,9 @@ function findInToolCache(toolName, versionSpec) { external_node_os_namespaceObject.arch(), ) - return external_node_fs_namespaceObject.existsSync(`${cachePath}.complete`) ? cachePath : '' + return external_node_fs_namespaceObject.existsSync(cachePath) && external_node_fs_namespaceObject.existsSync(`${cachePath}.complete`) + ? cachePath + : '' } function checkOtpArchitecture() { diff --git a/src/setup-beam.js b/src/setup-beam.js index c0726e61..972632c3 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -1297,7 +1297,9 @@ function findInToolCache(toolName, versionSpec) { os.arch(), ) - return fs.existsSync(`${cachePath}.complete`) ? cachePath : '' + return fs.existsSync(cachePath) && fs.existsSync(`${cachePath}.complete`) + ? cachePath + : '' } function checkOtpArchitecture() {