Compare commits

...

3 Commits

Author SHA1 Message Date
Bryan Paxton ade4f1d567 Don't preprend v when elixir version does not start with digits (#61) 2021-07-20 14:08:24 -05:00
Paulo F. Oliveira 9283f857f1 Improve warning message for missing ImageOS env. variable (self-hosted runners) (#59) 2021-07-12 18:35:08 +01:00
Paulo F. Oliveira 830642517c Improve visibility (+ fix bugs) over version-type and opt-in versions (#57)
(and tweak README regarding action versioning)
2021-07-09 10:11:50 +01:00
6 changed files with 200 additions and 55 deletions
+16 -4
View File
@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: '12' node-version: 12
- run: npm install -g npm - run: npm install -g npm
- run: npm install - run: npm install
- run: npm run build - run: npm run build
@@ -31,12 +31,24 @@ jobs:
- name: Check if build left artifacts - name: Check if build left artifacts
run: git diff --exit-code run: git diff --exit-code
unit_test: unit_tests_ubuntu:
name: Unit tests name: Unit tests (Ubuntu)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: {node-version: '12'} with:
node-version: 12
- run: npm ci - run: npm ci
- run: npm test - run: npm test
unit_tests_windows:
name: Unit tests (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm install --production
- run: npm test
+15
View File
@@ -92,6 +92,20 @@ jobs:
otp-version: '24' otp-version: '24'
rebar3-version: '3.15' rebar3-version: '3.15'
os: 'ubuntu-20.04' 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'
- elixir-version: 'master'
otp-version: '23.1'
os: 'ubuntu-20.04'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use erlef/setup-beam - name: Use erlef/setup-beam
@@ -101,6 +115,7 @@ jobs:
otp-version: ${{matrix.combo.otp-version}} otp-version: ${{matrix.combo.otp-version}}
elixir-version: ${{matrix.combo.elixir-version}} elixir-version: ${{matrix.combo.elixir-version}}
rebar3-version: ${{matrix.combo.rebar3-version}} rebar3-version: ${{matrix.combo.rebar3-version}}
version-type: ${{matrix.combo.version-type}}
- name: Erlang/OTP version (action) - name: Erlang/OTP version (action)
run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}" run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}"
- name: Elixir version (action) - name: Elixir version (action)
+44 -3
View File
@@ -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 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. 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 For pre-release versions, such as `v1.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 specifier (`v1.11.0-rc.0`) and set option `version-type` to `strict`. Pre-release versions are
not match a pre-release. opt-in, so `1.11.x` will not match a pre-release.
### Compatibility between Operating System and Erlang/OTP ### Compatibility between Operating System and Erlang/OTP
@@ -53,6 +53,35 @@ and Erlang/OTP.
**Note** *: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, 22.0, etc. **Note** *: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, 22.0, etc.
### Self-hosted runners
Self-hosted runners need to set env. variable `ImageOS` to one of the following, since the action
uses that to download assets:
| ImageOS | Operating system
|- |-
| ubuntu16 | ubuntu-16.04
| ubuntu18 | ubuntu-18.04
| ubuntu20 | ubuntu-20.04
| win16 | windows-2016
| win19 | windows-2019
as per the following example:
```yaml
...
jobs:
test:
runs-on: self-hosted
env:
ImageOS: ubuntu20 # equivalent to runs-on ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
...
```
### Basic example (Erlang/OTP + Elixir, on Ubuntu) ### Basic example (Erlang/OTP + Elixir, on Ubuntu)
```yaml ```yaml
@@ -159,6 +188,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. 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 ## License
The scripts and documentation in this project are released under the [MIT license](LICENSE.md). The scripts and documentation in this project are released under the [MIT license](LICENSE.md).
+33 -18
View File
@@ -136,7 +136,7 @@ async function testOTPVersions() {
spec = '23.2.x' spec = '23.2.x'
osVersion = 'windows-2016' osVersion = 'windows-2016'
expected = '23.2.7.4' expected = '23.2.7'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
@@ -166,6 +166,12 @@ async function testElixirVersions() {
got = await setupBeam.getElixirVersion(spec, otpVersion) got = await setupBeam.getElixirVersion(spec, otpVersion)
assert.deepStrictEqual(got, expected) 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') simulateInput('version-type', 'strict')
spec = 'v1.11.0-rc.0' spec = 'v1.11.0-rc.0'
otpVersion = 'OTP-23' otpVersion = 'OTP-23'
@@ -173,6 +179,22 @@ async function testElixirVersions() {
got = await setupBeam.getElixirVersion(spec, otpVersion) got = await setupBeam.getElixirVersion(spec, otpVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', 'loose') 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')
simulateInput('version-type', 'strict')
spec = 'master'
otpVersion = '23.1'
expected = 'master-otp-23'
got = await setupBeam.getElixirVersion(spec, otpVersion)
assert.deepStrictEqual(got, expected)
simulateInput('version-type', 'loose')
} }
async function testRebar3Versions() { async function testRebar3Versions() {
@@ -228,6 +250,7 @@ async function testGetVersionFromSpec() {
'22.3.4.10.1', '22.3.4.10.1',
'master', 'master',
'v11.11.0-rc.0-otp-23', 'v11.11.0-rc.0-otp-23',
'22.3.4.2',
] ]
spec = '1' spec = '1'
@@ -286,6 +309,11 @@ async function testGetVersionFromSpec() {
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', 'loose') simulateInput('version-type', 'loose')
spec = '24.0-rc2'
expected = '24.0-rc2'
got = setupBeam.getVersionFromSpec(spec, versions)
assert.deepStrictEqual(got, expected)
spec = '22.3' spec = '22.3'
expected = '22.3.4.12.1' expected = '22.3.4.12.1'
got = setupBeam.getVersionFromSpec(spec, versions) got = setupBeam.getVersionFromSpec(spec, versions)
@@ -318,25 +346,12 @@ async function testGetVersionFromSpec() {
got = setupBeam.getVersionFromSpec(spec, versions) got = setupBeam.getVersionFromSpec(spec, versions)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
spec = '11.11' simulateInput('version-type', 'strict')
expected = 'v11.11.0-rc.0-otp-23' spec = '22.3.4.2'
got = setupBeam.getVersionFromSpec(spec, versions) expected = '22.3.4.2'
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'
got = setupBeam.getVersionFromSpec(spec, versions) got = setupBeam.getVersionFromSpec(spec, versions)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', 'loose')
} }
function simulateInput(key, value) { function simulateInput(key, value) {
+46 -15
View File
@@ -4777,7 +4777,8 @@ async function getOTPVersion(otpSpec0, osVersion) {
} }
if (otpVersion === null) { if (otpVersion === null) {
throw new Error( 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 elixirVersions = await getElixirVersions()
const semverVersions = Array.from(elixirVersions.keys()).sort() 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 let elixirVersion
if (exSpec[2]) { if (exSpec[2]) {
throw new Error( throw new Error(
@@ -4801,7 +4802,8 @@ async function getElixirVersion(exSpec0, otpVersion) {
} }
if (!exSpec || elixirVersion === null) { if (!exSpec || elixirVersion === null) {
throw new Error( 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-)?([^.]+)/) const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/)
@@ -4824,11 +4826,14 @@ async function getElixirVersion(exSpec0, otpVersion) {
} else { } else {
throw new Error( throw new Error(
`Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` + `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 const DigitStart = new RegExp('^\\d+')
return DigitStart.test(elixirVersion)
? `v${elixirVersionWithOTP}`
: elixirVersionWithOTP
} }
async function getRebar3Version(r3Spec) { async function getRebar3Version(r3Spec) {
@@ -4836,7 +4841,8 @@ async function getRebar3Version(r3Spec) {
const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions) const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions)
if (rebar3Version === null) { if (rebar3Version === null) {
throw new Error( 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 +4902,7 @@ async function getElixirVersions() {
.split('\n') .split('\n')
.forEach((line) => { .forEach((line) => {
const elixirMatch = const elixirMatch =
line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/) line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/)
const elixirVersion = elixirMatch[1] const elixirVersion = elixirMatch[1]
const otpVersion = elixirMatch[2] const otpVersion = elixirMatch[2]
const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || [] const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || []
@@ -4928,18 +4934,19 @@ async function getRebar3Versions() {
function getVersionFromSpec(spec, versions) { function getVersionFromSpec(spec, versions) {
let version = null let version = null
if (core.getInput('version-type', { required: false }) === 'strict') { if (
spec.match(/rc/) ||
core.getInput('version-type', { required: false }) === 'strict'
) {
version = spec version = spec
} }
if (version === null) { if (version === null) {
// We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions // 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) => { const versionsMap = versions.sort(sortVersions).reduce((acc, v) => {
try { if (!v.match(/rc/)) {
acc[semver.coerce(v).version] = v // release candidates are opt-in
} catch { acc[maybeCoerced(v)] = v
// some stuff can't be coerced, like 'master'
acc[v] = v
} }
return acc return acc
}, {}) }, {})
@@ -4948,13 +4955,26 @@ function getVersionFromSpec(spec, versions) {
version = version =
versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)] versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)]
} else { } else {
version = versionsMap[spec] version = versionsMap[maybeCoerced(spec)]
} }
} }
return version 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) { function sortVersions(left, right) {
let ret = 0 let ret = 0
const newL = verAsComparableStr(left) const newL = verAsComparableStr(left)
@@ -4984,8 +5004,19 @@ function getRunnerOSVersion() {
win16: 'windows-2016', win16: 'windows-2016',
win19: 'windows-2019', win19: 'windows-2019',
} }
const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS]
return ImageOSToContainer[process.env.ImageOS] if (!containerFromEnvImageOS) {
throw new Error(
"Tried to map a target OS from env. variable 'ImageOS', but failed. If you're using a " +
"self-hosted runner, you should set 'env': 'ImageOS': ... to one of the following: " +
"['" +
`${Object.keys(ImageOSToContainer).join("', '")}` +
"']",
)
}
return containerFromEnvImageOS
} }
async function get(url0, pageIdxs) { async function get(url0, pageIdxs) {
+46 -15
View File
@@ -121,7 +121,8 @@ async function getOTPVersion(otpSpec0, osVersion) {
} }
if (otpVersion === null) { if (otpVersion === null) {
throw new Error( 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 elixirVersions = await getElixirVersions()
const semverVersions = Array.from(elixirVersions.keys()).sort() 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 let elixirVersion
if (exSpec[2]) { if (exSpec[2]) {
throw new Error( throw new Error(
@@ -145,7 +146,8 @@ async function getElixirVersion(exSpec0, otpVersion) {
} }
if (!exSpec || elixirVersion === null) { if (!exSpec || elixirVersion === null) {
throw new Error( 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-)?([^.]+)/) const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/)
@@ -168,11 +170,14 @@ async function getElixirVersion(exSpec0, otpVersion) {
} else { } else {
throw new Error( throw new Error(
`Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` + `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 const DigitStart = new RegExp('^\\d+')
return DigitStart.test(elixirVersion)
? `v${elixirVersionWithOTP}`
: elixirVersionWithOTP
} }
async function getRebar3Version(r3Spec) { async function getRebar3Version(r3Spec) {
@@ -180,7 +185,8 @@ async function getRebar3Version(r3Spec) {
const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions) const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions)
if (rebar3Version === null) { if (rebar3Version === null) {
throw new Error( 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 +246,7 @@ async function getElixirVersions() {
.split('\n') .split('\n')
.forEach((line) => { .forEach((line) => {
const elixirMatch = const elixirMatch =
line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/) line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/)
const elixirVersion = elixirMatch[1] const elixirVersion = elixirMatch[1]
const otpVersion = elixirMatch[2] const otpVersion = elixirMatch[2]
const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || [] const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || []
@@ -272,18 +278,19 @@ async function getRebar3Versions() {
function getVersionFromSpec(spec, versions) { function getVersionFromSpec(spec, versions) {
let version = null let version = null
if (core.getInput('version-type', { required: false }) === 'strict') { if (
spec.match(/rc/) ||
core.getInput('version-type', { required: false }) === 'strict'
) {
version = spec version = spec
} }
if (version === null) { if (version === null) {
// We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions // 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) => { const versionsMap = versions.sort(sortVersions).reduce((acc, v) => {
try { if (!v.match(/rc/)) {
acc[semver.coerce(v).version] = v // release candidates are opt-in
} catch { acc[maybeCoerced(v)] = v
// some stuff can't be coerced, like 'master'
acc[v] = v
} }
return acc return acc
}, {}) }, {})
@@ -292,13 +299,26 @@ function getVersionFromSpec(spec, versions) {
version = version =
versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)] versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)]
} else { } else {
version = versionsMap[spec] version = versionsMap[maybeCoerced(spec)]
} }
} }
return version 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) { function sortVersions(left, right) {
let ret = 0 let ret = 0
const newL = verAsComparableStr(left) const newL = verAsComparableStr(left)
@@ -328,8 +348,19 @@ function getRunnerOSVersion() {
win16: 'windows-2016', win16: 'windows-2016',
win19: 'windows-2019', win19: 'windows-2019',
} }
const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS]
return ImageOSToContainer[process.env.ImageOS] if (!containerFromEnvImageOS) {
throw new Error(
"Tried to map a target OS from env. variable 'ImageOS', but failed. If you're using a " +
"self-hosted runner, you should set 'env': 'ImageOS': ... to one of the following: " +
"['" +
`${Object.keys(ImageOSToContainer).join("', '")}` +
"']",
)
}
return containerFromEnvImageOS
} }
async function get(url0, pageIdxs) { async function get(url0, pageIdxs) {