neiam /action-setup-beam
action-setup-beam
public · Issues · Pulls · Labels · Forks · Compare · Actions queued
⭐
Log in to mark this repository.
Ease consumption by means of .tool-versions (#159)
c373088 · Paulo F. Oliveira · 2022-11-22 22:34
Message
{commit_body(@commit)}
Files changed
modified
.github/workflows/action.yml
+6
−0
@@ -40,6 +40,12 @@ jobs:
| 40 | 40 | node-version: '14' |
| 41 | 41 | - run: npm ci |
| 42 | 42 | - run: npm test |
| 43 | + - name: .tool-versions test | |
| 44 | + id: setup-beam | |
| 45 | + uses: ./ | |
| 46 | + with: | |
| 47 | + version-file: __tests__/.tool-versions | |
| 48 | + version-type: strict | |
| 43 | 49 | |
| 44 | 50 | unit_tests_windows: |
| 45 | 51 | name: Unit tests (Windows) |
modified
.gitignore
+1
−0
@@ -1,2 +1,3 @@
| 1 | 1 | node_modules/** |
| 2 | 2 | dist/.github/workflows |
| 3 | +__tests__/.tool-versions |
modified
README.md
+27
−0
@@ -16,9 +16,11 @@ workflow by:
| 16 | 16 | - optionally, installing [Gleam](https://gleam.run/) |
| 17 | 17 | - optionally, installing [`rebar3`](https://www.rebar3.org/) |
| 18 | 18 | - optionally, installing [`hex`](https://hex.pm/) |
| 19 | +- optionally, opting for strict or loose version matching | |
| 19 | 20 | - optionally, having |
| 20 | 21 | [problem matchers](https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md) show |
| 21 | 22 | warnings and errors on pull requests |
| 23 | +- optionally, using a version file (as explained in "Version file", below), to identify versions | |
| 22 | 24 | |
| 23 | 25 | **Note**: currently, this action only supports Actions' `ubuntu-` and `windows-` runtimes. |
| 24 | 26 |
@@ -86,6 +88,31 @@ jobs:
| 86 | 88 | ... |
| 87 | 89 | ``` |
| 88 | 90 | |
| 91 | +### Version file | |
| 92 | + | |
| 93 | +A version file is specified via input `version-file` (e.g.`.tool-versions`). This | |
| 94 | +allows not having to use YML input for versions, though the action does check (and | |
| 95 | +will exit with error) if both inputs are set. | |
| 96 | + | |
| 97 | +**Note**: if you're using a version file, option `version-type` is checked to be `strict`, | |
| 98 | +and will make the action exit with error otherwise. | |
| 99 | + | |
| 100 | +The following version file formats are supported: | |
| 101 | + | |
| 102 | +- `.tool-versions`, as specified by [asdf: Configuration](https://asdf-vm.com/manage/configuration.html) | |
| 103 | + | |
| 104 | +Supported version elements are the same as the ones defined for the YML portion of the action, | |
| 105 | +with the following correspondence. | |
| 106 | + | |
| 107 | +#### `.tool-versions` format | |
| 108 | + | |
| 109 | +| YML | `.tool-versions` | | |
| 110 | +|- |- | |
| 111 | +| `otp-version` | `erlang` | |
| 112 | +| `elixir-version` | `elixir` | |
| 113 | +| `gleam-version` | `gleam` | |
| 114 | +| `rebar3-version` | `rebar` | |
| 115 | + | |
| 89 | 116 | ### Example (Erlang/OTP + Elixir, on Ubuntu) |
| 90 | 117 | |
| 91 | 118 | ```yaml |
modified
__tests__/setup-beam.test.js
+35
−1
@@ -5,6 +5,7 @@ simulateInput('install-rebar', 'true')
| 5 | 5 | simulateInput('install-hex', 'true') |
| 6 | 6 | |
| 7 | 7 | const assert = require('assert') |
| 8 | +const fs = require('fs') | |
| 8 | 9 | const setupBeam = require('../src/setup-beam') |
| 9 | 10 | const installer = require('../src/installer') |
| 10 | 11 |
@@ -20,6 +21,8 @@ async function all() {
| 20 | 21 | await testRebar3Versions() |
| 21 | 22 | |
| 22 | 23 | await testGetVersionFromSpec() |
| 24 | + | |
| 25 | + await testParseVersionFile() | |
| 23 | 26 | } |
| 24 | 27 | |
| 25 | 28 | async function testFailInstallOTP() { |
@@ -405,8 +408,39 @@ async function testGetVersionFromSpec() {
| 405 | 408 | simulateInput('version-type', 'loose') |
| 406 | 409 | } |
| 407 | 410 | |
| 411 | +async function testParseVersionFile() { | |
| 412 | + const otpVersion = unsimulateInput('otp-version') | |
| 413 | + const elixirVersion = unsimulateInput('elixir-version') | |
| 414 | + | |
| 415 | + const erlang = '25.1.1' | |
| 416 | + const elixir = '1.14.1' | |
| 417 | + const toolVersions = `# a comment | |
| 418 | +erlang ${erlang}# comment, no space | |
| 419 | +elixir ${elixir} # comment, with space | |
| 420 | + gleam 0.23 # not picked up` | |
| 421 | + const filename = '__tests__/.tool-versions' | |
| 422 | + fs.writeFileSync(filename, toolVersions) | |
| 423 | + process.env.GITHUB_WORKSPACE = '' | |
| 424 | + const appVersions = setupBeam.parseVersionFile(filename) | |
| 425 | + assert.strictEqual(appVersions.get('erlang'), erlang) | |
| 426 | + assert.strictEqual(appVersions.get('elixir'), elixir) | |
| 427 | + | |
| 428 | + simulateInput('otp-version', otpVersion) | |
| 429 | + simulateInput('elixir-version', elixirVersion) | |
| 430 | +} | |
| 431 | + | |
| 432 | +function unsimulateInput(key) { | |
| 433 | + const before = process.env[input(key)] | |
| 434 | + simulateInput(key, '') | |
| 435 | + return before | |
| 436 | +} | |
| 437 | + | |
| 408 | 438 | function simulateInput(key, value) { |
| 409 | − process.env[`INPUT_${key.replace(/ /g, '_').toUpperCase()}`] = value | |
| 439 | + process.env[input(key)] = value | |
| 440 | +} | |
| 441 | + | |
| 442 | +function input(key) { | |
| 443 | + return `INPUT_${key.replace(/ /g, '_').toUpperCase()}` | |
| 410 | 444 | } |
| 411 | 445 | |
| 412 | 446 | all() |
modified
action.yml
+5
−2
@@ -11,7 +11,6 @@ inputs:
| 11 | 11 | otp-version: |
| 12 | 12 | description: Version range or exact version of Erlang/OTP to use, |
| 13 | 13 | or false when installing only Gleam without OTP |
| 14 | − required: true | |
| 15 | 14 | elixir-version: |
| 16 | 15 | description: Version range or exact version of Elixir to use |
| 17 | 16 | gleam-version: |
@@ -31,8 +30,12 @@ inputs:
| 31 | 30 | to guess versions based on semver rules |
| 32 | 31 | default: loose |
| 33 | 32 | disable_problem_matchers: |
| 34 | − description: whether to have the problem matchers present in the results (or not) | |
| 33 | + description: whether to have the problem matchers present in the results | |
| 34 | + (or not) | |
| 35 | 35 | default: false |
| 36 | + version-file: | |
| 37 | + description: a versions file (e.g. as used by `asdf`), which defines inputs | |
| 38 | + default: '' | |
| 36 | 39 | outputs: |
| 37 | 40 | elixir-version: |
| 38 | 41 | description: Exact version of Elixir that was installed |
modified
dist/index.js
+76
−14
@@ -7196,6 +7196,7 @@ const { exec } = __nccwpck_require__(1514)
| 7196 | 7196 | const path = __nccwpck_require__(1017) |
| 7197 | 7197 | const semver = __nccwpck_require__(1383) |
| 7198 | 7198 | const https = __nccwpck_require__(5687) |
| 7199 | +const fs = __nccwpck_require__(7147) | |
| 7199 | 7200 | const installer = __nccwpck_require__(2127) |
| 7200 | 7201 | |
| 7201 | 7202 | main().catch((err) => { |
@@ -7205,24 +7206,31 @@ main().catch((err) => {
| 7205 | 7206 | async function main() { |
| 7206 | 7207 | installer.checkPlatform() |
| 7207 | 7208 | |
| 7209 | + const versionFilePath = getInput('version-file', false) | |
| 7210 | + let versions | |
| 7211 | + if (versionFilePath) { | |
| 7212 | + if (!isStrictVersion()) { | |
| 7213 | + throw new Error( | |
| 7214 | + "you have to set version-type=strict if you're using version-file", | |
| 7215 | + ) | |
| 7216 | + } | |
| 7217 | + versions = parseVersionFile(versionFilePath) | |
| 7218 | + } | |
| 7219 | + | |
| 7208 | 7220 | const osVersion = getRunnerOSVersion() |
| 7209 | − const otpSpec = core.getInput('otp-version', { required: true }) | |
| 7210 | − const elixirSpec = core.getInput('elixir-version', { required: false }) | |
| 7211 | − const gleamSpec = core.getInput('gleam-version', { required: false }) | |
| 7212 | − const rebar3Spec = core.getInput('rebar3-version', { required: false }) | |
| 7221 | + const otpSpec = getInput('otp-version', true, 'erlang', versions) | |
| 7222 | + const elixirSpec = getInput('elixir-version', false, 'elixir', versions) | |
| 7223 | + const gleamSpec = getInput('gleam-version', false, 'gleam', versions) | |
| 7224 | + const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions) | |
| 7213 | 7225 | |
| 7214 | 7226 | if (otpSpec !== 'false') { |
| 7215 | 7227 | await installOTP(otpSpec, osVersion) |
| 7216 | 7228 | const elixirInstalled = await maybeInstallElixir(elixirSpec, otpSpec) |
| 7217 | 7229 | |
| 7218 | 7230 | if (elixirInstalled === true) { |
| 7219 | − const shouldMixRebar = core.getInput('install-rebar', { | |
| 7220 | − required: false, | |
| 7221 | − }) | |
| 7231 | + const shouldMixRebar = getInput('install-rebar', false) | |
| 7222 | 7232 | await mix(shouldMixRebar, 'rebar') |
| 7223 | − const shouldMixHex = core.getInput('install-hex', { | |
| 7224 | − required: false, | |
| 7225 | − }) | |
| 7233 | + const shouldMixHex = getInput('install-hex', false) | |
| 7226 | 7234 | await mix(shouldMixHex, 'hex') |
| 7227 | 7235 | } |
| 7228 | 7236 | } else if (!gleamSpec) { |
@@ -7254,9 +7262,7 @@ async function maybeInstallElixir(elixirSpec, otpVersion) {
| 7254 | 7262 | console.log(`##[group]Installing Elixir ${elixirVersion}`) |
| 7255 | 7263 | await installer.installElixir(elixirVersion) |
| 7256 | 7264 | core.setOutput('elixir-version', elixirVersion) |
| 7257 | − const disableProblemMatchers = core.getInput('disable_problem_matchers', { | |
| 7258 | − required: false, | |
| 7259 | − }) | |
| 7265 | + const disableProblemMatchers = getInput('disable_problem_matchers', false) | |
| 7260 | 7266 | if (disableProblemMatchers === 'false') { |
| 7261 | 7267 | const matchersPath = __nccwpck_require__.ab + ".github" |
| 7262 | 7268 | console.log( |
@@ -7503,7 +7509,7 @@ async function getRebar3Versions() {
| 7503 | 7509 | } |
| 7504 | 7510 | |
| 7505 | 7511 | function isStrictVersion() { |
| 7506 | − return core.getInput('version-type', { required: false }) === 'strict' | |
| 7512 | + return getInput('version-type', false) === 'strict' | |
| 7507 | 7513 | } |
| 7508 | 7514 | |
| 7509 | 7515 | function getVersionFromSpec(spec, versions, maybePrependWithV0) { |
@@ -7653,12 +7659,68 @@ function isVersion(v) {
| 7653 | 7659 | return /^v?\d+/.test(v) |
| 7654 | 7660 | } |
| 7655 | 7661 | |
| 7662 | +function getInput(inputName, required, alternativeName, alternatives) { | |
| 7663 | + const alternativeValue = (alternatives || new Map()).get(alternativeName) | |
| 7664 | + let input = core.getInput(inputName, { | |
| 7665 | + required: alternativeValue ? false : required, | |
| 7666 | + }) | |
| 7667 | + // We can't have both input and alternativeValue set | |
| 7668 | + if (input && alternativeValue) { | |
| 7669 | + throw new Error( | |
| 7670 | + `Found input ${inputName}=${input} (from the YML) \ | |
| 7671 | +alongside ${alternativeName}=${alternativeValue} \ | |
| 7672 | +(from the version file). You must choose one or the other.`, | |
| 7673 | + ) | |
| 7674 | + } else if (!input) { | |
| 7675 | + input = alternativeValue | |
| 7676 | + } | |
| 7677 | + return input | |
| 7678 | +} | |
| 7679 | + | |
| 7680 | +function parseVersionFile(versionFilePath0) { | |
| 7681 | + const versionFilePath = path.join( | |
| 7682 | + process.env.GITHUB_WORKSPACE, | |
| 7683 | + versionFilePath0, | |
| 7684 | + ) | |
| 7685 | + if (!fs.existsSync(versionFilePath)) { | |
| 7686 | + throw new Error( | |
| 7687 | + `The specified version file, ${versionFilePath0}, does not exist`, | |
| 7688 | + ) | |
| 7689 | + } | |
| 7690 | + console.log(`##[group]Parsing version file at ${versionFilePath0}`) | |
| 7691 | + const appVersions = new Map() | |
| 7692 | + const versions = fs.readFileSync(versionFilePath, 'utf8') | |
| 7693 | + // For the time being we parse .tool-versions | |
| 7694 | + // If we ever start parsing something else, this should | |
| 7695 | + // become default in a new option named e.g. version-file-type | |
| 7696 | + versions.split('\n').forEach((line) => { | |
| 7697 | + const appVersion = line.match(/^([^ ]+)[ ]+([^ #]+)/) | |
| 7698 | + if (appVersion) { | |
| 7699 | + const app = appVersion[1] | |
| 7700 | + if (['erlang', 'elixir', 'gleam', 'rebar'].includes(app)) { | |
| 7701 | + const [, , version] = appVersion | |
| 7702 | + console.log(`Consuming ${app} at version ${version}`) | |
| 7703 | + appVersions.set(app, version) | |
| 7704 | + } | |
| 7705 | + } | |
| 7706 | + }) | |
| 7707 | + if (!appVersions.size) { | |
| 7708 | + console.log('There was apparently nothing to consume') | |
| 7709 | + } else { | |
| 7710 | + console.log('... done!') | |
| 7711 | + } | |
| 7712 | + console.log('##[endgroup]') | |
| 7713 | + | |
| 7714 | + return appVersions | |
| 7715 | +} | |
| 7716 | + | |
| 7656 | 7717 | module.exports = { |
| 7657 | 7718 | getOTPVersion, |
| 7658 | 7719 | getElixirVersion, |
| 7659 | 7720 | getGleamVersion, |
| 7660 | 7721 | getRebar3Version, |
| 7661 | 7722 | getVersionFromSpec, |
| 7723 | + parseVersionFile, | |
| 7662 | 7724 | } |
| 7663 | 7725 | |
| 7664 | 7726 |
modified
src/setup-beam.js
+76
−14
@@ -3,6 +3,7 @@ const { exec } = require('@actions/exec')
| 3 | 3 | const path = require('path') |
| 4 | 4 | const semver = require('semver') |
| 5 | 5 | const https = require('https') |
| 6 | +const fs = require('fs') | |
| 6 | 7 | const installer = require('./installer') |
| 7 | 8 | |
| 8 | 9 | main().catch((err) => { |
@@ -12,24 +13,31 @@ main().catch((err) => {
| 12 | 13 | async function main() { |
| 13 | 14 | installer.checkPlatform() |
| 14 | 15 | |
| 16 | + const versionFilePath = getInput('version-file', false) | |
| 17 | + let versions | |
| 18 | + if (versionFilePath) { | |
| 19 | + if (!isStrictVersion()) { | |
| 20 | + throw new Error( | |
| 21 | + "you have to set version-type=strict if you're using version-file", | |
| 22 | + ) | |
| 23 | + } | |
| 24 | + versions = parseVersionFile(versionFilePath) | |
| 25 | + } | |
| 26 | + | |
| 15 | 27 | const osVersion = getRunnerOSVersion() |
| 16 | − const otpSpec = core.getInput('otp-version', { required: true }) | |
| 17 | − const elixirSpec = core.getInput('elixir-version', { required: false }) | |
| 18 | − const gleamSpec = core.getInput('gleam-version', { required: false }) | |
| 19 | − const rebar3Spec = core.getInput('rebar3-version', { required: false }) | |
| 28 | + const otpSpec = getInput('otp-version', true, 'erlang', versions) | |
| 29 | + const elixirSpec = getInput('elixir-version', false, 'elixir', versions) | |
| 30 | + const gleamSpec = getInput('gleam-version', false, 'gleam', versions) | |
| 31 | + const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions) | |
| 20 | 32 | |
| 21 | 33 | if (otpSpec !== 'false') { |
| 22 | 34 | await installOTP(otpSpec, osVersion) |
| 23 | 35 | const elixirInstalled = await maybeInstallElixir(elixirSpec, otpSpec) |
| 24 | 36 | |
| 25 | 37 | if (elixirInstalled === true) { |
| 26 | − const shouldMixRebar = core.getInput('install-rebar', { | |
| 27 | − required: false, | |
| 28 | − }) | |
| 38 | + const shouldMixRebar = getInput('install-rebar', false) | |
| 29 | 39 | await mix(shouldMixRebar, 'rebar') |
| 30 | − const shouldMixHex = core.getInput('install-hex', { | |
| 31 | − required: false, | |
| 32 | − }) | |
| 40 | + const shouldMixHex = getInput('install-hex', false) | |
| 33 | 41 | await mix(shouldMixHex, 'hex') |
| 34 | 42 | } |
| 35 | 43 | } else if (!gleamSpec) { |
@@ -61,9 +69,7 @@ async function maybeInstallElixir(elixirSpec, otpVersion) {
| 61 | 69 | console.log(`##[group]Installing Elixir ${elixirVersion}`) |
| 62 | 70 | await installer.installElixir(elixirVersion) |
| 63 | 71 | core.setOutput('elixir-version', elixirVersion) |
| 64 | − const disableProblemMatchers = core.getInput('disable_problem_matchers', { | |
| 65 | − required: false, | |
| 66 | − }) | |
| 72 | + const disableProblemMatchers = getInput('disable_problem_matchers', false) | |
| 67 | 73 | if (disableProblemMatchers === 'false') { |
| 68 | 74 | const matchersPath = path.join(__dirname, '..', '.github') |
| 69 | 75 | console.log( |
@@ -310,7 +316,7 @@ async function getRebar3Versions() {
| 310 | 316 | } |
| 311 | 317 | |
| 312 | 318 | function isStrictVersion() { |
| 313 | − return core.getInput('version-type', { required: false }) === 'strict' | |
| 319 | + return getInput('version-type', false) === 'strict' | |
| 314 | 320 | } |
| 315 | 321 | |
| 316 | 322 | function getVersionFromSpec(spec, versions, maybePrependWithV0) { |
@@ -460,10 +466,66 @@ function isVersion(v) {
| 460 | 466 | return /^v?\d+/.test(v) |
| 461 | 467 | } |
| 462 | 468 | |
| 469 | +function getInput(inputName, required, alternativeName, alternatives) { | |
| 470 | + const alternativeValue = (alternatives || new Map()).get(alternativeName) | |
| 471 | + let input = core.getInput(inputName, { | |
| 472 | + required: alternativeValue ? false : required, | |
| 473 | + }) | |
| 474 | + // We can't have both input and alternativeValue set | |
| 475 | + if (input && alternativeValue) { | |
| 476 | + throw new Error( | |
| 477 | + `Found input ${inputName}=${input} (from the YML) \ | |
| 478 | +alongside ${alternativeName}=${alternativeValue} \ | |
| 479 | +(from the version file). You must choose one or the other.`, | |
| 480 | + ) | |
| 481 | + } else if (!input) { | |
| 482 | + input = alternativeValue | |
| 483 | + } | |
| 484 | + return input | |
| 485 | +} | |
| 486 | + | |
| 487 | +function parseVersionFile(versionFilePath0) { | |
| 488 | + const versionFilePath = path.join( | |
| 489 | + process.env.GITHUB_WORKSPACE, | |
| 490 | + versionFilePath0, | |
| 491 | + ) | |
| 492 | + if (!fs.existsSync(versionFilePath)) { | |
| 493 | + throw new Error( | |
| 494 | + `The specified version file, ${versionFilePath0}, does not exist`, | |
| 495 | + ) | |
| 496 | + } | |
| 497 | + console.log(`##[group]Parsing version file at ${versionFilePath0}`) | |
| 498 | + const appVersions = new Map() | |
| 499 | + const versions = fs.readFileSync(versionFilePath, 'utf8') | |
| 500 | + // For the time being we parse .tool-versions | |
| 501 | + // If we ever start parsing something else, this should | |
| 502 | + // become default in a new option named e.g. version-file-type | |
| 503 | + versions.split('\n').forEach((line) => { | |
| 504 | + const appVersion = line.match(/^([^ ]+)[ ]+([^ #]+)/) | |
| 505 | + if (appVersion) { | |
| 506 | + const app = appVersion[1] | |
| 507 | + if (['erlang', 'elixir', 'gleam', 'rebar'].includes(app)) { | |
| 508 | + const [, , version] = appVersion | |
| 509 | + console.log(`Consuming ${app} at version ${version}`) | |
| 510 | + appVersions.set(app, version) | |
| 511 | + } | |
| 512 | + } | |
| 513 | + }) | |
| 514 | + if (!appVersions.size) { | |
| 515 | + console.log('There was apparently nothing to consume') | |
| 516 | + } else { | |
| 517 | + console.log('... done!') | |
| 518 | + } | |
| 519 | + console.log('##[endgroup]') | |
| 520 | + | |
| 521 | + return appVersions | |
| 522 | +} | |
| 523 | + | |
| 463 | 524 | module.exports = { |
| 464 | 525 | getOTPVersion, |
| 465 | 526 | getElixirVersion, |
| 466 | 527 | getGleamVersion, |
| 467 | 528 | getRebar3Version, |
| 468 | 529 | getVersionFromSpec, |
| 530 | + parseVersionFile, | |
| 469 | 531 | } |
Parents: 529221d