From a709f1e8535af9d0059045b3f668493324dbdfe1 Mon Sep 17 00:00:00 2001 From: Brandon Chinn Date: Wed, 12 Jul 2023 21:45:45 -0700 Subject: [PATCH] Add new ghcup-release-channels input --- .github/workflows/workflow.yml | 19 +++++++++++++- README.md | 47 ++++++++++++++++++++++------------ action.yml | 5 +++- dist/index.js | 45 +++++++++++++++++--------------- lib/opts.d.ts | 3 +-- lib/opts.js | 35 +++++++++++++++---------- lib/setup-haskell.js | 10 ++------ src/opts.ts | 41 +++++++++++++++++++---------- src/setup-haskell.ts | 12 ++------- 9 files changed, 131 insertions(+), 86 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 8a36768..1b6f592 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -95,13 +95,29 @@ jobs: # and of cabal just 2.4, 3.0, 3.2, 3.4 # according to https://launchpad.net/~hvr/+archive/ubuntu/ghc?field.series_filter=focal - # Test ghcup pre-release channel + # Any matrix combinations with latest-nightly should add the appropriate release channel + - plan: + ghc: latest-nightly + ghcup_release_channels: > + https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml + + # Test deprecated release channel still works for now - os: ubuntu-latest ghcup_release_channel: "https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml" plan: ghc: "9.6.0.20230111" cabal: "3.8" + # Test ghcup release channels + - os: ubuntu-latest + ghcup_release_channels: > + https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-0.0.7.yaml, + https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml, + https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-vanilla-0.0.7.yaml, + plan: + ghc: "9.6.0.20230111" + cabal: "3.8" + # setup does something special for 7.10.3 (issue #79) - os: ubuntu-20.04 plan: @@ -148,6 +164,7 @@ jobs: with: ghc-version: ${{ matrix.plan.ghc }} ghcup-release-channel: ${{ matrix.ghcup_release_channel }} + ghcup-release-channels: ${{ matrix.ghcup_release_channels }} cabal-version: ${{ matrix.plan.cabal }} cabal-update: ${{ matrix.cabal_update }} diff --git a/README.md b/README.md index c3a2eea..6624b89 100644 --- a/README.md +++ b/README.md @@ -184,23 +184,35 @@ jobs: ## Inputs -| Name | Description | Type | Default | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- | -| `ghc-version` | GHC version to use, e.g. `9.2` or `9.2.5`. | `string` | `latest` | -| `cabal-version` | Cabal version to use, e.g. `3.6`. | `string` | `latest` | -| `stack-version` | Stack version to use, e.g. `latest`. Stack will only be installed if `enable-stack` is set. | `string` | `latest` | -| `enable-stack` | If set, will setup Stack. | "boolean" | false/unset | -| `stack-no-global` | If set, `enable-stack` must be set. Prevents installing GHC and Cabal globally. | "boolean" | false/unset | -| `stack-setup-ghc` | If set, `enable-stack` must be set. Runs stack setup to install the specified GHC. (Note: setting this does _not_ imply `stack-no-global`.) | "boolean" | false/unset | -| `disable-matcher` | If set, disables match messages from GHC as GitHub CI annotations. | "boolean" | false/unset | -| `cabal-update` | If set to `false`, skip `cabal update` step. | `boolean` | `true` | -| `ghcup-release-channel` | If set, add a [release channel](https://www.haskell.org/ghcup/guide/#pre-release-channels) to ghcup. | `URL` | none | - -Note: "boolean" types are set/unset, not true/false. -That is, setting any "boolean" to a value other than the empty string (`""`) will be considered true/set. -However, to avoid confusion and for forward compatibility, it is still recommended to **only use value `true` to set a "boolean" flag.** - -In contrast, a proper `boolean` input like `cabal-update` only accepts values `true` and `false`. +| Name | Description | Type | Default | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- | +| `ghc-version` | GHC version to use, e.g. `9.2` or `9.2.5`. | `string` | `latest` | +| `cabal-version` | Cabal version to use, e.g. `3.6`. | `string` | `latest` | +| `stack-version` | Stack version to use, e.g. `latest`. Stack will only be installed if `enable-stack` is set. | `string` | `latest` | +| `enable-stack` | If set, will setup Stack. | "boolean" | false/unset | +| `stack-no-global` | If set, `enable-stack` must be set. Prevents installing GHC and Cabal globally. | "boolean" | false/unset | +| `stack-setup-ghc` | If set, `enable-stack` must be set. Runs stack setup to install the specified GHC. (Note: setting this does _not_ imply `stack-no-global`.) | "boolean" | false/unset | +| `disable-matcher` | If set, disables match messages from GHC as GitHub CI annotations. | "boolean" | false/unset | +| `cabal-update` | If set to `false`, skip `cabal update` step. | `boolean` | `true` | +| `ghcup-release-channels` | If set, add [release channels](https://www.haskell.org/ghcup/guide/#pre-release-channels) to ghcup. | `URL[]` | none | + +Notes: + +- "boolean" types are set/unset, not true/false. That is, setting any "boolean" to a value other than the empty string (`""`) will be considered true/set. + However, to avoid confusion and for forward compatibility, it is still recommended to **only use value `true` to set a "boolean" flag.** + + In contrast, a proper `boolean` input like `cabal-update` only accepts values `true` and `false`. + +- Inputs that can take multiple values (like `ghcup-release-channels`) should be specified as a comma separated list, e.g. + + ```yaml + - uses: haskell-actions/setup@v2 + with: + ghcup-release-channels: > + https://example.com/channel1, + https://example.com/channel2, + https://example.com/channel3, + ``` ## Outputs @@ -237,6 +249,7 @@ E.g., `8.10` will be resolved to `8.10.7`, and so will `8`. **GHC:** - `latest-nightly` + - This requires adding https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml to `ghcup-release-channels` - `latest` (default) - `9.6.2` `9.6` - `9.6.1` diff --git a/action.yml b/action.yml index fa7c89a..0e05e8d 100644 --- a/action.yml +++ b/action.yml @@ -30,9 +30,12 @@ inputs: # Note: 'cabal-update' only accepts 'true' and 'false' as values. # This is different from the other flags ('enable-stack', 'disable-matcher' etc.) # which are true as soon as they are not null. + ghcup-release-channels: + required: false + description: "Release channel URLs to add to ghcup via `ghcup config add-release-channel`." ghcup-release-channel: required: false - description: "A release channel URL to add to ghcup via `ghcup config add-release-channel`." + description: "Deprecated by ghcup-release-channels." disable-matcher: required: false description: 'If specified, disables match messages from GHC as GitHub CI annotations.' diff --git a/dist/index.js b/dist/index.js index e493cbf..18a89c7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -13665,7 +13665,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getOpts = exports.parseURL = exports.parseYAMLBoolean = exports.releaseRevision = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; +exports.getOpts = exports.parseYAMLBoolean = exports.releaseRevision = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; const core = __importStar(__nccwpck_require__(2186)); const fs_1 = __nccwpck_require__(7147); const js_yaml_1 = __nccwpck_require__(1917); @@ -13765,24 +13765,33 @@ function parseYAMLBoolean(name, val) { `Supported boolean values: \`true | True | TRUE | false | False | FALSE\``); } exports.parseYAMLBoolean = parseYAMLBoolean; -function parseURL(name, val) { - if (val === '') - return null; - try { - return new URL(val); - } - catch (e) { - throw new TypeError(`Action input "${name}" is not a valid URL`); - } +/** + * Parse a string as a comma-separated list. + */ +function parseCSV(val) { + return val + .split(',') + .map(s => s.trim()) + .filter(s => s != ''); } -exports.parseURL = parseURL; function getOpts({ ghc, cabal, stack }, os, inputs) { core.debug(`Inputs are: ${JSON.stringify(inputs)}`); const stackNoGlobal = (inputs['stack-no-global'] || '') !== ''; const stackSetupGhc = (inputs['stack-setup-ghc'] || '') !== ''; const stackEnable = (inputs['enable-stack'] || '') !== ''; const matcherDisable = (inputs['disable-matcher'] || '') !== ''; - const ghcupReleaseChannel = parseURL('ghcup-release-channel', inputs['ghcup-release-channel'] || ''); + if (inputs['ghcup-release-channel']) { + core.warning('ghcup-release-channel is deprecated in favor of ghcup-release-channels'); + inputs['ghcup-release-channels'] = inputs['ghcup-release-channel']; + } + const ghcupReleaseChannels = parseCSV(inputs['ghcup-release-channels'] ?? '').map(v => { + try { + return new URL(v); + } + catch (e) { + throw new TypeError(`Not a valid URL: ${v}`); + } + }); // Andreas, 2023-01-05, issue #29: // 'cabal-update' has a default value, so we should get a proper boolean always. // Andreas, 2023-01-06: This is not true if we use the action as a library. @@ -13814,7 +13823,7 @@ function getOpts({ ghc, cabal, stack }, os, inputs) { enable: ghcEnable }, ghcup: { - releaseChannel: ghcupReleaseChannel + releaseChannels: ghcupReleaseChannels }, cabal: { raw: verInpt.cabal, @@ -13898,15 +13907,9 @@ async function run(inputs) { core.debug(`run: inputs = ${JSON.stringify(inputs)}`); core.debug(`run: os = ${JSON.stringify(os)}`); core.debug(`run: opts = ${JSON.stringify(opts)}`); - const releaseChannels = [ - opts.ghcup.releaseChannel, - opts.ghc.raw === 'latest-nightly' - ? new URL('https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml') - : null - ].filter((v) => v !== null); - if (releaseChannels.length > 0) { + if (opts.ghcup.releaseChannels.length > 0) { await core.group(`Setting release channels`, async () => { - for (const channel of releaseChannels) { + for (const channel of opts.ghcup.releaseChannels) { await (0, installer_1.addGhcupReleaseChannel)(channel, os); } }); diff --git a/lib/opts.d.ts b/lib/opts.d.ts index 768fa1d..dce6ff8 100644 --- a/lib/opts.d.ts +++ b/lib/opts.d.ts @@ -16,7 +16,7 @@ export interface ProgramOpt { export interface Options { ghc: ProgramOpt; ghcup: { - releaseChannel: URL | null; + releaseChannels: URL[]; }; cabal: ProgramOpt & { update: boolean; @@ -85,6 +85,5 @@ export declare function releaseRevision(version: string, tool: Tool, os: OS): st * @returns boolean */ export declare function parseYAMLBoolean(name: string, val: string): boolean; -export declare function parseURL(name: string, val: string): URL | null; export declare function getOpts({ ghc, cabal, stack }: Defaults, os: OS, inputs: Record): Options; export {}; diff --git a/lib/opts.js b/lib/opts.js index e2734ca..b7ced5a 100644 --- a/lib/opts.js +++ b/lib/opts.js @@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getOpts = exports.parseURL = exports.parseYAMLBoolean = exports.releaseRevision = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; +exports.getOpts = exports.parseYAMLBoolean = exports.releaseRevision = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; const core = __importStar(require("@actions/core")); const fs_1 = require("fs"); const js_yaml_1 = require("js-yaml"); @@ -123,24 +123,33 @@ function parseYAMLBoolean(name, val) { `Supported boolean values: \`true | True | TRUE | false | False | FALSE\``); } exports.parseYAMLBoolean = parseYAMLBoolean; -function parseURL(name, val) { - if (val === '') - return null; - try { - return new URL(val); - } - catch (e) { - throw new TypeError(`Action input "${name}" is not a valid URL`); - } +/** + * Parse a string as a comma-separated list. + */ +function parseCSV(val) { + return val + .split(',') + .map(s => s.trim()) + .filter(s => s != ''); } -exports.parseURL = parseURL; function getOpts({ ghc, cabal, stack }, os, inputs) { core.debug(`Inputs are: ${JSON.stringify(inputs)}`); const stackNoGlobal = (inputs['stack-no-global'] || '') !== ''; const stackSetupGhc = (inputs['stack-setup-ghc'] || '') !== ''; const stackEnable = (inputs['enable-stack'] || '') !== ''; const matcherDisable = (inputs['disable-matcher'] || '') !== ''; - const ghcupReleaseChannel = parseURL('ghcup-release-channel', inputs['ghcup-release-channel'] || ''); + if (inputs['ghcup-release-channel']) { + core.warning('ghcup-release-channel is deprecated in favor of ghcup-release-channels'); + inputs['ghcup-release-channels'] = inputs['ghcup-release-channel']; + } + const ghcupReleaseChannels = parseCSV(inputs['ghcup-release-channels'] ?? '').map(v => { + try { + return new URL(v); + } + catch (e) { + throw new TypeError(`Not a valid URL: ${v}`); + } + }); // Andreas, 2023-01-05, issue #29: // 'cabal-update' has a default value, so we should get a proper boolean always. // Andreas, 2023-01-06: This is not true if we use the action as a library. @@ -172,7 +181,7 @@ function getOpts({ ghc, cabal, stack }, os, inputs) { enable: ghcEnable }, ghcup: { - releaseChannel: ghcupReleaseChannel + releaseChannels: ghcupReleaseChannels }, cabal: { raw: verInpt.cabal, diff --git a/lib/setup-haskell.js b/lib/setup-haskell.js index fb23933..8d4f8fc 100644 --- a/lib/setup-haskell.js +++ b/lib/setup-haskell.js @@ -52,15 +52,9 @@ async function run(inputs) { core.debug(`run: inputs = ${JSON.stringify(inputs)}`); core.debug(`run: os = ${JSON.stringify(os)}`); core.debug(`run: opts = ${JSON.stringify(opts)}`); - const releaseChannels = [ - opts.ghcup.releaseChannel, - opts.ghc.raw === 'latest-nightly' - ? new URL('https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml') - : null - ].filter((v) => v !== null); - if (releaseChannels.length > 0) { + if (opts.ghcup.releaseChannels.length > 0) { await core.group(`Setting release channels`, async () => { - for (const channel of releaseChannels) { + for (const channel of opts.ghcup.releaseChannels) { await (0, installer_1.addGhcupReleaseChannel)(channel, os); } }); diff --git a/src/opts.ts b/src/opts.ts index 7818af5..3abe297 100644 --- a/src/opts.ts +++ b/src/opts.ts @@ -24,7 +24,7 @@ export interface ProgramOpt { export interface Options { ghc: ProgramOpt; - ghcup: {releaseChannel: URL | null}; + ghcup: {releaseChannels: URL[]}; cabal: ProgramOpt & {update: boolean}; stack: ProgramOpt & {setup: boolean}; general: {matcher: {enable: boolean}}; @@ -138,13 +138,14 @@ export function parseYAMLBoolean(name: string, val: string): boolean { ); } -export function parseURL(name: string, val: string): URL | null { - if (val === '') return null; - try { - return new URL(val); - } catch (e) { - throw new TypeError(`Action input "${name}" is not a valid URL`); - } +/** + * Parse a string as a comma-separated list. + */ +function parseCSV(val: string): string[] { + return val + .split(',') + .map(s => s.trim()) + .filter(s => s != ''); } export function getOpts( @@ -157,10 +158,24 @@ export function getOpts( const stackSetupGhc = (inputs['stack-setup-ghc'] || '') !== ''; const stackEnable = (inputs['enable-stack'] || '') !== ''; const matcherDisable = (inputs['disable-matcher'] || '') !== ''; - const ghcupReleaseChannel = parseURL( - 'ghcup-release-channel', - inputs['ghcup-release-channel'] || '' - ); + + if (inputs['ghcup-release-channel']) { + core.warning( + 'ghcup-release-channel is deprecated in favor of ghcup-release-channels' + ); + inputs['ghcup-release-channels'] = inputs['ghcup-release-channel']; + } + + const ghcupReleaseChannels = parseCSV( + inputs['ghcup-release-channels'] ?? '' + ).map(v => { + try { + return new URL(v); + } catch (e) { + throw new TypeError(`Not a valid URL: ${v}`); + } + }); + // Andreas, 2023-01-05, issue #29: // 'cabal-update' has a default value, so we should get a proper boolean always. // Andreas, 2023-01-06: This is not true if we use the action as a library. @@ -204,7 +219,7 @@ export function getOpts( enable: ghcEnable }, ghcup: { - releaseChannel: ghcupReleaseChannel + releaseChannels: ghcupReleaseChannels }, cabal: { raw: verInpt.cabal, diff --git a/src/setup-haskell.ts b/src/setup-haskell.ts index a6b43d0..533e9a9 100644 --- a/src/setup-haskell.ts +++ b/src/setup-haskell.ts @@ -30,17 +30,9 @@ export default async function run( core.debug(`run: os = ${JSON.stringify(os)}`); core.debug(`run: opts = ${JSON.stringify(opts)}`); - const releaseChannels = [ - opts.ghcup.releaseChannel, - opts.ghc.raw === 'latest-nightly' - ? new URL( - 'https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml' - ) - : null - ].filter((v): v is URL => v !== null); - if (releaseChannels.length > 0) { + if (opts.ghcup.releaseChannels.length > 0) { await core.group(`Setting release channels`, async () => { - for (const channel of releaseChannels) { + for (const channel of opts.ghcup.releaseChannels) { await addGhcupReleaseChannel(channel, os); } });