Improve visibility (+ fix bugs) over version-type and opt-in versions (#57)

8306425 · Paulo F. Oliveira · 2021-07-09 09:11

10 files +113 -48
Message
{commit_body(@commit)}

Files changed

modified .github/workflows/ubuntu.yml
+12 −0
@@ -92,6 +92,17 @@ jobs:
92 92 otp-version: '24'
93 93 rebar3-version: '3.15'
94 94 os: 'ubuntu-20.04'
95 + - elixir-version: 'v1.11.0'
96 + otp-version: '22.3.4.2'
97 + os: 'ubuntu-20.04'
98 + version-type: 'strict'
99 + - elixir-version: '1.10.3'
100 + otp-version: '22.3.4.1'
101 + os: 'ubuntu-20.04'
102 + version-type: 'strict'
103 + - elixir-version: '1.10.3'
104 + otp-version: '22.3.4.1'
105 + os: 'ubuntu-20.04'
95 106 steps:
96 107 - uses: actions/checkout@v2
97 108 - name: Use erlef/setup-beam
@@ -101,6 +112,7 @@ jobs:
101 112 otp-version: ${{matrix.combo.otp-version}}
102 113 elixir-version: ${{matrix.combo.elixir-version}}
103 114 rebar3-version: ${{matrix.combo.rebar3-version}}
115 + version-type: ${{matrix.combo.version-type}}
104 116 - name: Erlang/OTP version (action)
105 117 run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}"
106 118 - name: Elixir version (action)
modified README.md
+15 −3
@@ -34,9 +34,9 @@ Additionally, it is recommended that one specifies Erlang/OTP, Elixir and `rebar
34 34 using YAML strings, as these examples do, so that numbers like `23.0` don't
35 35 end up being parsed as `23`, which is not equivalent.
36 36
37 For pre-release Elixir versions, such as `1.11.0-rc.0`, use the full version
38 specifier (`1.11.0-rc.0`). Pre-release versions are opt-in, so `1.11.x` will
39 not match a pre-release.
37 +For pre-release versions, such as `v1.11.0-rc.0`, use the full version
38 +specifier (`v1.11.0-rc.0`) and set option `version-type` to `strict`. Pre-release versions are
39 +opt-in, so `1.11.x` will not match a pre-release.
40 40
41 41 ### Compatibility between Operating System and Erlang/OTP
42 42
@@ -159,6 +159,18 @@ jobs:
159 159
160 160 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.
161 161
162 +## Action versioning
163 +
164 +`setup-beam` has three version paths, described below, for example version `1.8.0`:
165 +
166 +- `@v1`: the latest in the `1.y.z` series (this tag is movable),
167 +- `@v1.8`: the latest in the `1.8.z` series (this tag is movable),
168 +- `@v1.8.0`: release `1.8.0` (this tag is not movable).
169 +
170 +To prevent issues in CI (unless you're OK with potential incompatibility between versions - we
171 +make real a effort to not introduce those without bumping major) we suggest you to use `@vx.y.z`,
172 +whenever possible.
173 +
162 174 ## License
163 175
164 176 The scripts and documentation in this project are released under the [MIT license](LICENSE.md).
modified __tests__/setup-beam.test.js
+24 −17
@@ -166,6 +166,12 @@ async function testElixirVersions() {
166 166 got = await setupBeam.getElixirVersion(spec, otpVersion)
167 167 assert.deepStrictEqual(got, expected)
168 168
169 + spec = '1.12.1'
170 + otpVersion = 'OTP-24.0.2'
171 + expected = 'v1.12.1-otp-24'
172 + got = await setupBeam.getElixirVersion(spec, otpVersion)
173 + assert.deepStrictEqual(got, expected)
174 +
169 175 simulateInput('version-type', 'strict')
170 176 spec = 'v1.11.0-rc.0'
171 177 otpVersion = 'OTP-23'
@@ -173,6 +179,14 @@ async function testElixirVersions() {
173 179 got = await setupBeam.getElixirVersion(spec, otpVersion)
174 180 assert.deepStrictEqual(got, expected)
175 181 simulateInput('version-type', 'loose')
182 +
183 + simulateInput('version-type', 'strict')
184 + spec = 'v1.11.0'
185 + otpVersion = '22.3.4.2'
186 + expected = 'v1.11.0-otp-22'
187 + got = await setupBeam.getElixirVersion(spec, otpVersion)
188 + assert.deepStrictEqual(got, expected)
189 + simulateInput('version-type', 'loose')
176 190 }
177 191
178 192 async function testRebar3Versions() {
@@ -228,6 +242,7 @@ async function testGetVersionFromSpec() {
228 242 '22.3.4.10.1',
229 243 'master',
230 244 'v11.11.0-rc.0-otp-23',
245 + '22.3.4.2',
231 246 ]
232 247
233 248 spec = '1'
@@ -286,6 +301,11 @@ async function testGetVersionFromSpec() {
286 301 assert.deepStrictEqual(got, expected)
287 302 simulateInput('version-type', 'loose')
288 303
304 + spec = '24.0-rc2'
305 + expected = '24.0-rc2'
306 + got = setupBeam.getVersionFromSpec(spec, versions)
307 + assert.deepStrictEqual(got, expected)
308 +
289 309 spec = '22.3'
290 310 expected = '22.3.4.12.1'
291 311 got = setupBeam.getVersionFromSpec(spec, versions)
@@ -318,25 +338,12 @@ async function testGetVersionFromSpec() {
318 338 got = setupBeam.getVersionFromSpec(spec, versions)
319 339 assert.deepStrictEqual(got, expected)
320 340
321 spec = '11.11'
322 expected = 'v11.11.0-rc.0-otp-23'
323 got = setupBeam.getVersionFromSpec(spec, versions)
324 assert.deepStrictEqual(got, expected)
325
326 spec = '11.11.0'
327 expected = 'v11.11.0-rc.0-otp-23'
328 got = setupBeam.getVersionFromSpec(spec, versions)
329 assert.deepStrictEqual(got, expected)
330
331 spec = 'v11.11'
332 expected = 'v11.11.0-rc.0-otp-23'
333 got = setupBeam.getVersionFromSpec(spec, versions)
334 assert.deepStrictEqual(got, expected)
335
336 spec = 'v11.11.0'
337 expected = 'v11.11.0-rc.0-otp-23'
341 + simulateInput('version-type', 'strict')
342 + spec = '22.3.4.2'
343 + expected = '22.3.4.2'
338 344 got = setupBeam.getVersionFromSpec(spec, versions)
339 345 assert.deepStrictEqual(got, expected)
346 + simulateInput('version-type', 'loose')
340 347 }
341 348
342 349 function simulateInput(key, value) {
modified dist/index.js
+31 −14
@@ -4777,7 +4777,8 @@ async function getOTPVersion(otpSpec0, osVersion) {
4777 4777 }
4778 4778 if (otpVersion === null) {
4779 4779 throw new Error(
4780 `Requested Erlang/OTP version (${otpSpec0}) not found in version list`,
4780 + `Requested Erlang/OTP version (${otpSpec0}) not found in version list ` +
4781 + "(should you be using option 'version-type': 'strict'?)",
4781 4782 )
4782 4783 }
4783 4784
@@ -4788,7 +4789,7 @@ async function getElixirVersion(exSpec0, otpVersion) {
4788 4789 const elixirVersions = await getElixirVersions()
4789 4790 const semverVersions = Array.from(elixirVersions.keys()).sort()
4790 4791
4791 const exSpec = exSpec0.match(/^(.+)(-otp-.+)/) || exSpec0.match(/^(.+)/)
4792 + const exSpec = exSpec0.match(/^v?(.+)(-otp-.+)/) || exSpec0.match(/^v?(.+)/)
4792 4793 let elixirVersion
4793 4794 if (exSpec[2]) {
4794 4795 throw new Error(
@@ -4801,7 +4802,8 @@ async function getElixirVersion(exSpec0, otpVersion) {
4801 4802 }
4802 4803 if (!exSpec || elixirVersion === null) {
4803 4804 throw new Error(
4804 `Requested Elixir version (${exSpec0}) not found in version list`,
4805 + `Requested Elixir version (${exSpec0}) not found in version list ` +
4806 + "(should you be using option 'version-type': 'strict'?)",
4805 4807 )
4806 4808 }
4807 4809 const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/)
@@ -4824,11 +4826,11 @@ async function getElixirVersion(exSpec0, otpVersion) {
4824 4826 } else {
4825 4827 throw new Error(
4826 4828 `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` +
4827 'found in version list',
4829 + "found in version list (should you be using option 'version-type': 'strict'?)",
4828 4830 )
4829 4831 }
4830 4832
4831 return elixirVersionWithOTP
4833 + return `v${elixirVersionWithOTP}`
4832 4834 }
4833 4835
4834 4836 async function getRebar3Version(r3Spec) {
@@ -4836,7 +4838,8 @@ async function getRebar3Version(r3Spec) {
4836 4838 const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions)
4837 4839 if (rebar3Version === null) {
4838 4840 throw new Error(
4839 `Requested rebar3 version (${r3Spec}) not found in version list`,
4841 + `Requested rebar3 version (${r3Spec}) not found in version list ` +
4842 + "(should you be using option 'version-type': 'strict'?)",
4840 4843 )
4841 4844 }
4842 4845
@@ -4896,7 +4899,7 @@ async function getElixirVersions() {
4896 4899 .split('\n')
4897 4900 .forEach((line) => {
4898 4901 const elixirMatch =
4899 line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/)
4902 + line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/)
4900 4903 const elixirVersion = elixirMatch[1]
4901 4904 const otpVersion = elixirMatch[2]
4902 4905 const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || []
@@ -4928,18 +4931,19 @@ async function getRebar3Versions() {
4928 4931 function getVersionFromSpec(spec, versions) {
4929 4932 let version = null
4930 4933
4931 if (core.getInput('version-type', { required: false }) === 'strict') {
4934 + if (
4935 + spec.match(/rc/) ||
4936 + core.getInput('version-type', { required: false }) === 'strict'
4937 + ) {
4932 4938 version = spec
4933 4939 }
4934 4940
4935 4941 if (version === null) {
4936 4942 // We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions
4937 4943 const versionsMap = versions.sort(sortVersions).reduce((acc, v) => {
4938 try {
4939 acc[semver.coerce(v).version] = v
4940 } catch {
4941 // some stuff can't be coerced, like 'master'
4942 acc[v] = v
4944 + if (!v.match(/rc/)) {
4945 + // release candidates are opt-in
4946 + acc[maybeCoerced(v)] = v
4943 4947 }
4944 4948 return acc
4945 4949 }, {})
@@ -4948,13 +4952,26 @@ function getVersionFromSpec(spec, versions) {
4948 4952 version =
4949 4953 versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)]
4950 4954 } else {
4951 version = versionsMap[spec]
4955 + version = versionsMap[maybeCoerced(spec)]
4952 4956 }
4953 4957 }
4954 4958
4955 4959 return version
4956 4960 }
4957 4961
4962 +function maybeCoerced(v) {
4963 + let ret
4964 +
4965 + try {
4966 + ret = semver.coerce(v).version
4967 + } catch {
4968 + // some stuff can't be coerced, like 'master'
4969 + ret = v
4970 + }
4971 +
4972 + return ret
4973 +}
4974 +
4958 4975 function sortVersions(left, right) {
4959 4976 let ret = 0
4960 4977 const newL = verAsComparableStr(left)
modified src/setup-beam.js
+31 −14
@@ -121,7 +121,8 @@ async function getOTPVersion(otpSpec0, osVersion) {
121 121 }
122 122 if (otpVersion === null) {
123 123 throw new Error(
124 `Requested Erlang/OTP version (${otpSpec0}) not found in version list`,
124 + `Requested Erlang/OTP version (${otpSpec0}) not found in version list ` +
125 + "(should you be using option 'version-type': 'strict'?)",
125 126 )
126 127 }
127 128
@@ -132,7 +133,7 @@ async function getElixirVersion(exSpec0, otpVersion) {
132 133 const elixirVersions = await getElixirVersions()
133 134 const semverVersions = Array.from(elixirVersions.keys()).sort()
134 135
135 const exSpec = exSpec0.match(/^(.+)(-otp-.+)/) || exSpec0.match(/^(.+)/)
136 + const exSpec = exSpec0.match(/^v?(.+)(-otp-.+)/) || exSpec0.match(/^v?(.+)/)
136 137 let elixirVersion
137 138 if (exSpec[2]) {
138 139 throw new Error(
@@ -145,7 +146,8 @@ async function getElixirVersion(exSpec0, otpVersion) {
145 146 }
146 147 if (!exSpec || elixirVersion === null) {
147 148 throw new Error(
148 `Requested Elixir version (${exSpec0}) not found in version list`,
149 + `Requested Elixir version (${exSpec0}) not found in version list ` +
150 + "(should you be using option 'version-type': 'strict'?)",
149 151 )
150 152 }
151 153 const otpMatch = otpVersion.match(/^(?:OTP-)?([^.]+)/)
@@ -168,11 +170,11 @@ async function getElixirVersion(exSpec0, otpVersion) {
168 170 } else {
169 171 throw new Error(
170 172 `Requested Elixir / Erlang/OTP version (${exSpec0} / ${otpVersion}) not ` +
171 'found in version list',
173 + "found in version list (should you be using option 'version-type': 'strict'?)",
172 174 )
173 175 }
174 176
175 return elixirVersionWithOTP
177 + return `v${elixirVersionWithOTP}`
176 178 }
177 179
178 180 async function getRebar3Version(r3Spec) {
@@ -180,7 +182,8 @@ async function getRebar3Version(r3Spec) {
180 182 const rebar3Version = getVersionFromSpec(r3Spec, rebar3Versions)
181 183 if (rebar3Version === null) {
182 184 throw new Error(
183 `Requested rebar3 version (${r3Spec}) not found in version list`,
185 + `Requested rebar3 version (${r3Spec}) not found in version list ` +
186 + "(should you be using option 'version-type': 'strict'?)",
184 187 )
185 188 }
186 189
@@ -240,7 +243,7 @@ async function getElixirVersions() {
240 243 .split('\n')
241 244 .forEach((line) => {
242 245 const elixirMatch =
243 line.match(/^(.+)-otp-([^ ]+)/) || line.match(/^([^ ]+)/)
246 + line.match(/^v?(.+)-otp-([^ ]+)/) || line.match(/^v?([^ ]+)/)
244 247 const elixirVersion = elixirMatch[1]
245 248 const otpVersion = elixirMatch[2]
246 249 const otpVersions = otpVersionsForElixirMap.get(elixirVersion) || []
@@ -272,18 +275,19 @@ async function getRebar3Versions() {
272 275 function getVersionFromSpec(spec, versions) {
273 276 let version = null
274 277
275 if (core.getInput('version-type', { required: false }) === 'strict') {
278 + if (
279 + spec.match(/rc/) ||
280 + core.getInput('version-type', { required: false }) === 'strict'
281 + ) {
276 282 version = spec
277 283 }
278 284
279 285 if (version === null) {
280 286 // We keep a map of semver => "spec" in order to use semver ranges to find appropriate versions
281 287 const versionsMap = versions.sort(sortVersions).reduce((acc, v) => {
282 try {
283 acc[semver.coerce(v).version] = v
284 } catch {
285 // some stuff can't be coerced, like 'master'
286 acc[v] = v
288 + if (!v.match(/rc/)) {
289 + // release candidates are opt-in
290 + acc[maybeCoerced(v)] = v
287 291 }
288 292 return acc
289 293 }, {})
@@ -292,13 +296,26 @@ function getVersionFromSpec(spec, versions) {
292 296 version =
293 297 versionsMap[semver.maxSatisfying(Object.keys(versionsMap), rangeForMax)]
294 298 } else {
295 version = versionsMap[spec]
299 + version = versionsMap[maybeCoerced(spec)]
296 300 }
297 301 }
298 302
299 303 return version
300 304 }
301 305
306 +function maybeCoerced(v) {
307 + let ret
308 +
309 + try {
310 + ret = semver.coerce(v).version
311 + } catch {
312 + // some stuff can't be coerced, like 'master'
313 + ret = v
314 + }
315 +
316 + return ret
317 +}
318 +
302 319 function sortVersions(left, right) {
303 320 let ret = 0
304 321 const newL = verAsComparableStr(left)

Parents: d8a37f5