mirror of
https://github.com/erlef/setup-beam.git
synced 2026-07-23 07:06:07 +00:00
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:
committed by
GitHub
parent
86521d7b4d
commit
89cb00455a
+244
-135
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user