diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 1c560a3..3b13952 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -76,6 +76,7 @@ jobs: uses: ./ with: install-rebar: false + install-hex: false version-file: test/.tool-versions version-type: strict diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 94e8fbb..8051d67 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -25,6 +25,15 @@ jobs: fail-fast: false matrix: combo: + - otp-version: '<26' + elixir-version: '<1.17' + os: 'ubuntu-22.04' + - otp-version: 'master' + elixir-version: '> 0' + os: 'ubuntu-24.04' + - otp-version: 'master' + elixir-version: 'main' + os: 'ubuntu-24.04' - otp-version: '27' elixir-version: 'v1.17.0' rebar3-version: '3.23' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 55edfdb..9643c49 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -25,6 +25,12 @@ jobs: fail-fast: false matrix: combo: + - otp-version: '<26' + elixir-version: '<1.17' + os: 'ubuntu-22.04' + - otp-version: 'master' + elixir-version: '> 0' + os: 'ubuntu-24.04' - otp-version: '27' elixir-version: 'v1.17.0' rebar3-version: '3.23' diff --git a/dist/index.js b/dist/index.js index c26eacb..b01d76e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -25890,9 +25890,11 @@ const _ = __nccwpck_require__(2356) const MAX_HTTP_RETRIES = 3 -main().catch((err) => { - core.setFailed(err.message) -}) +if (process.env.NODE_ENV !== 'test') { + main().catch((err) => { + core.setFailed(err.message) + }) +} async function main() { checkOtpArchitecture() @@ -25908,15 +25910,14 @@ async function main() { versions = parseVersionFile(versionFilePath) } - const osVersion = getRunnerOSVersion() const otpSpec = getInput('otp-version', true, 'erlang', versions) const elixirSpec = getInput('elixir-version', false, 'elixir', versions) const gleamSpec = getInput('gleam-version', false, 'gleam', versions) const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions) if (otpSpec !== 'false') { - await installOTP(otpSpec, osVersion) - const elixirInstalled = await maybeInstallElixir(elixirSpec, otpSpec) + await installOTP(otpSpec) + const elixirInstalled = await maybeInstallElixir(elixirSpec) if (elixirInstalled === true) { const shouldMixRebar = getInput('install-rebar', false) await mix(shouldMixRebar, 'rebar') @@ -25936,7 +25937,8 @@ async function main() { core.setOutput('setup-beam-version', setupBeamVersion) } -async function installOTP(otpSpec, osVersion) { +async function installOTP(otpSpec) { + const osVersion = getRunnerOSVersion() const otpVersion = await getOTPVersion(otpSpec, osVersion) core.startGroup( `Installing Erlang/OTP ${otpVersion} - built on ${getRunnerOSArchitecture()}/${osVersion}`, @@ -25958,10 +25960,10 @@ async function installOTP(otpSpec, osVersion) { return otpVersion } -async function maybeInstallElixir(elixirSpec, otpSpec) { +async function maybeInstallElixir(elixirSpec) { let installed = false if (elixirSpec) { - const elixirVersion = await getElixirVersion(elixirSpec, otpSpec) + const elixirVersion = await getElixirVersion(elixirSpec) core.startGroup(`Installing Elixir ${elixirVersion}`) await doWithMirrors({ hexMirrors: hexMirrorsInput(), @@ -26070,16 +26072,30 @@ function requestedVersionFor(tool, version, originListing, mirrors) { const knownBranches = ['main', 'master', 'maint'] const nonSpecificVersions = ['nightly', 'latest'] -async function getElixirVersion(exSpec0, otpVersion0) { - const otpVersion = otpVersion0.match(/^(?:OTP-)?(.+)$/)[1] - const regex = `^(\\d{1,3}|${knownBranches.join('|')}|${nonSpecificVersions.join('|')})?.*$` - let otpVersionMajor = otpVersion.match(new RegExp(regex))[1] - +async function getElixirVersion(exSpec0) { const otpSuffix = /-otp-(\d+)/ const userSuppliedOtp = exSpec0.match(otpSuffix)?.[1] ?? null + let otpVersionMajor = '' if (userSuppliedOtp && isVersion(userSuppliedOtp)) { otpVersionMajor = userSuppliedOtp + } else { + let cmd = 'erl' + if (process.platform === 'win32') { + cmd = 'erl.exe' + } + const args = [ + '-noshell', + '-eval', + 'io:format(erlang:system_info(otp_release)), halt().', + ] + await exec(cmd, args, { + listeners: { + stdout: (data) => { + otpVersionMajor = data.toString() + }, + }, + }) } const [otpVersionsForElixirMap, elixirVersions, originListing, hexMirrors] = @@ -26094,17 +26110,29 @@ async function getElixirVersion(exSpec0, otpVersion0) { ) } - const elixirVersionComp = otpVersionsForElixirMap[elixirVersionFromSpec] - if ( - (elixirVersionComp && elixirVersionComp.includes(otpVersionMajor)) || - !isVersion(otpVersionMajor) - ) { - core.info( - `Using Elixir ${elixirVersionFromSpec} (built for Erlang/OTP ${otpVersionMajor})`, - ) - } else { + let foundCombo = false + let otpVersionMajorIter = parseInt(otpVersionMajor) + let otpVersionsMajor = [] + while (otpVersionMajorIter > otpVersionMajor - 3) { + otpVersionMajorIter += '' + otpVersionsMajor.push(otpVersionMajorIter) + const elixirVersionComp = otpVersionsForElixirMap[elixirVersionFromSpec] + if ( + (elixirVersionComp && elixirVersionComp.includes(otpVersionMajorIter)) || + !isVersion(otpVersionMajorIter) + ) { + core.info( + `Using Elixir ${elixirVersionFromSpec} (built for Erlang/OTP ${otpVersionMajorIter})`, + ) + foundCombo = true + break + } + otpVersionMajorIter = parseInt(otpVersionMajorIter) - 1 + } + + if (!foundCombo) { throw new Error( - `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion0}) not ` + + `Requested Elixir / Erlang/OTP version (${exSpec0} / tried ${otpVersionsMajor}) not ` + 'found in version list (did you check Compatibility between Elixir and Erlang/OTP?).' + 'Elixir and Erlang/OTP compatibility can be found at: ' + 'https://hexdocs.pm/elixir/compatibility-and-deprecations.html', @@ -26113,8 +26141,8 @@ async function getElixirVersion(exSpec0, otpVersion0) { let elixirVersionForDownload = elixirVersionFromSpec - if (isVersion(otpVersionMajor)) { - elixirVersionForDownload = `${elixirVersionFromSpec}-otp-${otpVersionMajor}` + if (isVersion(otpVersionMajorIter)) { + elixirVersionForDownload = `${elixirVersionFromSpec}-otp-${otpVersionMajorIter}` } return maybePrependWithV(elixirVersionForDownload) @@ -27073,14 +27101,18 @@ function debugLoggingEnabled() { module.exports = { get, - getOTPVersion, getElixirVersion, getGleamVersion, + getOTPVersion, getRebar3Version, getVersionFromSpec, githubAMDRunnerArchs, githubARMRunnerArchs, install, + installOTP, + maybeInstallElixir, + maybeInstallGleam, + maybeInstallRebar3, parseVersionFile, } diff --git a/src/setup-beam.js b/src/setup-beam.js index c4083c0..6794fa2 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -10,9 +10,11 @@ const _ = require('lodash') const MAX_HTTP_RETRIES = 3 -main().catch((err) => { - core.setFailed(err.message) -}) +if (process.env.NODE_ENV !== 'test') { + main().catch((err) => { + core.setFailed(err.message) + }) +} async function main() { checkOtpArchitecture() @@ -28,15 +30,14 @@ async function main() { versions = parseVersionFile(versionFilePath) } - const osVersion = getRunnerOSVersion() const otpSpec = getInput('otp-version', true, 'erlang', versions) const elixirSpec = getInput('elixir-version', false, 'elixir', versions) const gleamSpec = getInput('gleam-version', false, 'gleam', versions) const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions) if (otpSpec !== 'false') { - await installOTP(otpSpec, osVersion) - const elixirInstalled = await maybeInstallElixir(elixirSpec, otpSpec) + await installOTP(otpSpec) + const elixirInstalled = await maybeInstallElixir(elixirSpec) if (elixirInstalled === true) { const shouldMixRebar = getInput('install-rebar', false) await mix(shouldMixRebar, 'rebar') @@ -56,7 +57,8 @@ async function main() { core.setOutput('setup-beam-version', setupBeamVersion) } -async function installOTP(otpSpec, osVersion) { +async function installOTP(otpSpec) { + const osVersion = getRunnerOSVersion() const otpVersion = await getOTPVersion(otpSpec, osVersion) core.startGroup( `Installing Erlang/OTP ${otpVersion} - built on ${getRunnerOSArchitecture()}/${osVersion}`, @@ -78,10 +80,10 @@ async function installOTP(otpSpec, osVersion) { return otpVersion } -async function maybeInstallElixir(elixirSpec, otpSpec) { +async function maybeInstallElixir(elixirSpec) { let installed = false if (elixirSpec) { - const elixirVersion = await getElixirVersion(elixirSpec, otpSpec) + const elixirVersion = await getElixirVersion(elixirSpec) core.startGroup(`Installing Elixir ${elixirVersion}`) await doWithMirrors({ hexMirrors: hexMirrorsInput(), @@ -195,16 +197,30 @@ function requestedVersionFor(tool, version, originListing, mirrors) { const knownBranches = ['main', 'master', 'maint'] const nonSpecificVersions = ['nightly', 'latest'] -async function getElixirVersion(exSpec0, otpVersion0) { - const otpVersion = otpVersion0.match(/^(?:OTP-)?(.+)$/)[1] - const regex = `^(\\d{1,3}|${knownBranches.join('|')}|${nonSpecificVersions.join('|')})?.*$` - let otpVersionMajor = otpVersion.match(new RegExp(regex))[1] - +async function getElixirVersion(exSpec0) { const otpSuffix = /-otp-(\d+)/ const userSuppliedOtp = exSpec0.match(otpSuffix)?.[1] ?? null + let otpVersionMajor = '' if (userSuppliedOtp && isVersion(userSuppliedOtp)) { otpVersionMajor = userSuppliedOtp + } else { + let cmd = 'erl' + if (process.platform === 'win32') { + cmd = 'erl.exe' + } + const args = [ + '-noshell', + '-eval', + 'io:format(erlang:system_info(otp_release)), halt().', + ] + await exec(cmd, args, { + listeners: { + stdout: (data) => { + otpVersionMajor = data.toString() + }, + }, + }) } const [otpVersionsForElixirMap, elixirVersions, originListing, hexMirrors] = @@ -219,17 +235,29 @@ async function getElixirVersion(exSpec0, otpVersion0) { ) } - const elixirVersionComp = otpVersionsForElixirMap[elixirVersionFromSpec] - if ( - (elixirVersionComp && elixirVersionComp.includes(otpVersionMajor)) || - !isVersion(otpVersionMajor) - ) { - core.info( - `Using Elixir ${elixirVersionFromSpec} (built for Erlang/OTP ${otpVersionMajor})`, - ) - } else { + let foundCombo = false + let otpVersionMajorIter = parseInt(otpVersionMajor) + let otpVersionsMajor = [] + while (otpVersionMajorIter > otpVersionMajor - 3) { + otpVersionMajorIter += '' + otpVersionsMajor.push(otpVersionMajorIter) + const elixirVersionComp = otpVersionsForElixirMap[elixirVersionFromSpec] + if ( + (elixirVersionComp && elixirVersionComp.includes(otpVersionMajorIter)) || + !isVersion(otpVersionMajorIter) + ) { + core.info( + `Using Elixir ${elixirVersionFromSpec} (built for Erlang/OTP ${otpVersionMajorIter})`, + ) + foundCombo = true + break + } + otpVersionMajorIter = parseInt(otpVersionMajorIter) - 1 + } + + if (!foundCombo) { throw new Error( - `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion0}) not ` + + `Requested Elixir / Erlang/OTP version (${exSpec0} / tried ${otpVersionsMajor}) not ` + 'found in version list (did you check Compatibility between Elixir and Erlang/OTP?).' + 'Elixir and Erlang/OTP compatibility can be found at: ' + 'https://hexdocs.pm/elixir/compatibility-and-deprecations.html', @@ -238,8 +266,8 @@ async function getElixirVersion(exSpec0, otpVersion0) { let elixirVersionForDownload = elixirVersionFromSpec - if (isVersion(otpVersionMajor)) { - elixirVersionForDownload = `${elixirVersionFromSpec}-otp-${otpVersionMajor}` + if (isVersion(otpVersionMajorIter)) { + elixirVersionForDownload = `${elixirVersionFromSpec}-otp-${otpVersionMajorIter}` } return maybePrependWithV(elixirVersionForDownload) @@ -1198,13 +1226,17 @@ function debugLoggingEnabled() { module.exports = { get, - getOTPVersion, getElixirVersion, getGleamVersion, + getOTPVersion, getRebar3Version, getVersionFromSpec, githubAMDRunnerArchs, githubARMRunnerArchs, install, + installOTP, + maybeInstallElixir, + maybeInstallGleam, + maybeInstallRebar3, parseVersionFile, } diff --git a/test/setup-beam.test.js b/test/setup-beam.test.js index 45b10a1..89b3daa 100644 --- a/test/setup-beam.test.js +++ b/test/setup-beam.test.js @@ -1,3 +1,5 @@ +process.env.NODE_ENV = 'test' + simulateInput('otp-version', '25.1.2') simulateInput('otp-architecture', '64') simulateInput('elixir-version', '1.14.2') @@ -536,77 +538,70 @@ describe('.getOTPVersion(_) - Elixir', () => { let spec let otpVersion let before - const hexMirrors = simulateInput('hexpm-mirrors', 'https://repo.hex.pm', { - multiline: true, - }) + const previousRunnerArch = process.env.RUNNER_ARCH - it('returns the expected value', async () => { - spec = '1.1.x' - otpVersion = 'OTP-17' - expected = 'v1.1.1-otp-17' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) + if (process.platform === 'linux') { + process.env.RUNNER_ARCH = 'X64' - spec = '1.10.4' - otpVersion = 'OTP-23' - expected = 'v1.10.4-otp-23' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) + it('returns the expected value', async () => { + spec = '1.18.x' + otpVersion = 'OTP-27' + expected = 'v1.18.4-otp-27' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) - spec = '1.16.2-otp-26' - otpVersion = 'OTP-27' - expected = 'v1.16.2-otp-26' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) + spec = '1.18.2' + otpVersion = 'OTP-27' + expected = 'v1.18.2-otp-27' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) - spec = '1.12.1' - otpVersion = 'OTP-24.0.2' - expected = 'v1.12.1-otp-24' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) + spec = '1.16.2-otp-26' + otpVersion = 'OTP-27' + expected = 'v1.16.2-otp-26' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) - before = simulateInput('version-type', 'strict') - spec = '1.14.0' - otpVersion = 'main' - expected = 'v1.14.0' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) - simulateInput('version-type', before) + spec = '1.12.1' + otpVersion = 'OTP-24.3.4' + expected = 'v1.12.1-otp-24' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) - before = simulateInput('version-type', 'strict') - spec = 'v1.11.0-rc.0' - otpVersion = 'OTP-23' - expected = 'v1.11.0-rc.0-otp-23' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) - simulateInput('version-type', before) + before = simulateInput('version-type', 'strict') + spec = '1.17' + otpVersion = 'master' + expected = 'v1.17-otp-27' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) + simulateInput('version-type', before) - before = simulateInput('version-type', 'strict') - spec = 'v1.11.0-rc.0-otp-23' - otpVersion = 'OTP-23' - expected = 'v1.11.0-rc.0-otp-23' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) - simulateInput('version-type', before) + before = simulateInput('version-type', 'strict') + spec = 'v1.15.0-rc.2' + otpVersion = 'OTP-26' + expected = 'v1.15.0-rc.2-otp-26' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) + simulateInput('version-type', before) - before = simulateInput('version-type', 'strict') - spec = 'v1.11.0' - otpVersion = '22.3.4.2' - expected = 'v1.11.0-otp-22' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) - simulateInput('version-type', before) + before = simulateInput('version-type', 'strict') + spec = 'main' + otpVersion = '25.2' + expected = 'main-otp-25' + await setupBeam.installOTP(otpVersion) + got = await setupBeam.getElixirVersion(spec, otpVersion) + assert.deepStrictEqual(got, expected) + simulateInput('version-type', before) + }) + } - before = simulateInput('version-type', 'strict') - spec = 'main' - otpVersion = '23.1' - expected = 'main-otp-23' - got = await setupBeam.getElixirVersion(spec, otpVersion) - assert.deepStrictEqual(got, expected) - simulateInput('version-type', before) - }) - - simulateInput('hexpm-mirrors', hexMirrors, { multiline: true }) + process.env.RUNNER_ARCH = previousRunnerArch }) describe('.getOTPVersion(_) - Gleam', () => { @@ -941,18 +936,16 @@ describe('version file', () => { const otpVersion = unsimulateInput('otp-version') const elixirVersion = unsimulateInput('elixir-version') const gleamVersion = unsimulateInput('gleam-version') - const rebar3Version = unsimulateInput('rebar3-version') it('is parsed correctly', async () => { const erlang = '27' const elixir = '1.17.0' const gleam = '0.23.0' - const rebar3 = '3.24.0' const toolVersions = `# a comment erlang ref:v${erlang}# comment, no space, and ref:v elixir ref:${elixir} # comment, with space and ref: not-gleam 0.23 # not picked up -gleam ${gleam} \nrebar ${rebar3}` +gleam ${gleam} \n` const filename = 'test/.tool-versions' fs.writeFileSync(filename, toolVersions) process.env.GITHUB_WORKSPACE = '' @@ -977,15 +970,11 @@ gleam ${gleam} \nrebar ${rebar3}` assert.ok(async () => { await setupBeam.install('gleam', { toolVersion: gleam }) }) - assert.ok(async () => { - await setupBeam.install('rebar3', { toolVersion: rebar3 }) - }) }) simulateInput('otp-version', otpVersion) simulateInput('elixir-version', elixirVersion) simulateInput('gleam-version', gleamVersion) - simulateInput('rebar3-version', rebar3Version) }) describe('.get(_)', () => {