diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ef014d6..837071e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -92,6 +92,17 @@ jobs: otp-version: '24' rebar3-version: '3.15' os: 'ubuntu-20.04' + - elixir-version: 'v1.11.0' + otp-version: '22.3.4.2' + os: 'ubuntu-20.04' + version-type: 'strict' + - elixir-version: '1.10.3' + otp-version: '22.3.4.1' + os: 'ubuntu-20.04' + version-type: 'strict' + - elixir-version: '1.10.3' + otp-version: '22.3.4.1' + os: 'ubuntu-20.04' steps: - uses: actions/checkout@v2 - name: Use erlef/setup-beam @@ -101,6 +112,7 @@ jobs: otp-version: ${{matrix.combo.otp-version}} elixir-version: ${{matrix.combo.elixir-version}} rebar3-version: ${{matrix.combo.rebar3-version}} + version-type: ${{matrix.combo.version-type}} - name: Erlang/OTP version (action) run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}" - name: Elixir version (action) diff --git a/README.md b/README.md index 65b6ee8..f232fb3 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ Additionally, it is recommended that one specifies Erlang/OTP, Elixir and `rebar using YAML strings, as these examples do, so that numbers like `23.0` don't end up being parsed as `23`, which is not equivalent. -For pre-release Elixir versions, such as `1.11.0-rc.0`, use the full version -specifier (`1.11.0-rc.0`). Pre-release versions are opt-in, so `1.11.x` will -not match a pre-release. +For pre-release versions, such as `v1.11.0-rc.0`, use the full version +specifier (`v1.11.0-rc.0`) and set option `version-type` to `strict`. Pre-release versions are +opt-in, so `1.11.x` will not match a pre-release. ### Compatibility between Operating System and Erlang/OTP @@ -159,6 +159,18 @@ jobs: The Elixir Problem Matchers in this repository are adapted from [here](https://github.com/fr1zle/vscode-elixir/blob/45eddb589acd7ac98e0c7305d1c2b24668ca709a/package.json#L70-L118). See [MATCHER_NOTICE](MATCHER_NOTICE.md) for license details. +## Action versioning + +`setup-beam` has three version paths, described below, for example version `1.8.0`: + +- `@v1`: the latest in the `1.y.z` series (this tag is movable), +- `@v1.8`: the latest in the `1.8.z` series (this tag is movable), +- `@v1.8.0`: release `1.8.0` (this tag is not movable). + +To prevent issues in CI (unless you're OK with potential incompatibility between versions - we +make real a effort to not introduce those without bumping major) we suggest you to use `@vx.y.z`, +whenever possible. + ## License The scripts and documentation in this project are released under the [MIT license](LICENSE.md). diff --git a/__tests__/setup-beam.test.js b/__tests__/setup-beam.test.js index 1307d94..394b03a 100644 --- a/__tests__/setup-beam.test.js +++ b/__tests__/setup-beam.test.js @@ -166,6 +166,12 @@ async function testElixirVersions() { 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) + simulateInput('version-type', 'strict') spec = 'v1.11.0-rc.0' otpVersion = 'OTP-23' @@ -173,6 +179,14 @@ async function testElixirVersions() { got = await setupBeam.getElixirVersion(spec, otpVersion) assert.deepStrictEqual(got, expected) simulateInput('version-type', 'loose') + + 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', 'loose') } async function testRebar3Versions() { @@ -228,6 +242,7 @@ async function testGetVersionFromSpec() { '22.3.4.10.1', 'master', 'v11.11.0-rc.0-otp-23', + '22.3.4.2', ] spec = '1' @@ -286,6 +301,11 @@ async function testGetVersionFromSpec() { assert.deepStrictEqual(got, expected) simulateInput('version-type', 'loose') + spec = '24.0-rc2' + expected = '24.0-rc2' + got = setupBeam.getVersionFromSpec(spec, versions) + assert.deepStrictEqual(got, expected) + spec = '22.3' expected = '22.3.4.12.1' got = setupBeam.getVersionFromSpec(spec, versions) @@ -318,25 +338,12 @@ async function testGetVersionFromSpec() { got = setupBeam.getVersionFromSpec(spec, versions) assert.deepStrictEqual(got, expected) - spec = '11.11' - expected = 'v11.11.0-rc.0-otp-23' - got = setupBeam.getVersionFromSpec(spec, versions) - assert.deepStrictEqual(got, expected) - - spec = '11.11.0' - expected = 'v11.11.0-rc.0-otp-23' - got = setupBeam.getVersionFromSpec(spec, versions) - assert.deepStrictEqual(got, expected) - - spec = 'v11.11' - expected = 'v11.11.0-rc.0-otp-23' - got = setupBeam.getVersionFromSpec(spec, versions) - assert.deepStrictEqual(got, expected) - - spec = 'v11.11.0' - expected = 'v11.11.0-rc.0-otp-23' + simulateInput('version-type', 'strict') + spec = '22.3.4.2' + expected = '22.3.4.2' got = setupBeam.getVersionFromSpec(spec, versions) assert.deepStrictEqual(got, expected) + simulateInput('version-type', 'loose') } function simulateInput(key, value) { diff --git a/dist/index.js b/dist/index.js index 85c92e4..6a97199 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4777,7 +4777,8 @@ async function getOTPVersion(otpSpec0, osVersion) { } if (otpVersion === null) { throw new Error( - `Requested Erlang/OTP version (${otpSpec0}) not found in version list`, + `Requested Erlang/OTP version (${otpSpec0}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } @@ -4788,7 +4789,7 @@ async function getElixirVersion(exSpec0, otpVersion) { const elixirVersions = await getElixirVersions() const semverVersions = Array.from(elixirVersions.keys()).sort() - const exSpec = exSpec0.match(/^(.+)(-otp-.+)/) || exSpec0.match(/^(.+)/) + const exSpec = exSpec0.match(/^v?(.+)(-otp-.+)/) || exSpec0.match(/^v?(.+)/) let elixirVersion if (exSpec[2]) { throw new Error( @@ -4801,7 +4802,8 @@ async function getElixirVersion(exSpec0, otpVersion) { } if (!exSpec || elixirVersion === null) { throw new Error( - `Requested Elixir version (${exSpec0}) not found in version list`, + `Requested Elixir version (${exSpec0}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/) @@ -4824,11 +4826,11 @@ async function getElixirVersion(exSpec0, otpVersion) { } else { throw new Error( `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` + - 'found in version list', + "found in version list (should you be using option 'version-type': 'strict'?)", ) } - return elixirVersionWithOTP + return `v${elixirVersionWithOTP}` } async function getRebar3Version(r3Spec) { @@ -4836,7 +4838,8 @@ async function getRebar3Version(r3Spec) { const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions) if (rebar3Version === null) { throw new Error( - `Requested rebar3 version (${r3Spec}) not found in version list`, + `Requested rebar3 version (${r3Spec}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } @@ -4896,7 +4899,7 @@ async function getElixirVersions() { .split('\n') .forEach((line) => { const elixirMatch = - line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/) + line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/) const elixirVersion = elixirMatch[1] const otpVersion = elixirMatch[2] const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || [] @@ -4928,18 +4931,19 @@ async function getRebar3Versions() { function getVersionFromSpec(spec, versions) { let version = null - if (core.getInput('version-type', { required: false }) === 'strict') { + if ( + spec.match(/rc/) || + core.getInput('version-type', { required: false }) === 'strict' + ) { version = spec } if (version === null) { // We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions const versionsMap = versions.sort(sortVersions).reduce((acc, v) => { - try { - acc[semver.coerce(v).version] = v - } catch { - // some stuff can't be coerced, like 'master' - acc[v] = v + if (!v.match(/rc/)) { + // release candidates are opt-in + acc[maybeCoerced(v)] = v } return acc }, {}) @@ -4948,13 +4952,26 @@ function getVersionFromSpec(spec, versions) { version = versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)] } else { - version = versionsMap[spec] + version = versionsMap[maybeCoerced(spec)] } } return version } +function maybeCoerced(v) { + let ret + + try { + ret = semver.coerce(v).version + } catch { + // some stuff can't be coerced, like 'master' + ret = v + } + + return ret +} + function sortVersions(left, right) { let ret = 0 const newL = verAsComparableStr(left) diff --git a/src/setup-beam.js b/src/setup-beam.js index 5210dab..125f874 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -121,7 +121,8 @@ async function getOTPVersion(otpSpec0, osVersion) { } if (otpVersion === null) { throw new Error( - `Requested Erlang/OTP version (${otpSpec0}) not found in version list`, + `Requested Erlang/OTP version (${otpSpec0}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } @@ -132,7 +133,7 @@ async function getElixirVersion(exSpec0, otpVersion) { const elixirVersions = await getElixirVersions() const semverVersions = Array.from(elixirVersions.keys()).sort() - const exSpec = exSpec0.match(/^(.+)(-otp-.+)/) || exSpec0.match(/^(.+)/) + const exSpec = exSpec0.match(/^v?(.+)(-otp-.+)/) || exSpec0.match(/^v?(.+)/) let elixirVersion if (exSpec[2]) { throw new Error( @@ -145,7 +146,8 @@ async function getElixirVersion(exSpec0, otpVersion) { } if (!exSpec || elixirVersion === null) { throw new Error( - `Requested Elixir version (${exSpec0}) not found in version list`, + `Requested Elixir version (${exSpec0}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/) @@ -168,11 +170,11 @@ async function getElixirVersion(exSpec0, otpVersion) { } else { throw new Error( `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` + - 'found in version list', + "found in version list (should you be using option 'version-type': 'strict'?)", ) } - return elixirVersionWithOTP + return `v${elixirVersionWithOTP}` } async function getRebar3Version(r3Spec) { @@ -180,7 +182,8 @@ async function getRebar3Version(r3Spec) { const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions) if (rebar3Version === null) { throw new Error( - `Requested rebar3 version (${r3Spec}) not found in version list`, + `Requested rebar3 version (${r3Spec}) not found in version list ` + + "(should you be using option 'version-type': 'strict'?)", ) } @@ -240,7 +243,7 @@ async function getElixirVersions() { .split('\n') .forEach((line) => { const elixirMatch = - line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/) + line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/) const elixirVersion = elixirMatch[1] const otpVersion = elixirMatch[2] const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || [] @@ -272,18 +275,19 @@ async function getRebar3Versions() { function getVersionFromSpec(spec, versions) { let version = null - if (core.getInput('version-type', { required: false }) === 'strict') { + if ( + spec.match(/rc/) || + core.getInput('version-type', { required: false }) === 'strict' + ) { version = spec } if (version === null) { // We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions const versionsMap = versions.sort(sortVersions).reduce((acc, v) => { - try { - acc[semver.coerce(v).version] = v - } catch { - // some stuff can't be coerced, like 'master' - acc[v] = v + if (!v.match(/rc/)) { + // release candidates are opt-in + acc[maybeCoerced(v)] = v } return acc }, {}) @@ -292,13 +296,26 @@ function getVersionFromSpec(spec, versions) { version = versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)] } else { - version = versionsMap[spec] + version = versionsMap[maybeCoerced(spec)] } } return version } +function maybeCoerced(v) { + let ret + + try { + ret = semver.coerce(v).version + } catch { + // some stuff can't be coerced, like 'master' + ret = v + } + + return ret +} + function sortVersions(left, right) { let ret = 0 const newL = verAsComparableStr(left)