Introduce rebar-version (#9)

Previously (Remove redundant code) (+27 squashed commits)
Squashed commits:
[0b79c31] Rename as setup-beam
[312b42b] Simplify platform verification
[b43ca96] Prevent use of OTP- prefixed Erlang/OTP versions
[103a236] Move the promise-based approach to an async-based approach

(not testing for failure, so not guaranteeing readable/usable output either)
[1e7f631] Remove local test files
[d03e0c6] Prevent use of -otp- infixed Elixir versions
[697110e] Have versions output in action, not just in tests
[7360781] Ease test validations
[71e6999] Reinstall tests that were there before
[6602f20] Attempt at simplifying regexp matching with build listings
[09247af] Provide for an Elixir to "not OTP" fallback mechanism

(as explained by the comments)
[dfc4083] Attempt at renaming for clarity

We also get rid of return values when not reachable
We also move the `-otp-` part of the Elixir version upstream so it's easy to reason about
  (and update the tests accordingly)
[0d4eb28] Bump our dep.s

(trying to figure out why core.setFailed isn't making the action fail, as it should)
[ebf48ef] Have action fail (with an exception) when no expected version is found

(we otherwise feel that a simple warning is not enough, as it may end up hiding potential bugs)
[8478364] Adapt tests to our actual inputs
[16ea63d] Add prettier again, now sync'ed with jslint

(pass it on the code, already)
[cfc65df] Improve error messages and tests
[a017948] Test unavailable Elixir upon mix
[7f2a3e9] Test failing installations

Also output core.error when expected
[08061ca] Attempt at making shell scripts more robust
[4ee73cb] Separate test code from production code

1. inputs (for CI) are managed by code, not by .yml env. variables
2. rebar3_builds.txt added for stable repeatability
3. tests tweaked to make it easier to modify and drop dependency on production code
4. `dist` regenerated (duplicate files gone)
5. setup-elixir.js tweaked to this new reality
[d17fed4] Remove prettier from the package
[7d6f8f5] Actually break CI when we should
[b972eaf] Remove unrequired element
[0ffa28a] Ease maintenance and readability
[4690169] Fix as per broken tests
[a1371c6] Merge setup-erlang in
This commit is contained in:
Paulo F. Oliveira
2021-03-30 17:54:20 +01:00
committed by GitHub
parent 86521d7b4d
commit 89cb00455a
112 changed files with 10429 additions and 6316 deletions
+244 -135
View File
@@ -1,64 +1,238 @@
const core = require('@actions/core')
const {exec} = require('@actions/exec')
const {installElixir, installOTP} = require('./installer')
const installer = require('./installer')
const path = require('path')
const semver = require('semver')
const https = require('https')
const {
fstat,
promises: {readFile},
} = require('fs')
main().catch(err => {
main().catch((err) => {
core.setFailed(err.message)
})
async function main() {
checkPlatform()
installer.checkPlatform()
const osVersion = getRunnerOSVersion()
const otpSpec = core.getInput('otp-version', {required: true})
const elixirSpec = core.getInput('elixir-version', {required: true})
const otpVersion = await getOtpVersion(otpSpec, osVersion)
const [elixirVersion, otpMajor] = await getElixirVersion(
elixirSpec,
otpVersion
const otpVersion = await installOTP(otpSpec, osVersion)
const elixirSpec = core.getInput('elixir-version', {required: false})
const elixirInstalled = await maybeInstallElixir(elixirSpec, otpVersion)
if (elixirInstalled === true) {
const shouldMixRebar = core.getInput('install-rebar', {
required: false,
})
await mix(shouldMixRebar, 'rebar')
const shouldMixHex = core.getInput('install-hex', {
required: false,
})
await mix(shouldMixHex, 'hex')
}
const rebar3Spec = core.getInput('rebar3-version', {required: false})
maybeInstallRebar3(rebar3Spec)
}
async function installOTP(otpSpec, osVersion) {
const otpVersion = await getOTPVersion(otpSpec, osVersion)
console.log(
`##[group]Installing Erlang/OTP ${otpVersion} - built on ${osVersion}`,
)
let installHex = core.getInput('install-hex')
installHex = installHex == null ? 'true' : installHex
let installRebar = core.getInput('install-rebar')
installRebar = installRebar == null ? 'true' : installRebar
console.log(`##[group]Installing OTP ${otpVersion} - built on ${osVersion}`)
await installOTP(otpVersion, osVersion)
console.log(`##[endgroup]`)
console.log(`##[group]Installing Elixir ${elixirVersion}`)
await installElixir(elixirVersion, otpMajor)
console.log(`##[endgroup]`)
process.env.PATH = `${process.env.RUNNER_TEMP}/.setup-beam/elixir/bin:${process.env.RUNNER_TEMP}/.setup-beam/otp/bin:${process.env.PATH}`
if (installRebar === 'true') await exec('mix local.rebar --force')
if (installHex === 'true') await exec('mix local.hex --force')
const matchersPath = path.join(__dirname, '..', '.github')
console.log(`##[add-matcher]${path.join(matchersPath, 'elixir.json')}`)
await installer.installOTP(osVersion, otpVersion)
core.setOutput('otp-version', otpVersion)
core.setOutput('elixir-version', elixirVersion)
core.setOutput('osVersion', osVersion)
prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`)
console.log(`##[endgroup]`)
return otpVersion
}
function checkPlatform() {
if (process.platform !== 'linux')
throw new Error('@erlef/setup-beam only supports Ubuntu Linux at this time')
async function maybeInstallElixir(elixirSpec, otpVersion) {
if (elixirSpec) {
const elixirVersion = await getElixirVersion(elixirSpec, otpVersion)
console.log(`##[group]Installing Elixir ${elixirVersion}`)
await installer.installElixir(elixirVersion)
core.setOutput('elixir-version', elixirVersion)
const matchersPath = path.join(__dirname, '..', '.github')
console.log(`##[add-matcher]${path.join(matchersPath, 'elixir.json')}`)
prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/elixir/bin`)
console.log(`##[endgroup]`)
return true
} else {
return false
}
}
async function getOtpVersion(spec, osVersion) {
const version = getVersionFromSpec(spec, await getOtpVersions(osVersion))
return version ? `OTP-${version}` : spec
async function mix(shouldMix, what) {
if (shouldMix) {
const cmd = 'mix'
const args = [`local.${what}`, '--force']
console.log(`##[group]Running ${cmd} ${args}`)
await exec(cmd, args)
console.log(`##[endgroup]`)
}
}
async function maybeInstallRebar3(rebar3Spec) {
if (rebar3Spec) {
const rebar3Version = await getRebar3Version(rebar3Spec)
console.log(`##[group]Installing rebar3 ${rebar3Version}`)
await installer.installRebar3(rebar3Version)
core.setOutput('rebar3-version', rebar3Version)
prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/rebar3/bin`)
console.log(`##[endgroup]`)
return true
} else {
return false
}
}
async function getOTPVersion(otpSpec0, osVersion) {
const otpVersions = await getOTPVersions(osVersion)
const otpSpec = otpSpec0.match(/^(OTP-)?([^ ]+)/)
let otpVersion
if (otpSpec[1]) {
throw new Error(
`Requested Erlang/OTP version (from spec ${otpSpec0}) ` +
`should not contain 'OTP-'`,
)
}
if (otpSpec) {
otpVersion = getVersionFromSpec(
otpSpec[2],
Array.from(otpVersions.keys()).sort(),
)
}
if (otpVersion === null) {
throw new Error(
`Requested Erlang/OTP version (from spec ${otpSpec0}) not found in build listing`,
)
}
return otpVersions.get(otpVersion) // from the reference, for download
}
async function getElixirVersion(exSpec0, otpVersion) {
const elixirVersions = await getElixirVersions()
const semverVersions = Array.from(elixirVersions.keys()).sort()
const exSpec = exSpec0.match(/^(.+)(-otp-.+)/) || exSpec0.match(/^(.+)/)
let elixirVersion
if (exSpec[2]) {
throw new Error(
`Requested Elixir / Erlang/OTP version (from spec ${exSpec0} / ${otpVersion}) ` +
`should not contain '-otp-...'`,
)
}
if (exSpec) {
elixirVersion = getVersionFromSpec(exSpec[1], semverVersions)
}
if (!exSpec || elixirVersion === null) {
throw new Error(
`Requested Elixir version (from spec ${exSpec0}) not found in build listing`,
)
}
const otpMatch = otpVersion.match(/^(?:OTP-)?([^\.]+)/)
let elixirVersionWithOTP
if (elixirVersions.get(elixirVersion)) {
const otpVersionMajor = otpMatch[1]
// We try for a version like `v1.4.5-otp-20`...
if (elixirVersions.get(elixirVersion).includes(otpMatch[1])) {
// ... and it's available: use it!
elixirVersionWithOTP = elixirVersion + `-otp-${otpVersionMajor}`
core.info(`Using Elixir / -otp- version ${elixirVersionWithOTP}`)
} else {
// ... and it's not available: fallback to the "generic" version (v1.4.5 only).
elixirVersionWithOTP = elixirVersion
core.info(`Using Elixir ${elixirVersionWithOTP}`)
}
} else {
throw new Error(
`Requested Elixir / Erlang/OTP version (from spec ${exSpec0} / ${otpVersion}) not ` +
`found in build listing`,
)
}
return elixirVersionWithOTP
}
async function getRebar3Version(r3Spec) {
const rebar3Versions = await getRebar3Versions()
const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions)
if (rebar3Version === null) {
throw new Error(
`Requested rebar3 version (from spec ${r3Spec}) not found in build listing`,
)
}
return rebar3Version
}
async function getOTPVersions(osVersion) {
const otpVersionsListing = await get(
`https://repo.hex.pm/builds/otp/${osVersion}/builds.txt`,
)
const otpVersions = new Map()
otpVersionsListing
.trim()
.split('\n')
.map((line) => {
debugger
const otpMatch = line.match(/^(OTP-)?([^ ]+)/)
const otpVersion = otpMatch[2]
otpVersions.set(otpVersion, otpMatch[0]) // we keep the original for later reference
})
.sort()
return otpVersions
}
async function getElixirVersions() {
const elixirVersionsListing = await get(
'https://repo.hex.pm/builds/elixir/builds.txt',
)
const otpVersionsForElixirMap = new Map()
elixirVersionsListing
.trim()
.split('\n')
.forEach((line) => {
const elixirMatch =
line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/)
const elixirVersion = elixirMatch[1]
const otpVersion = elixirMatch[2]
const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || []
if (otpVersion) {
// -otp- present (special case)
otpVersions.push(otpVersion)
}
otpVersionsForElixirMap.set(elixirVersion, otpVersions)
})
return otpVersionsForElixirMap
}
async function getRebar3Versions() {
const resultJSON = await get(
'https://api.github.com/repos/erlang/rebar3/releases',
)
const rebar3VersionsListing = JSON.parse(resultJSON)
.map((x) => x.tag_name)
.sort()
return rebar3VersionsListing
}
function getVersionFromSpec(spec, versions) {
if (versions.includes(spec)) {
return spec
} else {
return semver.maxSatisfying(versions, semver.validRange(spec))
}
}
function getRunnerOSVersion() {
@@ -71,101 +245,36 @@ function getRunnerOSVersion() {
return mapToUbuntuVersion[process.env.ImageOS] || 'ubuntu-18.04'
}
exports.getElixirVersion = getElixirVersion
async function getElixirVersion(spec, otpVersion) {
const versions = await getElixirVersions()
const semverRegex = /^v(\d+\.\d+\.\d+(?:-.+)?)/
const semverVersions = Array.from(versions.keys())
.filter(str => str.match(semverRegex))
.map(str => str.match(semverRegex)[1])
const version = getVersionFromSpec(spec, semverVersions)
const gitRef = version ? `v${version}` : spec
const otpMatch = otpVersion.match(/^OTP-([\.\d]+)/)
if (otpMatch != null && versions.get(gitRef).includes(otpMatch[0])) {
return [gitRef, otpMatch[0]]
} else {
return [gitRef, null]
}
}
function getVersionFromSpec(spec, versions) {
if (versions.includes(spec)) {
return spec
} else {
const range = semver.validRange(spec)
return semver.maxSatisfying(versions, range)
}
}
async function getOtpVersions(osVersion) {
const result = await get(
`https://repo.hex.pm/builds/otp/${osVersion}/builds.txt`
)
return result
.trim()
.split('\n')
.map(line => {
const match = line.match(/^OTP-([\.\d]+)/)
if (match) {
const [_, version] = match
return version
} else {
return line
}
})
}
async function getElixirVersions() {
const result = await get('https://repo.hex.pm/builds/elixir/builds.txt')
const map = new Map()
result
.trim()
.split('\n')
.forEach(line => {
const match =
line.match(/^(v\d+\.\d+\.\d+(?:-.+)?)-otp-(\d+)/) ||
line.match(/^([^-]+)-otp-(\d+)/)
if (match) {
const [_, version, otp] = match
const array = map.get(version) || []
array.push(otp)
map.set(version, array)
}
})
return map
}
function get(url) {
if (process.env.NODE_ENV === 'test') {
return readFile(
path.join(__dirname, '..', '__tests__', 'builds.txt')
).then(buf => buf.toString())
}
return new Promise((resolve, reject) => {
const req = https.get(url)
req.on('response', res => {
let data = ''
res.on('data', chunk => {
data += chunk
https
.get(
url,
{
headers: {'user-agent': 'setup-beam'},
},
(res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
resolve(data)
})
},
)
.on('error', (err) => {
reject(err)
})
res.on('end', () => {
resolve(data)
})
})
req.on('error', err => {
reject(err)
})
})
}
function prependToPath(what) {
process.env.PATH = `${what}:${process.env.PATH}`
}
module.exports = {
getOTPVersion: getOTPVersion,
getElixirVersion: getElixirVersion,
getRebar3Version: getRebar3Version,
}