From a95db45ae27cf3480cbf376dc3a04c193892d375 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 21:58:03 +0100 Subject: [PATCH 1/7] Duplicate ubuntu.yml as macos.yml, adapting os --- .github/workflows/macos.yml | 137 ++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .github/workflows/macos.yml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 00000000..13a83d04 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,137 @@ +--- +name: macos +permissions: + contents: read + +on: + push: + branches: + - main + pull_request: + branches: + - '*' + workflow_dispatch: {} + +jobs: + integration_test: + name: > + OTP ${{matrix.combo.otp-version}}, + Elixir ${{matrix.combo.elixir-version}}, + rebar3 ${{matrix.combo.rebar3-version}} + runs-on: ${{matrix.combo.os}} + strategy: + fail-fast: false + matrix: + combo: + - otp-version: '27' + elixir-version: 'v1.17.0' + rebar3-version: '3.23' + os: 'macos-latest' + - otp-version: '26.0' + elixir-version: 'v1.14-otp-25' + os: 'macos-latest' + version-type: 'strict' + - otp-version: '26.0' + elixir-version: '1.14.5' + os: 'macos-latest' + version-type: 'strict' + - otp-version: '25.2' + elixir-version: '1.14.2' + os: 'macos-latest' + - otp-version: '25.2' + elixir-version: '1.14.3' + os: 'macos-latest' + version-type: 'strict' + - otp-version: '27' + os: 'macos-24.04' + version-type: 'strict' + - otp-version: '26' + os: 'macos-24.04' + version-type: 'strict' + - otp-version: '25' + os: 'macos-24.04' + - otp-version: '26' + elixir-version: '1.16' + rebar3-version: '3.25' + os: 'macos-22.04' + - otp-version: '25.0' + elixir-version: 'v1.13.4-otp-25' + rebar3-version: '3.18.0' + os: 'macos-latest' + version-type: 'strict' + - otp-version: '25.0' + elixir-version: 'v1.13.4' + rebar3-version: '3.18.0' + os: 'macos-latest' + version-type: 'strict' + - otp-version: 'latest' + rebar3-version: 'latest' + os: 'macos-latest' + steps: + - uses: actions/checkout@v4 + - name: Use erlef/setup-beam + id: setup-beam + uses: ./ + with: + otp-version: ${{matrix.combo.otp-version}} + elixir-version: ${{matrix.combo.elixir-version}} + rebar3-version: ${{matrix.combo.rebar3-version}} + version-type: ${{matrix.combo.version-type}} + - name: Erlang/OTP version (action) + run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}" + if: ${{matrix.combo.otp-version}} + - name: Elixir version (action) + run: echo "Elixir ${{steps.setup-beam.outputs.elixir-version}}" + if: ${{matrix.combo.elixir-version}} + - name: rebar3 version (action) + run: echo "rebar3 ${{steps.setup-beam.outputs.rebar3-version}}" + if: ${{matrix.combo.rebar3-version}} + - name: mix version and help (CLI) + run: | + mix -v + mix help local.rebar + mix help local.hex + if: ${{matrix.combo.elixir-version}} + - name: Run Elixir/Mix project tests + run: | + cd test/projects/elixir_mix + mix deps.get + mix test + if: ${{matrix.combo.elixir-version}} + - name: Run Erlang/rebar3 project tests + run: | + cd test/projects/erlang_rebar3 + rebar3 ct + if: ${{matrix.combo.rebar3-version}} + - name: Run escript + run: | + mix escript.install --force ${{matrix.combo.escript_packages}} + ${{matrix.combo.escript_script}} + if: ${{matrix.combo.escript_packages && matrix.combo.escript_script}} + + environment_variables: + name: Environment variables + runs-on: ${{matrix.combo.os}} + strategy: + fail-fast: false + matrix: + combo: + - otp-version: latest + elixir-version: latest + rebar3-version: nightly + os: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Use erlef/setup-beam + id: setup-beam + uses: ./ + with: + otp-version: ${{matrix.combo.otp-version}} + elixir-version: ${{matrix.combo.elixir-version}} + rebar3-version: ${{matrix.combo.rebar3-version}} + - run: env + - name: Check environment variables + run: | + ${INSTALL_DIR_FOR_ELIXIR}/bin/elixir -v + ${INSTALL_DIR_FOR_OTP}/bin/erl -version + ${INSTALL_DIR_FOR_REBAR3}/bin/rebar3 version From 199b2040ba70108be6aff56ed6bd784151fdf310 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 21:59:44 +0100 Subject: [PATCH 2/7] Prepare to adopt macOS from otp_builds --- README.md | 62 ++++++++++++++++++++++++++++++++--------------- src/setup-beam.js | 20 +++++---------- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 83e2908e..5bf6f52d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# setup-beam [![Action][action-img]][action] [![Ubuntu][ubuntu-img]][ubuntu] [![Windows][windows-img]][windows] +# setup-beam [![Action][action-img]][action] [![Ubuntu][ubuntu-img]][ubuntu] [![Windows][windows-img]][windows] [![macOS][macos-img]][macos] [action]: https://github.com/erlef/setup-beam/actions/workflows/action.yml [action-img]: https://github.com/erlef/setup-beam/actions/workflows/action.yml/badge.svg @@ -6,6 +6,8 @@ [ubuntu-img]: https://github.com/erlef/setup-beam/actions/workflows/ubuntu.yml/badge.svg [windows]: https://github.com/erlef/setup-beam/actions/workflows/windows.yml [windows-img]: https://github.com/erlef/setup-beam/actions/workflows/windows.yml/badge.svg +[macos]: https://github.com/erlef/setup-beam/actions/workflows/macos.yml +[macos-img]: https://github.com/erlef/setup-beam/actions/workflows/macos.yml/badge.svg This action sets up an Erlang/OTP environment for use in a GitHub Actions workflow by: @@ -21,8 +23,6 @@ workflow by: warnings and errors on pull requests - optionally, using a version file (as explained in "Version file", below), to identify versions -**Note**: currently, this action only supports Actions' `ubuntu-` and `windows-` runtimes. - ## Usage See [action.yml](action.yml) for the action's specification. @@ -71,14 +71,17 @@ be the latest. This list presents the known working version combos between the target operating system and Erlang/OTP. -| Operating system | Erlang/OTP | OTP Architecture | Status -|- |- | - |- -| `ubuntu-18.04` | 17.0 - 25.3 | x86_64, arm64 | ✅ -| `ubuntu-20.04` | 21.0 - 27 | x86_64, arm64 | ✅ -| `ubuntu-22.04` | 24.2 - 27 | x86_64, arm64 | ✅ -| `ubuntu-24.04` | 24.3 - 27 | x86_64, arm64 | ✅ -| `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ -| `windows-2022` | 21\* - 27 | x86_64, x86 | ✅ +| Operating system | Erlang/OTP | OTP Architecture | Status | Supported by GHA? +|- |- | - |- |- +| `ubuntu-18.04` | 17.0 - 26.2 | x86_64, arm64 | ✅ | ❌ +| `ubuntu-20.04` | 20.0 - 28 | x86_64, arm64 | ✅ | ❌ +| `ubuntu-22.04` | 24.2 - 28 | x86_64, arm64 | ✅ | ✅ +| `ubuntu-24.04` | 24.3 - 28 | x86_64, arm64 | ✅ | ✅ +| `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ | ✅ +| `windows-2022` | 21\* - 28 | x86_64, x86 | ✅ | ✅ +| `macOS-13` | 25.0 - 28 | x86_64, x86 | ✅ | ✅ +| `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ | ✅ +| `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅ | ✅ **Note** \*: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, 22.0, etc. @@ -88,14 +91,17 @@ and Erlang/OTP. Self-hosted runners need to set env. variable `ImageOS` to one of the following, since the action uses that to download assets: -| ImageOS | Operating system -|- |- -| `ubuntu18` | `ubuntu-18.04` -| `ubuntu20` | `ubuntu-20.04` -| `ubuntu22` | `ubuntu-22.04` -| `ubuntu24` | `ubuntu-24.04` -| `win19` | `windows-2019` -| `win22` | `windows-2022` +| ImageOS | Operating system +|- |- +| `ubuntu18` | `ubuntu-18.04` +| `ubuntu20` | `ubuntu-20.04` +| `ubuntu22` | `ubuntu-22.04` +| `ubuntu24` | `ubuntu-24.04` +| `win19` | `windows-2019` +| `win22` | `windows-2022` +| `macos13` | `macOS-13` +| `macos14` | `macOS-14` +| `macos15` | `macOS-15` as per the following example: @@ -298,6 +304,24 @@ jobs: - run: rebar3 ct ``` +### Erlang/OTP + `rebar3`, on macOS + +```yaml +# create this in .github/workflows/ci.yml +on: push + +jobs: + test: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: '28' + rebar3-version: '3.25' + - run: rebar3 ct +``` + ### Gleam on Ubuntu ```yaml diff --git a/src/setup-beam.js b/src/setup-beam.js index 1561c9a5..8b29e27d 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -13,7 +13,6 @@ main().catch((err) => { }) async function main() { - checkPlatform() checkOtpArchitecture() const versionFilePath = getInput('version-file', false) @@ -269,7 +268,7 @@ async function getOTPVersions(osVersion) { hexMirrors: hexMirrorsInput(), actionTitle: `fetch ${originListing}`, action: async (hexMirror) => { - return get(`${hexMirror}${originListing}`, []) + return get(`${hexMirror}${originListing}`) }, }) } else if (process.platform === 'win32') { @@ -323,7 +322,7 @@ async function getElixirVersions() { hexMirrors: hexMirrorsInput(), actionTitle: `fetch ${originListing}`, action: async (hexMirror) => { - return get(`${hexMirror}${originListing}`, []) + return get(`${hexMirror}${originListing}`) }, }) const otpVersionsForElixirMap = {} @@ -545,9 +544,10 @@ function getRunnerOSArchitecture() { } function getRunnerOSVersion() { + // List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images const ImageOSToContainer = { - ubuntu18: 'ubuntu-18.04', - ubuntu20: 'ubuntu-20.04', + ubuntu18: 'ubuntu-18.04', // no longer supported by GHA + ubuntu20: 'ubuntu-20.04', // no longer supported by GHA ubuntu22: 'ubuntu-22.04', ubuntu24: 'ubuntu-24.04', win19: 'windows-2019', @@ -608,7 +608,7 @@ async function get(url0, pageIdxs) { headers.authorization = `Bearer ${GithubToken}` } - if (pageIdxs.length === 0) { + if ((pageIdxs || []).length === 0) { return getUrlResponse(url, headers) } else { return Promise.all( @@ -1059,14 +1059,6 @@ async function installTool(opts) { await exec(cmd, args, { env: { ...process.env, ...env } }) } -function checkPlatform() { - if (process.platform !== 'linux' && process.platform !== 'win32') { - throw new Error( - '@erlef/setup-beam only supports Ubuntu and Windows at this time', - ) - } -} - function checkOtpArchitecture() { if (process.platform !== 'win32' && getInput('otp-architecture') == '32') { throw new Error( From 8074034cccde574292f847db8b87991a759ea911 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 22:00:17 +0100 Subject: [PATCH 3/7] Act on security guidelines proposed by our CI workflows --- .github/workflows/action.yml | 4 ++++ package-lock.json | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 5bd12eaa..dd2f460c 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -18,6 +18,8 @@ jobs: name: Version with commit unique id runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' + permissions: + contents: write steps: - uses: actions/checkout@v4 - name: Version it! @@ -39,6 +41,8 @@ jobs: name: Expected local npm actions runs-on: ubuntu-latest if: github.ref != 'refs/heads/main' + permissions: + contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/package-lock.json b/package-lock.json index 5ed14417..4b5ebffe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -601,10 +601,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1849,12 +1850,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { From 390e37ba1c527bcb5745c99410bcf98d4f9dfcdb Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 22:01:36 +0100 Subject: [PATCH 4/7] Implement picking macOS builds from erlef/otp_builds --- .github/workflows/action.yml | 13 + .github/workflows/hexpm-mirrors.yml | 6 +- dist/index.js | 1860 ++++++++++++++++++++++- package-lock.json | 7 + package.json | 1 + src/setup-beam.js | 57 + test/otp/macos/aarch64-apple-darwin.csv | 97 ++ test/otp/macos/x86_64-apple-darwin.csv | 97 ++ test/setup-beam.test.js | 52 + 9 files changed, 2173 insertions(+), 17 deletions(-) create mode 100644 test/otp/macos/aarch64-apple-darwin.csv create mode 100644 test/otp/macos/x86_64-apple-darwin.csv diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index dd2f460c..387fa2b9 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -80,3 +80,16 @@ jobs: node-version: '20' - run: npm install --production - run: npm test + + unit_tests_macos: + name: Unit tests (macOS) + runs-on: macos-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci + - run: npm test diff --git a/.github/workflows/hexpm-mirrors.yml b/.github/workflows/hexpm-mirrors.yml index 45eecd3e..211a5c7e 100644 --- a/.github/workflows/hexpm-mirrors.yml +++ b/.github/workflows/hexpm-mirrors.yml @@ -17,9 +17,9 @@ jobs: strategy: fail-fast: false matrix: - os: ['ubuntu-22.04', 'windows-2022'] - otp-version: ['24'] - elixir-version: ['v1.14', ''] + os: ['ubuntu-24.04', 'windows-2022', 'macos-15'] + otp-version: ['27'] + elixir-version: ['v1.18', ''] install-rebar: [true, false] install-hex: [true, false] steps: diff --git a/dist/index.js b/dist/index.js index fd99027c..f6c3058b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9110,6 +9110,7 @@ const path = __nccwpck_require__(1017) const semver = __nccwpck_require__(1383) const fs = __nccwpck_require__(7147) const os = __nccwpck_require__(2037) +const csv = __nccwpck_require__(4393) const MAX_HTTP_RETRIES = 3 @@ -9118,7 +9119,6 @@ main().catch((err) => { }) async function main() { - checkPlatform() checkOtpArchitecture() const versionFilePath = getInput('version-file', false) @@ -9369,13 +9369,28 @@ async function getOTPVersions(osVersion) { hexMirrors: hexMirrorsInput(), actionTitle: `fetch ${originListing}`, action: async (hexMirror) => { - return get(`${hexMirror}${originListing}`, []) + 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}`, otpVersionsListings) @@ -9410,6 +9425,18 @@ async function getOTPVersions(osVersion) { otpVersions[otpVersion] = otpVersion }) }) + } else if (process.platform === 'darwin') { + csv + .parse(otpVersionsListings, { + columns: true, + }) + .forEach((line) => { + const otpMatch = line.ref_name.match(/^([^-]+-)?(.+)$/) + const otpVersion = otpMatch[2] + const otpVersionOrig = otpMatch[0] + debugLog('OTP line and parsing', [line, otpVersion, otpMatch]) + otpVersions[otpVersion] = otpVersionOrig // we keep the original for later reference + }) } debugLog(`OTP versions from ${originListing}`, JSON.stringify(otpVersions)) @@ -9423,7 +9450,7 @@ async function getElixirVersions() { hexMirrors: hexMirrorsInput(), actionTitle: `fetch ${originListing}`, action: async (hexMirror) => { - return get(`${hexMirror}${originListing}`, []) + return get(`${hexMirror}${originListing}`) }, }) const otpVersionsForElixirMap = {} @@ -9645,13 +9672,19 @@ function getRunnerOSArchitecture() { } function getRunnerOSVersion() { + // List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images const ImageOSToContainer = { - ubuntu18: 'ubuntu-18.04', - ubuntu20: 'ubuntu-20.04', + ubuntu18: 'ubuntu-18.04', // no longer supported by GHA + ubuntu20: 'ubuntu-20.04', // no longer supported by GHA ubuntu22: 'ubuntu-22.04', ubuntu24: 'ubuntu-24.04', win19: 'windows-2019', win22: 'windows-2022', + // The default, from GHA, for macos is always macOS but if we're using + // that we can't target a specific build + macos13: 'macOS-13', + macos14: 'macOS-14', + macos15: 'macOS-15', } const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS] if (!containerFromEnvImageOS) { @@ -9708,7 +9741,7 @@ async function get(url0, pageIdxs) { headers.authorization = `Bearer ${GithubToken}` } - if (pageIdxs.length === 0) { + if ((pageIdxs || []).length === 0) { return getUrlResponse(url, headers) } else { return Promise.all( @@ -9923,6 +9956,28 @@ async function install(toolName, opts) { const args = ['+V'] const env = {} + return [cmd, args, env] + }, + }, + darwin: { + downloadToolURL: () => + `https://github.com/erlef/otp_builds/releases/download/` + + `${toolVersion}/${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] }, }, @@ -10032,6 +10087,7 @@ async function install(toolName, opts) { }, }, } + installOpts.darwin = installOpts.linux break case 'rebar3': installOpts = { @@ -10105,6 +10161,7 @@ async function install(toolName, opts) { }, }, } + installOpts.darwin = installOpts.linux break default: throw new Error(`no installer for ${toolName}`) @@ -10159,14 +10216,6 @@ async function installTool(opts) { await exec(cmd, args, { env: { ...process.env, ...env } }) } -function checkPlatform() { - if (process.platform !== 'linux' && process.platform !== 'win32') { - throw new Error( - '@erlef/setup-beam only supports Ubuntu and Windows at this time', - ) - } -} - function checkOtpArchitecture() { if (process.platform !== 'win32' && getInput('otp-architecture') == '32') { throw new Error( @@ -10320,6 +10369,1789 @@ module.exports = require("tls"); "use strict"; module.exports = require("util"); +/***/ }), + +/***/ 4393: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +class CsvError extends Error { + constructor(code, message, options, ...contexts) { + if (Array.isArray(message)) message = message.join(" ").trim(); + super(message); + if (Error.captureStackTrace !== undefined) { + Error.captureStackTrace(this, CsvError); + } + this.code = code; + for (const context of contexts) { + for (const key in context) { + const value = context[key]; + this[key] = Buffer.isBuffer(value) + ? value.toString(options.encoding) + : value == null + ? value + : JSON.parse(JSON.stringify(value)); + } + } + } +} + +const is_object = function (obj) { + return typeof obj === "object" && obj !== null && !Array.isArray(obj); +}; + +const normalize_columns_array = function (columns) { + const normalizedColumns = []; + for (let i = 0, l = columns.length; i < l; i++) { + const column = columns[i]; + if (column === undefined || column === null || column === false) { + normalizedColumns[i] = { disabled: true }; + } else if (typeof column === "string") { + normalizedColumns[i] = { name: column }; + } else if (is_object(column)) { + if (typeof column.name !== "string") { + throw new CsvError("CSV_OPTION_COLUMNS_MISSING_NAME", [ + "Option columns missing name:", + `property "name" is required at position ${i}`, + "when column is an object literal", + ]); + } + normalizedColumns[i] = column; + } else { + throw new CsvError("CSV_INVALID_COLUMN_DEFINITION", [ + "Invalid column definition:", + "expect a string or a literal object,", + `got ${JSON.stringify(column)} at position ${i}`, + ]); + } + } + return normalizedColumns; +}; + +class ResizeableBuffer { + constructor(size = 100) { + this.size = size; + this.length = 0; + this.buf = Buffer.allocUnsafe(size); + } + prepend(val) { + if (Buffer.isBuffer(val)) { + const length = this.length + val.length; + if (length >= this.size) { + this.resize(); + if (length >= this.size) { + throw Error("INVALID_BUFFER_STATE"); + } + } + const buf = this.buf; + this.buf = Buffer.allocUnsafe(this.size); + val.copy(this.buf, 0); + buf.copy(this.buf, val.length); + this.length += val.length; + } else { + const length = this.length++; + if (length === this.size) { + this.resize(); + } + const buf = this.clone(); + this.buf[0] = val; + buf.copy(this.buf, 1, 0, length); + } + } + append(val) { + const length = this.length++; + if (length === this.size) { + this.resize(); + } + this.buf[length] = val; + } + clone() { + return Buffer.from(this.buf.slice(0, this.length)); + } + resize() { + const length = this.length; + this.size = this.size * 2; + const buf = Buffer.allocUnsafe(this.size); + this.buf.copy(buf, 0, 0, length); + this.buf = buf; + } + toString(encoding) { + if (encoding) { + return this.buf.slice(0, this.length).toString(encoding); + } else { + return Uint8Array.prototype.slice.call(this.buf.slice(0, this.length)); + } + } + toJSON() { + return this.toString("utf8"); + } + reset() { + this.length = 0; + } +} + +// white space characters +// https://en.wikipedia.org/wiki/Whitespace_character +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#Types +// \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff +const np = 12; +const cr$1 = 13; // `\r`, carriage return, 0x0D in hexadécimal, 13 in decimal +const nl$1 = 10; // `\n`, newline, 0x0A in hexadecimal, 10 in decimal +const space = 32; +const tab = 9; + +const init_state = function (options) { + return { + bomSkipped: false, + bufBytesStart: 0, + castField: options.cast_function, + commenting: false, + // Current error encountered by a record + error: undefined, + enabled: options.from_line === 1, + escaping: false, + escapeIsQuote: + Buffer.isBuffer(options.escape) && + Buffer.isBuffer(options.quote) && + Buffer.compare(options.escape, options.quote) === 0, + // columns can be `false`, `true`, `Array` + expectedRecordLength: Array.isArray(options.columns) + ? options.columns.length + : undefined, + field: new ResizeableBuffer(20), + firstLineToHeaders: options.cast_first_line_to_header, + needMoreDataSize: Math.max( + // Skip if the remaining buffer smaller than comment + options.comment !== null ? options.comment.length : 0, + // Skip if the remaining buffer can be delimiter + ...options.delimiter.map((delimiter) => delimiter.length), + // Skip if the remaining buffer can be escape sequence + options.quote !== null ? options.quote.length : 0, + ), + previousBuf: undefined, + quoting: false, + stop: false, + rawBuffer: new ResizeableBuffer(100), + record: [], + recordHasError: false, + record_length: 0, + recordDelimiterMaxLength: + options.record_delimiter.length === 0 + ? 0 + : Math.max(...options.record_delimiter.map((v) => v.length)), + trimChars: [ + Buffer.from(" ", options.encoding)[0], + Buffer.from("\t", options.encoding)[0], + ], + wasQuoting: false, + wasRowDelimiter: false, + timchars: [ + Buffer.from(Buffer.from([cr$1], "utf8").toString(), options.encoding), + Buffer.from(Buffer.from([nl$1], "utf8").toString(), options.encoding), + Buffer.from(Buffer.from([np], "utf8").toString(), options.encoding), + Buffer.from(Buffer.from([space], "utf8").toString(), options.encoding), + Buffer.from(Buffer.from([tab], "utf8").toString(), options.encoding), + ], + }; +}; + +const underscore = function (str) { + return str.replace(/([A-Z])/g, function (_, match) { + return "_" + match.toLowerCase(); + }); +}; + +const normalize_options = function (opts) { + const options = {}; + // Merge with user options + for (const opt in opts) { + options[underscore(opt)] = opts[opt]; + } + // Normalize option `encoding` + // Note: defined first because other options depends on it + // to convert chars/strings into buffers. + if (options.encoding === undefined || options.encoding === true) { + options.encoding = "utf8"; + } else if (options.encoding === null || options.encoding === false) { + options.encoding = null; + } else if ( + typeof options.encoding !== "string" && + options.encoding !== null + ) { + throw new CsvError( + "CSV_INVALID_OPTION_ENCODING", + [ + "Invalid option encoding:", + "encoding must be a string or null to return a buffer,", + `got ${JSON.stringify(options.encoding)}`, + ], + options, + ); + } + // Normalize option `bom` + if ( + options.bom === undefined || + options.bom === null || + options.bom === false + ) { + options.bom = false; + } else if (options.bom !== true) { + throw new CsvError( + "CSV_INVALID_OPTION_BOM", + [ + "Invalid option bom:", + "bom must be true,", + `got ${JSON.stringify(options.bom)}`, + ], + options, + ); + } + // Normalize option `cast` + options.cast_function = null; + if ( + options.cast === undefined || + options.cast === null || + options.cast === false || + options.cast === "" + ) { + options.cast = undefined; + } else if (typeof options.cast === "function") { + options.cast_function = options.cast; + options.cast = true; + } else if (options.cast !== true) { + throw new CsvError( + "CSV_INVALID_OPTION_CAST", + [ + "Invalid option cast:", + "cast must be true or a function,", + `got ${JSON.stringify(options.cast)}`, + ], + options, + ); + } + // Normalize option `cast_date` + if ( + options.cast_date === undefined || + options.cast_date === null || + options.cast_date === false || + options.cast_date === "" + ) { + options.cast_date = false; + } else if (options.cast_date === true) { + options.cast_date = function (value) { + const date = Date.parse(value); + return !isNaN(date) ? new Date(date) : value; + }; + } else if (typeof options.cast_date !== "function") { + throw new CsvError( + "CSV_INVALID_OPTION_CAST_DATE", + [ + "Invalid option cast_date:", + "cast_date must be true or a function,", + `got ${JSON.stringify(options.cast_date)}`, + ], + options, + ); + } + // Normalize option `columns` + options.cast_first_line_to_header = null; + if (options.columns === true) { + // Fields in the first line are converted as-is to columns + options.cast_first_line_to_header = undefined; + } else if (typeof options.columns === "function") { + options.cast_first_line_to_header = options.columns; + options.columns = true; + } else if (Array.isArray(options.columns)) { + options.columns = normalize_columns_array(options.columns); + } else if ( + options.columns === undefined || + options.columns === null || + options.columns === false + ) { + options.columns = false; + } else { + throw new CsvError( + "CSV_INVALID_OPTION_COLUMNS", + [ + "Invalid option columns:", + "expect an array, a function or true,", + `got ${JSON.stringify(options.columns)}`, + ], + options, + ); + } + // Normalize option `group_columns_by_name` + if ( + options.group_columns_by_name === undefined || + options.group_columns_by_name === null || + options.group_columns_by_name === false + ) { + options.group_columns_by_name = false; + } else if (options.group_columns_by_name !== true) { + throw new CsvError( + "CSV_INVALID_OPTION_GROUP_COLUMNS_BY_NAME", + [ + "Invalid option group_columns_by_name:", + "expect an boolean,", + `got ${JSON.stringify(options.group_columns_by_name)}`, + ], + options, + ); + } else if (options.columns === false) { + throw new CsvError( + "CSV_INVALID_OPTION_GROUP_COLUMNS_BY_NAME", + [ + "Invalid option group_columns_by_name:", + "the `columns` mode must be activated.", + ], + options, + ); + } + // Normalize option `comment` + if ( + options.comment === undefined || + options.comment === null || + options.comment === false || + options.comment === "" + ) { + options.comment = null; + } else { + if (typeof options.comment === "string") { + options.comment = Buffer.from(options.comment, options.encoding); + } + if (!Buffer.isBuffer(options.comment)) { + throw new CsvError( + "CSV_INVALID_OPTION_COMMENT", + [ + "Invalid option comment:", + "comment must be a buffer or a string,", + `got ${JSON.stringify(options.comment)}`, + ], + options, + ); + } + } + // Normalize option `comment_no_infix` + if ( + options.comment_no_infix === undefined || + options.comment_no_infix === null || + options.comment_no_infix === false + ) { + options.comment_no_infix = false; + } else if (options.comment_no_infix !== true) { + throw new CsvError( + "CSV_INVALID_OPTION_COMMENT", + [ + "Invalid option comment_no_infix:", + "value must be a boolean,", + `got ${JSON.stringify(options.comment_no_infix)}`, + ], + options, + ); + } + // Normalize option `delimiter` + const delimiter_json = JSON.stringify(options.delimiter); + if (!Array.isArray(options.delimiter)) + options.delimiter = [options.delimiter]; + if (options.delimiter.length === 0) { + throw new CsvError( + "CSV_INVALID_OPTION_DELIMITER", + [ + "Invalid option delimiter:", + "delimiter must be a non empty string or buffer or array of string|buffer,", + `got ${delimiter_json}`, + ], + options, + ); + } + options.delimiter = options.delimiter.map(function (delimiter) { + if (delimiter === undefined || delimiter === null || delimiter === false) { + return Buffer.from(",", options.encoding); + } + if (typeof delimiter === "string") { + delimiter = Buffer.from(delimiter, options.encoding); + } + if (!Buffer.isBuffer(delimiter) || delimiter.length === 0) { + throw new CsvError( + "CSV_INVALID_OPTION_DELIMITER", + [ + "Invalid option delimiter:", + "delimiter must be a non empty string or buffer or array of string|buffer,", + `got ${delimiter_json}`, + ], + options, + ); + } + return delimiter; + }); + // Normalize option `escape` + if (options.escape === undefined || options.escape === true) { + options.escape = Buffer.from('"', options.encoding); + } else if (typeof options.escape === "string") { + options.escape = Buffer.from(options.escape, options.encoding); + } else if (options.escape === null || options.escape === false) { + options.escape = null; + } + if (options.escape !== null) { + if (!Buffer.isBuffer(options.escape)) { + throw new Error( + `Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`, + ); + } + } + // Normalize option `from` + if (options.from === undefined || options.from === null) { + options.from = 1; + } else { + if (typeof options.from === "string" && /\d+/.test(options.from)) { + options.from = parseInt(options.from); + } + if (Number.isInteger(options.from)) { + if (options.from < 0) { + throw new Error( + `Invalid Option: from must be a positive integer, got ${JSON.stringify(opts.from)}`, + ); + } + } else { + throw new Error( + `Invalid Option: from must be an integer, got ${JSON.stringify(options.from)}`, + ); + } + } + // Normalize option `from_line` + if (options.from_line === undefined || options.from_line === null) { + options.from_line = 1; + } else { + if ( + typeof options.from_line === "string" && + /\d+/.test(options.from_line) + ) { + options.from_line = parseInt(options.from_line); + } + if (Number.isInteger(options.from_line)) { + if (options.from_line <= 0) { + throw new Error( + `Invalid Option: from_line must be a positive integer greater than 0, got ${JSON.stringify(opts.from_line)}`, + ); + } + } else { + throw new Error( + `Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`, + ); + } + } + // Normalize options `ignore_last_delimiters` + if ( + options.ignore_last_delimiters === undefined || + options.ignore_last_delimiters === null + ) { + options.ignore_last_delimiters = false; + } else if (typeof options.ignore_last_delimiters === "number") { + options.ignore_last_delimiters = Math.floor(options.ignore_last_delimiters); + if (options.ignore_last_delimiters === 0) { + options.ignore_last_delimiters = false; + } + } else if (typeof options.ignore_last_delimiters !== "boolean") { + throw new CsvError( + "CSV_INVALID_OPTION_IGNORE_LAST_DELIMITERS", + [ + "Invalid option `ignore_last_delimiters`:", + "the value must be a boolean value or an integer,", + `got ${JSON.stringify(options.ignore_last_delimiters)}`, + ], + options, + ); + } + if (options.ignore_last_delimiters === true && options.columns === false) { + throw new CsvError( + "CSV_IGNORE_LAST_DELIMITERS_REQUIRES_COLUMNS", + [ + "The option `ignore_last_delimiters`", + "requires the activation of the `columns` option", + ], + options, + ); + } + // Normalize option `info` + if ( + options.info === undefined || + options.info === null || + options.info === false + ) { + options.info = false; + } else if (options.info !== true) { + throw new Error( + `Invalid Option: info must be true, got ${JSON.stringify(options.info)}`, + ); + } + // Normalize option `max_record_size` + if ( + options.max_record_size === undefined || + options.max_record_size === null || + options.max_record_size === false + ) { + options.max_record_size = 0; + } else if ( + Number.isInteger(options.max_record_size) && + options.max_record_size >= 0 + ) ; else if ( + typeof options.max_record_size === "string" && + /\d+/.test(options.max_record_size) + ) { + options.max_record_size = parseInt(options.max_record_size); + } else { + throw new Error( + `Invalid Option: max_record_size must be a positive integer, got ${JSON.stringify(options.max_record_size)}`, + ); + } + // Normalize option `objname` + if ( + options.objname === undefined || + options.objname === null || + options.objname === false + ) { + options.objname = undefined; + } else if (Buffer.isBuffer(options.objname)) { + if (options.objname.length === 0) { + throw new Error(`Invalid Option: objname must be a non empty buffer`); + } + if (options.encoding === null) ; else { + options.objname = options.objname.toString(options.encoding); + } + } else if (typeof options.objname === "string") { + if (options.objname.length === 0) { + throw new Error(`Invalid Option: objname must be a non empty string`); + } + // Great, nothing to do + } else if (typeof options.objname === "number") ; else { + throw new Error( + `Invalid Option: objname must be a string or a buffer, got ${options.objname}`, + ); + } + if (options.objname !== undefined) { + if (typeof options.objname === "number") { + if (options.columns !== false) { + throw Error( + "Invalid Option: objname index cannot be combined with columns or be defined as a field", + ); + } + } else { + // A string or a buffer + if (options.columns === false) { + throw Error( + "Invalid Option: objname field must be combined with columns or be defined as an index", + ); + } + } + } + // Normalize option `on_record` + if (options.on_record === undefined || options.on_record === null) { + options.on_record = undefined; + } else if (typeof options.on_record !== "function") { + throw new CsvError( + "CSV_INVALID_OPTION_ON_RECORD", + [ + "Invalid option `on_record`:", + "expect a function,", + `got ${JSON.stringify(options.on_record)}`, + ], + options, + ); + } + // Normalize option `on_skip` + // options.on_skip ??= (err, chunk) => { + // this.emit('skip', err, chunk); + // }; + if ( + options.on_skip !== undefined && + options.on_skip !== null && + typeof options.on_skip !== "function" + ) { + throw new Error( + `Invalid Option: on_skip must be a function, got ${JSON.stringify(options.on_skip)}`, + ); + } + // Normalize option `quote` + if ( + options.quote === null || + options.quote === false || + options.quote === "" + ) { + options.quote = null; + } else { + if (options.quote === undefined || options.quote === true) { + options.quote = Buffer.from('"', options.encoding); + } else if (typeof options.quote === "string") { + options.quote = Buffer.from(options.quote, options.encoding); + } + if (!Buffer.isBuffer(options.quote)) { + throw new Error( + `Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`, + ); + } + } + // Normalize option `raw` + if ( + options.raw === undefined || + options.raw === null || + options.raw === false + ) { + options.raw = false; + } else if (options.raw !== true) { + throw new Error( + `Invalid Option: raw must be true, got ${JSON.stringify(options.raw)}`, + ); + } + // Normalize option `record_delimiter` + if (options.record_delimiter === undefined) { + options.record_delimiter = []; + } else if ( + typeof options.record_delimiter === "string" || + Buffer.isBuffer(options.record_delimiter) + ) { + if (options.record_delimiter.length === 0) { + throw new CsvError( + "CSV_INVALID_OPTION_RECORD_DELIMITER", + [ + "Invalid option `record_delimiter`:", + "value must be a non empty string or buffer,", + `got ${JSON.stringify(options.record_delimiter)}`, + ], + options, + ); + } + options.record_delimiter = [options.record_delimiter]; + } else if (!Array.isArray(options.record_delimiter)) { + throw new CsvError( + "CSV_INVALID_OPTION_RECORD_DELIMITER", + [ + "Invalid option `record_delimiter`:", + "value must be a string, a buffer or array of string|buffer,", + `got ${JSON.stringify(options.record_delimiter)}`, + ], + options, + ); + } + options.record_delimiter = options.record_delimiter.map(function (rd, i) { + if (typeof rd !== "string" && !Buffer.isBuffer(rd)) { + throw new CsvError( + "CSV_INVALID_OPTION_RECORD_DELIMITER", + [ + "Invalid option `record_delimiter`:", + "value must be a string, a buffer or array of string|buffer", + `at index ${i},`, + `got ${JSON.stringify(rd)}`, + ], + options, + ); + } else if (rd.length === 0) { + throw new CsvError( + "CSV_INVALID_OPTION_RECORD_DELIMITER", + [ + "Invalid option `record_delimiter`:", + "value must be a non empty string or buffer", + `at index ${i},`, + `got ${JSON.stringify(rd)}`, + ], + options, + ); + } + if (typeof rd === "string") { + rd = Buffer.from(rd, options.encoding); + } + return rd; + }); + // Normalize option `relax_column_count` + if (typeof options.relax_column_count === "boolean") ; else if ( + options.relax_column_count === undefined || + options.relax_column_count === null + ) { + options.relax_column_count = false; + } else { + throw new Error( + `Invalid Option: relax_column_count must be a boolean, got ${JSON.stringify(options.relax_column_count)}`, + ); + } + if (typeof options.relax_column_count_less === "boolean") ; else if ( + options.relax_column_count_less === undefined || + options.relax_column_count_less === null + ) { + options.relax_column_count_less = false; + } else { + throw new Error( + `Invalid Option: relax_column_count_less must be a boolean, got ${JSON.stringify(options.relax_column_count_less)}`, + ); + } + if (typeof options.relax_column_count_more === "boolean") ; else if ( + options.relax_column_count_more === undefined || + options.relax_column_count_more === null + ) { + options.relax_column_count_more = false; + } else { + throw new Error( + `Invalid Option: relax_column_count_more must be a boolean, got ${JSON.stringify(options.relax_column_count_more)}`, + ); + } + // Normalize option `relax_quotes` + if (typeof options.relax_quotes === "boolean") ; else if ( + options.relax_quotes === undefined || + options.relax_quotes === null + ) { + options.relax_quotes = false; + } else { + throw new Error( + `Invalid Option: relax_quotes must be a boolean, got ${JSON.stringify(options.relax_quotes)}`, + ); + } + // Normalize option `skip_empty_lines` + if (typeof options.skip_empty_lines === "boolean") ; else if ( + options.skip_empty_lines === undefined || + options.skip_empty_lines === null + ) { + options.skip_empty_lines = false; + } else { + throw new Error( + `Invalid Option: skip_empty_lines must be a boolean, got ${JSON.stringify(options.skip_empty_lines)}`, + ); + } + // Normalize option `skip_records_with_empty_values` + if (typeof options.skip_records_with_empty_values === "boolean") ; else if ( + options.skip_records_with_empty_values === undefined || + options.skip_records_with_empty_values === null + ) { + options.skip_records_with_empty_values = false; + } else { + throw new Error( + `Invalid Option: skip_records_with_empty_values must be a boolean, got ${JSON.stringify(options.skip_records_with_empty_values)}`, + ); + } + // Normalize option `skip_records_with_error` + if (typeof options.skip_records_with_error === "boolean") ; else if ( + options.skip_records_with_error === undefined || + options.skip_records_with_error === null + ) { + options.skip_records_with_error = false; + } else { + throw new Error( + `Invalid Option: skip_records_with_error must be a boolean, got ${JSON.stringify(options.skip_records_with_error)}`, + ); + } + // Normalize option `rtrim` + if ( + options.rtrim === undefined || + options.rtrim === null || + options.rtrim === false + ) { + options.rtrim = false; + } else if (options.rtrim !== true) { + throw new Error( + `Invalid Option: rtrim must be a boolean, got ${JSON.stringify(options.rtrim)}`, + ); + } + // Normalize option `ltrim` + if ( + options.ltrim === undefined || + options.ltrim === null || + options.ltrim === false + ) { + options.ltrim = false; + } else if (options.ltrim !== true) { + throw new Error( + `Invalid Option: ltrim must be a boolean, got ${JSON.stringify(options.ltrim)}`, + ); + } + // Normalize option `trim` + if ( + options.trim === undefined || + options.trim === null || + options.trim === false + ) { + options.trim = false; + } else if (options.trim !== true) { + throw new Error( + `Invalid Option: trim must be a boolean, got ${JSON.stringify(options.trim)}`, + ); + } + // Normalize options `trim`, `ltrim` and `rtrim` + if (options.trim === true && opts.ltrim !== false) { + options.ltrim = true; + } else if (options.ltrim !== true) { + options.ltrim = false; + } + if (options.trim === true && opts.rtrim !== false) { + options.rtrim = true; + } else if (options.rtrim !== true) { + options.rtrim = false; + } + // Normalize option `to` + if (options.to === undefined || options.to === null) { + options.to = -1; + } else { + if (typeof options.to === "string" && /\d+/.test(options.to)) { + options.to = parseInt(options.to); + } + if (Number.isInteger(options.to)) { + if (options.to <= 0) { + throw new Error( + `Invalid Option: to must be a positive integer greater than 0, got ${JSON.stringify(opts.to)}`, + ); + } + } else { + throw new Error( + `Invalid Option: to must be an integer, got ${JSON.stringify(opts.to)}`, + ); + } + } + // Normalize option `to_line` + if (options.to_line === undefined || options.to_line === null) { + options.to_line = -1; + } else { + if (typeof options.to_line === "string" && /\d+/.test(options.to_line)) { + options.to_line = parseInt(options.to_line); + } + if (Number.isInteger(options.to_line)) { + if (options.to_line <= 0) { + throw new Error( + `Invalid Option: to_line must be a positive integer greater than 0, got ${JSON.stringify(opts.to_line)}`, + ); + } + } else { + throw new Error( + `Invalid Option: to_line must be an integer, got ${JSON.stringify(opts.to_line)}`, + ); + } + } + return options; +}; + +const isRecordEmpty = function (record) { + return record.every( + (field) => + field == null || (field.toString && field.toString().trim() === ""), + ); +}; + +const cr = 13; // `\r`, carriage return, 0x0D in hexadécimal, 13 in decimal +const nl = 10; // `\n`, newline, 0x0A in hexadecimal, 10 in decimal + +const boms = { + // Note, the following are equals: + // Buffer.from("\ufeff") + // Buffer.from([239, 187, 191]) + // Buffer.from('EFBBBF', 'hex') + utf8: Buffer.from([239, 187, 191]), + // Note, the following are equals: + // Buffer.from "\ufeff", 'utf16le + // Buffer.from([255, 254]) + utf16le: Buffer.from([255, 254]), +}; + +const transform = function (original_options = {}) { + const info = { + bytes: 0, + comment_lines: 0, + empty_lines: 0, + invalid_field_length: 0, + lines: 1, + records: 0, + }; + const options = normalize_options(original_options); + return { + info: info, + original_options: original_options, + options: options, + state: init_state(options), + __needMoreData: function (i, bufLen, end) { + if (end) return false; + const { encoding, escape, quote } = this.options; + const { quoting, needMoreDataSize, recordDelimiterMaxLength } = + this.state; + const numOfCharLeft = bufLen - i - 1; + const requiredLength = Math.max( + needMoreDataSize, + // Skip if the remaining buffer smaller than record delimiter + // If "record_delimiter" is yet to be discovered: + // 1. It is equals to `[]` and "recordDelimiterMaxLength" equals `0` + // 2. We set the length to windows line ending in the current encoding + // Note, that encoding is known from user or bom discovery at that point + // recordDelimiterMaxLength, + recordDelimiterMaxLength === 0 + ? Buffer.from("\r\n", encoding).length + : recordDelimiterMaxLength, + // Skip if remaining buffer can be an escaped quote + quoting ? (escape === null ? 0 : escape.length) + quote.length : 0, + // Skip if remaining buffer can be record delimiter following the closing quote + quoting ? quote.length + recordDelimiterMaxLength : 0, + ); + return numOfCharLeft < requiredLength; + }, + // Central parser implementation + parse: function (nextBuf, end, push, close) { + const { + bom, + comment_no_infix, + encoding, + from_line, + ltrim, + max_record_size, + raw, + relax_quotes, + rtrim, + skip_empty_lines, + to, + to_line, + } = this.options; + let { comment, escape, quote, record_delimiter } = this.options; + const { bomSkipped, previousBuf, rawBuffer, escapeIsQuote } = this.state; + let buf; + if (previousBuf === undefined) { + if (nextBuf === undefined) { + // Handle empty string + close(); + return; + } else { + buf = nextBuf; + } + } else if (previousBuf !== undefined && nextBuf === undefined) { + buf = previousBuf; + } else { + buf = Buffer.concat([previousBuf, nextBuf]); + } + // Handle UTF BOM + if (bomSkipped === false) { + if (bom === false) { + this.state.bomSkipped = true; + } else if (buf.length < 3) { + // No enough data + if (end === false) { + // Wait for more data + this.state.previousBuf = buf; + return; + } + } else { + for (const encoding in boms) { + if (boms[encoding].compare(buf, 0, boms[encoding].length) === 0) { + // Skip BOM + const bomLength = boms[encoding].length; + this.state.bufBytesStart += bomLength; + buf = buf.slice(bomLength); + // Renormalize original options with the new encoding + this.options = normalize_options({ + ...this.original_options, + encoding: encoding, + }); + // Options will re-evaluate the Buffer with the new encoding + ({ comment, escape, quote } = this.options); + break; + } + } + this.state.bomSkipped = true; + } + } + const bufLen = buf.length; + let pos; + for (pos = 0; pos < bufLen; pos++) { + // Ensure we get enough space to look ahead + // There should be a way to move this out of the loop + if (this.__needMoreData(pos, bufLen, end)) { + break; + } + if (this.state.wasRowDelimiter === true) { + this.info.lines++; + this.state.wasRowDelimiter = false; + } + if (to_line !== -1 && this.info.lines > to_line) { + this.state.stop = true; + close(); + return; + } + // Auto discovery of record_delimiter, unix, mac and windows supported + if (this.state.quoting === false && record_delimiter.length === 0) { + const record_delimiterCount = this.__autoDiscoverRecordDelimiter( + buf, + pos, + ); + if (record_delimiterCount) { + record_delimiter = this.options.record_delimiter; + } + } + const chr = buf[pos]; + if (raw === true) { + rawBuffer.append(chr); + } + if ( + (chr === cr || chr === nl) && + this.state.wasRowDelimiter === false + ) { + this.state.wasRowDelimiter = true; + } + // Previous char was a valid escape char + // treat the current char as a regular char + if (this.state.escaping === true) { + this.state.escaping = false; + } else { + // Escape is only active inside quoted fields + // We are quoting, the char is an escape chr and there is a chr to escape + // if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){ + if ( + escape !== null && + this.state.quoting === true && + this.__isEscape(buf, pos, chr) && + pos + escape.length < bufLen + ) { + if (escapeIsQuote) { + if (this.__isQuote(buf, pos + escape.length)) { + this.state.escaping = true; + pos += escape.length - 1; + continue; + } + } else { + this.state.escaping = true; + pos += escape.length - 1; + continue; + } + } + // Not currently escaping and chr is a quote + // TODO: need to compare bytes instead of single char + if (this.state.commenting === false && this.__isQuote(buf, pos)) { + if (this.state.quoting === true) { + const nextChr = buf[pos + quote.length]; + const isNextChrTrimable = + rtrim && this.__isCharTrimable(buf, pos + quote.length); + const isNextChrComment = + comment !== null && + this.__compareBytes(comment, buf, pos + quote.length, nextChr); + const isNextChrDelimiter = this.__isDelimiter( + buf, + pos + quote.length, + nextChr, + ); + const isNextChrRecordDelimiter = + record_delimiter.length === 0 + ? this.__autoDiscoverRecordDelimiter(buf, pos + quote.length) + : this.__isRecordDelimiter(nextChr, buf, pos + quote.length); + // Escape a quote + // Treat next char as a regular character + if ( + escape !== null && + this.__isEscape(buf, pos, chr) && + this.__isQuote(buf, pos + escape.length) + ) { + pos += escape.length - 1; + } else if ( + !nextChr || + isNextChrDelimiter || + isNextChrRecordDelimiter || + isNextChrComment || + isNextChrTrimable + ) { + this.state.quoting = false; + this.state.wasQuoting = true; + pos += quote.length - 1; + continue; + } else if (relax_quotes === false) { + const err = this.__error( + new CsvError( + "CSV_INVALID_CLOSING_QUOTE", + [ + "Invalid Closing Quote:", + `got "${String.fromCharCode(nextChr)}"`, + `at line ${this.info.lines}`, + "instead of delimiter, record delimiter, trimable character", + "(if activated) or comment", + ], + this.options, + this.__infoField(), + ), + ); + if (err !== undefined) return err; + } else { + this.state.quoting = false; + this.state.wasQuoting = true; + this.state.field.prepend(quote); + pos += quote.length - 1; + } + } else { + if (this.state.field.length !== 0) { + // In relax_quotes mode, treat opening quote preceded by chrs as regular + if (relax_quotes === false) { + const info = this.__infoField(); + const bom = Object.keys(boms) + .map((b) => + boms[b].equals(this.state.field.toString()) ? b : false, + ) + .filter(Boolean)[0]; + const err = this.__error( + new CsvError( + "INVALID_OPENING_QUOTE", + [ + "Invalid Opening Quote:", + `a quote is found on field ${JSON.stringify(info.column)} at line ${info.lines}, value is ${JSON.stringify(this.state.field.toString(encoding))}`, + bom ? `(${bom} bom)` : undefined, + ], + this.options, + info, + { + field: this.state.field, + }, + ), + ); + if (err !== undefined) return err; + } + } else { + this.state.quoting = true; + pos += quote.length - 1; + continue; + } + } + } + if (this.state.quoting === false) { + const recordDelimiterLength = this.__isRecordDelimiter( + chr, + buf, + pos, + ); + if (recordDelimiterLength !== 0) { + // Do not emit comments which take a full line + const skipCommentLine = + this.state.commenting && + this.state.wasQuoting === false && + this.state.record.length === 0 && + this.state.field.length === 0; + if (skipCommentLine) { + this.info.comment_lines++; + // Skip full comment line + } else { + // Activate records emition if above from_line + if ( + this.state.enabled === false && + this.info.lines + + (this.state.wasRowDelimiter === true ? 1 : 0) >= + from_line + ) { + this.state.enabled = true; + this.__resetField(); + this.__resetRecord(); + pos += recordDelimiterLength - 1; + continue; + } + // Skip if line is empty and skip_empty_lines activated + if ( + skip_empty_lines === true && + this.state.wasQuoting === false && + this.state.record.length === 0 && + this.state.field.length === 0 + ) { + this.info.empty_lines++; + pos += recordDelimiterLength - 1; + continue; + } + this.info.bytes = this.state.bufBytesStart + pos; + const errField = this.__onField(); + if (errField !== undefined) return errField; + this.info.bytes = + this.state.bufBytesStart + pos + recordDelimiterLength; + const errRecord = this.__onRecord(push); + if (errRecord !== undefined) return errRecord; + if (to !== -1 && this.info.records >= to) { + this.state.stop = true; + close(); + return; + } + } + this.state.commenting = false; + pos += recordDelimiterLength - 1; + continue; + } + if (this.state.commenting) { + continue; + } + if ( + comment !== null && + (comment_no_infix === false || + (this.state.record.length === 0 && + this.state.field.length === 0)) + ) { + const commentCount = this.__compareBytes(comment, buf, pos, chr); + if (commentCount !== 0) { + this.state.commenting = true; + continue; + } + } + const delimiterLength = this.__isDelimiter(buf, pos, chr); + if (delimiterLength !== 0) { + this.info.bytes = this.state.bufBytesStart + pos; + const errField = this.__onField(); + if (errField !== undefined) return errField; + pos += delimiterLength - 1; + continue; + } + } + } + if (this.state.commenting === false) { + if ( + max_record_size !== 0 && + this.state.record_length + this.state.field.length > max_record_size + ) { + return this.__error( + new CsvError( + "CSV_MAX_RECORD_SIZE", + [ + "Max Record Size:", + "record exceed the maximum number of tolerated bytes", + `of ${max_record_size}`, + `at line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + ), + ); + } + } + const lappend = + ltrim === false || + this.state.quoting === true || + this.state.field.length !== 0 || + !this.__isCharTrimable(buf, pos); + // rtrim in non quoting is handle in __onField + const rappend = rtrim === false || this.state.wasQuoting === false; + if (lappend === true && rappend === true) { + this.state.field.append(chr); + } else if (rtrim === true && !this.__isCharTrimable(buf, pos)) { + return this.__error( + new CsvError( + "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE", + [ + "Invalid Closing Quote:", + "found non trimable byte after quote", + `at line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + ), + ); + } else { + if (lappend === false) { + pos += this.__isCharTrimable(buf, pos) - 1; + } + continue; + } + } + if (end === true) { + // Ensure we are not ending in a quoting state + if (this.state.quoting === true) { + const err = this.__error( + new CsvError( + "CSV_QUOTE_NOT_CLOSED", + [ + "Quote Not Closed:", + `the parsing is finished with an opening quote at line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + ), + ); + if (err !== undefined) return err; + } else { + // Skip last line if it has no characters + if ( + this.state.wasQuoting === true || + this.state.record.length !== 0 || + this.state.field.length !== 0 + ) { + this.info.bytes = this.state.bufBytesStart + pos; + const errField = this.__onField(); + if (errField !== undefined) return errField; + const errRecord = this.__onRecord(push); + if (errRecord !== undefined) return errRecord; + } else if (this.state.wasRowDelimiter === true) { + this.info.empty_lines++; + } else if (this.state.commenting === true) { + this.info.comment_lines++; + } + } + } else { + this.state.bufBytesStart += pos; + this.state.previousBuf = buf.slice(pos); + } + if (this.state.wasRowDelimiter === true) { + this.info.lines++; + this.state.wasRowDelimiter = false; + } + }, + __onRecord: function (push) { + const { + columns, + group_columns_by_name, + encoding, + info, + from, + relax_column_count, + relax_column_count_less, + relax_column_count_more, + raw, + skip_records_with_empty_values, + } = this.options; + const { enabled, record } = this.state; + if (enabled === false) { + return this.__resetRecord(); + } + // Convert the first line into column names + const recordLength = record.length; + if (columns === true) { + if (skip_records_with_empty_values === true && isRecordEmpty(record)) { + this.__resetRecord(); + return; + } + return this.__firstLineToColumns(record); + } + if (columns === false && this.info.records === 0) { + this.state.expectedRecordLength = recordLength; + } + if (recordLength !== this.state.expectedRecordLength) { + const err = + columns === false + ? new CsvError( + "CSV_RECORD_INCONSISTENT_FIELDS_LENGTH", + [ + "Invalid Record Length:", + `expect ${this.state.expectedRecordLength},`, + `got ${recordLength} on line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + { + record: record, + }, + ) + : new CsvError( + "CSV_RECORD_INCONSISTENT_COLUMNS", + [ + "Invalid Record Length:", + `columns length is ${columns.length},`, // rename columns + `got ${recordLength} on line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + { + record: record, + }, + ); + if ( + relax_column_count === true || + (relax_column_count_less === true && + recordLength < this.state.expectedRecordLength) || + (relax_column_count_more === true && + recordLength > this.state.expectedRecordLength) + ) { + this.info.invalid_field_length++; + this.state.error = err; + // Error is undefined with skip_records_with_error + } else { + const finalErr = this.__error(err); + if (finalErr) return finalErr; + } + } + if (skip_records_with_empty_values === true && isRecordEmpty(record)) { + this.__resetRecord(); + return; + } + if (this.state.recordHasError === true) { + this.__resetRecord(); + this.state.recordHasError = false; + return; + } + this.info.records++; + if (from === 1 || this.info.records >= from) { + const { objname } = this.options; + // With columns, records are object + if (columns !== false) { + const obj = {}; + // Transform record array to an object + for (let i = 0, l = record.length; i < l; i++) { + if (columns[i] === undefined || columns[i].disabled) continue; + // Turn duplicate columns into an array + if ( + group_columns_by_name === true && + obj[columns[i].name] !== undefined + ) { + if (Array.isArray(obj[columns[i].name])) { + obj[columns[i].name] = obj[columns[i].name].concat(record[i]); + } else { + obj[columns[i].name] = [obj[columns[i].name], record[i]]; + } + } else { + obj[columns[i].name] = record[i]; + } + } + // Without objname (default) + if (raw === true || info === true) { + const extRecord = Object.assign( + { record: obj }, + raw === true + ? { raw: this.state.rawBuffer.toString(encoding) } + : {}, + info === true ? { info: this.__infoRecord() } : {}, + ); + const err = this.__push( + objname === undefined ? extRecord : [obj[objname], extRecord], + push, + ); + if (err) { + return err; + } + } else { + const err = this.__push( + objname === undefined ? obj : [obj[objname], obj], + push, + ); + if (err) { + return err; + } + } + // Without columns, records are array + } else { + if (raw === true || info === true) { + const extRecord = Object.assign( + { record: record }, + raw === true + ? { raw: this.state.rawBuffer.toString(encoding) } + : {}, + info === true ? { info: this.__infoRecord() } : {}, + ); + const err = this.__push( + objname === undefined ? extRecord : [record[objname], extRecord], + push, + ); + if (err) { + return err; + } + } else { + const err = this.__push( + objname === undefined ? record : [record[objname], record], + push, + ); + if (err) { + return err; + } + } + } + } + this.__resetRecord(); + }, + __firstLineToColumns: function (record) { + const { firstLineToHeaders } = this.state; + try { + const headers = + firstLineToHeaders === undefined + ? record + : firstLineToHeaders.call(null, record); + if (!Array.isArray(headers)) { + return this.__error( + new CsvError( + "CSV_INVALID_COLUMN_MAPPING", + [ + "Invalid Column Mapping:", + "expect an array from column function,", + `got ${JSON.stringify(headers)}`, + ], + this.options, + this.__infoField(), + { + headers: headers, + }, + ), + ); + } + const normalizedHeaders = normalize_columns_array(headers); + this.state.expectedRecordLength = normalizedHeaders.length; + this.options.columns = normalizedHeaders; + this.__resetRecord(); + return; + } catch (err) { + return err; + } + }, + __resetRecord: function () { + if (this.options.raw === true) { + this.state.rawBuffer.reset(); + } + this.state.error = undefined; + this.state.record = []; + this.state.record_length = 0; + }, + __onField: function () { + const { cast, encoding, rtrim, max_record_size } = this.options; + const { enabled, wasQuoting } = this.state; + // Short circuit for the from_line options + if (enabled === false) { + return this.__resetField(); + } + let field = this.state.field.toString(encoding); + if (rtrim === true && wasQuoting === false) { + field = field.trimRight(); + } + if (cast === true) { + const [err, f] = this.__cast(field); + if (err !== undefined) return err; + field = f; + } + this.state.record.push(field); + // Increment record length if record size must not exceed a limit + if (max_record_size !== 0 && typeof field === "string") { + this.state.record_length += field.length; + } + this.__resetField(); + }, + __resetField: function () { + this.state.field.reset(); + this.state.wasQuoting = false; + }, + __push: function (record, push) { + const { on_record } = this.options; + if (on_record !== undefined) { + const info = this.__infoRecord(); + try { + record = on_record.call(null, record, info); + } catch (err) { + return err; + } + if (record === undefined || record === null) { + return; + } + } + push(record); + }, + // Return a tuple with the error and the casted value + __cast: function (field) { + const { columns, relax_column_count } = this.options; + const isColumns = Array.isArray(columns); + // Dont loose time calling cast + // because the final record is an object + // and this field can't be associated to a key present in columns + if ( + isColumns === true && + relax_column_count && + this.options.columns.length <= this.state.record.length + ) { + return [undefined, undefined]; + } + if (this.state.castField !== null) { + try { + const info = this.__infoField(); + return [undefined, this.state.castField.call(null, field, info)]; + } catch (err) { + return [err]; + } + } + if (this.__isFloat(field)) { + return [undefined, parseFloat(field)]; + } else if (this.options.cast_date !== false) { + const info = this.__infoField(); + return [undefined, this.options.cast_date.call(null, field, info)]; + } + return [undefined, field]; + }, + // Helper to test if a character is a space or a line delimiter + __isCharTrimable: function (buf, pos) { + const isTrim = (buf, pos) => { + const { timchars } = this.state; + loop1: for (let i = 0; i < timchars.length; i++) { + const timchar = timchars[i]; + for (let j = 0; j < timchar.length; j++) { + if (timchar[j] !== buf[pos + j]) continue loop1; + } + return timchar.length; + } + return 0; + }; + return isTrim(buf, pos); + }, + // Keep it in case we implement the `cast_int` option + // __isInt(value){ + // // return Number.isInteger(parseInt(value)) + // // return !isNaN( parseInt( obj ) ); + // return /^(\-|\+)?[1-9][0-9]*$/.test(value) + // } + __isFloat: function (value) { + return value - parseFloat(value) + 1 >= 0; // Borrowed from jquery + }, + __compareBytes: function (sourceBuf, targetBuf, targetPos, firstByte) { + if (sourceBuf[0] !== firstByte) return 0; + const sourceLength = sourceBuf.length; + for (let i = 1; i < sourceLength; i++) { + if (sourceBuf[i] !== targetBuf[targetPos + i]) return 0; + } + return sourceLength; + }, + __isDelimiter: function (buf, pos, chr) { + const { delimiter, ignore_last_delimiters } = this.options; + if ( + ignore_last_delimiters === true && + this.state.record.length === this.options.columns.length - 1 + ) { + return 0; + } else if ( + ignore_last_delimiters !== false && + typeof ignore_last_delimiters === "number" && + this.state.record.length === ignore_last_delimiters - 1 + ) { + return 0; + } + loop1: for (let i = 0; i < delimiter.length; i++) { + const del = delimiter[i]; + if (del[0] === chr) { + for (let j = 1; j < del.length; j++) { + if (del[j] !== buf[pos + j]) continue loop1; + } + return del.length; + } + } + return 0; + }, + __isRecordDelimiter: function (chr, buf, pos) { + const { record_delimiter } = this.options; + const recordDelimiterLength = record_delimiter.length; + loop1: for (let i = 0; i < recordDelimiterLength; i++) { + const rd = record_delimiter[i]; + const rdLength = rd.length; + if (rd[0] !== chr) { + continue; + } + for (let j = 1; j < rdLength; j++) { + if (rd[j] !== buf[pos + j]) { + continue loop1; + } + } + return rd.length; + } + return 0; + }, + __isEscape: function (buf, pos, chr) { + const { escape } = this.options; + if (escape === null) return false; + const l = escape.length; + if (escape[0] === chr) { + for (let i = 0; i < l; i++) { + if (escape[i] !== buf[pos + i]) { + return false; + } + } + return true; + } + return false; + }, + __isQuote: function (buf, pos) { + const { quote } = this.options; + if (quote === null) return false; + const l = quote.length; + for (let i = 0; i < l; i++) { + if (quote[i] !== buf[pos + i]) { + return false; + } + } + return true; + }, + __autoDiscoverRecordDelimiter: function (buf, pos) { + const { encoding } = this.options; + // Note, we don't need to cache this information in state, + // It is only called on the first line until we find out a suitable + // record delimiter. + const rds = [ + // Important, the windows line ending must be before mac os 9 + Buffer.from("\r\n", encoding), + Buffer.from("\n", encoding), + Buffer.from("\r", encoding), + ]; + loop: for (let i = 0; i < rds.length; i++) { + const l = rds[i].length; + for (let j = 0; j < l; j++) { + if (rds[i][j] !== buf[pos + j]) { + continue loop; + } + } + this.options.record_delimiter.push(rds[i]); + this.state.recordDelimiterMaxLength = rds[i].length; + return rds[i].length; + } + return 0; + }, + __error: function (msg) { + const { encoding, raw, skip_records_with_error } = this.options; + const err = typeof msg === "string" ? new Error(msg) : msg; + if (skip_records_with_error) { + this.state.recordHasError = true; + if (this.options.on_skip !== undefined) { + this.options.on_skip( + err, + raw ? this.state.rawBuffer.toString(encoding) : undefined, + ); + } + // this.emit('skip', err, raw ? this.state.rawBuffer.toString(encoding) : undefined); + return undefined; + } else { + return err; + } + }, + __infoDataSet: function () { + return { + ...this.info, + columns: this.options.columns, + }; + }, + __infoRecord: function () { + const { columns, raw, encoding } = this.options; + return { + ...this.__infoDataSet(), + error: this.state.error, + header: columns === true, + index: this.state.record.length, + raw: raw ? this.state.rawBuffer.toString(encoding) : undefined, + }; + }, + __infoField: function () { + const { columns } = this.options; + const isColumns = Array.isArray(columns); + return { + ...this.__infoRecord(), + column: + isColumns === true + ? columns.length > this.state.record.length + ? columns[this.state.record.length].name + : null + : this.state.record.length, + quoting: this.state.wasQuoting, + }; + }, + }; +}; + +const parse = function (data, opts = {}) { + if (typeof data === "string") { + data = Buffer.from(data); + } + const records = opts && opts.objname ? {} : []; + const parser = transform(opts); + const push = (record) => { + if (parser.options.objname === undefined) records.push(record); + else { + records[record[0]] = record[1]; + } + }; + const close = () => {}; + const err1 = parser.parse(data, false, push, close); + if (err1 !== undefined) throw err1; + const err2 = parser.parse(undefined, true, push, close); + if (err2 !== undefined) throw err2; + return records; +}; + +exports.CsvError = CsvError; +exports.parse = parse; + + /***/ }) /******/ }); diff --git a/package-lock.json b/package-lock.json index 4b5ebffe..259d0a65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@actions/core": "1.10.0", "@actions/exec": "1.1.1", "@actions/tool-cache": "2.0.1", + "csv-parse": "5.6.0", "semver": "7.6.2" }, "devDependencies": { @@ -615,6 +616,12 @@ "node": ">= 8" } }, + "node_modules/csv-parse": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz", + "integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 483f7ccf..ddfc7a60 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@actions/core": "1.10.0", "@actions/exec": "1.1.1", "@actions/tool-cache": "2.0.1", + "csv-parse": "5.6.0", "semver": "7.6.2" }, "devDependencies": { diff --git a/src/setup-beam.js b/src/setup-beam.js index 8b29e27d..7f0aad87 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -5,6 +5,7 @@ const path = require('path') const semver = require('semver') const fs = require('fs') const os = require('os') +const csv = require('csv-parse/sync') const MAX_HTTP_RETRIES = 3 @@ -275,6 +276,21 @@ async function getOTPVersions(osVersion) { 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}`, otpVersionsListings) @@ -309,6 +325,18 @@ async function getOTPVersions(osVersion) { otpVersions[otpVersion] = otpVersion }) }) + } else if (process.platform === 'darwin') { + csv + .parse(otpVersionsListings, { + columns: true, + }) + .forEach((line) => { + const otpMatch = line.ref_name.match(/^([^-]+-)?(.+)$/) + const otpVersion = otpMatch[2] + const otpVersionOrig = otpMatch[0] + debugLog('OTP line and parsing', [line, otpVersion, otpMatch]) + otpVersions[otpVersion] = otpVersionOrig // we keep the original for later reference + }) } debugLog(`OTP versions from ${originListing}`, JSON.stringify(otpVersions)) @@ -552,6 +580,11 @@ function getRunnerOSVersion() { ubuntu24: 'ubuntu-24.04', win19: 'windows-2019', win22: 'windows-2022', + // The default, from GHA, for macos is always macOS but if we're using + // that we can't target a specific build + macos13: 'macOS-13', + macos14: 'macOS-14', + macos15: 'macOS-15', } const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS] if (!containerFromEnvImageOS) { @@ -823,6 +856,28 @@ async function install(toolName, opts) { const args = ['+V'] const env = {} + return [cmd, args, env] + }, + }, + darwin: { + downloadToolURL: () => + `https://github.com/erlef/otp_builds/releases/download/` + + `${toolVersion}/${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] }, }, @@ -932,6 +987,7 @@ async function install(toolName, opts) { }, }, } + installOpts.darwin = installOpts.linux break case 'rebar3': installOpts = { @@ -1005,6 +1061,7 @@ async function install(toolName, opts) { }, }, } + installOpts.darwin = installOpts.linux break default: throw new Error(`no installer for ${toolName}`) diff --git a/test/otp/macos/aarch64-apple-darwin.csv b/test/otp/macos/aarch64-apple-darwin.csv new file mode 100644 index 00000000..bda34a18 --- /dev/null +++ b/test/otp/macos/aarch64-apple-darwin.csv @@ -0,0 +1,97 @@ +ref_name,ref,datetime,sha256,openssl,wxwidgets +master,09e092774c00416bb1635880ef210d13aa99fe98,2025-05-25T00:14:48Z,fa14de4db946fa512193853bca8147e679feb1037d6cd33d3a6a1c156b86ee32,openssl-3.1.6,wxwidgets-3.2.6 +maint-27,c388a2d1b3f9918652276d4798692dd4d8ef97fc,2025-05-25T00:24:14Z,7fdd623e4a13ff3ca10d62f180e721014a2cd5c4341e7d81d8182a56d21a1141,openssl-3.1.6,wxwidgets-3.2.6 +maint-26,7335f79eeaa0094c274dab2aa2ced9e138027c75,2025-05-25T00:20:18Z,5fa92c05bf904a0fc20313f4f9c625c620b71fd905dbe5fc78c8af0f8285f341,openssl-3.1.6,wxwidgets-3.2.6 +maint-25,52199ed7e79646b73bacc47c92967ce9970b2373,2025-05-25T00:16:01Z,c12998e8ce96d9e2126e45a87428a1efde4a344f46d789ba62b46b02ae8222de,openssl-1.1.1w,wxwidgets-3.2.6 +maint,c16299eb821718afd337db67f89c0fac56f79496,2025-05-25T00:15:17Z,b5385d907b9617d3c793a203f29d33617bf0a7c6c2d96beaebb02d743396b3e3,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc4,bdd4594181242f0d681b2e464b8bf12205fba30b,2025-05-05T18:42:29Z,4d178928ffa49ad443bda09e336ff47cc0930998405ac77c56a31a98373b819c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc3,cf38f1efc91853e384542ce105639226b7367ea2,2025-04-16T11:36:45Z,027bc40d1cbda304a83f368e46f3d7e66ca92f61b9af1507d14a83d52d75052b,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc2,5ddf7a91568cdc29cb85f43640dbdb40fb3b3985,2025-03-19T09:09:12Z,086d560a45d19add332ce62c42c9ebf09d7423ffea60c5b8c13d9a54389f21c8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc1,ba8f061732c41ad4bca7895e47acb5d26f54c032,2025-02-12T10:40:45Z,35d1538c04274bc30ffc2c5c28d2550cd7bf1dd361ea35b158066919b48d0fb8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0,7a57568c13dedf4ab82ea2b881619788749bbdc5,2025-05-21T08:09:39Z,769d3712096a9e4b93754d7c9b3bd09ce705f1b22ba23adffc706facbfb05ec8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.4,fe31f32661e4e90ae4a1bb32551d2937f7c4e12a,2025-05-08T13:43:25Z,c6ed988d1c32baef98e77a18131b5fe380c2ddb98792cefec0683778797989fa,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.3,48a429f47c03e0fee574417345be90efec815abc,2025-04-16T14:09:21Z,feba87f1ee977730bfea58098ace38671c329e2f6bb3fbbe132b22b2dcd6a399,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.2,94c2583f43441ecd788e85ad00982b03e6b572f7,2025-04-04T08:41:14Z,19dc3404f2bb09a8cdd0b08ab896dfe02cb9680bf5974bcaa2b60d06d455a325,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.1,901752e05d3ef678b22ecddb7379e8fe2a5facdd,2025-03-28T13:14:09Z,f56d69e199f675cc490e43a96de6522f104d3e2b5a07563a890f3542b557f011,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3,15637bf0ed9e2a5d015ae9260c0912e2de2d7108,2025-03-05T10:11:36Z,a76eb513202c7131bcd62ee516f8498098b8adadd417bd90e30ae3a2e3f6762d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.4,4df6b11359260d5e01161a651a35c7391021a218,2025-02-20T15:09:39Z,4acbc151870b3e4e295133f211da7d7904df7c093386e2229c6d62d617336c28,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.3,7eb2efcf82912f24e3d8ccf4854b42f28b5955bd,2025-02-17T17:07:52Z,472bfc5303367a48ffe2ebe963556bfb7b02bc5cb442ee58de802a9a4b6ae731,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.2,d7f17f87b48465e4a3ff8d427df1551f91672d2f,2025-02-06T14:40:59Z,5293c8338846eea6f4197775504fc047363adb57d2fdc34c36de88e530c9269f,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.1,c5725e0f7397b4c1296190bd96ac0fa50b850f20,2025-01-23T18:10:29Z,4f7b34919c31e9b34e749d7b9c9bfa6ddebb562621ff6dd1814d02d4e078a14a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2,46f2418451d1991d2060dc5b4d7d88f8259c9291,2024-12-11T10:09:04Z,cfdc9eff4bc3d7ac1b61e1d7adee92153e707249e9a1ade899ea5b7c52f6850d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.3,e8ab3f7fdc59da5dd4dc8ef8c1c7bcec22b52faa,2024-12-05T15:08:49Z,5c1714f94851c7523ab8f97b6c0d303e216b82eb4e7bb57eff3864369b1e402b,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.2,1926819171af845832795d87bf1be0220710852b,2024-10-28T13:04:09Z,14d65f1f918cc91d42318d72dcd07f8c8439d25385fba2ac5b7a87bc51d6ccb5,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.1,67ded430d9be65e2e6c11bba323c0eb62d07cd1f,2024-10-28T13:03:51Z,292e121817b3a7c4276ac71fb4c5d8469f0d7e83691a4becfb29e6d890cb5939,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1,9ae2ef54fc6dacb5bfbe2dc6fc6f2522d57ce1fb,2024-10-26T01:30:37Z,2b60294d04245c89aa0e2d6269a4f78eb55c17573c98bea355a9848517761a89,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0.1,ee9628e7ed09ef02e767994a6da5b7a225316aaa,2024-10-26T01:17:39Z,c709ae30c356888e3f361a3691c100eff90df5b0248ef413d6565faaa0b8ccb9,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc3,5df3992d695da4d7e8777cf7605279ce9d131f1c,2024-10-26T01:08:27Z,058846679e52c761ad2a1cfdf6e310ec959f65c557f607ce52388afdac21f275,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc2,e651174c569694c92b1794ddd0a1a4a199610091,2024-10-26T01:04:25Z,b9f5efb99fd81f05ed840d6867a64f1ca13fe4f83599f52ba602781ac4a5e81c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc1,b74bd21d5cb52e0fdc5ea321439c428783feea23,2024-10-26T01:03:29Z,7b0c54b1b8205a5c15e2df61cc446498ea8c40fffebff2b00d03a97722edac92,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0,f1c72ea22f0d0479807fbe543191f7a1ef325838,2024-10-29T19:25:07Z,9e1c483d12e9554257c5edffb9b07a03001a3ddd131bbda05519638aa8f2206b,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.9,f900850abbd2c45b618675d1342ac85492ed1c5d,2025-02-20T16:10:43Z,f96951f4226b53ec529a1b4ee65cc85f7ffa93a5f08fee105033253ceacd19ac,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.8,45fd6b3adb81f0b245d9baf4dcbdde049221fbff,2025-02-12T14:07:59Z,95a823c5f504c6a9b5a6f92c243783af065b58f6771e3cd8512315e4d6084ea6,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.7,fa62351cef47e7dc502c5d0b1348ef8988b5d7b8,2025-01-27T12:50:05Z,0b5fa2704e170871995a41a3bb4fbb327f885735482d85406cbfe3db9b2cad7b,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.6,1f4a3b013b1df4034d6fb63623e77bfb142cadda,2024-12-05T14:41:44Z,b5cbcb34067420175e49b4d4ec3d2df88622d6b79ff410182abe46ef565dfefd,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.5,81399ff534e71be2425a0cae96e26c104fcde2f3,2024-11-01T07:09:27Z,24e1f777c46b2444cdaa965fe64da483c0609c2e9657567d359dc4a241b3dad1,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.4,b6e7e233d0b781728e745dc04f4cb2645c6632ec,2024-10-26T00:55:17Z,1d2d18a519c82442d4b278844b2050d3c55dfe224e9c39174c2fc5c66030c4e9,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.3,84152c2c9d85128d3cce471f645f154c34132d82,2024-10-26T00:55:31Z,0e1d19aad5078e9689574af9fce7b156ef1cf63acf1f248cb347e98bd75bfca3,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.2,c1805ad6200cec57bf86640fb9a1c715db515b78,2024-10-26T00:49:14Z,63aca8ad98e2be6ec5656708f97aa35d8b627515b9b6f52a4edc5051e0c444e8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.12,a2f903599fe779380ec197c8ab41a193eec2d148,2025-05-08T15:41:10Z,34cebb1732abc21e2748dfe592f8d2a5ebfb70f34ff49b32ecd614e697332895,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.11,c2fcd5185c0651630196b6997be848a344c4aa71,2025-04-16T14:40:01Z,576e1a5453e6774924dfbc8cfc0b4e4276951be6e9fdec3b07db0c566b039852,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.10,eaa1b25cd65de117e82a4fbf709cb5cf6836f5ff,2025-03-28T14:41:04Z,369c227a41b6f8db3c67f8ef07c623f17692b7d518b62d240ca3ef6d66ebc602,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.1,858efd5b35b572c71a8010ad323c4127eef5bc29,2024-10-26T00:48:18Z,f010d0c5f6d202496608b7d6a23f5f6d855297423f9d4e706cd443be722c49e4,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5,412bff5196fc0ab88a61fe37ca30e5226fc7872d,2024-10-26T00:43:56Z,171a4e07c6ee48207a6245ddf7c7ea1be435c0d93e9230d85076d07d9c06f3cf,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.4,e26c5206dc98ec1b8f978fceaa61fd1354266ccb,2024-10-26T00:42:41Z,e00dca5237cc0e3bb6b2533e7952bb5950948c0620d9bda38b2cd18ac0a8e22e,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.3,928d03e6da416208fce7b9a7dbbfbb4f25d26c37,2024-10-26T00:38:24Z,510f554e4de8f25ca7aff3cd400b8143ef8fa8fd6da3603b42a79f58e6ba64ec,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.2,b83df13eec5446beab06dd24315d37a5b0633fd2,2024-10-26T00:37:43Z,2f2076d378b7b1983e9a84cc96609887164c7860dbeba4f22dcfdc2d6c00545b,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.1,ca8b893f9d5bdd0957b78514ba523032e762c644,2024-10-26T00:33:38Z,137f717281ccc11d0df31b31aaf7485b1fa89967716f56ac1c77f8a9747ae80a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2,7fc0898502cb3370b7c6d523a7393cd101808493,2024-10-26T00:28:49Z,99c70f904e0f7d4d9337cd896a03da341f5e0e308c8f875c622c0cd07daa9cf2,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1.2,c41d424db42ba84b72f3e25167470c3555723d87,2024-10-26T00:24:31Z,8f3b51e4cc1cbf7d3cc012b52fadba57e27d54d6448c5e920b03d29ac13155bf,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1.1,2bdd30b872ab91dc376998d2c417f2a8b514d1aa,2024-10-26T00:21:07Z,90e6418caa2554e5fd2dfc543c262b7b04456970208ec57714c1e00fb98c8a2a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1,e962af35263618665c1df57df5135c0c703ad502,2024-10-26T00:20:06Z,d9fe597017557b37d02a377a66b5f582822c9d817eb08797740547bc396becc0,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0.2,d051172925a5c84b2f21850a188a533f885f201c,2024-10-26T00:19:26Z,216948062d898401b2294eb969f485542b7695640e3f45b7f2ada2d24c653d4d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0.1,b54b86ad4f1253f46fd4552a73923756660c1d53,2024-10-26T00:16:01Z,3eb422a565156cc03e3161d6e8c6ffe696141ad416bf24eeb0613683904dcf4f,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc3,1f897adc9df5e0de5d5a85633a8629a7e45ddeab,2024-10-26T00:16:19Z,c3953f88da37c2d1a96f98f475ff79816493c3a3e282ee040ed10f09ec607e40,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc2,dac89a6acc1a93a615930ca18f1dbf4e9323b038,2024-10-26T00:11:55Z,2f2d1d5d757489de3848334da9807d8f3e042828cd9d62f384f318e6c0622893,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc1,127026003180a834e9fa5d5919c824a184faeb92,2024-10-26T00:10:07Z,dafb60e6bc73a5a56d021cf9c1890c1932a32799321c70c917ee214ba04ac8ad,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0,8c0ea6bd3306cfd6ca19730d180a2a93a716e1ee,2024-10-26T00:08:49Z,97424466074b66d51345dfa62776685ba7b92022bb6d8a3ed2d181d93ccf8386,openssl-3.1.6,wxwidgets-3.2.6 +OTP-25.3.2.9,68fa9c42426380772784eed26d24747bfee38fcc,2024-10-26T00:05:02Z,2231d17ea69be892c929f44f223bed471b1a782680a40d9b38e94ecd3ba2d1e6,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.8,c08f5acfb4e47933d8128d0b41b01487d0eb97a1,2024-10-26T00:04:34Z,ac22ac879d0aced4a6e9c7ff36930a0e35b3b2a60532177147d92c566e98cd99,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.7,d3c9eeb17042e5567337e1f87c924b25186daae0,2024-10-26T00:01:05Z,8e6dd919122c09748c65bf85493d1111b4f3bfe0b8cfb2ece2925333cb6dea69,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.6,fd0e27a5c1ca7afde30b10c1b33a35ebb3313fd1,2024-10-25T23:59:16Z,520c8f37a5362c9e00fa7d2d43ef851a7ba96005618b3bcae6fb64046bd472c5,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.5,5cb360d524a5777ed4243ac03a0162a95f545a16,2024-10-25T23:58:39Z,7aaa85be4ac8c4c991a7109e2bbd9b574f9e19814933e2e948ca19d52a99dd5f,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.4,9e6d04dd18bef6136fc2b45cc60fe1348c4664a7,2024-10-25T23:54:16Z,bdb23c1b6d23da81044147efa2227985a55ab064feb475d0d22258137cc954c8,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.3,da340fd39fc6550b1432662ffa55c3b6da3db41f,2024-10-25T23:54:17Z,f63ab07211826bf34e71f32e8fc9209dfe291a74c7b7896e749d3bf815afa729,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.21,eb0176f76725c1d6cc594ac3c899cf5340f6a4aa,2025-05-08T16:43:21Z,2d5d58162888119919dc03e1b1a65c4a4dadb9081317f070336b9d9410fab360,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.20,3d83272458f7b0fb05aed4981a15cfa72ef7d2ba,2025-04-16T15:07:42Z,1bb2f2efc91d4c6efeb9c9d3f16635286fe1af49b0aeb2f1b12aa8f5b4eedb0d,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.2,ba144817d5478b15a50ef0c398d925232005277e,2024-10-25T23:49:44Z,2e75e4cfcdd0437f618881d0e229cdea3b4d437a5ac0777b9cd9e2edcd1dc3d9,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.19,2dc11e177ba8e5e051a2e88b731ec6de0eb8bb4c,2025-03-28T14:40:58Z,1997fa6b329a737cd4dabb06b1c21ccb77a3759b3191526d207ae9ef7764d741,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.18,e1346297304a626ef9126f77e8b943d51646fe5a,2025-02-20T16:09:10Z,185c3eac7b10fb6864f72331a8b13145caa71cee6115f9cc5c6658cf84ff710f,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.17,90a7540d93530990531ec48a14594efea8974ca2,2025-02-12T15:08:50Z,33bbc526906eb14ffba2f0213ba3105476cdb395cc7026212d37fccd8d2c0410,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.16,b21c9ee6961e5b6aa1595d1f7547b349973ea833,2024-12-05T15:49:06Z,89ef79aabbd97f4ae27f5ae91d5a68fd122de0b14f6d76ab3055766a5e24cbe7,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.15,cd91473539f38e88d10cf9efce3551afa1dbdb45,2024-10-25T23:48:26Z,427455c139fca6937bd56f0fd041d4aabb512c699c30270e7c604b46e1275c13,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.14,412febbe2eb15958f92dd6f908b14cfcd2ff2c16,2024-10-25T23:45:50Z,0316bd01404b8ed92807bdd903fcf2a1d5efe0534e4f524192e48f54d46fe013,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.13,00c3a73a1a043f3ca76cc2cfd20ac5ccc1e3377a,2024-10-25T23:44:26Z,2823f24541a189d5024e2c121ff9d2032d40b6893f2dacfefa40a8ccdec790b7,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.12,cee6d023f30d5cedb4eaa2b1384b49597a9bde66,2024-10-25T23:41:52Z,592fa60e391e2a10802e797ea260417b44c4f1e53c57bf5cf72da7a0274c5ee9,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.11,e1e117f0e95cb373f9bd5e159e54e6ffae1bb32b,2024-10-25T23:38:27Z,e29ae556a166a0cd71d4e855581c7e4e8231c40d0506cb29395d6d3a87cfe96b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.10,733439dc03b21ea92c096b6cc31befc133cd9081,2024-10-25T23:37:09Z,1d1b51d5d09ccbaae94ca7f53d1d145bb92e10f90699a5306c020c994fc19d38,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.1,3f0a5f136aa72f785ca86df64c4384db2e9c8bbb,2024-10-25T23:34:24Z,9155c5fc846d98128da1b0651729cfa381e38a33b9b9253bcd5c208574b1b811,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2,0418c10ec37fa374867eae49f2f776c72c55d623,2024-10-25T23:33:11Z,73f44c0dfeb77f46e1be5a6e8a8edc32202fe0a6fa1e52468f4c5ad5144679ee,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.1,c487c0ea418315edd4a8614854e82c5359f4b8f8,2024-10-25T23:30:56Z,b351a214f26f0acb73e0d034eadeea27ffc83ed048a117bb121324e04f48104c,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3,5400ccf243a31d664153a4b9ceb9de3edfce1e0e,2024-10-25T23:29:58Z,e619064424f2e9b61fa06ea1c28fdeca9dab915910a8f2dbfe6fe933d02cbf57,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.3,07cf1c5caab19852ccf85ae7000d788913f3411d,2024-10-25T23:27:02Z,9cfa97270bcb7107753a2e5d22afc72fa144eb83204dfb25ec5be0f92d0d55c3,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.2,8adae7eebbc791433d5f0f58daab558a8fbe847d,2024-10-25T23:23:02Z,9d99b562eaaedca1b85cb90c3fa7c4348ee350d35176fc1703d4b3b9ed70eb2f,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.1,43361e793b63d57570a5a46da4a5cb4a1c3563c5,2024-10-25T23:22:57Z,bc25a8c440bdb7ce597c25362430d3bd4a69338d013fe786547c4b169d416166,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2,d6eb1c53d688a242bfb5e3b4febcea66c49c0fe7,2024-10-25T23:18:52Z,06dabd5bf940f12315a3ce4c422de5279debaa7d22e4222d60ff9e8314ddf997,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.2.1,7dc04c755eef47b34f5763dcfc8d16913c1b7a08,2024-10-25T23:18:49Z,b991aa4334859db303bbeeaa4b707536e01f2b3250152379e3bf68e4be1e39d3,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.2,38ad8e28421c745c06ef9bd55f6e6204cd1f15ef,2024-10-25T23:14:57Z,3ae9c79be39211330e9a3609df06e5de7c5413c9191507901a04e939d4c2bc0d,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.1,be6f05d34736fadf37328058f00e788cb88da61b,2024-10-25T23:14:47Z,e26266e2cc4ac2a7b34e76aabec0a3f9a7c44479b13297938fb479d1f043c77c,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1,6efb5e31df6bc512ed6c466584ef15b846dcecab,2024-10-25T23:09:56Z,80a1dad1f1fc792c6aec58fc2236937637589167d1f9072e748e7f0ac8e88a56,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.4,c028b855ee3f12c835ff3538a90ac5dbc155631b,2024-10-25T23:09:55Z,4e7e2f39cb14e36641c9b7d8255629ccccd11cf2ce466797ab0c75bf3369c3e4,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.3,89c04fb1836fb865565125a4d5880b93e784d856,2024-10-25T23:08:37Z,b92aece9b6a5b1d010fac6bd603f12b3c3d1e897c18c77a1ba75e28c246082c1,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.2,ac0c9879c68b23278178a7afe738285b33ff1832,2024-10-25T23:05:22Z,1fcd537d0254783b3fa5906831cb8e4ed042f667948908d541812eba5f0df0a8,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.1,cf18f250a40f33cc648d869eac833cd9aee86ed6,2024-10-25T23:02:52Z,133d88f6a8b3e734bce16b226c956e56b10f198a442d872069cd3a850c0fd9ab,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc3,47f121af8ee55a0dbe2a8c9ab85031ba052bad6b,2024-10-25T22:57:10Z,9a42a8210f21099380bcf471fc5cb2914ac24fe0424b250f3bcc80de3d79a099,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc2,85d0a8366e64f8272e332a63a2cba59afa3f7eb9,2024-10-25T22:57:09Z,f06a17a07f0744fd60671fdf89845617712dd200e138dfbb4cc54a20b0ad2a51,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc1,3c68ceea1f93119bed871866c690fbf5a95048c1,2024-10-25T23:00:53Z,368d833b711ad3a11b3241e6abee7184f103476a8d46e6d2d4940f7b454ec708,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0,4ed7957623e5ccbd420a09a506bd6bc9930fe93c,2024-10-25T22:03:32Z,d3728dd03c29d29237c2bc682884f5fad5535b41256a78f3314240e6458215b2,openssl-1.1.1w,wxwidgets-3.2.6 diff --git a/test/otp/macos/x86_64-apple-darwin.csv b/test/otp/macos/x86_64-apple-darwin.csv new file mode 100644 index 00000000..206c16a8 --- /dev/null +++ b/test/otp/macos/x86_64-apple-darwin.csv @@ -0,0 +1,97 @@ +ref_name,ref,datetime,sha256,openssl,wxwidgets +master,09e092774c00416bb1635880ef210d13aa99fe98,2025-05-25T00:21:50Z,5a405999f2332111d10d70699ec659d7766a34c4339c915fe75aca16324a68e6,openssl-3.1.6,wxwidgets-3.2.6 +maint-27,c388a2d1b3f9918652276d4798692dd4d8ef97fc,2025-05-25T00:28:26Z,c1c79695b4559eba35cc4df207b6b6063fa38bd2f67bbf1531f43dddfff171e1,openssl-3.1.6,wxwidgets-3.2.6 +maint-26,7335f79eeaa0094c274dab2aa2ced9e138027c75,2025-05-25T00:24:18Z,74323d309a36c3cd21a7e4daa4989852c6077949d1ee5a4ab2403ddba5757234,openssl-3.1.6,wxwidgets-3.2.6 +maint-25,52199ed7e79646b73bacc47c92967ce9970b2373,2025-05-25T00:22:01Z,cc76a4f8a183c5b077a955a520b7ff12afe4063bcae74de26e9bdd71b5fb39a0,openssl-1.1.1w,wxwidgets-3.2.6 +maint,c16299eb821718afd337db67f89c0fac56f79496,2025-05-25T00:19:44Z,b345cb6ac07ba03469833927f4de4e3e44885e44996d7f3e3e30e9b4f6dce0dc,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc4,bdd4594181242f0d681b2e464b8bf12205fba30b,2025-05-05T18:58:38Z,584ee2ce5b63331ba57bf362fb20a6e28b7e0b26040ea9100d5ee7522a7148ce,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc3,cf38f1efc91853e384542ce105639226b7367ea2,2025-04-16T11:51:19Z,bce2b8383898fb5386bfc14ca4462e509ed7f5a0466fe5d215fd8a2dfb2f5c64,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc2,5ddf7a91568cdc29cb85f43640dbdb40fb3b3985,2025-03-19T09:16:11Z,e1d04af4b57b921b0edbddf0595bbda3d89f50db67460a04ce77ede3bad1b90a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0-rc1,ba8f061732c41ad4bca7895e47acb5d26f54c032,2025-02-12T10:47:48Z,028793678f35df042a565fb17552717506bcfbbce6e7dc395ed9f896cb7eee62,openssl-3.1.6,wxwidgets-3.2.6 +OTP-28.0,7a57568c13dedf4ab82ea2b881619788749bbdc5,2025-05-21T08:16:14Z,31a8a7deb607f936a24024a6b3ed278a2fd16af608687e1e2f91c9a6554b43f8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.4,fe31f32661e4e90ae4a1bb32551d2937f7c4e12a,2025-05-08T13:53:15Z,2d63ddb0d51d2964e9d5f01bb2a54c43ec4840cffb243f6a70d300628e891b99,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.3,48a429f47c03e0fee574417345be90efec815abc,2025-04-16T14:24:16Z,b923cfb68d483cd3de99f1c00944e36b5596a7239c77675e5d07ce675357109f,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.2,94c2583f43441ecd788e85ad00982b03e6b572f7,2025-04-04T08:53:14Z,7e6f744f0b92bcf36697db476988ecfd9be96840667667561496cfa631bae402,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3.1,901752e05d3ef678b22ecddb7379e8fe2a5facdd,2025-03-28T13:21:24Z,a1b94b8ce7c151c59e309599bc2c290c824e4805b66b135f5ff2db261bb1eac0,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.3,15637bf0ed9e2a5d015ae9260c0912e2de2d7108,2025-03-05T10:13:39Z,f52be7289b13755ee20121a40e2a910f707fce3c2e4f1d358a525377da7789de,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.4,4df6b11359260d5e01161a651a35c7391021a218,2025-02-20T15:13:30Z,7797d076b5e0d8df7f36fcb73b3d88898da8ab98d801441fb78a06bf609db6ac,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.3,7eb2efcf82912f24e3d8ccf4854b42f28b5955bd,2025-02-17T17:14:28Z,bd7f58deb6c97cd75fe63f9d36395d1675aec4ca0914f8f0f389fd7f0d2286d1,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.2,d7f17f87b48465e4a3ff8d427df1551f91672d2f,2025-02-06T15:01:49Z,ef44d779e4f8cfebdd59240a41b3ef4e99bd2e35ca84f219b7842d42f1ec0721,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2.1,c5725e0f7397b4c1296190bd96ac0fa50b850f20,2025-01-23T18:18:42Z,13151d5f3f07efa4b30c9bd824b34d7d85f289bf28baf633e504d5eb4f8177ae,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.2,46f2418451d1991d2060dc5b4d7d88f8259c9291,2024-12-11T10:22:06Z,91fd068ed9748b83f95b6e4a8c60e59562567a507cf6d4da8f8d44a88523ce54,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.3,e8ab3f7fdc59da5dd4dc8ef8c1c7bcec22b52faa,2024-12-05T15:17:20Z,ea878ed7d9c13c2aa44f6ea5af8321e1111967828577e5532d6ee04d92f7194c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.2,1926819171af845832795d87bf1be0220710852b,2024-10-28T13:12:50Z,7a634452ab85a23d210ef482c89af7e7e8448e063a1ccaf92e815daf52b1a135,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1.1,67ded430d9be65e2e6c11bba323c0eb62d07cd1f,2024-10-28T13:12:16Z,36790419f0f30260174474d2bda9a3a8e269b0f3ebc6d873af03a3da93243565,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.1,9ae2ef54fc6dacb5bfbe2dc6fc6f2522d57ce1fb,2024-10-26T01:31:19Z,99601924bf4b9656e4ecaff9936d4a3c15761b69a64cec24a8b89f6cb4f0e4c7,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0.1,ee9628e7ed09ef02e767994a6da5b7a225316aaa,2024-10-26T01:24:32Z,f95748869c9206d394369932532ca2c23a6d21447607c6705cea4d5ab28c921d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc3,5df3992d695da4d7e8777cf7605279ce9d131f1c,2024-10-26T01:20:53Z,b8cdb3aba95f10ca0b2c61a0a6e2383dfc1d9cee49ac77d1955f6f0e5e80e58a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc2,e651174c569694c92b1794ddd0a1a4a199610091,2024-10-26T01:23:23Z,4e0351fcac3399a217fddf6af30db45377ffbba1fe59c5d3e7dc6d364c077aec,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0-rc1,b74bd21d5cb52e0fdc5ea321439c428783feea23,2024-10-26T01:13:02Z,cf0b249e72a1ec1db93c4d081bdf20309383a0ab47971f392484323ab58b62af,openssl-3.1.6,wxwidgets-3.2.6 +OTP-27.0,f1c72ea22f0d0479807fbe543191f7a1ef325838,2024-10-29T19:30:52Z,41b4ad3e7081151c10b964d1b63982704d6a840da8a525bda0423f42028d180c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.9,f900850abbd2c45b618675d1342ac85492ed1c5d,2025-02-20T16:14:22Z,2f1cba5d0a752edab7f4394bc361854b69cd89fe71f06acfc9e42b82b9a42324,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.8,45fd6b3adb81f0b245d9baf4dcbdde049221fbff,2025-02-12T14:13:26Z,21d6aa90527a2fa16a47a73ad739925c6d8811d47d530a81a987cb53f2def3bb,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.7,fa62351cef47e7dc502c5d0b1348ef8988b5d7b8,2025-01-27T12:57:08Z,c204588b446faf19c189ca536803b818c5b8b46c529a3b0b7df67a11d92b8412,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.6,1f4a3b013b1df4034d6fb63623e77bfb142cadda,2024-12-05T14:50:08Z,db9635c0b1d6d79dc0a66bdf7b64c41a1323b8790551a6df3ddb659f3deb1d38,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.5,81399ff534e71be2425a0cae96e26c104fcde2f3,2024-11-01T07:16:35Z,fc58d3d471a552451e6682df652fe989d5be639e9179e3d6b44deb78d4867501,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.4,b6e7e233d0b781728e745dc04f4cb2645c6632ec,2024-10-26T01:07:55Z,bd3f246ddd8448c1cb877dd9d9137ab7e77f9700608b4c0f68968a81f13fbb04,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.3,84152c2c9d85128d3cce471f645f154c34132d82,2024-10-26T00:58:33Z,941970ed37bb76d690e03355b7d4e6770af076d89b62f542ac140efafdb7a179,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.2,c1805ad6200cec57bf86640fb9a1c715db515b78,2024-10-26T00:55:50Z,628bcc158e5662004135241e7ab3e71146860c9fc5d1f86829061da38119144a,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.12,a2f903599fe779380ec197c8ab41a193eec2d148,2025-05-08T15:46:20Z,6cd58a09426aff768fb0117b27e60cd2b671c1336424aada0d48e47e26d4449f,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.11,c2fcd5185c0651630196b6997be848a344c4aa71,2025-04-16T14:47:54Z,dec9c637e619dbd9add023c50a5ba0b23a30657d543417d7ff0d9033abdbbebe,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.10,eaa1b25cd65de117e82a4fbf709cb5cf6836f5ff,2025-03-28T14:48:04Z,aa5a5759222e4eec003f5aae0f2804e2e487da4c81feaed7305c3a9e87fcdbfd,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5.1,858efd5b35b572c71a8010ad323c4127eef5bc29,2024-10-26T00:51:06Z,587a9b9f7d5178895194691fdb57fab4d9b819cba644129381a73944eadbc821,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.5,412bff5196fc0ab88a61fe37ca30e5226fc7872d,2024-10-26T00:49:32Z,8eb273cf5c459829cca28a8caada5b0e289f43c126b0718fa85888731dccf7d4,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.4,e26c5206dc98ec1b8f978fceaa61fd1354266ccb,2024-10-26T00:43:46Z,ed9a515471bcf07ce392b0d3837f001b555f55d670f07f32cdd6c5ce510d78fb,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.3,928d03e6da416208fce7b9a7dbbfbb4f25d26c37,2024-10-26T00:44:24Z,92ec2364d9948b4885714aca32a1beb1115c753cf0a2b19b03012bc1805fe556,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.2,b83df13eec5446beab06dd24315d37a5b0633fd2,2024-10-26T00:39:35Z,e23a011e026687b66a25da9a1f6bcc4da94494c5de1eba6440b5b25b6f00ddb8,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2.1,ca8b893f9d5bdd0957b78514ba523032e762c644,2024-10-26T00:33:26Z,459bbf4efe65a4575bd16b26ab7d69d39327433233a712ebbe1dccd531fcf87c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.2,7fc0898502cb3370b7c6d523a7393cd101808493,2024-10-26T00:33:56Z,6b9760085de085f3a12d20935f338fc99ee67fd35d2e62a7e3e9cffb9b21242c,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1.2,c41d424db42ba84b72f3e25167470c3555723d87,2024-10-26T00:33:32Z,82ba154649c17be8ff45268239d36e4d34e82a520ee57a49b9b337e3fa77d69d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1.1,2bdd30b872ab91dc376998d2c417f2a8b514d1aa,2024-10-26T00:28:08Z,d5ce0b59d827c751ab2f68e5e7f1757dabee399040eaedcfba491c8c79c66cc3,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.1,e962af35263618665c1df57df5135c0c703ad502,2024-10-26T00:26:33Z,236ea16e738e5c57deae3ff424ede7944ffce14fac8b939a18ba2ccad85e4d81,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0.2,d051172925a5c84b2f21850a188a533f885f201c,2024-10-26T00:25:48Z,8cb58daa3694229068f5a3f621c5508907a20593955abfd17185720505946764,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0.1,b54b86ad4f1253f46fd4552a73923756660c1d53,2024-10-26T00:19:22Z,fb7d35f7838fd9422cdc215f2177953fa43bf72357628c59eefeb4c0852c5121,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc3,1f897adc9df5e0de5d5a85633a8629a7e45ddeab,2024-10-26T00:16:30Z,ff2a6fe3e3c6f7aa851604c0ff3163cfe3dbf9ed06d2f1a62f753a758d9d39bc,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc2,dac89a6acc1a93a615930ca18f1dbf4e9323b038,2024-10-26T00:15:34Z,c9f23a971cde0b7bb25d0b82a18f55f958ac169d499593c17b3d0c5d847a4714,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0-rc1,127026003180a834e9fa5d5919c824a184faeb92,2024-10-26T00:12:17Z,abf7d8ffafe4dafc39df96c062a0aad392f03c0abbe9a5d9f7a5b01189e48f9d,openssl-3.1.6,wxwidgets-3.2.6 +OTP-26.0,8c0ea6bd3306cfd6ca19730d180a2a93a716e1ee,2024-10-26T00:11:24Z,8952b4fb76ce8d1b95b2de8643ddf19ad84993563860b313382915b290ef2d97,openssl-3.1.6,wxwidgets-3.2.6 +OTP-25.3.2.9,68fa9c42426380772784eed26d24747bfee38fcc,2024-10-26T00:08:03Z,5702fa82c48a9732a5f1196cb32b617c475851e3471fd2b6e7e719792dfc032a,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.8,c08f5acfb4e47933d8128d0b41b01487d0eb97a1,2024-10-26T00:06:15Z,c6c1929c5408e9d6fc66c1b606e0926f362cd057db838b6013dac54b94cab39d,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.7,d3c9eeb17042e5567337e1f87c924b25186daae0,2024-10-26T00:04:48Z,fe43af77747d7034204451ed630c4851d86c23432ce89d5f253505988656036a,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.6,fd0e27a5c1ca7afde30b10c1b33a35ebb3313fd1,2024-10-26T00:00:23Z,83bf16f30737ec14bf32a1acbfd86f65b89be2fa8b8bd4462c70c6c48d1e61b1,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.5,5cb360d524a5777ed4243ac03a0162a95f545a16,2024-10-26T00:01:37Z,ad254c611b29df084470bf3f757e619287b52c3145dced61a46aaa85bd58b36b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.4,9e6d04dd18bef6136fc2b45cc60fe1348c4664a7,2024-10-25T23:56:58Z,e1fd6458c109d6fc3f8ebca01b5d29a718d9612a7ffbac464ce870daf69cd6fa,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.3,da340fd39fc6550b1432662ffa55c3b6da3db41f,2024-10-25T23:55:04Z,3b40c8fbee2bab28e5d979994dce3dbb0761df348a21798b1c50467783781324,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.21,eb0176f76725c1d6cc594ac3c899cf5340f6a4aa,2025-05-08T16:49:06Z,8aed83bc19798efd25e0a32fb1eb174904a2881b3d9a8039d86f7aff6375cdb1,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.20,3d83272458f7b0fb05aed4981a15cfa72ef7d2ba,2025-04-16T15:09:30Z,7456c582f71f1f8448cd6d2349302985e4691bc24785f72495da7db9989c54f0,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.2,ba144817d5478b15a50ef0c398d925232005277e,2024-10-25T23:54:34Z,e7d36cab3aa0096c549f8e8fcd381fb3f4005dba698fc1f096dd9373b3862cd8,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.19,2dc11e177ba8e5e051a2e88b731ec6de0eb8bb4c,2025-03-28T14:45:35Z,23b29d4fa18af2f0252b7d878316d492d114a0d3c2a97eed5e874601b36bddc7,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.18,e1346297304a626ef9126f77e8b943d51646fe5a,2025-02-20T16:10:45Z,adf16cfeab7e02003be2e13b23e37bc7952b45025e2400a008acab0e36c805db,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.17,90a7540d93530990531ec48a14594efea8974ca2,2025-02-12T15:11:54Z,6bd872e819aabdf2c07f81d6a0436f1a639c778440cf10be3dff783a88a9e4fc,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.16,b21c9ee6961e5b6aa1595d1f7547b349973ea833,2024-12-05T15:46:47Z,571677874adeeb77e585b0e732a13ccfe707497a6abfd45aa2c11df59ba4c46a,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.15,cd91473539f38e88d10cf9efce3551afa1dbdb45,2024-10-25T23:49:57Z,1f0d974438da48dbd15823a3e2753d5ca4a5aeccef49d7814af40393a3aff26f,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.14,412febbe2eb15958f92dd6f908b14cfcd2ff2c16,2024-10-25T23:49:51Z,9584a37799b64b3f095e36f000b0574ed5119ed8200899065da298b7e59aac32,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.13,00c3a73a1a043f3ca76cc2cfd20ac5ccc1e3377a,2024-10-25T23:44:27Z,eed02812b0104e5fa530bc2a932266fe5102412ced43efcbd7ce2ed9962c4017,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.12,cee6d023f30d5cedb4eaa2b1384b49597a9bde66,2024-10-25T23:43:41Z,c033ffd03966263de25794a1768813a0434c77e575b546a0b087fd587d5b63fa,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.11,e1e117f0e95cb373f9bd5e159e54e6ffae1bb32b,2024-10-25T23:42:59Z,5dbcce0b562c03c273c29acb92e56d0d8a1fd25da15a665ffc3f621d8abdf94a,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.10,733439dc03b21ea92c096b6cc31befc133cd9081,2024-10-25T23:40:26Z,ec0305a15e69fa9aa669fc2c95b34ded49597e77be84121da7bba104ac92f06c,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2.1,3f0a5f136aa72f785ca86df64c4384db2e9c8bbb,2024-10-25T23:37:53Z,08c5b69026a6405d8b7f684cc295d75c09c1caaa8b339019dda35daa29db11ba,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.2,0418c10ec37fa374867eae49f2f776c72c55d623,2024-10-25T23:36:51Z,8bd9f9a7f4580258b69dc6e4408ea300adc375c652ed519c5a51783aac200793,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3.1,c487c0ea418315edd4a8614854e82c5359f4b8f8,2024-10-25T23:34:37Z,d8b7c21118650c8f2703b09b4af92ced093e497e54e876e77c0ca66f75a91605,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.3,5400ccf243a31d664153a4b9ceb9de3edfce1e0e,2024-10-25T23:28:52Z,92a0ff47f7358c3d54dd2662f31209fcd429e625662891c34f995ae4f5046dcb,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.3,07cf1c5caab19852ccf85ae7000d788913f3411d,2024-10-25T23:29:00Z,73663617ee99d142ade4fb1f22148542d1c2cc81d34cb4dc9ca3a6df0fd6934b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.2,8adae7eebbc791433d5f0f58daab558a8fbe847d,2024-10-25T23:26:48Z,e4f1f0d8e822e2c65ea10d187b1123d421583db7c58a8ca75159d4387274b294,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2.1,43361e793b63d57570a5a46da4a5cb4a1c3563c5,2024-10-25T23:24:37Z,0ea3e0bf2e0e4b392cf5673df947087c9be8383c9a6bee33ce371529737c7e23,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.2,d6eb1c53d688a242bfb5e3b4febcea66c49c0fe7,2024-10-25T23:22:03Z,adb12459803d0eb2c7033af4a1262a788ef7d00a43bf3be83cb25d7ab3a63370,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.2.1,7dc04c755eef47b34f5763dcfc8d16913c1b7a08,2024-10-25T23:21:03Z,67fe4b03b2d2539397df718c2d0161012fa4c6586e342021d6d47c04574551d5,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.2,38ad8e28421c745c06ef9bd55f6e6204cd1f15ef,2024-10-25T23:16:01Z,4a1705e5976b51e24159dc7cd06d25d58aa0dbe343e30119c6211a26edf825d1,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1.1,be6f05d34736fadf37328058f00e788cb88da61b,2024-10-25T23:16:23Z,62640b84c7a211c3d66919dd21505e42c9cc6fbf2deaafc71b1d7afbdba0a851,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.1,6efb5e31df6bc512ed6c466584ef15b846dcecab,2024-10-25T23:14:41Z,121b4ff6f86788cffb889836602f148dcb71ff60104cee838d5b953e2ddfc864,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.4,c028b855ee3f12c835ff3538a90ac5dbc155631b,2024-10-25T23:10:57Z,bedcacf4cddf158f68c6972a86be6fe1362957741738c53f37ba77d5902abed1,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.3,89c04fb1836fb865565125a4d5880b93e784d856,2024-10-25T23:09:39Z,fc5e45caad51eb4b684eef752ea8eb96385de1d826b4551e07f8e0b03df2c87b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.2,ac0c9879c68b23278178a7afe738285b33ff1832,2024-10-25T23:04:51Z,cdec63b483271cff74566977f346271ce3cc3508599de150d427a2e97e67fe4b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0.1,cf18f250a40f33cc648d869eac833cd9aee86ed6,2024-10-25T23:05:17Z,04f463cee1e252480916f221556e72bf1b1d03df45d58c6d09afd9adefd6699d,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc3,47f121af8ee55a0dbe2a8c9ab85031ba052bad6b,2024-10-25T22:57:47Z,d287456c7bf237c5884d07445cfca277eb541e2fbae49d0d3b70946ba8132e59,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc2,85d0a8366e64f8272e332a63a2cba59afa3f7eb9,2024-10-25T22:57:08Z,949bad8c339694cbf4e4f6a84a3dd36914718e37358e257705512ea7e6605c44,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0-rc1,3c68ceea1f93119bed871866c690fbf5a95048c1,2024-10-25T23:04:36Z,9a39912e8fb6c12f9bc7e589be7cc176667d3b8a9c48c525a83e069f1ef18b1b,openssl-1.1.1w,wxwidgets-3.2.6 +OTP-25.0,4ed7957623e5ccbd420a09a506bd6bc9930fe93c,2024-10-25T22:09:27Z,a6d318a64dc1593b6bea301a799352229b904ac6ff18a1c1fcaec97f7718f83d,openssl-1.1.1w,wxwidgets-3.2.6 diff --git a/test/setup-beam.test.js b/test/setup-beam.test.js index de2d6dcc..af3f817d 100644 --- a/test/setup-beam.test.js +++ b/test/setup-beam.test.js @@ -15,6 +15,7 @@ const path = require('path') const setupBeam = require('../src/setup-beam') const { problemMatcher } = require('../matchers/elixir-matchers.json') const { describe, it } = require('node:test') +const csv = require('csv-parse/sync') const matrix = { otp: { @@ -23,6 +24,8 @@ const matrix = { 'ubuntu-22.04': parseBuild('test/otp/ubuntu-22.04/builds.txt'), 'ubuntu-24.04': parseBuild('test/otp/ubuntu-24.04/builds.txt'), windows: parseReleases('test/otp/releases.json'), + 'macos-aarch64': parseCsv('test/otp/macos/aarch64-apple-darwin.csv'), + 'macos-x86_64': parseCsv('test/otp/macos/x86_64-apple-darwin.csv'), }, elixir: parseBuild('test/elixir/builds.txt'), gleam: parseReleases('test/gleam/releases.json'), @@ -58,6 +61,15 @@ function parseReleases(version) { } } +function parseCsv(file) { + let versions = {} + let fileH = fs.readFileSync(file, 'utf8') + csv + .parse(fileH, { columns: true }) + .forEach((line) => (versions[line.ref_name] = line.ref_name)) + return versions +} + describe('OTP install', () => { it('fails for invalid OS version', async () => { const otpOSVersion = 'ubuntu-08.04' @@ -460,6 +472,36 @@ describe('.getOTPVersion(_) - Erlang', () => { }) } + if (process.platform === 'darwin') { + it('is Ok for known macos ARM64 version', async () => { + const arm64Options = setupBeam.githubARMRunnerArchs() + process.env.RUNNER_ARCH = + arm64Options[Math.floor(Math.random() * arm64Options.length)] + + before = simulateInput('version-type', 'strict') + spec = '28.0' + osVersion = 'macos-15-arm64' + expected = 'OTP-28.0' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + simulateInput('version-type', before) + }) + + it('is Ok for known macos AMD64 version', async () => { + const amd64Options = setupBeam.githubARMRunnerArchs() + process.env.RUNNER_ARCH = + amd64Options[Math.floor(Math.random() * amd64Options.length)] + + before = simulateInput('version-type', 'strict') + spec = '28.0' + osVersion = 'macos-15' + expected = 'OTP-28.0' + got = await setupBeam.getOTPVersion(spec, osVersion) + assert.deepStrictEqual(got, expected) + simulateInput('version-type', before) + }) + } + simulateInput('hexpm-mirrors', hexMirrors, { multiline: true }) process.env.RUNNER_ARCH = previousRunnerArch }) @@ -848,6 +890,16 @@ describe('.getVersionFromSpec(_)', () => { got = setupBeam.getVersionFromSpec(spec, matrix.otp.windows) assert.deepStrictEqual(got, expected) + spec = 'latest' + expected = 'OTP-28.0' + got = setupBeam.getVersionFromSpec(spec, matrix.otp['macos-aarch64']) + assert.deepStrictEqual(got, expected) + + spec = 'latest' + expected = 'OTP-28.0' + got = setupBeam.getVersionFromSpec(spec, matrix.otp['macos-x86_64']) + assert.deepStrictEqual(got, expected) + spec = 'latest' expected = 'v1.16.2' got = setupBeam.getVersionFromSpec(spec, matrix.elixir) From 87b0c112818adfd98554169f050d813c4bc2704f Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 22:34:43 +0100 Subject: [PATCH 5/7] Add exception for the way otp_builds creates -latest --- dist/index.js | 24 +++++++++++++++++++----- src/setup-beam.js | 24 +++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/dist/index.js b/dist/index.js index f6c3058b..8751ecc0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9641,8 +9641,14 @@ function isRC(ver) { return ver.match(xyzAbcVersion('^', '(?:-rc\\.?\\d+)')) } +const knownBranches = ['main', 'master', 'maint'] + function isKnownBranch(ver) { - return ['main', 'master', 'maint'].includes(ver) + return knownBranches.includes(ver) +} + +function isKnownVerBranch(ver) { + return knownBranches.some((b) => ver.match(b)) } function githubARMRunnerArchs() { @@ -9960,9 +9966,17 @@ async function install(toolName, opts) { }, }, darwin: { - downloadToolURL: () => - `https://github.com/erlef/otp_builds/releases/download/` + - `${toolVersion}/${toolVersion}-macos-${getRunnerOSArchitecture()}.tar.gz`, + 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'] @@ -10178,7 +10192,7 @@ async function installTool(opts) { core.debug(`Checking if ${installOpts.tool} is already cached...`) if (cachePath === '') { core.debug(" ... it isn't!") - const downloadToolURL = platformOpts.downloadToolURL() + const downloadToolURL = platformOpts.downloadToolURL(versionSpec) const file = await tc.downloadTool(downloadToolURL) const [targetElemType, targetElem] = await platformOpts.extract(file) diff --git a/src/setup-beam.js b/src/setup-beam.js index 7f0aad87..dd20c086 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -541,8 +541,14 @@ function isRC(ver) { return ver.match(xyzAbcVersion('^', '(?:-rc\\.?\\d+)')) } +const knownBranches = ['main', 'master', 'maint'] + function isKnownBranch(ver) { - return ['main', 'master', 'maint'].includes(ver) + return knownBranches.includes(ver) +} + +function isKnownVerBranch(ver) { + return knownBranches.some((b) => ver.match(b)) } function githubARMRunnerArchs() { @@ -860,9 +866,17 @@ async function install(toolName, opts) { }, }, darwin: { - downloadToolURL: () => - `https://github.com/erlef/otp_builds/releases/download/` + - `${toolVersion}/${toolVersion}-macos-${getRunnerOSArchitecture()}.tar.gz`, + 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'] @@ -1078,7 +1092,7 @@ async function installTool(opts) { core.debug(`Checking if ${installOpts.tool} is already cached...`) if (cachePath === '') { core.debug(" ... it isn't!") - const downloadToolURL = platformOpts.downloadToolURL() + const downloadToolURL = platformOpts.downloadToolURL(versionSpec) const file = await tc.downloadTool(downloadToolURL) const [targetElemType, targetElem] = await platformOpts.extract(file) From 28b2f9725749abf970a11e8e91867cf0310664d4 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 25 May 2025 22:54:04 +0100 Subject: [PATCH 6/7] Fix it for existing runners --- .github/workflows/macos.yml | 8 ++++---- README.md | 22 +++++++++------------- dist/index.js | 4 ---- src/setup-beam.js | 4 ---- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 13a83d04..4750d7d0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -43,17 +43,17 @@ jobs: os: 'macos-latest' version-type: 'strict' - otp-version: '27' - os: 'macos-24.04' + os: 'macos-15' version-type: 'strict' - otp-version: '26' - os: 'macos-24.04' + os: 'macos-15' version-type: 'strict' - otp-version: '25' - os: 'macos-24.04' + os: 'macos-15' - otp-version: '26' elixir-version: '1.16' rebar3-version: '3.25' - os: 'macos-22.04' + os: 'macos-14' - otp-version: '25.0' elixir-version: 'v1.13.4-otp-25' rebar3-version: '3.18.0' diff --git a/README.md b/README.md index 5bf6f52d..11704379 100644 --- a/README.md +++ b/README.md @@ -71,17 +71,15 @@ be the latest. This list presents the known working version combos between the target operating system and Erlang/OTP. -| Operating system | Erlang/OTP | OTP Architecture | Status | Supported by GHA? -|- |- | - |- |- -| `ubuntu-18.04` | 17.0 - 26.2 | x86_64, arm64 | ✅ | ❌ -| `ubuntu-20.04` | 20.0 - 28 | x86_64, arm64 | ✅ | ❌ -| `ubuntu-22.04` | 24.2 - 28 | x86_64, arm64 | ✅ | ✅ -| `ubuntu-24.04` | 24.3 - 28 | x86_64, arm64 | ✅ | ✅ -| `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ | ✅ -| `windows-2022` | 21\* - 28 | x86_64, x86 | ✅ | ✅ -| `macOS-13` | 25.0 - 28 | x86_64, x86 | ✅ | ✅ -| `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ | ✅ -| `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅ | ✅ +| Operating system | Erlang/OTP | OTP Architecture | Status +|- |- | - |- +| `ubuntu-22.04` | 24.2 - 28 | x86_64, arm64 | ✅ +| `ubuntu-24.04` | 24.3 - 28 | x86_64, arm64 | ✅ +| `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ +| `windows-2022` | 21\* - 28 | x86_64, x86 | ✅ +| `macOS-13` | 25.0 - 28 | x86_64, x86 | ✅ +| `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ +| `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅ **Note** \*: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, 22.0, etc. @@ -93,8 +91,6 @@ uses that to download assets: | ImageOS | Operating system |- |- -| `ubuntu18` | `ubuntu-18.04` -| `ubuntu20` | `ubuntu-20.04` | `ubuntu22` | `ubuntu-22.04` | `ubuntu24` | `ubuntu-24.04` | `win19` | `windows-2019` diff --git a/dist/index.js b/dist/index.js index 8751ecc0..7089b707 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9680,14 +9680,10 @@ function getRunnerOSArchitecture() { function getRunnerOSVersion() { // List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images const ImageOSToContainer = { - ubuntu18: 'ubuntu-18.04', // no longer supported by GHA - ubuntu20: 'ubuntu-20.04', // no longer supported by GHA ubuntu22: 'ubuntu-22.04', ubuntu24: 'ubuntu-24.04', win19: 'windows-2019', win22: 'windows-2022', - // The default, from GHA, for macos is always macOS but if we're using - // that we can't target a specific build macos13: 'macOS-13', macos14: 'macOS-14', macos15: 'macOS-15', diff --git a/src/setup-beam.js b/src/setup-beam.js index dd20c086..7f91ac24 100644 --- a/src/setup-beam.js +++ b/src/setup-beam.js @@ -580,14 +580,10 @@ function getRunnerOSArchitecture() { function getRunnerOSVersion() { // List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images const ImageOSToContainer = { - ubuntu18: 'ubuntu-18.04', // no longer supported by GHA - ubuntu20: 'ubuntu-20.04', // no longer supported by GHA ubuntu22: 'ubuntu-22.04', ubuntu24: 'ubuntu-24.04', win19: 'windows-2019', win22: 'windows-2022', - // The default, from GHA, for macos is always macOS but if we're using - // that we can't target a specific build macos13: 'macOS-13', macos14: 'macOS-14', macos15: 'macOS-15', From 44aa4b53835b28e574626bd6dae0ada199d3eb63 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Wed, 28 May 2025 20:54:48 +0100 Subject: [PATCH 7/7] Act on review suggestion Co-authored-by: Wojtek Mach --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11704379..2fb28521 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ and Erlang/OTP. | `ubuntu-24.04` | 24.3 - 28 | x86_64, arm64 | ✅ | `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ | `windows-2022` | 21\* - 28 | x86_64, x86 | ✅ -| `macOS-13` | 25.0 - 28 | x86_64, x86 | ✅ +| `macOS-13` | 25.0 - 28 | x86_64, arm64 | ✅ | `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ | `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅