New option to work with one or more hex.pm mirrors (#197)

cf692c3 · Paul Guyot · 2023-05-04 06:27

13 files +360 -97
Message
{commit_body(@commit)}

Files changed

added .github/workflows/hexpm-mirrors.yml
+58 −0
@@ -0,0 +1,58 @@
1 +---
2 +name: hexpm-mirrors
3 +
4 +on:
5 + push:
6 + branches:
7 + - main
8 + pull_request:
9 + branches:
10 + - main
11 +
12 +jobs:
13 + test-failing-first-mirror:
14 + name: Test option hexpm-mirrors with a dummy first mirror
15 + runs-on: ${{matrix.os}}
16 + strategy:
17 + fail-fast: false
18 + matrix:
19 + os: ['ubuntu-22.04', 'windows-2022']
20 + otp-version: ['24']
21 + elixir-version: ['v1.14', '']
22 + install-rebar: [true, false]
23 + install-hex: [true, false]
24 + steps:
25 + - uses: actions/checkout@v3
26 + - name: Use erlef/setup-beam
27 + id: setup-beam
28 + uses: ./
29 + with:
30 + otp-version: ${{matrix.otp-version}}
31 + elixir-version: ${{matrix.elixir-version}}
32 + install-rebar: ${{matrix.install-rebar}}
33 + install-hex: ${{matrix.install-hex}}
34 + hexpm-mirrors: |
35 + https://mirror.invalid
36 + https://cdn.jsdelivr.net/hex
37 + https://builds.hex.pm
38 +
39 + test-invalid-mirror:
40 + name: Test errors with mirror
41 + runs-on: ubuntu-22.04
42 + steps:
43 + - uses: actions/checkout@v3
44 + - name: Use erlef/setup-beam
45 + id: failing-setup-beam
46 + uses: ./
47 + continue-on-error: true
48 + with:
49 + otp-version: '24.3.4.6'
50 + elixir-version: 'v1.13.4'
51 + install-rebar: true
52 + install-hex: true
53 + hexpm-mirrors: https://mirror.invalid
54 + - name: Report unexpected success
55 + if: ${{ steps.failing-setup-beam.outcome == 'success' }}
56 + run: |
57 + echo "Action is expected to fail"
58 + exit 1
modified README.md
+41 −0
@@ -216,6 +216,47 @@ jobs:
216 216
217 217 **Note**: the `otp-version: false` input is only applicable when installing Gleam.
218 218
219 +## Alternative hex.pm mirrors
220 +
221 +It is possible to use alternative hex.pm mirror(s), in their declared order, with
222 +option `hexpm-mirrors`. By default, the action will use `builds.hex.pm`.
223 +To use other alternative mirrors, add one per line, as shown below.
224 +
225 +```yaml
226 +# create this in .github/workflows/ci.yml
227 +on: push
228 +
229 +jobs:
230 + test:
231 + runs-on: ubuntu-latest
232 + steps:
233 + - uses: actions/checkout@v2
234 + - uses: erlef/setup-beam@v1
235 + with:
236 + otp-version: '25'
237 + # Use `cdn.jsdelivr.net/hex` as an alternative to `builds.hex.pm`
238 + hexpm-mirrors: https://cdn.jsdelivr.net/hex
239 +```
240 +
241 +Alternatively, you may try `cdn.jsdelivr.net/hex` if `builds.hex.pm` fails:
242 +
243 +```yaml
244 +# create this in .github/workflows/ci.yml
245 +on: push
246 +
247 +jobs:
248 + test:
249 + runs-on: ubuntu-latest
250 + steps:
251 + - uses: actions/checkout@v2
252 + - uses: erlef/setup-beam@v1
253 + with:
254 + otp-version: '25'
255 + hexpm-mirrors: |
256 + https://builds.hex.pm
257 + https://cdn.jsdelivr.net/hex
258 +```
259 +
219 260 ## Environment variables
220 261
221 262 Base installation folders (useful for e.g. fetching headers for NIFs) are available in the following
modified __tests__/setup-beam.test.js
+19 −17
@@ -102,48 +102,49 @@ async function testOTPVersions() {
102 102 let expected
103 103 let spec
104 104 let osVersion
105 + const hexMirrors = ['https://repo.hex.pm', 'https://cdn.jsdelivr.net/hex']
105 106
106 107 if (process.platform === 'linux') {
107 108 spec = '19.3.x'
108 109 osVersion = 'ubuntu-16.04'
109 110 expected = 'OTP-19.3.6.13'
110 got = await setupBeam.getOTPVersion(spec, osVersion)
111 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
111 112 assert.deepStrictEqual(got, expected)
112 113
113 114 spec = '^19.3.6'
114 115 osVersion = 'ubuntu-16.04'
115 116 expected = 'OTP-19.3.6.13'
116 got = await setupBeam.getOTPVersion(spec, osVersion)
117 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
117 118 assert.deepStrictEqual(got, expected)
118 119
119 120 spec = '^19.3'
120 121 osVersion = 'ubuntu-18.04'
121 122 expected = 'OTP-19.3.6.13'
122 got = await setupBeam.getOTPVersion(spec, osVersion)
123 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
123 124 assert.deepStrictEqual(got, expected)
124 125
125 126 spec = '20'
126 127 osVersion = 'ubuntu-20.04'
127 128 expected = 'OTP-20.3.8.26'
128 got = await setupBeam.getOTPVersion(spec, osVersion)
129 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
129 130 assert.deepStrictEqual(got, expected)
130 131
131 132 spec = '20.x'
132 133 osVersion = 'ubuntu-20.04'
133 134 expected = 'OTP-20.3.8.26'
134 got = await setupBeam.getOTPVersion(spec, osVersion)
135 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
135 136 assert.deepStrictEqual(got, expected)
136 137
137 138 spec = '20.0'
138 139 osVersion = 'ubuntu-20.04'
139 140 expected = 'OTP-20.0.5'
140 got = await setupBeam.getOTPVersion(spec, osVersion)
141 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
141 142 assert.deepStrictEqual(got, expected)
142 143
143 144 spec = '20.0.x'
144 145 osVersion = 'ubuntu-20.04'
145 146 expected = 'OTP-20.0.5'
146 got = await setupBeam.getOTPVersion(spec, osVersion)
147 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
147 148 assert.deepStrictEqual(got, expected)
148 149 }
149 150
@@ -151,19 +152,19 @@ async function testOTPVersions() {
151 152 spec = '24.0.1'
152 153 osVersion = 'windows-latest'
153 154 expected = '24.0.1'
154 got = await setupBeam.getOTPVersion(spec, osVersion)
155 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
155 156 assert.deepStrictEqual(got, expected)
156 157
157 158 spec = '23.2.x'
158 159 osVersion = 'windows-2016'
159 160 expected = '23.2.7'
160 got = await setupBeam.getOTPVersion(spec, osVersion)
161 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
161 162 assert.deepStrictEqual(got, expected)
162 163
163 164 spec = '23.0'
164 165 osVersion = 'windows-2019'
165 166 expected = '23.0.4'
166 got = await setupBeam.getOTPVersion(spec, osVersion)
167 + got = await setupBeam.getOTPVersion(spec, osVersion, hexMirrors)
167 168 assert.deepStrictEqual(got, expected)
168 169 }
169 170 }
@@ -173,30 +174,31 @@ async function testElixirVersions() {
173 174 let expected
174 175 let spec
175 176 let otpVersion
177 + const hexMirrors = ['https://repo.hex.pm']
176 178
177 179 spec = '1.1.x'
178 180 otpVersion = 'OTP-17'
179 181 expected = 'v1.1.1-otp-17'
180 got = await setupBeam.getElixirVersion(spec, otpVersion)
182 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
181 183 assert.deepStrictEqual(got, expected)
182 184
183 185 spec = '1.10.4'
184 186 otpVersion = 'OTP-23'
185 187 expected = 'v1.10.4-otp-23'
186 got = await setupBeam.getElixirVersion(spec, otpVersion)
188 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
187 189 assert.deepStrictEqual(got, expected)
188 190
189 191 spec = '1.12.1'
190 192 otpVersion = 'OTP-24.0.2'
191 193 expected = 'v1.12.1-otp-24'
192 got = await setupBeam.getElixirVersion(spec, otpVersion)
194 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
193 195 assert.deepStrictEqual(got, expected)
194 196
195 197 simulateInput('version-type', 'strict')
196 198 spec = '1.14.0'
197 199 otpVersion = 'master'
198 200 expected = 'v1.14.0'
199 got = await setupBeam.getElixirVersion(spec, otpVersion)
201 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
200 202 assert.deepStrictEqual(got, expected)
201 203 simulateInput('version-type', 'loose')
202 204
@@ -204,7 +206,7 @@ async function testElixirVersions() {
204 206 spec = 'v1.11.0-rc.0'
205 207 otpVersion = 'OTP-23'
206 208 expected = 'v1.11.0-rc.0-otp-23'
207 got = await setupBeam.getElixirVersion(spec, otpVersion)
209 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
208 210 assert.deepStrictEqual(got, expected)
209 211 simulateInput('version-type', 'loose')
210 212
@@ -212,7 +214,7 @@ async function testElixirVersions() {
212 214 spec = 'v1.11.0'
213 215 otpVersion = '22.3.4.2'
214 216 expected = 'v1.11.0-otp-22'
215 got = await setupBeam.getElixirVersion(spec, otpVersion)
217 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
216 218 assert.deepStrictEqual(got, expected)
217 219 simulateInput('version-type', 'loose')
218 220
@@ -220,7 +222,7 @@ async function testElixirVersions() {
220 222 spec = 'main'
221 223 otpVersion = '23.1'
222 224 expected = 'main-otp-23'
223 got = await setupBeam.getElixirVersion(spec, otpVersion)
225 + got = await setupBeam.getElixirVersion(spec, otpVersion, hexMirrors)
224 226 assert.deepStrictEqual(got, expected)
225 227 simulateInput('version-type', 'loose')
226 228 }
modified action.yml
+4 −0
@@ -45,6 +45,10 @@ inputs:
45 45 version-file:
46 46 description: a versions file (e.g. as used by `asdf`), which defines inputs
47 47 default: ''
48 + hexpm-mirrors:
49 + description: mirror(s) for hex.pm, one per line
50 + default: |
51 + https://builds.hex.pm
48 52 outputs:
49 53 elixir-version:
50 54 description: Exact version of Elixir that was installed
modified dist/index.js
+113 −36
@@ -7120,6 +7120,7 @@ try {
7120 7120 /***/ 2127:
7121 7121 /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
7122 7122
7123 +const core = __nccwpck_require__(2186)
7123 7124 const { exec } = __nccwpck_require__(1514)
7124 7125 const path = __nccwpck_require__(1017)
7125 7126
@@ -7128,11 +7129,28 @@ const path = __nccwpck_require__(1017)
7128 7129 *
7129 7130 * @param {string} osVersion
7130 7131 * @param {string} otpVersion
7132 + * @param {string[]} hexMirrors
7131 7133 */
7132 async function installOTP(osVersion, otpVersion) {
7134 +async function installOTP(osVersion, otpVersion, hexMirrors) {
7133 7135 const OS = process.platform
7134 7136 if (OS === 'linux') {
7135 await exec(__nccwpck_require__.ab + "install-otp.sh", [osVersion, otpVersion])
7137 + if (hexMirrors.length === 0) {
7138 + throw new Error(
7139 + `Could not install Erlang/OTP ${otpVersion} from any hex.pm mirror`,
7140 + )
7141 + }
7142 + const [hexMirror, ...hexMirrorsT] = hexMirrors
7143 + try {
7144 + await exec(__nccwpck_require__.ab + "install-otp.sh", [
7145 + osVersion,
7146 + otpVersion,
7147 + hexMirror,
7148 + ])
7149 + return
7150 + } catch (err) {
7151 + core.info(`install-otp.sh failed for mirror ${hexMirror}`)
7152 + }
7153 + await installOTP(osVersion, otpVersion, hexMirrorsT)
7136 7154 } else if (OS === 'win32') {
7137 7155 const script = __nccwpck_require__.ab + "install-otp.ps1"
7138 7156 await exec(`pwsh.exe ${script} -VSN:${otpVersion}`)
@@ -7143,15 +7161,34 @@ async function installOTP(osVersion, otpVersion) {
7143 7161 * Install Elixir.
7144 7162 *
7145 7163 * @param {string} elixirVersion
7164 + * @param {string[]} hexMirrors
7146 7165 */
7147 async function installElixir(elixirVersion) {
7166 +async function installElixir(elixirVersion, hexMirrors) {
7167 + if (hexMirrors.length === 0) {
7168 + throw new Error(
7169 + `Could not install Elixir ${elixirVersion} from any hex.pm mirror`,
7170 + )
7171 + }
7172 + const [hexMirror, ...hexMirrorsT] = hexMirrors
7148 7173 const OS = process.platform
7149 if (OS === 'linux') {
7150 await exec(__nccwpck_require__.ab + "install-elixir.sh", [elixirVersion])
7151 } else if (OS === 'win32') {
7152 const script = __nccwpck_require__.ab + "install-elixir.ps1"
7153 await exec(`pwsh.exe ${script} -VSN:${elixirVersion}`)
7174 + let script
7175 + try {
7176 + if (OS === 'linux') {
7177 + script = __nccwpck_require__.ab + "install-elixir.sh"
7178 + await exec(__nccwpck_require__.ab + "install-elixir.sh", [elixirVersion, hexMirror])
7179 + return
7180 + }
7181 + if (OS === 'win32') {
7182 + script = __nccwpck_require__.ab + "install-elixir.ps1"
7183 + await exec(
7184 + `pwsh.exe ${script} -VSN:${elixirVersion} -HEX_MIRROR:${hexMirror}`,
7185 + )
7186 + return
7187 + }
7188 + } catch (err) {
7189 + core.info(`${script} failed for mirror ${hexMirror}`)
7154 7190 }
7191 + await installElixir(elixirVersion, hexMirrorsT)
7155 7192 }
7156 7193
7157 7194 /**
@@ -7238,16 +7275,23 @@ async function main() {
7238 7275 const elixirSpec = getInput('elixir-version', false, 'elixir', versions)
7239 7276 const gleamSpec = getInput('gleam-version', false, 'gleam', versions)
7240 7277 const rebar3Spec = getInput('rebar3-version', false, 'rebar', versions)
7278 + const hexMirrors = core.getMultilineInput('hexpm-mirrors', {
7279 + required: false,
7280 + })
7241 7281
7242 7282 if (otpSpec !== 'false') {
7243 await installOTP(otpSpec, osVersion)
7244 const elixirInstalled = await maybeInstallElixir(elixirSpec, otpSpec)
7283 + await installOTP(otpSpec, osVersion, hexMirrors)
7284 + const elixirInstalled = await maybeInstallElixir(
7285 + elixirSpec,
7286 + otpSpec,
7287 + hexMirrors,
7288 + )
7245 7289
7246 7290 if (elixirInstalled === true) {
7247 7291 const shouldMixRebar = getInput('install-rebar', false)
7248 await mix(shouldMixRebar, 'rebar')
7292 + await mix(shouldMixRebar, 'rebar', hexMirrors)
7249 7293 const shouldMixHex = getInput('install-hex', false)
7250 await mix(shouldMixHex, 'hex')
7294 + await mix(shouldMixHex, 'hex', hexMirrors)
7251 7295 }
7252 7296 } else if (!gleamSpec) {
7253 7297 throw new Error('otp-version=false is only available when installing Gleam')
@@ -7257,12 +7301,12 @@ async function main() {
7257 7301 await maybeInstallRebar3(rebar3Spec)
7258 7302 }
7259 7303
7260 async function installOTP(otpSpec, osVersion) {
7261 const otpVersion = await getOTPVersion(otpSpec, osVersion)
7304 +async function installOTP(otpSpec, osVersion, hexMirrors) {
7305 + const otpVersion = await getOTPVersion(otpSpec, osVersion, hexMirrors)
7262 7306 console.log(
7263 7307 `##[group]Installing Erlang/OTP ${otpVersion} - built on ${osVersion}`,
7264 7308 )
7265 await installer.installOTP(osVersion, otpVersion)
7309 + await installer.installOTP(osVersion, otpVersion, hexMirrors)
7266 7310 core.setOutput('otp-version', otpVersion)
7267 7311 core.addPath(`${process.env.RUNNER_TEMP}/.setup-beam/otp/bin`)
7268 7312 console.log('##[endgroup]')
@@ -7270,13 +7314,17 @@ async function installOTP(otpSpec, osVersion) {
7270 7314 return otpVersion
7271 7315 }
7272 7316
7273 async function maybeInstallElixir(elixirSpec, otpVersion) {
7317 +async function maybeInstallElixir(elixirSpec, otpSpec, hexMirrors) {
7274 7318 let installed = false
7275 7319
7276 7320 if (elixirSpec) {
7277 const elixirVersion = await getElixirVersion(elixirSpec, otpVersion)
7321 + const elixirVersion = await getElixirVersion(
7322 + elixirSpec,
7323 + otpSpec,
7324 + hexMirrors,
7325 + )
7278 7326 console.log(`##[group]Installing Elixir ${elixirVersion}`)
7279 await installer.installElixir(elixirVersion)
7327 + await installer.installElixir(elixirVersion, hexMirrors)
7280 7328 core.setOutput('elixir-version', elixirVersion)
7281 7329 const disableProblemMatchers = getInput('disable_problem_matchers', false)
7282 7330 if (disableProblemMatchers === 'false') {
@@ -7295,12 +7343,28 @@ async function maybeInstallElixir(elixirSpec, otpVersion) {
7295 7343 return installed
7296 7344 }
7297 7345
7298 async function mix(shouldMix, what) {
7346 +async function mixWithMirrors(cmd, args, hexMirrors) {
7347 + if (hexMirrors.length === 0) {
7348 + throw new Error('mix failed with every mirror')
7349 + }
7350 + const [hexMirror, ...hexMirrorsT] = hexMirrors
7351 + process.env.HEX_MIRROR = hexMirror
7352 + try {
7353 + return await exec(cmd, args)
7354 + } catch (err) {
7355 + core.info(
7356 + `mix failed with mirror ${process.env.HEX_MIRROR} with message ${err.message})`,
7357 + )
7358 + }
7359 + return mixWithMirrors(cmd, args, hexMirrorsT)
7360 +}
7361 +
7362 +async function mix(shouldMix, what, hexMirrors) {
7299 7363 if (shouldMix === 'true') {
7300 7364 const cmd = 'mix'
7301 7365 const args = [`local.${what}`, '--force']
7302 7366 console.log(`##[group]Running ${cmd} ${args}`)
7303 await exec(cmd, args)
7367 + await mixWithMirrors(cmd, args, hexMirrors)
7304 7368 console.log('##[endgroup]')
7305 7369 }
7306 7370 }
@@ -7344,8 +7408,8 @@ async function maybeInstallRebar3(rebar3Spec) {
7344 7408 return installed
7345 7409 }
7346 7410
7347 async function getOTPVersion(otpSpec0, osVersion) {
7348 const otpVersions = await getOTPVersions(osVersion)
7411 +async function getOTPVersion(otpSpec0, osVersion, hexMirrors) {
7412 + const otpVersions = await getOTPVersions(osVersion, hexMirrors)
7349 7413 let otpSpec = otpSpec0 // might be a branch (?)
7350 7414 const otpVersion = getVersionFromSpec(
7351 7415 otpSpec,
@@ -7364,10 +7428,10 @@ async function getOTPVersion(otpSpec0, osVersion) {
7364 7428 return otpVersions.get(otpVersion) // from the reference, for download
7365 7429 }
7366 7430
7367 async function getElixirVersion(exSpec0, otpVersion0) {
7431 +async function getElixirVersion(exSpec0, otpVersion0, hexMirrors) {
7368 7432 const otpVersion = otpVersion0.match(/^([^-]+-)?(.+)$/)[2]
7369 7433 const otpVersionMajor = otpVersion.match(/^([^.]+).*$/)[1]
7370 const elixirVersions = await getElixirVersions()
7434 + const elixirVersions = await getElixirVersions(hexMirrors)
7371 7435 const semverVersions = Array.from(elixirVersions.keys()).sort()
7372 7436 const exSpec = exSpec0.replace(/-otp-.*$/, '')
7373 7437 const elixirVersionFromSpec = getVersionFromSpec(exSpec, semverVersions, true)
@@ -7427,19 +7491,19 @@ async function getRebar3Version(r3Spec) {
7427 7491 return rebar3Version
7428 7492 }
7429 7493
7430 async function getOTPVersions(osVersion) {
7431 let originListing
7432 let pageIdxs
7494 +async function getOTPVersions(osVersion, hexMirrors) {
7495 + let otpVersionsListings
7433 7496 if (process.platform === 'linux') {
7434 originListing = `https://builds.hex.pm/builds/otp/${osVersion}/builds.txt`
7435 pageIdxs = [null]
7497 + otpVersionsListings = await getWithMirrors(
7498 + `/builds/otp/${osVersion}/builds.txt`,
7499 + hexMirrors,
7500 + )
7436 7501 } else if (process.platform === 'win32') {
7437 originListing =
7502 + const originListing =
7438 7503 'https://api.github.com/repos/erlang/otp/releases?per_page=100'
7439 pageIdxs = [1, 2, 3]
7504 + otpVersionsListings = await get(originListing, [1, 2, 3])
7440 7505 }
7441 7506
7442 const otpVersionsListings = await get(originListing, pageIdxs)
7443 7507 const otpVersions = new Map()
7444 7508
7445 7509 if (process.platform === 'linux') {
@@ -7469,10 +7533,10 @@ async function getOTPVersions(osVersion) {
7469 7533 return otpVersions
7470 7534 }
7471 7535
7472 async function getElixirVersions() {
7473 const elixirVersionsListings = await get(
7474 'https://builds.hex.pm/builds/elixir/builds.txt',
7475 [null],
7536 +async function getElixirVersions(hexMirrors) {
7537 + const elixirVersionsListings = await getWithMirrors(
7538 + '/builds/elixir/builds.txt',
7539 + hexMirrors,
7476 7540 )
7477 7541 const otpVersionsForElixirMap = new Map()
7478 7542
@@ -7656,6 +7720,19 @@ async function get(url0, pageIdxs) {
7656 7720 return Promise.all(pageIdxs.map(getPage))
7657 7721 }
7658 7722
7723 +async function getWithMirrors(resourcePath, hexMirrors) {
7724 + if (hexMirrors.length === 0) {
7725 + throw new Error(`Could not fetch ${resourcePath} from any hex.pm mirror`)
7726 + }
7727 + const [hexMirror, ...hexMirrorsT] = hexMirrors
7728 + try {
7729 + return await get(`${hexMirror}${resourcePath}`, [null])
7730 + } catch (err) {
7731 + core.info(`get failed for URL ${hexMirror}${resourcePath}`)
7732 + }
7733 + return getWithMirrors(resourcePath, hexMirrorsT)
7734 +}
7735 +
7659 7736 function maybePrependWithV(v) {
7660 7737 if (isVersion(v)) {
7661 7738 return `v${v.replace('v', '')}`
modified dist/install-elixir.ps1
+2 −2

Click to load diff…

modified dist/install-elixir.sh
+2 −1

Click to load diff…

modified dist/install-otp.sh
+2 −1

Click to load diff…

modified src/install-elixir.ps1
+2 −2

Click to load diff…

modified src/install-elixir.sh
+2 −1

Click to load diff…

modified src/install-otp.sh
+2 −1

Click to load diff…

modified src/installer.js
+45 −8

Click to load diff…

modified src/setup-beam.js
+68 −28

Click to load diff…

Parents: 9641cb9