mirror of
https://github.com/erlef/setup-beam.git
synced 2026-07-22 22:56:07 +00:00
1312 lines
38 KiB
JavaScript
1312 lines
38 KiB
JavaScript
import path from 'node:path'
|
|
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import { fileURLToPath } from 'node:url'
|
|
import * as core from '@actions/core'
|
|
import { exec } from '@actions/exec'
|
|
import * as tc from '@actions/tool-cache'
|
|
import * as semver from 'semver'
|
|
import * as csv from 'csv-parse/sync'
|
|
import _ from 'lodash'
|
|
import toml from 'smol-toml'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
const MAX_HTTP_RETRIES = 3
|
|
const APPS = ['erlang', 'elixir', 'gleam', 'rebar']
|
|
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
main().catch((err) => {
|
|
core.setFailed(err.message)
|
|
})
|
|
}
|
|
|
|
async function main() {
|
|
checkOtpArchitecture()
|
|
|
|
const versionFilePath = getInput('version-file', false)
|
|
let versions
|
|
if (versionFilePath) {
|
|
if (!isStrictVersion()) {
|
|
throw new Error(
|
|
"you have to set version-type=strict if you're using version-file",
|
|
)
|
|
}
|
|
versions = parseVersionFile(versionFilePath)
|
|
}
|
|
|
|
const otpSpec = getInput('otp-version', true, 'erlang', versions)
|
|
const elixirSpec = getInput('elixir-version', false, 'elixir', versions)
|
|
const gleamSpec = getInput('gleam-version', false, 'gleam', versions)
|
|
const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions)
|
|
|
|
if (otpSpec !== 'false') {
|
|
await installOTP(otpSpec)
|
|
const elixirInstalled = await maybeInstallElixir(elixirSpec)
|
|
if (elixirInstalled === true) {
|
|
const shouldMixRebar = getInput('install-rebar', false)
|
|
await mix(shouldMixRebar, 'rebar')
|
|
|
|
const shouldMixHex = getInput('install-hex', false)
|
|
await mix(shouldMixHex, 'hex')
|
|
}
|
|
} else if (!gleamSpec) {
|
|
throw new Error('otp-version=false is only available when installing Gleam')
|
|
}
|
|
|
|
await maybeInstallGleam(gleamSpec)
|
|
await maybeInstallRebar3(rebar3Spec)
|
|
|
|
// undefined is replaced by a function, post- main branch merge
|
|
const setupBeamVersion = '9eabb2c'
|
|
core.setOutput('setup-beam-version', setupBeamVersion)
|
|
}
|
|
|
|
async function installOTP(otpSpec) {
|
|
const osVersion = getRunnerOSVersion()
|
|
const otpVersion = await getOTPVersion(otpSpec, osVersion)
|
|
core.startGroup(
|
|
`Installing Erlang/OTP ${otpVersion} - built on ${getRunnerOSArchitecture()}/${osVersion}`,
|
|
)
|
|
await doWithMirrors({
|
|
hexMirrors: hexMirrorsInput(),
|
|
actionTitle: `install Erlang/OTP ${otpVersion}`,
|
|
action: async (hexMirror) => {
|
|
await install('otp', {
|
|
osVersion,
|
|
toolVersion: otpVersion,
|
|
hexMirror,
|
|
})
|
|
},
|
|
})
|
|
core.setOutput('otp-version', otpVersion)
|
|
maybeEnableProblemMatchers('erlang')
|
|
core.endGroup()
|
|
|
|
return otpVersion
|
|
}
|
|
|
|
async function maybeInstallElixir(elixirSpec) {
|
|
let installed = false
|
|
if (elixirSpec) {
|
|
const elixirVersion = await getElixirVersion(elixirSpec)
|
|
core.startGroup(`Installing Elixir ${elixirVersion}`)
|
|
await doWithMirrors({
|
|
hexMirrors: hexMirrorsInput(),
|
|
actionTitle: `install Elixir ${elixirVersion}`,
|
|
action: async (hexMirror) => {
|
|
await install('elixir', {
|
|
toolVersion: elixirVersion,
|
|
hexMirror,
|
|
})
|
|
},
|
|
})
|
|
core.setOutput('elixir-version', elixirVersion)
|
|
maybeEnableProblemMatchers('elixir')
|
|
core.endGroup()
|
|
|
|
installed = true
|
|
}
|
|
|
|
return installed
|
|
}
|
|
|
|
function problemMatchersEnabled() {
|
|
return getInput('disable_problem_matchers', false) === 'false'
|
|
}
|
|
|
|
// path.join with static strings helps ncc resolve and bundle the JSON files
|
|
function maybeEnableProblemMatchers(language) {
|
|
if (problemMatchersEnabled()) {
|
|
const matcherFiles = {
|
|
erlang: path.join(`${__dirname}/../matchers/erlang-matchers.json`),
|
|
elixir: path.join(`${__dirname}/../matchers/elixir-matchers.json`),
|
|
}
|
|
core.info(`##[add-matcher]${matcherFiles[language]}`)
|
|
}
|
|
}
|
|
|
|
async function mix(shouldMix, what) {
|
|
if (shouldMix === 'true') {
|
|
const cmd = 'mix'
|
|
const args = [`local.${what}`, '--force']
|
|
core.startGroup(`Running ${cmd} ${args}`)
|
|
await doWithMirrors({
|
|
hexMirrors: hexMirrorsInput(),
|
|
actionTitle: `mix ${what}`,
|
|
action: async (hexMirror) => {
|
|
process.env.HEX_MIRROR = hexMirror
|
|
await exec(cmd, args)
|
|
},
|
|
})
|
|
core.endGroup()
|
|
}
|
|
}
|
|
|
|
async function maybeInstallGleam(gleamSpec) {
|
|
let installed = false
|
|
if (gleamSpec) {
|
|
const gleamVersion = await getGleamVersion(gleamSpec)
|
|
core.startGroup(`Installing Gleam ${gleamVersion}`)
|
|
await install('gleam', { toolVersion: gleamVersion })
|
|
core.setOutput('gleam-version', gleamVersion)
|
|
core.endGroup()
|
|
|
|
installed = true
|
|
}
|
|
|
|
return installed
|
|
}
|
|
|
|
async function maybeInstallRebar3(rebar3Spec) {
|
|
let installed = false
|
|
let rebar3Version
|
|
if (rebar3Spec) {
|
|
if (rebar3Spec === 'nightly') {
|
|
rebar3Version = 'nightly'
|
|
} else {
|
|
rebar3Version = await getRebar3Version(rebar3Spec)
|
|
}
|
|
core.startGroup(`Installing rebar3 ${rebar3Version}`)
|
|
await install('rebar3', { toolVersion: rebar3Version })
|
|
core.setOutput('rebar3-version', rebar3Version)
|
|
core.endGroup()
|
|
|
|
installed = true
|
|
}
|
|
|
|
return installed
|
|
}
|
|
|
|
function maybeRemoveOTPPrefix(otpSpec) {
|
|
return otpSpec.replace(/^OTP-/, '')
|
|
}
|
|
|
|
async function getOTPVersion(otpSpec0, osVersion) {
|
|
const [otpVersions, originListing, hexMirrors] =
|
|
await getOTPVersions(osVersion)
|
|
let spec = maybeRemoveOTPPrefix(otpSpec0)
|
|
const versions = otpVersions
|
|
const otpVersion = getVersionFromSpec(spec, versions)
|
|
|
|
if (otpVersion === null) {
|
|
throw new Error(
|
|
requestedVersionFor('Erlang/OTP', otpSpec0, originListing, hexMirrors),
|
|
)
|
|
}
|
|
|
|
return otpVersion // from the reference, for download
|
|
}
|
|
|
|
function requestedVersionFor(tool, version, originListing, mirrors) {
|
|
const isVersionTypeStrict = isStrictVersion()
|
|
|
|
let versionType = 'loose'
|
|
if (isVersionTypeStrict) {
|
|
versionType = 'strict'
|
|
}
|
|
|
|
let ret =
|
|
`Requested ${versionType} ${tool} version (${version}) not found in version list, ` +
|
|
`at ${originListing}${mirrors ? `, with mirrors ${mirrors}` : ''}.`
|
|
if (!isVersionTypeStrict) {
|
|
ret = `${ret} Should you be using option 'version-type': 'strict'?`
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
const knownBranches = ['main', 'master', 'maint']
|
|
const nonSpecificVersions = ['nightly', 'latest']
|
|
|
|
async function getElixirVersion(exSpec0) {
|
|
const otpSuffix = /-otp-(\d+)/
|
|
const userSuppliedOtp = exSpec0.match(otpSuffix)?.[1] ?? null
|
|
let otpVersionMajor = ''
|
|
|
|
if (userSuppliedOtp && isVersion(userSuppliedOtp)) {
|
|
otpVersionMajor = userSuppliedOtp
|
|
} else {
|
|
let cmd = 'erl'
|
|
if (process.platform === 'win32') {
|
|
cmd = 'erl.exe'
|
|
}
|
|
const args = [
|
|
'-noshell',
|
|
'-eval',
|
|
'io:format(erlang:system_info(otp_release)), halt().',
|
|
]
|
|
await exec(cmd, args, {
|
|
listeners: {
|
|
stdout: (data) => {
|
|
otpVersionMajor = data.toString()
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const [otpVersionsForElixirMap, elixirVersions, originListing, hexMirrors] =
|
|
await getElixirVersions()
|
|
const spec = exSpec0.replace(otpSuffix, '')
|
|
const versions = elixirVersions
|
|
const elixirVersionFromSpec = getVersionFromSpec(spec, versions)
|
|
|
|
if (elixirVersionFromSpec === null) {
|
|
throw new Error(
|
|
requestedVersionFor('Elixir', exSpec0, originListing, hexMirrors),
|
|
)
|
|
}
|
|
|
|
let foundCombo = false
|
|
let otpVersionMajorIter = parseInt(otpVersionMajor)
|
|
let otpVersionsMajor = []
|
|
while (otpVersionMajorIter > otpVersionMajor - 3) {
|
|
otpVersionMajorIter += ''
|
|
otpVersionsMajor.push(otpVersionMajorIter)
|
|
const elixirVersionComp = otpVersionsForElixirMap[elixirVersionFromSpec]
|
|
if (
|
|
(elixirVersionComp && elixirVersionComp.includes(otpVersionMajorIter)) ||
|
|
!isVersion(otpVersionMajorIter)
|
|
) {
|
|
core.info(
|
|
`Using Elixir ${elixirVersionFromSpec} (built for Erlang/OTP ${otpVersionMajorIter})`,
|
|
)
|
|
foundCombo = true
|
|
break
|
|
}
|
|
otpVersionMajorIter = parseInt(otpVersionMajorIter) - 1
|
|
}
|
|
|
|
if (!foundCombo) {
|
|
throw new Error(
|
|
`Requested Elixir / Erlang/OTP version (${exSpec0} / tried ${otpVersionsMajor}) not ` +
|
|
'found in version list (did you check Compatibility between Elixir and Erlang/OTP?).' +
|
|
'Elixir and Erlang/OTP compatibility can be found at: ' +
|
|
'https://hexdocs.pm/elixir/compatibility-and-deprecations.html',
|
|
)
|
|
}
|
|
|
|
let elixirVersionForDownload = elixirVersionFromSpec
|
|
|
|
if (isVersion(otpVersionMajorIter)) {
|
|
elixirVersionForDownload = `${elixirVersionFromSpec}-otp-${otpVersionMajorIter}`
|
|
}
|
|
|
|
return maybePrependWithV(elixirVersionForDownload)
|
|
}
|
|
|
|
async function getGleamVersion(gleamSpec0) {
|
|
const [gleamVersions, originListing] = await getGleamVersions()
|
|
const spec = gleamSpec0
|
|
const versions = gleamVersions
|
|
const gleamVersion = getVersionFromSpec(spec, versions)
|
|
|
|
if (gleamVersion === null) {
|
|
throw new Error(requestedVersionFor('Gleam', gleamSpec0, originListing))
|
|
}
|
|
|
|
return maybePrependWithV(gleamVersion)
|
|
}
|
|
|
|
async function getRebar3Version(r3Spec) {
|
|
const [rebar3Versions, originListing] = await getRebar3Versions()
|
|
const spec = r3Spec
|
|
const versions = rebar3Versions
|
|
const rebar3Version = getVersionFromSpec(spec, versions)
|
|
|
|
if (rebar3Version === null) {
|
|
throw new Error(requestedVersionFor('rebar3', r3Spec, originListing))
|
|
}
|
|
|
|
return rebar3Version
|
|
}
|
|
|
|
function otpArchitecture() {
|
|
return getInput('otp-architecture', false)
|
|
}
|
|
|
|
async function getOTPVersions(osVersion) {
|
|
let otpVersionsListings
|
|
let originListing
|
|
let hexMirrors = null
|
|
if (process.platform === 'linux') {
|
|
originListing = `/builds/otp/${getRunnerOSArchitecture()}/${osVersion}/builds.txt`
|
|
hexMirrors = hexMirrorsInput()
|
|
otpVersionsListings = await doWithMirrors({
|
|
hexMirrors,
|
|
actionTitle: `fetch ${originListing}`,
|
|
action: async (hexMirror) => {
|
|
return get(`${hexMirror}${originListing}`)
|
|
},
|
|
})
|
|
} else if (process.platform === 'win32') {
|
|
originListing =
|
|
'https://api.github.com/repos/erlang/otp/releases?per_page=100'
|
|
otpVersionsListings = await get(originListing, [1, 2, 3])
|
|
} else if (process.platform === 'darwin') {
|
|
const arch = getRunnerOSArchitecture()
|
|
let targetArch
|
|
switch (arch) {
|
|
case 'amd64':
|
|
targetArch = 'x86_64'
|
|
break
|
|
case 'arm64':
|
|
targetArch = 'aarch64'
|
|
break
|
|
}
|
|
originListing =
|
|
`https://raw.githubusercontent.com/erlef/otp_builds/refs/heads/main` +
|
|
`/builds/${targetArch}-apple-darwin.csv`
|
|
otpVersionsListings = await get(originListing)
|
|
}
|
|
|
|
debugLog(
|
|
`OTP versions listings from ${originListing}, mirrors ${hexMirrors}`,
|
|
otpVersionsListings,
|
|
)
|
|
|
|
const otpVersions = {}
|
|
if (process.platform === 'linux') {
|
|
otpVersionsListings
|
|
.trim()
|
|
.split('\n')
|
|
.forEach((line) => {
|
|
const otpVersionOrig = line.match(/^([^ ]+)?( .+)/)[1]
|
|
const otpVersion = maybeRemoveOTPPrefix(otpVersionOrig)
|
|
debugLog('OTP line and parsing', [line, otpVersion, otpVersionOrig])
|
|
otpVersions[otpVersion] = otpVersionOrig // we keep the original for later reference
|
|
})
|
|
} else if (process.platform === 'win32') {
|
|
const otpArch = otpArchitecture()
|
|
const file_regex = new RegExp(
|
|
`^otp_win${_.escapeRegExp(otpArch)}_(.*).exe$`,
|
|
)
|
|
otpVersionsListings.forEach((otpVersionsListing) => {
|
|
otpVersionsListing
|
|
.map((x) => x.assets)
|
|
.flat()
|
|
.filter((x) => x.name.match(file_regex))
|
|
.forEach((x) => {
|
|
const otpVersionOrig = x.name.match(file_regex)[1]
|
|
const otpVersion = otpVersionOrig
|
|
debugLog('OTP line and parsing', [x.name, otpVersion, otpVersionOrig])
|
|
otpVersions[otpVersion] = otpVersionOrig
|
|
})
|
|
})
|
|
} else if (process.platform === 'darwin') {
|
|
csv
|
|
.parse(otpVersionsListings, {
|
|
columns: true,
|
|
})
|
|
.forEach((line) => {
|
|
const otpVersionOrig = line.ref_name
|
|
const otpVersion = maybeRemoveOTPPrefix(otpVersionOrig)
|
|
debugLog('OTP line and parsing', [line, otpVersion, otpVersionOrig])
|
|
otpVersions[otpVersion] = otpVersionOrig // we keep the original for later reference
|
|
})
|
|
}
|
|
|
|
debugLog(
|
|
`OTP versions from ${originListing}, mirrors ${hexMirrors}`,
|
|
JSON.stringify(otpVersions),
|
|
)
|
|
|
|
return [otpVersions, originListing, hexMirrors]
|
|
}
|
|
|
|
async function getElixirVersions() {
|
|
const originListing = '/builds/elixir/builds.txt'
|
|
const hexMirrors = hexMirrorsInput()
|
|
const elixirVersionsListings = await doWithMirrors({
|
|
hexMirrors,
|
|
actionTitle: `fetch ${originListing}`,
|
|
action: async (hexMirror) => {
|
|
return get(`${hexMirror}${originListing}`)
|
|
},
|
|
})
|
|
const otpVersionsForElixirMap = {}
|
|
const elixirVersions = {}
|
|
|
|
elixirVersionsListings
|
|
.trim()
|
|
.split('\n')
|
|
.forEach((line) => {
|
|
const elixirMatch =
|
|
line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/)
|
|
const elixirVersion = elixirMatch[1]
|
|
const otpVersion = elixirMatch[2]
|
|
const otpVersions = otpVersionsForElixirMap[elixirVersion] || []
|
|
if (otpVersion) {
|
|
// -otp- present (special case)
|
|
otpVersions.push(otpVersion)
|
|
}
|
|
otpVersionsForElixirMap[elixirVersion] = otpVersions
|
|
elixirVersions[elixirVersion] = elixirVersion
|
|
})
|
|
|
|
return [otpVersionsForElixirMap, elixirVersions, originListing, hexMirrors]
|
|
}
|
|
|
|
async function getGleamVersions() {
|
|
const originListing =
|
|
'https://api.github.com/repos/gleam-lang/gleam/releases?per_page=100'
|
|
const resultJSONs = await get(originListing, [1, 2, 3])
|
|
const gleamVersionsListing = {}
|
|
resultJSONs.forEach((resultJSON) => {
|
|
resultJSON
|
|
.map((x) => x.tag_name)
|
|
.forEach((ver) => {
|
|
const gleamMatch = ver.match(/^v?([^ ]+)/)
|
|
const gleamVersion = gleamMatch[1]
|
|
gleamVersionsListing[gleamVersion] = gleamVersion
|
|
})
|
|
})
|
|
|
|
return [gleamVersionsListing, originListing]
|
|
}
|
|
|
|
async function getRebar3Versions() {
|
|
const originListing =
|
|
'https://api.github.com/repos/erlang/rebar3/releases?per_page=100'
|
|
const resultJSONs = await get(originListing, [1, 2, 3])
|
|
const rebar3VersionsListing = {}
|
|
resultJSONs.forEach((resultJSON) => {
|
|
resultJSON
|
|
.map((x) => x.tag_name)
|
|
.forEach((ver) => {
|
|
rebar3VersionsListing[ver] = ver
|
|
})
|
|
})
|
|
|
|
return [rebar3VersionsListing, originListing]
|
|
}
|
|
|
|
function isStrictVersion() {
|
|
return getInput('version-type', false) === 'strict'
|
|
}
|
|
|
|
function gt(left, right) {
|
|
return semver.gt(parseVersion(left), parseVersion(right))
|
|
}
|
|
|
|
function validVersion(v) {
|
|
return (
|
|
v.match(
|
|
new RegExp(`${knownBranches.join('|')}|${nonSpecificVersions.join('|')}`),
|
|
) == null &&
|
|
// these ones are for rebar3, which has alpha and beta releases
|
|
!v.startsWith('a') &&
|
|
!v.startsWith('b')
|
|
)
|
|
}
|
|
|
|
function parseVersion(v) {
|
|
v = v.includes('rc') ? v : v.split('.')
|
|
if (v instanceof Array) {
|
|
v = `${[v.shift(), v.shift(), v.shift()].join('.')}+${v.join('.')}`
|
|
}
|
|
return semver.coerce(v, { includePrerelease: true, loose: true })
|
|
}
|
|
|
|
function getVersionFromSpec(spec0, versions0) {
|
|
let latest
|
|
Object.keys(versions0).forEach((v) => {
|
|
if (validVersion(v)) {
|
|
latest = latest && gt(latest, v) ? latest : v
|
|
}
|
|
})
|
|
versions0.latest = latest
|
|
const spec = maybeRemoveVPrefix(spec0)
|
|
|
|
const altVersions = {}
|
|
Object.entries(versions0).forEach(([version, altVersion]) => {
|
|
let coerced
|
|
if (
|
|
isStrictVersion() ||
|
|
isRC(version) ||
|
|
isKnownBranch(version) ||
|
|
isKnownVerBranch(version)
|
|
) {
|
|
// If `version-type: strict`, version is an RC, or version is "a branch"
|
|
// we just try to remove a potential initial v
|
|
coerced = maybeRemoveVPrefix(version)
|
|
} else {
|
|
// Otherwise, we place the version into a version bucket
|
|
coerced = maybeCoerced(version)
|
|
}
|
|
const alt = (altVersions[coerced] || []).concat(altVersion)
|
|
alt.sort(sortVersions)
|
|
altVersions[coerced] = alt
|
|
})
|
|
|
|
let versions = Object.keys(altVersions)
|
|
|
|
const rangeForMax = semver.validRange(spec0) || maybeCoerced(spec)
|
|
const rangeMax = semver.maxSatisfying(versions, rangeForMax)
|
|
let version = null
|
|
|
|
if (spec0 === 'latest') {
|
|
version = versions0[versions0.latest]
|
|
} else if (
|
|
isStrictVersion() ||
|
|
isRC(spec0) ||
|
|
isKnownBranch(spec0) ||
|
|
isKnownVerBranch(spec0) ||
|
|
spec0 === 'nightly'
|
|
) {
|
|
if (versions0[spec]) {
|
|
// We obtain it directly
|
|
version = versions0[spec]
|
|
}
|
|
} else if (rangeMax !== null) {
|
|
// Otherwise, we compare alt. versions' semver ranges to this version, from highest to lowest
|
|
const thatVersion = spec
|
|
const thatVersionAbc = versionAbc(thatVersion)
|
|
const thatVersionAbcRange = semver.validRange(thatVersionAbc)
|
|
|
|
versions = altVersions[rangeMax]
|
|
for (let i = versions.length - 1; i >= 0; i -= 1) {
|
|
const thisVersion = versions[i]
|
|
const thisVersionAbc = versionAbc(thisVersion)
|
|
const thisVersionAbcRange = semver.validRange(thisVersionAbc)
|
|
|
|
if (
|
|
thatVersionAbcRange &&
|
|
semver.intersects(thatVersionAbcRange, thisVersionAbcRange)
|
|
) {
|
|
version = thisVersion
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return version || null
|
|
}
|
|
|
|
function maybeCoerced(v) {
|
|
let ret
|
|
try {
|
|
if (!isRC(v)) {
|
|
ret = semver.coerce(v).version
|
|
} else {
|
|
ret = maybeRemoveVPrefix(v)
|
|
}
|
|
} catch {
|
|
// some stuff can't be coerced, like 'main'
|
|
core.debug(`Was not able to coerce ${v} with semver`)
|
|
ret = v
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
function sortVersions(left, right) {
|
|
let ret = 0
|
|
const newL = verAsComparableStr(left)
|
|
const newR = verAsComparableStr(right)
|
|
function verAsComparableStr(ver) {
|
|
const matchGroups = 6
|
|
const verSpec = xyzAbcVersion('', '')
|
|
const matches = ver.match(verSpec).splice(1, matchGroups)
|
|
|
|
return matches.reduce((acc, v) => acc + (v || '0').padStart(3, '0'), '')
|
|
}
|
|
|
|
if (newL < newR) {
|
|
ret = -1
|
|
} else if (newL > newR) {
|
|
ret = 1
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
function isRC(ver) {
|
|
return ver.match(xyzAbcVersion('^', '(?:-rc\\.?\\d+)'))
|
|
}
|
|
|
|
function isKnownBranch(ver) {
|
|
return knownBranches.includes(ver)
|
|
}
|
|
|
|
function isKnownVerBranch(ver) {
|
|
return knownBranches.some((b) => ver.match(b))
|
|
}
|
|
|
|
function githubARMRunnerArchs() {
|
|
return ['ARM', 'ARM64']
|
|
}
|
|
|
|
function githubAMDRunnerArchs() {
|
|
return ['X86', 'X64']
|
|
}
|
|
|
|
function getRunnerOSArchitecture() {
|
|
// These options come from:
|
|
// https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
|
|
if (githubARMRunnerArchs().includes(process.env.RUNNER_ARCH)) {
|
|
return 'arm64'
|
|
}
|
|
|
|
if (githubAMDRunnerArchs().includes(process.env.RUNNER_ARCH)) {
|
|
return 'amd64'
|
|
}
|
|
|
|
throw new Error(
|
|
'Invalid Github runner architecture, expected one of ' +
|
|
`${githubAMDRunnerArchs().concat(githubARMRunnerArchs()).join(', ')} ` +
|
|
`but got process.env.RUNNER_ARCH = ${process.env.RUNNER_ARCH}`,
|
|
)
|
|
}
|
|
|
|
function getRunnerOSVersion() {
|
|
// List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
|
const ImageOSToContainer = {
|
|
ubuntu22: 'ubuntu-22.04',
|
|
ubuntu24: 'ubuntu-24.04',
|
|
win19: 'windows-2019',
|
|
win22: 'windows-2022',
|
|
win25: 'windows-2025',
|
|
macos13: 'macOS-13',
|
|
macos14: 'macOS-14',
|
|
macos15: 'macOS-15',
|
|
macos26: 'macOS-26',
|
|
}
|
|
const deprecatedImageOSToContainer = {
|
|
ubuntu18: 'ubuntu-18.04',
|
|
ubuntu20: 'ubuntu-20.04',
|
|
}
|
|
const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS]
|
|
if (!containerFromEnvImageOS) {
|
|
const deprecatedContainerFromEnvImageOS =
|
|
deprecatedImageOSToContainer[process.env.ImageOS]
|
|
if (deprecatedContainerFromEnvImageOS) {
|
|
core.warning(
|
|
`You are using deprecated ImageOS ${deprecatedContainerFromEnvImageOS}. ` +
|
|
'Support for maintenance is very limited. Consider a non-deprecated version as ' +
|
|
'mentioned in the README.md.',
|
|
)
|
|
|
|
return deprecatedContainerFromEnvImageOS
|
|
} else {
|
|
throw new Error(
|
|
"Tried to map a target OS from env. variable 'ImageOS' (got " +
|
|
`${process.env.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 getUrlResponse(url, headers, attempt = 1) {
|
|
try {
|
|
const response = await fetch(url, {
|
|
headers,
|
|
signal: AbortSignal.timeout(10000),
|
|
})
|
|
const contentType = response.headers.get('content-type') || ''
|
|
|
|
if (!response.ok) {
|
|
throw new Error(response.statusText)
|
|
}
|
|
|
|
if (contentType.indexOf('application/json') !== -1) {
|
|
return response.json()
|
|
} else {
|
|
return response.text()
|
|
}
|
|
} catch (err) {
|
|
core.debug(`Error fetching from ${url}: ${err}`)
|
|
|
|
if (attempt <= MAX_HTTP_RETRIES) {
|
|
const delay = attempt * 2 * 1000
|
|
core.debug(`Error during fetch. Retrying in ${delay}ms`)
|
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
return getUrlResponse(url, headers, attempt + 1)
|
|
} else {
|
|
throw err
|
|
}
|
|
}
|
|
}
|
|
|
|
async function get(url0, pageIdxs) {
|
|
const url = new URL(url0)
|
|
const headers = {}
|
|
const GithubToken = getInput('github-token', false)
|
|
if (GithubToken && url.host === 'api.github.com') {
|
|
headers.authorization = `Bearer ${GithubToken}`
|
|
}
|
|
|
|
if ((pageIdxs || []).length === 0) {
|
|
return getUrlResponse(url, headers)
|
|
} else {
|
|
return Promise.all(
|
|
pageIdxs.map((page) => {
|
|
const urlWithPage = new URL(url)
|
|
urlWithPage.searchParams.append('page', page)
|
|
return getUrlResponse(urlWithPage, headers)
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
function maybePrependWithV(v) {
|
|
if (isVersion(v)) {
|
|
return `v${v.replace('v', '')}`
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
function maybeRemoveVPrefix(ver) {
|
|
let ret = ver
|
|
if (isVersion(ver)) {
|
|
ret = ver.replace('v', '')
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
function xyzAbcVersion(pref, suf) {
|
|
// This accounts for stuff like 6.0.2.0.1.0, as proposed by Erlang's
|
|
// https://www.erlang.org/doc/system_principles/versions.html
|
|
const dd = '\\.?(\\d+)?'
|
|
return new RegExp(
|
|
`${pref}(?:OTP-)?v?(\\d+)${dd}${dd}${dd}${dd}${dd}(?:-rc\\.?\\d+)?(?:-otp-\\d+)?${suf}`,
|
|
)
|
|
}
|
|
|
|
function versionAbc(ver) {
|
|
// For a version like 6.0.2.0.1.0, return 0.1.0
|
|
return ver.match(/\d+(?:\.[^.]+)?(?:\.[^.]+)?(?:\.)?(.*)/)[1]
|
|
}
|
|
|
|
function isVersion(v) {
|
|
return v.match(xyzAbcVersion('^', '$')) !== null
|
|
}
|
|
|
|
function getInput(inputName, required, alternativeName, alternatives) {
|
|
const alternativeValue = (alternatives || new Map()).get(alternativeName)
|
|
let input = core.getInput(inputName, {
|
|
required: alternativeValue ? false : required,
|
|
})
|
|
// We can't have both input and alternativeValue set
|
|
if (input && alternativeValue) {
|
|
throw new Error(
|
|
`Found input ${inputName}=${input} (from the YML) \
|
|
alongside ${alternativeName}=${alternativeValue} \
|
|
(from the version file). You must choose one or the other.`,
|
|
)
|
|
} else if (!input) {
|
|
input = alternativeValue
|
|
}
|
|
|
|
return input
|
|
}
|
|
|
|
function parseToolVersionsFile(versionFilePath) {
|
|
const appVersions = new Map()
|
|
const versions = fs.readFileSync(versionFilePath, 'utf8')
|
|
versions.split(/\r?\n/).forEach((line) => {
|
|
const appVersion = line.match(/^([^ ]+)[ ]+(ref:v?)?([^ #]+)/)
|
|
if (appVersion) {
|
|
const app = appVersion[1]
|
|
if (APPS.includes(app)) {
|
|
const [, , , version] = appVersion
|
|
core.info(`Consuming ${app} at version ${version}`)
|
|
appVersions.set(app, version)
|
|
}
|
|
}
|
|
})
|
|
if (!appVersions.size) {
|
|
core.info('There was apparently nothing to consume')
|
|
} else {
|
|
core.info('... done!')
|
|
}
|
|
core.endGroup()
|
|
|
|
return appVersions
|
|
}
|
|
|
|
function parseMiseTomlFile(versionFilePath) {
|
|
const appVersions = new Map()
|
|
const miseToml = fs.readFileSync(versionFilePath, 'utf8')
|
|
const miseTomlParsed = toml.parse(miseToml)
|
|
for (const app in miseTomlParsed.tools) {
|
|
if (APPS.includes(app)) {
|
|
const appVersion = miseTomlParsed.tools[app]
|
|
const version =
|
|
typeof appVersion == 'object' ? appVersion.version : appVersion
|
|
|
|
core.info(`Consuming ${app} at version ${version}`)
|
|
appVersions.set(app, version)
|
|
}
|
|
}
|
|
|
|
return appVersions
|
|
}
|
|
|
|
function parseVersionFile(versionFilePath0) {
|
|
const versionFilePath = path.resolve(
|
|
process.env.GITHUB_WORKSPACE,
|
|
versionFilePath0,
|
|
)
|
|
if (!fs.existsSync(versionFilePath)) {
|
|
throw new Error(
|
|
`The specified version file, ${versionFilePath0}, does not exist`,
|
|
)
|
|
}
|
|
|
|
if (versionFilePath0.endsWith('.toml')) {
|
|
core.startGroup(
|
|
`Parsing ${path.basename(versionFilePath0)} file at ${versionFilePath0}`,
|
|
)
|
|
return parseMiseTomlFile(versionFilePath)
|
|
}
|
|
|
|
core.startGroup(`Parsing .tool-versions file at ${versionFilePath0}`)
|
|
return parseToolVersionsFile(versionFilePath)
|
|
}
|
|
|
|
function debugLog(groupName, message) {
|
|
const group = `Debugging for ${groupName}`
|
|
core.debug(
|
|
'┌──────────────────────────────────────────────────────────────────────────',
|
|
)
|
|
core.debug(`│ ${group} - start`)
|
|
core.debug(message)
|
|
core.debug(`│ ${group} - stop`)
|
|
core.debug(
|
|
'└──────────────────────────────────────────────────────────────────────────',
|
|
)
|
|
}
|
|
|
|
function hexMirrorsInput() {
|
|
return core.getMultilineInput('hexpm-mirrors', {
|
|
required: false,
|
|
})
|
|
}
|
|
|
|
async function doWithMirrors(opts) {
|
|
const { hexMirrors, actionTitle, action } = opts
|
|
let actionRes
|
|
|
|
if (hexMirrors.length === 0) {
|
|
throw new Error(`Could not ${actionTitle} from any hex.pm mirror`)
|
|
}
|
|
|
|
const [hexMirror, ...hexMirrorsT] = hexMirrors
|
|
try {
|
|
actionRes = await action(hexMirror)
|
|
} catch (err) {
|
|
core.info(
|
|
`Action ${actionTitle} failed for mirror ${hexMirror}, with ${err}`,
|
|
)
|
|
core.debug(`Stacktrace: ${err.stack}`)
|
|
actionRes = await doWithMirrors({
|
|
hexMirrors: hexMirrorsT,
|
|
actionTitle,
|
|
action,
|
|
})
|
|
}
|
|
|
|
return actionRes
|
|
}
|
|
|
|
async function install(toolName, opts) {
|
|
const { osVersion, toolVersion, hexMirror } = opts
|
|
const versionSpec =
|
|
osVersion !== undefined ? `${osVersion}/${toolVersion}` : toolVersion
|
|
let installOpts
|
|
|
|
// The installOpts object is composed of supported processPlatform keys
|
|
// (e.g. 'linux', 'win32', or 'all' - in case there's no distinction between platforms)
|
|
// In each of these keys there's an object with keys:
|
|
// * downloadToolURL
|
|
// - where to fetch the downloadable from
|
|
// * extract
|
|
// - if the downloadable is compressed: how to extract it
|
|
// - return ['dir', targetDir]
|
|
// - if the downloadable is not compressed: a filename.ext you want to cache it under
|
|
// - return ['file', filenameWithExt]
|
|
// * postExtract
|
|
// - stuff to execute outside the cache scope (just after it's created)
|
|
// * reportVersion
|
|
/// - configuration elements on how to output the tool version, post-install
|
|
|
|
switch (toolName) {
|
|
case 'otp':
|
|
installOpts = {
|
|
tool: 'Erlang/OTP',
|
|
linux: {
|
|
downloadToolURL: () =>
|
|
`${hexMirror}/builds/otp/${getRunnerOSArchitecture()}/${versionSpec}.tar.gz`,
|
|
extract: async (file) => {
|
|
const dest = undefined
|
|
const flags = ['zx', '--strip-components=1']
|
|
const targetDir = await tc.extractTar(file, dest, flags)
|
|
|
|
return ['dir', targetDir]
|
|
},
|
|
postExtract: async (cachePath) => {
|
|
const cmd = path.join(cachePath, 'Install')
|
|
const args = ['-minimal', cachePath]
|
|
await exec(cmd, args)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'erl'
|
|
const args = ['-version']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
win32: {
|
|
downloadToolURL: () => {
|
|
const otpArch = otpArchitecture()
|
|
return (
|
|
'https://github.com/erlang/otp/releases/download/' +
|
|
`OTP-${toolVersion}/otp_win${_.escapeRegExp(otpArch)}_${toolVersion}.exe`
|
|
)
|
|
},
|
|
extract: async () => ['file', 'otp.exe'],
|
|
postExtract: async (cachePath) => {
|
|
const cmd = path.join(cachePath, 'otp.exe')
|
|
const args = ['/S', `/D=${cachePath}`]
|
|
await exec(cmd, args)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'erl.exe'
|
|
const args = ['+V']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
darwin: {
|
|
downloadToolURL: (versionSpec) => {
|
|
let suffix = ''
|
|
if (isKnownVerBranch(versionSpec)) {
|
|
// for these otp_builds adds `-latest` in the folder path
|
|
suffix = '-latest'
|
|
}
|
|
return (
|
|
`https://github.com/erlef/otp_builds/releases/download/` +
|
|
`${toolVersion}${suffix}/${toolVersion}-macos-${getRunnerOSArchitecture()}.tar.gz`
|
|
)
|
|
},
|
|
extract: async (file) => {
|
|
const dest = undefined
|
|
const flags = ['zx']
|
|
const targetDir = await tc.extractTar(file, dest, flags)
|
|
|
|
return ['dir', targetDir]
|
|
},
|
|
postExtract: async (/*cachePath*/) => {
|
|
// nothing to do
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'erl'
|
|
const args = ['-version']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
}
|
|
break
|
|
case 'elixir':
|
|
installOpts = {
|
|
tool: 'Elixir',
|
|
all: {
|
|
downloadToolURL: () =>
|
|
`${hexMirror}/builds/elixir/${versionSpec}.zip`,
|
|
extract: async (file) => {
|
|
const targetDir = await tc.extractZip(file)
|
|
|
|
return ['dir', targetDir]
|
|
},
|
|
postExtract: async () => {
|
|
const escriptsPath = path.join(os.homedir(), '.mix', 'escripts')
|
|
fs.mkdirSync(escriptsPath, { recursive: true })
|
|
core.addPath(escriptsPath)
|
|
|
|
if (debugLoggingEnabled()) {
|
|
core.exportVariable('ELIXIR_CLI_ECHO', 'true')
|
|
}
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'elixir'
|
|
const args = ['-v']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
}
|
|
break
|
|
case 'gleam':
|
|
installOpts = {
|
|
tool: 'Gleam',
|
|
linux: {
|
|
downloadToolURL: () => {
|
|
const arch = getRunnerOSArchitecture()
|
|
let gz
|
|
if (
|
|
versionSpec === 'nightly' ||
|
|
semver.gt(versionSpec, 'v0.22.1')
|
|
) {
|
|
const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
|
|
gz = `gleam-${versionSpec}-${target}-unknown-linux-musl.tar.gz`
|
|
} else {
|
|
gz = `gleam-${versionSpec}-linux-amd64.tar.gz`
|
|
}
|
|
|
|
return `https://github.com/gleam-lang/gleam/releases/download/${versionSpec}/${gz}`
|
|
},
|
|
extract: async (file) => {
|
|
const dest = undefined
|
|
const flags = ['zx']
|
|
const targetDir = await tc.extractTar(file, dest, flags)
|
|
|
|
return ['dir', targetDir]
|
|
},
|
|
postExtract: async (cachePath) => {
|
|
const bindir = path.join(cachePath, 'bin')
|
|
const oldPath = path.join(cachePath, 'gleam')
|
|
const newPath = path.join(bindir, 'gleam')
|
|
fs.mkdirSync(bindir)
|
|
fs.renameSync(oldPath, newPath)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'gleam'
|
|
const args = ['--version']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
win32: {
|
|
downloadToolURL: () => {
|
|
let zip
|
|
if (
|
|
versionSpec === 'nightly' ||
|
|
semver.gt(versionSpec, 'v0.22.1')
|
|
) {
|
|
zip = `gleam-${versionSpec}-x86_64-pc-windows-msvc.zip`
|
|
} else {
|
|
zip = `gleam-${versionSpec}-windows-64bit.zip`
|
|
}
|
|
|
|
return `https://github.com/gleam-lang/gleam/releases/download/${versionSpec}/${zip}`
|
|
},
|
|
extract: async (file) => {
|
|
const targetDir = await tc.extractZip(file)
|
|
|
|
return ['dir', targetDir]
|
|
},
|
|
postExtract: async (cachePath) => {
|
|
const bindir = path.join(cachePath, 'bin')
|
|
const oldPath = path.join(cachePath, 'gleam.exe')
|
|
const newPath = path.join(bindir, 'gleam.exe')
|
|
fs.mkdirSync(bindir)
|
|
fs.renameSync(oldPath, newPath)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'gleam.exe'
|
|
const args = ['--version']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
}
|
|
installOpts.darwin = {
|
|
downloadToolURL: () => {
|
|
const arch = getRunnerOSArchitecture()
|
|
let gz
|
|
if (versionSpec === 'nightly' || semver.gt(versionSpec, 'v0.22.1')) {
|
|
const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
|
|
gz = `gleam-${versionSpec}-${target}-apple-darwin.tar.gz`
|
|
} else {
|
|
gz =
|
|
arch === 'arm64'
|
|
? `gleam-${versionSpec}-macos-arm64.tar.gz`
|
|
: `gleam-${versionSpec}-macos.tar.gz`
|
|
}
|
|
|
|
return `https://github.com/gleam-lang/gleam/releases/download/${versionSpec}/${gz}`
|
|
},
|
|
extract: installOpts.linux.extract,
|
|
postExtract: installOpts.linux.postExtract,
|
|
reportVersion: installOpts.linux.reportVersion,
|
|
}
|
|
break
|
|
case 'rebar3':
|
|
installOpts = {
|
|
tool: 'Rebar3',
|
|
linux: {
|
|
downloadToolURL: () => {
|
|
let url
|
|
if (versionSpec === 'nightly') {
|
|
url = 'https://s3.amazonaws.com/rebar3-nightly/rebar3'
|
|
} else {
|
|
url = `https://github.com/erlang/rebar3/releases/download/${versionSpec}/rebar3`
|
|
}
|
|
|
|
return url
|
|
},
|
|
extract: async () => ['file', 'rebar3'],
|
|
postExtract: async (cachePath) => {
|
|
const bindir = path.join(cachePath, 'bin')
|
|
const oldPath = path.join(cachePath, 'rebar3')
|
|
const newPath = path.join(bindir, 'rebar3')
|
|
fs.mkdirSync(bindir, { recursive: true })
|
|
fs.renameSync(oldPath, newPath)
|
|
fs.chmodSync(newPath, 0o755)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'rebar3'
|
|
const args = ['version']
|
|
const env = {
|
|
REBAR_GLOBAL_CONFIG_DIR: '/fake-dir',
|
|
REBAR_CONFIG: 'fake.config',
|
|
}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
win32: {
|
|
downloadToolURL: () => {
|
|
let url
|
|
if (versionSpec === 'nightly') {
|
|
url = 'https://s3.amazonaws.com/rebar3-nightly/rebar3'
|
|
} else {
|
|
url = `https://github.com/erlang/rebar3/releases/download/${versionSpec}/rebar3`
|
|
}
|
|
|
|
return url
|
|
},
|
|
extract: async () => ['file', 'rebar3'],
|
|
postExtract: async (cachePath) => {
|
|
const bindir = path.join(cachePath, 'bin')
|
|
const oldPath = path.join(cachePath, 'rebar3')
|
|
const newPath = path.join(bindir, 'rebar3')
|
|
fs.mkdirSync(bindir)
|
|
fs.renameSync(oldPath, newPath)
|
|
fs.chmodSync(newPath, 0o755)
|
|
|
|
const ps1Filename = path.join(bindir, 'rebar3.ps1')
|
|
fs.writeFileSync(ps1Filename, `& escript.exe ${newPath} \${args}`)
|
|
|
|
const cmdFilename = path.join(bindir, 'rebar3.cmd')
|
|
fs.writeFileSync(
|
|
cmdFilename,
|
|
`@echo off\r\nescript.exe ${newPath} %*`,
|
|
)
|
|
},
|
|
reportVersion: () => {
|
|
const cmd = 'rebar3.cmd'
|
|
const args = ['version']
|
|
const env = {}
|
|
|
|
return [cmd, args, env]
|
|
},
|
|
},
|
|
}
|
|
installOpts.darwin = installOpts.linux
|
|
break
|
|
default:
|
|
throw new Error(`no installer for ${toolName}`)
|
|
}
|
|
|
|
await installTool({ toolName, versionSpec, installOpts })
|
|
}
|
|
|
|
async function installTool(opts) {
|
|
const { toolName, versionSpec, installOpts } = opts
|
|
const platformOpts = installOpts[process.platform] || installOpts.all
|
|
let cachePath = tc.find(toolName, versionSpec)
|
|
|
|
core.debug(`Checking if ${installOpts.tool} is already cached...`)
|
|
if (cachePath === '') {
|
|
core.debug(" ... it isn't!")
|
|
const downloadToolURL = platformOpts.downloadToolURL(versionSpec)
|
|
const file = await tc.downloadTool(downloadToolURL)
|
|
const [targetElemType, targetElem] = await platformOpts.extract(file)
|
|
|
|
if (targetElemType === 'dir') {
|
|
cachePath = await tc.cacheDir(targetElem, toolName, versionSpec)
|
|
} else if (targetElemType === 'file') {
|
|
cachePath = await tc.cacheFile(file, targetElem, toolName, versionSpec)
|
|
}
|
|
} else {
|
|
core.debug(` ... it is, at ${cachePath}`)
|
|
}
|
|
|
|
// This makes sure we run, e.g. in Windows, the installer in the runner
|
|
// We're not caching the install, just the downloaded tool
|
|
const runnerToolPath = path.join(
|
|
process.env.RUNNER_TEMP,
|
|
'.setup-beam',
|
|
toolName,
|
|
)
|
|
fs.cpSync(cachePath, runnerToolPath, { recursive: true })
|
|
|
|
core.debug('Performing post extract operations...')
|
|
await platformOpts.postExtract(runnerToolPath)
|
|
|
|
core.debug(`Adding ${runnerToolPath}' bin to system path`)
|
|
const runnerToolPathBin = path.join(runnerToolPath, 'bin')
|
|
core.addPath(runnerToolPathBin)
|
|
|
|
const installDirForVarName = `INSTALL_DIR_FOR_${toolName}`.toUpperCase()
|
|
core.debug(`Exporting ${installDirForVarName} as ${runnerToolPath}`)
|
|
core.exportVariable(installDirForVarName, runnerToolPath)
|
|
|
|
core.info(`Installed ${installOpts.tool} version`)
|
|
const [cmd, args, env] = platformOpts.reportVersion()
|
|
await exec(cmd, args, { env: { ...process.env, ...env } })
|
|
}
|
|
|
|
function checkOtpArchitecture() {
|
|
const otpArch = otpArchitecture()
|
|
|
|
if (process.platform !== 'win32' && otpArch == '32') {
|
|
throw new Error(
|
|
'@erlef/setup-beam only supports otp-architecture=32 on Windows',
|
|
)
|
|
}
|
|
|
|
if (!['32', '64'].includes(otpArch)) {
|
|
throw new Error('otp-architecture must be 32 or 64')
|
|
}
|
|
}
|
|
|
|
function debugLoggingEnabled() {
|
|
return !!process.env.RUNNER_DEBUG
|
|
}
|
|
|
|
export default {
|
|
get,
|
|
getElixirVersion,
|
|
getGleamVersion,
|
|
getOTPVersion,
|
|
getRebar3Version,
|
|
getVersionFromSpec,
|
|
githubAMDRunnerArchs,
|
|
githubARMRunnerArchs,
|
|
install,
|
|
installOTP,
|
|
maybeInstallElixir,
|
|
maybeInstallGleam,
|
|
maybeInstallRebar3,
|
|
parseVersionFile,
|
|
}
|