diff --git a/.eslintrc.yml b/.eslintrc.yml index 75615a43..39ace7e8 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -6,14 +6,17 @@ extends: - airbnb parserOptions: ecmaVersion: 12 -rules: { - indent: ["warn", 2], - max-len: ["warn", 100], - no-console: 0, - no-undef: "error", - no-unused-vars: ["error", {"varsIgnorePattern": "^_"}], - no-use-before-define: 0, - operator-linebreak: 0, - require-jsdoc: 0, +rules: + indent: ["warn", 2] + max-len: ["warn", 100] + no-console: 0 + no-undef: "error" + no-unused-vars: ["error", {"varsIgnorePattern": "^_"}] + no-use-before-define: 0 + operator-linebreak: 0 + require-jsdoc: 0 semi: 0 -} + +settings: + react: + version: 999.999.999 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c802574..357ed8a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,12 @@ jobs: - run: npm install -g npm - run: npm install - run: npm run build + - run: npm run format - run: npm install -g markdownlint-cli - run: npm run markdownlint - run: npm run shellcheck - run: npm run yamllint - run: npm run jslint - run: npm run licenses - - run: npm run format - name: Check if build left artifacts run: git diff --exit-code diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 763ff496..50229d86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,10 +2,12 @@ name: test on: - push: {branches: main} - pull_request: {branches: main} - repository_dispatch: - workflow_dispatch: + push: + branches: + - main + pull_request: + branches: + - main jobs: unit_test: @@ -18,7 +20,7 @@ jobs: - run: npm ci - run: npm test - integration_test: + integration_test_ubuntu: name: > Pre-release integration tests (Ubuntu ${{matrix.combo.os}}, @@ -136,3 +138,43 @@ jobs: cd test-projects/rebar3 rebar3 ct if: ${{matrix.combo.rebar3-version}} + + integration_test_windows: + name: > + Pre-release integration tests + (Windows ${{matrix.combo.os}}, + Erlang/OTP ${{matrix.combo.otp-version}}, + rebar3 ${{matrix.combo.rebar3-version}}) + runs-on: ${{matrix.combo.os}} + strategy: + fail-fast: false + matrix: + combo: + - otp-version: '24.0.2' + rebar3-version: '3.16' + os: 'windows-2019' + - otp-version: '23.0' + rebar3-version: '3.15' + os: 'windows-2019' + - otp-version: '24.0.2' + rebar3-version: '3.16' + os: 'windows-2016' + - otp-version: '23.0' + rebar3-version: '3.15' + os: 'windows-2016' + steps: + - uses: actions/checkout@v2 + - name: Use erlef/setup-beam + id: setup-beam + uses: ./ + with: + otp-version: ${{matrix.combo.otp-version}} + rebar3-version: ${{matrix.combo.rebar3-version}} + - name: Erlang/OTP version (action) + run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}" + - name: rebar3 version (action) + run: echo "rebar3 ${{steps.setup-beam.outputs.rebar3-version}}" + - name: Run rebar3 project tests + run: | + cd test-projects/rebar3 + rebar3 ct diff --git a/README.md b/README.md index dda925db..fcbb8480 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ and Erlang/OTP. | ubuntu-16.04 | 17 - 24 | ✅ | ubuntu-18.04 | 17 - 24 | ✅ | ubuntu-20.04 | 20 - 24 | ✅ +| windows-2016 | 23 - 24 | ✅ +| windows-2019 | 23 - 24 | ✅ ### Basic example (Elixir) @@ -130,6 +132,24 @@ jobs: - run: rebar3 ct ``` +### Basic example (`rebar3` on Windows 2016) + +```yaml +# create this in .github/workflows/ci.yml +on: push + +jobs: + test: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: '24.0.2' + rebar3-version: '3.16.1' + - run: rebar3 ct +``` + ## Elixir Problem Matchers 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. diff --git a/__tests__/setup-beam.test.js b/__tests__/setup-beam.test.js index f308c653..58b99379 100644 --- a/__tests__/setup-beam.test.js +++ b/__tests__/setup-beam.test.js @@ -81,47 +81,69 @@ async function testOTPVersions() { let spec let osVersion - spec = '19.3.x' - osVersion = 'ubuntu-16.04' - expected = 'OTP-19.3.6' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '^19.3.6' - osVersion = 'ubuntu-16.04' - expected = 'OTP-19.3.6' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '^19.3' - osVersion = 'ubuntu-18.04' - expected = 'OTP-19.3.6' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '20' - osVersion = 'ubuntu-20.04' - expected = 'OTP-20.3.8' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '20.x' - osVersion = 'ubuntu-20.04' - expected = 'OTP-20.3.8' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '20.0' - osVersion = 'ubuntu-20.04' - expected = 'OTP-20.0.5' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) - - spec = '20.0.x' - osVersion = 'ubuntu-20.04' - expected = 'OTP-20.0.5' - got = await setupBeam.getOTPVersion(spec, osVersion) - assert.deepStrictEqual(got, expected) + if (process.platform === 'linux') { + spec = '19.3.x' + osVersion = 'ubuntu-16.04' + expected = 'OTP-19.3.6' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '^19.3.6' + osVersion = 'ubuntu-16.04' + expected = 'OTP-19.3.6' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '^19.3' + osVersion = 'ubuntu-18.04' + expected = 'OTP-19.3.6' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '20' + osVersion = 'ubuntu-20.04' + expected = 'OTP-20.3.8' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '20.x' + osVersion = 'ubuntu-20.04' + expected = 'OTP-20.3.8' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '20.0' + osVersion = 'ubuntu-20.04' + expected = 'OTP-20.0' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '20.0.x' + osVersion = 'ubuntu-20.04' + expected = 'OTP-20.0.5' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + } + + if (process.platform === 'win32') { + spec = '24.0.1' + osVersion = 'windows-latest' + expected = '24.0.1' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '23.2.x' + osVersion = 'windows-2016' + expected = '23.2.7' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + + spec = '23.0' + osVersion = 'windows-2019' + expected = '23.0' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + } } async function testElixirVersions() { diff --git a/dist/index.js b/dist/index.js index f8d65388..d2615a36 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4594,7 +4594,13 @@ const path = __nccwpck_require__(5622) * @param {string} otpVersion */ async function installOTP(osVersion, otpVersion) { - await exec(__nccwpck_require__.ab + "install-otp", [osVersion, otpVersion]) + const OS = process.platform + if (OS === 'linux') { + await exec(__nccwpck_require__.ab + "install-otp.sh", [osVersion, otpVersion]) + } else if (OS === 'win32') { + const script = __nccwpck_require__.ab + "install-otp.ps1" + await exec(`powershell.exe ${script} -VSN:${otpVersion}`) + } } /** @@ -4603,7 +4609,13 @@ async function installOTP(osVersion, otpVersion) { * @param {string} elixirVersion */ async function installElixir(elixirVersion) { - await exec(__nccwpck_require__.ab + "install-elixir", [elixirVersion]) + const OS = process.platform + if (OS === 'linux') { + await exec(__nccwpck_require__.ab + "install-elixir.sh", [elixirVersion]) + } else if (OS === 'win32') { + const script = __nccwpck_require__.ab + "install-elixir.ps1" + await exec(`powershell.exe ${script} ${elixirVersion}`) + } } /** @@ -4612,12 +4624,20 @@ async function installElixir(elixirVersion) { * @param {string} rebar3Version */ async function installRebar3(rebar3Version) { - await exec(__nccwpck_require__.ab + "install-rebar3", [rebar3Version]) + const OS = process.platform + if (OS === 'linux') { + await exec(__nccwpck_require__.ab + "install-rebar3.sh", [rebar3Version]) + } else if (OS === 'win32') { + const script = __nccwpck_require__.ab + "install-rebar3.ps1" + await exec(`powershell.exe ${script} -VSN:${rebar3Version}`) + } } function checkPlatform() { - if (process.platform !== 'linux') { - throw new Error('@erlef/setup-beam only supports Ubuntu Linux at this time') + if (process.platform !== 'linux' && process.platform !== 'win32') { + throw new Error( + '@erlef/setup-beam only supports Ubuntu and Windows at this time', + ) } } @@ -4653,20 +4673,24 @@ async function main() { const otpVersion = await installOTP(otpSpec, osVersion) const elixirSpec = core.getInput('elixir-version', { required: false }) - const elixirInstalled = await maybeInstallElixir(elixirSpec, otpVersion) + const shouldMixHex = core.getInput('install-hex', { + required: false, + }) + const elixirInstalled = await maybeInstallElixir( + elixirSpec, + otpVersion, + shouldMixHex, + ) 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) + await maybeInstallRebar3(rebar3Spec) } async function installOTP(otpSpec, osVersion) { @@ -4676,13 +4700,17 @@ async function installOTP(otpSpec, osVersion) { ) await installer.installOTP(osVersion, otpVersion) core.setOutput('otp-version', otpVersion) - prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`) + if (process.platform === 'linux') { + prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`) + } else if (process.platform === 'win32') { + prependToPath(`C:/Program Files/erl-${otpVersion}/bin`) + } console.log('##[endgroup]') return otpVersion } -async function maybeInstallElixir(elixirSpec, otpVersion) { +async function maybeInstallElixir(elixirSpec, otpVersion, shouldMixHex) { if (elixirSpec) { const elixirVersion = await getElixirVersion(elixirSpec, otpVersion) console.log(`##[group]Installing Elixir ${elixirVersion}`) @@ -4698,6 +4726,12 @@ async function maybeInstallElixir(elixirSpec, otpVersion) { return true } + if (shouldMixHex) { + console.log( + "hex will not be installed (overriding default) since Elixir wasn't either", + ) + } + return false } @@ -4784,7 +4818,7 @@ async function getElixirVersion(exSpec0, otpVersion) { `Using Elixir ${elixirVersion} (built for OTP ${otpVersionMajor})`, ) } else { - // ... and it's not available: fallback to the "generic" version (v1.4.5 only). + // ... and it's not available: fallback to the 'generic' version (v1.4.5 only). elixirVersionWithOTP = elixirVersion core.info(`Using Elixir ${elixirVersion}`) } @@ -4811,34 +4845,61 @@ async function getRebar3Version(r3Spec) { } async function getOTPVersions(osVersion) { - const otpVersionsListing = await get( - `https://repo.hex.pm/builds/otp/${osVersion}/builds.txt`, - ) + let originListing + let pageIdxs + if (process.platform === 'linux') { + originListing = `https://repo.hex.pm/builds/otp/${osVersion}/builds.txt` + pageIdxs = [null] + } else if (process.platform === 'win32') { + originListing = + 'https://api.github.com/repos/erlang/otp/releases?per_page=100' + pageIdxs = [1, 2, 3] + } + + const otpVersionsListings = await get(originListing, pageIdxs) const otpVersions = new Map() - otpVersionsListing - .trim() - .split('\n') - .forEach((line) => { - const otpMatch = line.match(/^(OTP-)?([^ ]+)/) + if (process.platform === 'linux') { + otpVersionsListings + .trim() + .split('\n') + .forEach((line) => { + const otpMatch = line.match(/^(OTP-)?([^ ]+)/) - let otpVersion = otpMatch[2] - if (semver.validRange(otpVersion)) { - otpVersion = semver.minVersion(otpVersion).version - } - otpVersions.set(otpVersion, otpMatch[0]) // we keep the original for later reference + let otpVersion = otpMatch[2] + if (semver.validRange(otpVersion) && hasPatch(otpVersion)) { + otpVersion = semver.minVersion(otpVersion).version + } + otpVersions.set(otpVersion, otpMatch[0]) // we keep the original for later reference + }) + } else if (process.platform === 'win32') { + otpVersionsListings.forEach((otpVersionsListing) => { + JSON.parse(otpVersionsListing) + .map((x) => x.assets) + .flat() + .filter((x) => x.name.match(/^otp_win64_.*.exe$/)) + .forEach((x) => { + const otpMatch = x.name.match(/^otp_win64_(.*).exe$/) + let otpVersion = otpMatch[1] + if (semver.validRange(otpVersion) && hasPatch(otpVersion)) { + otpVersion = semver.minVersion(otpVersion).version + } + otpVersions.set(otpVersion, otpVersion) + }) }) + } return otpVersions } async function getElixirVersions() { - const elixirVersionsListing = await get( + const elixirVersionsListings = await get( 'https://repo.hex.pm/builds/elixir/builds.txt', + [null], ) const otpVersionsForElixirMap = new Map() - elixirVersionsListing + elixirVersionsListings .trim() .split('\n') .forEach((line) => { @@ -4858,13 +4919,17 @@ async function getElixirVersions() { } async function getRebar3Versions() { - const resultJSON = await get( + const resultJSONs = await get( 'https://api.github.com/repos/erlang/rebar3/releases?per_page=100', + [1, 2, 3], ) - const rebar3VersionsListing = JSON.parse(resultJSON) - .map((x) => x.tag_name) - .sort() - + const rebar3VersionsListing = [] + resultJSONs.forEach((resultJSON) => { + JSON.parse(resultJSON) + .map((x) => x.tag_name) + .sort() + .forEach((v) => rebar3VersionsListing.push(v)) + }) return rebar3VersionsListing } @@ -4877,43 +4942,79 @@ function getVersionFromSpec(spec, versions) { } function getRunnerOSVersion() { - const mapToUbuntuVersion = { + const ImageOSToContainer = { ubuntu16: 'ubuntu-16.04', ubuntu18: 'ubuntu-18.04', ubuntu20: 'ubuntu-20.04', + win16: 'windows-2016', + win19: 'windows-2019', } - return mapToUbuntuVersion[process.env.ImageOS] || 'ubuntu-18.04' -} - -function get(url) { - return new Promise((resolve, reject) => { - 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) - }) - }) + return ImageOSToContainer[process.env.ImageOS] +} + +async function get(url0, pageIdxs) { + function getPage(pageIdx) { + return new Promise((resolve, reject) => { + const url = new URL(url0) + if (pageIdx !== null) { + url.searchParams.append('page', pageIdx) + } + https + .get( + url, + { + headers: { 'user-agent': 'setup-beam' }, + }, + (res) => { + let data = '' + res.on('data', (chunk) => { + data += chunk + }) + res.on('end', () => { + if (res.statusCode >= 400 && res.statusCode <= 599) { + reject( + new Error( + `Got ${res.statusCode} from ${url}. Exiting with error`, + ), + ) + } else { + resolve(data) + } + }) + }, + ) + .on('error', (err) => { + reject(err) + }) + }) + } + let ret + if (pageIdxs[0] === null) { + ret = getPage(null) + } else { + ret = Promise.all(pageIdxs.map((pageIdx) => getPage(pageIdx))) + } + return ret } function prependToPath(what) { - process.env.PATH = `${what}:${process.env.PATH}` + if (process.platform === 'linux') { + process.env.PATH = `${what}:${process.env.PATH}` + } else if (process.platform === 'win32') { + process.env.PATH = `${what};${process.env.PATH}` + } } +function hasPatch(v) { + try { + semver.patch(v) + } catch { + return false + } + + return true +} module.exports = { getOTPVersion, getElixirVersion, diff --git a/dist/install-elixir.ps1 b/dist/install-elixir.ps1 new file mode 100644 index 00000000..7674016f --- /dev/null +++ b/dist/install-elixir.ps1 @@ -0,0 +1,5 @@ +Write-Output "Installer for Elixir for Windows not available" + +$ErrorActionPreference="Stop" + +Exit 1 diff --git a/dist/install-elixir b/dist/install-elixir.sh similarity index 100% rename from dist/install-elixir rename to dist/install-elixir.sh diff --git a/dist/install-otp.ps1 b/dist/install-otp.ps1 new file mode 100644 index 00000000..7f0c30e1 --- /dev/null +++ b/dist/install-otp.ps1 @@ -0,0 +1,19 @@ +param([Parameter(Mandatory=$true)][string]$VSN) + +$ErrorActionPreference="Stop" + +Set-Location $Env:RUNNER_TEMP + +$FILE_OUTPUT="otp.exe" +$DIR_FOR_BIN=".setup-beam/otp" + +Remove-Item -Recurse -Force "$DIR_FOR_BIN" -ErrorAction SilentlyContinue +$ProgressPreference="SilentlyContinue" +Invoke-WebRequest "https://github.com/erlang/otp/releases/download/OTP-$VSN/otp_win64_$VSN.exe" -OutFile "$FILE_OUTPUT" +$ProgressPreference="Continue" +New-Item "$DIR_FOR_BIN" -ItemType Directory | Out-Null +Move-Item "$FILE_OUTPUT" "$DIR_FOR_BIN" +Start-Process "./$DIR_FOR_BIN/$FILE_OUTPUT" /S -Wait +Write-Output "C:/Program Files/erl-$VSN/bin" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append +Write-Output "Installed Erlang/OTP version follows" +& "C:/Program Files/erl-$VSN/bin/erl.exe" "+V" | Write-Output diff --git a/dist/install-otp b/dist/install-otp.sh similarity index 100% rename from dist/install-otp rename to dist/install-otp.sh diff --git a/dist/install-rebar3.ps1 b/dist/install-rebar3.ps1 new file mode 100644 index 00000000..296c93e2 --- /dev/null +++ b/dist/install-rebar3.ps1 @@ -0,0 +1,24 @@ +param([Parameter(Mandatory=$true)][string]$VSN) + +$ErrorActionPreference="Stop" + +Set-Location $Env:RUNNER_TEMP + +$FILE_INPUT="rebar3" +$FILE_OUTPUT="rebar3" +$FILE_OUTPUT_PS1="rebar3.ps1" +$DIR_FOR_BIN=".setup-beam/rebar3" + +Remove-Item -Force "$FILE_OUTPUT" -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force "$DIR_FOR_BIN" -ErrorAction SilentlyContinue +$ProgressPreference="SilentlyContinue" +Invoke-WebRequest "https://github.com/erlang/rebar3/releases/download/${VSN}/${FILE_INPUT}" -OutFile "$FILE_OUTPUT" +$ProgressPreference="Continue" +New-Item "$DIR_FOR_BIN/bin" -ItemType Directory | Out-Null +Move-Item "$FILE_OUTPUT" "$DIR_FOR_BIN/bin" +Write-Output "& escript.exe $PWD/$DIR_FOR_BIN/bin/$FILE_OUTPUT `$args" | Out-File -FilePath "$FILE_OUTPUT_PS1" -Encoding utf8 -Append +type $FILE_OUTPUT_PS1 +Move-Item "$FILE_OUTPUT_PS1" "$DIR_FOR_BIN/bin" +Write-Output "$PWD/$DIR_FOR_BIN/bin" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append +Write-Output "Installed rebar3 version follows" +& "$DIR_FOR_BIN/bin/rebar3" "version" | Write-Output diff --git a/dist/install-rebar3 b/dist/install-rebar3.sh similarity index 100% rename from dist/install-rebar3 rename to dist/install-rebar3.sh diff --git a/package.json b/package.json index 4801dda1..519763f2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "jslint": "eslint src/**/*.js && eslint __tests__/**/*.js", "licenses": "yarn licenses generate-disclaimer > 3RD_PARTY_LICENSES", "markdownlint": "markdownlint *.md", - "shellcheck": "shellcheck src/install-*", + "shellcheck": "shellcheck src/install-*.sh", "test": "node __tests__/setup-beam.test.js", "yamllint": "yamllint .github/workflows/**.yml && yamllint .*.yml && yamllint *.yml" }, diff --git a/src/install-elixir.ps1 b/src/install-elixir.ps1 new file mode 100644 index 00000000..7674016f --- /dev/null +++ b/src/install-elixir.ps1 @@ -0,0 +1,5 @@ +Write-Output "Installer for Elixir for Windows not available" + +$ErrorActionPreference="Stop" + +Exit 1 diff --git a/src/install-elixir b/src/install-elixir.sh similarity index 100% rename from src/install-elixir rename to src/install-elixir.sh diff --git a/src/install-otp.ps1 b/src/install-otp.ps1 new file mode 100644 index 00000000..7f0c30e1 --- /dev/null +++ b/src/install-otp.ps1 @@ -0,0 +1,19 @@ +param([Parameter(Mandatory=$true)][string]$VSN) + +$ErrorActionPreference="Stop" + +Set-Location $Env:RUNNER_TEMP + +$FILE_OUTPUT="otp.exe" +$DIR_FOR_BIN=".setup-beam/otp" + +Remove-Item -Recurse -Force "$DIR_FOR_BIN" -ErrorAction SilentlyContinue +$ProgressPreference="SilentlyContinue" +Invoke-WebRequest "https://github.com/erlang/otp/releases/download/OTP-$VSN/otp_win64_$VSN.exe" -OutFile "$FILE_OUTPUT" +$ProgressPreference="Continue" +New-Item "$DIR_FOR_BIN" -ItemType Directory | Out-Null +Move-Item "$FILE_OUTPUT" "$DIR_FOR_BIN" +Start-Process "./$DIR_FOR_BIN/$FILE_OUTPUT" /S -Wait +Write-Output "C:/Program Files/erl-$VSN/bin" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append +Write-Output "Installed Erlang/OTP version follows" +& "C:/Program Files/erl-$VSN/bin/erl.exe" "+V" | Write-Output diff --git a/src/install-otp b/src/install-otp.sh similarity index 100% rename from src/install-otp rename to src/install-otp.sh diff --git a/src/install-rebar3.ps1 b/src/install-rebar3.ps1 new file mode 100644 index 00000000..296c93e2 --- /dev/null +++ b/src/install-rebar3.ps1 @@ -0,0 +1,24 @@ +param([Parameter(Mandatory=$true)][string]$VSN) + +$ErrorActionPreference="Stop" + +Set-Location $Env:RUNNER_TEMP + +$FILE_INPUT="rebar3" +$FILE_OUTPUT="rebar3" +$FILE_OUTPUT_PS1="rebar3.ps1" +$DIR_FOR_BIN=".setup-beam/rebar3" + +Remove-Item -Force "$FILE_OUTPUT" -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force "$DIR_FOR_BIN" -ErrorAction SilentlyContinue +$ProgressPreference="SilentlyContinue" +Invoke-WebRequest "https://github.com/erlang/rebar3/releases/download/${VSN}/${FILE_INPUT}" -OutFile "$FILE_OUTPUT" +$ProgressPreference="Continue" +New-Item "$DIR_FOR_BIN/bin" -ItemType Directory | Out-Null +Move-Item "$FILE_OUTPUT" "$DIR_FOR_BIN/bin" +Write-Output "& escript.exe $PWD/$DIR_FOR_BIN/bin/$FILE_OUTPUT `$args" | Out-File -FilePath "$FILE_OUTPUT_PS1" -Encoding utf8 -Append +type $FILE_OUTPUT_PS1 +Move-Item "$FILE_OUTPUT_PS1" "$DIR_FOR_BIN/bin" +Write-Output "$PWD/$DIR_FOR_BIN/bin" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append +Write-Output "Installed rebar3 version follows" +& "$DIR_FOR_BIN/bin/rebar3" "version" | Write-Output diff --git a/src/install-rebar3 b/src/install-rebar3.sh similarity index 100% rename from src/install-rebar3 rename to src/install-rebar3.sh diff --git a/src/installer.js b/src/installer.js index 8403275d..275252ff 100644 --- a/src/installer.js +++ b/src/installer.js @@ -8,7 +8,13 @@ const path = require('path') * @param {string} otpVersion */ async function installOTP(osVersion, otpVersion) { - await exec(path.join(__dirname, 'install-otp'), [osVersion, otpVersion]) + const OS = process.platform + if (OS === 'linux') { + await exec(path.join(__dirname, 'install-otp.sh'), [osVersion, otpVersion]) + } else if (OS === 'win32') { + const script = path.join(__dirname, 'install-otp.ps1') + await exec(`powershell.exe ${script} -VSN:${otpVersion}`) + } } /** @@ -17,7 +23,13 @@ async function installOTP(osVersion, otpVersion) { * @param {string} elixirVersion */ async function installElixir(elixirVersion) { - await exec(path.join(__dirname, 'install-elixir'), [elixirVersion]) + const OS = process.platform + if (OS === 'linux') { + await exec(path.join(__dirname, 'install-elixir.sh'), [elixirVersion]) + } else if (OS === 'win32') { + const script = path.join(__dirname, 'install-elixir.ps1') + await exec(`powershell.exe ${script} ${elixirVersion}`) + } } /** @@ -26,12 +38,20 @@ async function installElixir(elixirVersion) { * @param {string} rebar3Version */ async function installRebar3(rebar3Version) { - await exec(path.join(__dirname, 'install-rebar3'), [rebar3Version]) + const OS = process.platform + if (OS === 'linux') { + await exec(path.join(__dirname, 'install-rebar3.sh'), [rebar3Version]) + } else if (OS === 'win32') { + const script = path.join(__dirname, 'install-rebar3.ps1') + await exec(`powershell.exe ${script} -VSN:${rebar3Version}`) + } } function checkPlatform() { - if (process.platform !== 'linux') { - throw new Error('@erlef/setup-beam only supports Ubuntu Linux at this time') + if (process.platform !== 'linux' && process.platform !== 'win32') { + throw new Error( + '@erlef/setup-beam only supports Ubuntu and Windows at this time', + ) } } diff --git a/src/setup-beam.js b/src/setup-beam.js index 90782f14..234aa2c6 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -17,20 +17,24 @@ async function main() { const otpVersion = await installOTP(otpSpec, osVersion) const elixirSpec = core.getInput('elixir-version', { required: false }) - const elixirInstalled = await maybeInstallElixir(elixirSpec, otpVersion) + const shouldMixHex = core.getInput('install-hex', { + required: false, + }) + const elixirInstalled = await maybeInstallElixir( + elixirSpec, + otpVersion, + shouldMixHex, + ) 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) + await maybeInstallRebar3(rebar3Spec) } async function installOTP(otpSpec, osVersion) { @@ -40,13 +44,17 @@ async function installOTP(otpSpec, osVersion) { ) await installer.installOTP(osVersion, otpVersion) core.setOutput('otp-version', otpVersion) - prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`) + if (process.platform === 'linux') { + prependToPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`) + } else if (process.platform === 'win32') { + prependToPath(`C:/Program Files/erl-${otpVersion}/bin`) + } console.log('##[endgroup]') return otpVersion } -async function maybeInstallElixir(elixirSpec, otpVersion) { +async function maybeInstallElixir(elixirSpec, otpVersion, shouldMixHex) { if (elixirSpec) { const elixirVersion = await getElixirVersion(elixirSpec, otpVersion) console.log(`##[group]Installing Elixir ${elixirVersion}`) @@ -62,6 +70,12 @@ async function maybeInstallElixir(elixirSpec, otpVersion) { return true } + if (shouldMixHex) { + console.log( + "hex will not be installed (overriding default) since Elixir wasn't either", + ) + } + return false } @@ -148,7 +162,7 @@ async function getElixirVersion(exSpec0, otpVersion) { `Using Elixir ${elixirVersion} (built for OTP ${otpVersionMajor})`, ) } else { - // ... and it's not available: fallback to the "generic" version (v1.4.5 only). + // ... and it's not available: fallback to the 'generic' version (v1.4.5 only). elixirVersionWithOTP = elixirVersion core.info(`Using Elixir ${elixirVersion}`) } @@ -175,34 +189,61 @@ async function getRebar3Version(r3Spec) { } async function getOTPVersions(osVersion) { - const otpVersionsListing = await get( - `https://repo.hex.pm/builds/otp/${osVersion}/builds.txt`, - ) - const otpVersions = new Map() + let originListing + let pageIdxs + if (process.platform === 'linux') { + originListing = `https://repo.hex.pm/builds/otp/${osVersion}/builds.txt` + pageIdxs = [null] + } else if (process.platform === 'win32') { + originListing = + 'https://api.github.com/repos/erlang/otp/releases?per_page=100' + pageIdxs = [1, 2, 3] + } - otpVersionsListing - .trim() - .split('\n') - .forEach((line) => { - const otpMatch = line.match(/^(OTP-)?([^ ]+)/) + const otpVersionsListings = await get(originListing, pageIdxs) + const otpVersions = new Map() - let otpVersion = otpMatch[2] - if (semver.validRange(otpVersion)) { - otpVersion = semver.minVersion(otpVersion).version - } - otpVersions.set(otpVersion, otpMatch[0]) // we keep the original for later reference + if (process.platform === 'linux') { + otpVersionsListings + .trim() + .split('\n') + .forEach((line) => { + const otpMatch = line.match(/^(OTP-)?([^ ]+)/) + + let otpVersion = otpMatch[2] + if (semver.validRange(otpVersion) && hasPatch(otpVersion)) { + otpVersion = semver.minVersion(otpVersion).version + } + otpVersions.set(otpVersion, otpMatch[0]) // we keep the original for later reference + }) + } else if (process.platform === 'win32') { + otpVersionsListings.forEach((otpVersionsListing) => { + JSON.parse(otpVersionsListing) + .map((x) => x.assets) + .flat() + .filter((x) => x.name.match(/^otp_win64_.*.exe$/)) + .forEach((x) => { + const otpMatch = x.name.match(/^otp_win64_(.*).exe$/) + let otpVersion = otpMatch[1] + if (semver.validRange(otpVersion) && hasPatch(otpVersion)) { + otpVersion = semver.minVersion(otpVersion).version + } + otpVersions.set(otpVersion, otpVersion) + }) }) + } return otpVersions } async function getElixirVersions() { - const elixirVersionsListing = await get( + const elixirVersionsListings = await get( 'https://repo.hex.pm/builds/elixir/builds.txt', + [null], ) const otpVersionsForElixirMap = new Map() - elixirVersionsListing + elixirVersionsListings .trim() .split('\n') .forEach((line) => { @@ -222,13 +263,17 @@ async function getElixirVersions() { } async function getRebar3Versions() { - const resultJSON = await get( + const resultJSONs = await get( 'https://api.github.com/repos/erlang/rebar3/releases?per_page=100', + [1, 2, 3], ) - const rebar3VersionsListing = JSON.parse(resultJSON) - .map((x) => x.tag_name) - .sort() - + const rebar3VersionsListing = [] + resultJSONs.forEach((resultJSON) => { + JSON.parse(resultJSON) + .map((x) => x.tag_name) + .sort() + .forEach((v) => rebar3VersionsListing.push(v)) + }) return rebar3VersionsListing } @@ -241,43 +286,79 @@ function getVersionFromSpec(spec, versions) { } function getRunnerOSVersion() { - const mapToUbuntuVersion = { + const ImageOSToContainer = { ubuntu16: 'ubuntu-16.04', ubuntu18: 'ubuntu-18.04', ubuntu20: 'ubuntu-20.04', + win16: 'windows-2016', + win19: 'windows-2019', } - return mapToUbuntuVersion[process.env.ImageOS] || 'ubuntu-18.04' + return ImageOSToContainer[process.env.ImageOS] } -function get(url) { - return new Promise((resolve, reject) => { - 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) - }) - }) +async function get(url0, pageIdxs) { + function getPage(pageIdx) { + return new Promise((resolve, reject) => { + const url = new URL(url0) + if (pageIdx !== null) { + url.searchParams.append('page', pageIdx) + } + https + .get( + url, + { + headers: { 'user-agent': 'setup-beam' }, + }, + (res) => { + let data = '' + res.on('data', (chunk) => { + data += chunk + }) + res.on('end', () => { + if (res.statusCode >= 400 && res.statusCode <= 599) { + reject( + new Error( + `Got ${res.statusCode} from ${url}. Exiting with error`, + ), + ) + } else { + resolve(data) + } + }) + }, + ) + .on('error', (err) => { + reject(err) + }) + }) + } + let ret + if (pageIdxs[0] === null) { + ret = getPage(null) + } else { + ret = Promise.all(pageIdxs.map((pageIdx) => getPage(pageIdx))) + } + return ret } function prependToPath(what) { - process.env.PATH = `${what}:${process.env.PATH}` + if (process.platform === 'linux') { + process.env.PATH = `${what}:${process.env.PATH}` + } else if (process.platform === 'win32') { + process.env.PATH = `${what};${process.env.PATH}` + } } +function hasPatch(v) { + try { + semver.patch(v) + } catch { + return false + } + + return true +} module.exports = { getOTPVersion, getElixirVersion,