Skip to content

Commit 76c6a05

Browse files
committed
Proper boolean options; enable-matcher; imply enable-stack
The options `enable-stack`, `stack-no-global`, `stack-setup-ghc` and `disable-matcher` are now proper booleans only accepting `true` or `false`. Previously, they were true when set to some non-empty string, even when set to "false" or "off" etc. `disable-matcher` is deprecated in favour of a new positive form `enable-matcher`. `enable-stack` is now implied by setting another stack-related option, i.e., one of `stack-version`, `stack-no-global` and `stack-setup-ghc`. Previously, it was a prerequisite to these options. Contradictory options now give an error, such as `stack-no-global` with `ghc-version` or `enable-stack: false` with `stack-version`. Fixes: haskell/actions#142
1 parent f8aac33 commit 76c6a05

File tree

7 files changed

+309
-104
lines changed

7 files changed

+309
-104
lines changed

README.md

+12-17
Original file line numberDiff line numberDiff line change
@@ -187,23 +187,18 @@ jobs:
187187
188188
## Inputs
189189
190-
| Name | Description | Type | Default |
191-
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- |
192-
| `ghc-version` | GHC version to use, e.g. `9.2` or `9.2.5`. | `string` | `latest` |
193-
| `cabal-version` | Cabal version to use, e.g. `3.6`. | `string` | `latest` |
194-
| `stack-version` | Stack version to use, e.g. `latest`. Stack will only be installed if `enable-stack` is set. | `string` | `latest` |
195-
| `enable-stack` | If set, will setup Stack. | "boolean" | false/unset |
196-
| `stack-no-global` | If set, `enable-stack` must be set. Prevents installing GHC and Cabal globally. | "boolean" | false/unset |
197-
| `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 |
198-
| `disable-matcher` | If set, disables match messages from GHC as GitHub CI annotations. | "boolean" | false/unset |
199-
| `cabal-update` | If set to `false`, skip `cabal update` step. | `boolean` | `true` |
200-
| `ghcup-release-channel` | If set, add a [release channel](https://www.haskell.org/ghcup/guide/#pre-release-channels) to ghcup. | `URL` | none |
201-
202-
Note: "boolean" types are set/unset, not true/false.
203-
That is, setting any "boolean" to a value other than the empty string (`""`) will be considered true/set.
204-
However, to avoid confusion and for forward compatibility, it is still recommended to **only use value `true` to set a "boolean" flag.**
205-
206-
In contrast, a proper `boolean` input like `cabal-update` only accepts values `true` and `false`.
190+
| Name | Description | Type | Default |
191+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- |
192+
| `ghc-version` | GHC version to use, e.g. `9.2` or `9.2.5`. | `string` | `latest` |
193+
| `cabal-version` | Cabal version to use, e.g. `3.6`. | `string` | `latest` |
194+
| `stack-version` | Stack version to use, e.g. `latest`. Implies `enable-stack`. | `string` | `latest` |
195+
| `enable-stack` | Setup Stack. Implied by `stack-version`, `stack-no-global`, `stack-setup-ghc`. | `boolean` | `false` |
196+
| `stack-no-global` | Implies `enable-stack`. Prevents installing GHC and Cabal globally. | `boolean` | `false` |
197+
| `stack-setup-ghc` | Implies `enable-stack`. Runs stack setup to install the specified GHC. (Note: setting this does _not_ imply `stack-no-global`.) | `boolean` | `false` |
198+
| `enable-matcher` | Enable match messages from GHC as GitHub CI annotations. | `boolean` | `true` |
199+
| `disable-matcher` | Disable match messages from GHC as GitHub CI annotations. (Legacy option, deprecated in favour of `enable-matcher`.) | `boolean` | `false` |
200+
| `cabal-update` | Perform `cabal update` step. (Default if Cabal is enabled.) | `boolean` | `true` |
201+
| `ghcup-release-channel` | If set, add a [release channel](https://www.haskell.org/ghcup/guide/#pre-release-channels) to ghcup. | `URL` | none |
207202

208203
## Outputs
209204

__tests__/find-haskell.test.ts

+51-4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ describe('haskell/actions/setup', () => {
4343
forAllTools(t => expect(def(os)[t].supported).toBe(supported_versions[t]))
4444
));
4545

46+
it('Setting enable-matcher to false disables matcher', () => {
47+
forAllOS(os => {
48+
const options = getOpts(def(os), os, {
49+
'enable-matcher': 'false'
50+
});
51+
expect(options.general.matcher.enable).toBe(false);
52+
});
53+
});
4654
it('Setting disable-matcher to true disables matcher', () => {
4755
forAllOS(os => {
4856
const options = getOpts(def(os), os, {
@@ -51,6 +59,25 @@ describe('haskell/actions/setup', () => {
5159
expect(options.general.matcher.enable).toBe(false);
5260
});
5361
});
62+
it('Setting both enable-matcher to false and disable-matcher to true disables matcher', () => {
63+
forAllOS(os => {
64+
const options = getOpts(def(os), os, {
65+
'enable-matcher': 'false',
66+
'disable-matcher': 'true'
67+
});
68+
expect(options.general.matcher.enable).toBe(false);
69+
});
70+
});
71+
it('Setting both enable-matcher and disable-matcher to true errors', () => {
72+
forAllOS(os =>
73+
expect(() =>
74+
getOpts(def(os), os, {
75+
'enable-matcher': 'true',
76+
'disable-matcher': 'true'
77+
})
78+
).toThrow()
79+
);
80+
});
5481

5582
it('getOpts grabs default general settings correctly from environment', () => {
5683
forAllOS(os => {
@@ -135,15 +162,35 @@ describe('haskell/actions/setup', () => {
135162
});
136163
});
137164

138-
it('Enabling stack-no-global without setting enable-stack errors', () => {
165+
it('Enabling stack-no-global but disabling enable-stack errors', () => {
139166
forAllOS(os =>
140-
expect(() => getOpts(def(os), os, {'stack-no-global': 'true'})).toThrow()
167+
expect(() =>
168+
getOpts(def(os), os, {
169+
'stack-no-global': 'true',
170+
'enable-stack': 'false'
171+
})
172+
).toThrow()
141173
);
142174
});
143175

144-
it('Enabling stack-setup-ghc without setting enable-stack errors', () => {
176+
it('Enabling stack-no-global but setting ghc-version errors', () => {
177+
forAllOS(os =>
178+
expect(() =>
179+
getOpts(def(os), os, {
180+
'stack-no-global': 'true',
181+
'ghc-version': 'latest'
182+
})
183+
).toThrow()
184+
);
185+
});
186+
it('Enabling stack-no-global but setting cabal-version errors', () => {
145187
forAllOS(os =>
146-
expect(() => getOpts(def(os), os, {'stack-setup-ghc': 'true'})).toThrow()
188+
expect(() =>
189+
getOpts(def(os), os, {
190+
'stack-no-global': 'true',
191+
'cabal-version': 'latest'
192+
})
193+
).toThrow()
147194
);
148195
});
149196
});

action.yml

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: 'Setup Haskell'
22
description: 'Set up a specific version of GHC and Cabal and add the command-line tools to the PATH'
3-
author: 'GitHub'
3+
author: 'Haskell community'
44
inputs:
55
ghc-version:
66
required: false
@@ -16,13 +16,16 @@ inputs:
1616
default: 'latest'
1717
enable-stack:
1818
required: false
19-
description: 'If specified, will setup Stack.'
19+
default: false
20+
description: 'If set to `true`, will setup default Stack. Implied by any of `stack-version`, `stack-no-global`, `stack-setup-ghc`.'
2021
stack-no-global:
2122
required: false
22-
description: 'If specified, enable-stack must be set. Prevents installing GHC and Cabal globally.'
23+
default: false
24+
description: 'If set to `true`, will setup Stack but will not install GHC and Cabal globally.'
2325
stack-setup-ghc:
2426
required: false
25-
description: 'If specified, enable-stack must be set. Will run stack setup to install the specified GHC.'
27+
default: false
28+
description: 'If set to `true`, will setup Stack. Will run `stack setup` to install the specified GHC.'
2629
cabal-update:
2730
required: false
2831
default: true
@@ -33,9 +36,14 @@ inputs:
3336
ghcup-release-channel:
3437
required: false
3538
description: "A release channel URL to add to ghcup via `ghcup config add-release-channel`."
39+
enable-matcher:
40+
required: false
41+
default: true
42+
description: 'Enable match messages from GHC as GitHub CI annotations.'
3643
disable-matcher:
3744
required: false
38-
description: 'If specified, disables match messages from GHC as GitHub CI annotations.'
45+
default: false
46+
description: 'Legacy input, use `enable-matcher` instead.'
3947
outputs:
4048
ghc-path:
4149
description: 'The path of the ghc executable _directory_'

dist/index.js

+69-24
Original file line numberDiff line numberDiff line change
@@ -13714,7 +13714,8 @@ exports.ghcup_version = sv.ghcup[0]; // Known to be an array of length 1
1371413714
* },
1371513715
* 'enable-stack': {
1371613716
* required: false,
13717-
* default: 'latest'
13717+
* description: '...',
13718+
* default: false
1371813719
* },
1371913720
* ...
1372013721
* }
@@ -13777,8 +13778,39 @@ function parseYAMLBoolean(name, val) {
1377713778
`Supported boolean values: \`true | True | TRUE | false | False | FALSE\``);
1377813779
}
1377913780
exports.parseYAMLBoolean = parseYAMLBoolean;
13781+
function parseBooleanInput(inputs, name, def) {
13782+
const val = inputs[name];
13783+
return val ? parseYAMLBoolean(name, val) : def;
13784+
}
13785+
/**
13786+
* Parse two opposite boolean options, one with default 'true' and the other with default 'false'.
13787+
* Return the value of the positive option.
13788+
* E.g. 'enable-matcher: true' and 'disable-matcher: false' would result in 'true'.
13789+
*
13790+
* @param inputs options as key-value map
13791+
* @param positive name (key) of the positive option (defaults to 'true')
13792+
* @param negative name (key) of the negative option (defaults to 'false')
13793+
*/
13794+
function parseOppositeBooleanInputs(inputs, positive, negative) {
13795+
if (!inputs[negative]) {
13796+
return parseBooleanInput(inputs, positive, true);
13797+
}
13798+
else if (!inputs[positive]) {
13799+
return !parseBooleanInput(inputs, negative, false);
13800+
}
13801+
else {
13802+
const pos = parseBooleanInput(inputs, positive, true);
13803+
const neg = parseBooleanInput(inputs, negative, false);
13804+
if (pos == !neg) {
13805+
return pos;
13806+
}
13807+
else {
13808+
throw new Error(`Action input ${positive}: ${pos} contradicts ${negative}: ${neg}`);
13809+
}
13810+
}
13811+
}
1378013812
function parseURL(name, val) {
13781-
if (val === '')
13813+
if (!val)
1378213814
return undefined;
1378313815
try {
1378413816
return new URL(val);
@@ -13788,36 +13820,49 @@ function parseURL(name, val) {
1378813820
}
1378913821
}
1379013822
exports.parseURL = parseURL;
13823+
function parseURLInput(inputs, name) {
13824+
return parseURL(name, inputs[name]);
13825+
}
1379113826
function getOpts({ ghc, cabal, stack }, os, inputs) {
1379213827
core.debug(`Inputs are: ${JSON.stringify(inputs)}`);
13793-
const stackNoGlobal = (inputs['stack-no-global'] || '') !== '';
13794-
const stackSetupGhc = (inputs['stack-setup-ghc'] || '') !== '';
13795-
const stackEnable = (inputs['enable-stack'] || '') !== '';
13796-
const matcherDisable = (inputs['disable-matcher'] || '') !== '';
13797-
const ghcupReleaseChannel = parseURL('ghcup-release-channel', inputs['ghcup-release-channel'] || '');
13798-
// Andreas, 2023-01-05, issue #29:
13799-
// 'cabal-update' has a default value, so we should get a proper boolean always.
13800-
// Andreas, 2023-01-06: This is not true if we use the action as a library.
13801-
// Thus, need to patch with default value here.
13802-
const cabalUpdate = parseYAMLBoolean('cabal-update', inputs['cabal-update'] || 'true');
13803-
core.debug(`${stackNoGlobal}/${stackSetupGhc}/${stackEnable}`);
13828+
const ghcVersion = inputs['ghc-version'];
13829+
const cabalVersion = inputs['cabal-version'];
13830+
const stackVersion = inputs['stack-version'];
13831+
const stackNoGlobal = parseBooleanInput(inputs, 'stack-no-global', false);
13832+
const stackSetupGhc = parseBooleanInput(inputs, 'stack-setup-ghc', false);
13833+
const stackDefault = stackNoGlobal || stackSetupGhc || !!stackVersion;
13834+
const stackEnable = parseBooleanInput(inputs, 'enable-stack', stackDefault);
13835+
const ghcEnable = !stackNoGlobal;
13836+
const cabalEnable = !stackNoGlobal;
13837+
const cabalUpdate = parseBooleanInput(inputs, 'cabal-update', cabalEnable);
13838+
const matcherEnable = parseOppositeBooleanInputs(inputs, 'enable-matcher', 'disable-matcher');
13839+
// disable-matcher is kept for backwards compatibility
13840+
// positive options like enable-matcher are preferable
13841+
const ghcupReleaseChannel = parseURLInput(inputs, 'ghcup-release-channel');
1380413842
const verInpt = {
13805-
ghc: inputs['ghc-version'] || ghc.version,
13806-
cabal: inputs['cabal-version'] || cabal.version,
13807-
stack: inputs['stack-version'] || stack.version
13843+
ghc: ghcVersion || ghc.version,
13844+
cabal: cabalVersion || cabal.version,
13845+
stack: stackVersion || stack.version
1380813846
};
13847+
// Check inputs for consistency
1380913848
const errors = [];
13810-
if (stackNoGlobal && !stackEnable) {
13811-
errors.push('enable-stack is required if stack-no-global is set');
13812-
}
13813-
if (stackSetupGhc && !stackEnable) {
13814-
errors.push('enable-stack is required if stack-setup-ghc is set');
13849+
if (!stackEnable) {
13850+
if (stackNoGlobal)
13851+
errors.push('Action input `enable-stack: false` contradicts `stack-no-global: true`');
13852+
if (stackSetupGhc)
13853+
errors.push('Action input `enable-stack: false` contradicts `stack-setup-ghc: true`');
13854+
if (stackVersion)
13855+
errors.push('Action input `enable-stack: false` contradicts setting `stack-version`');
13856+
}
13857+
if (stackNoGlobal) {
13858+
if (ghcVersion)
13859+
errors.push('Action input `stack-no-global: true` contradicts setting `ghc-version');
13860+
if (cabalVersion)
13861+
errors.push('Action input `stack-no-global: true` contradicts setting `cabal-version');
1381513862
}
1381613863
if (errors.length > 0) {
1381713864
throw new Error(errors.join('\n'));
1381813865
}
13819-
const ghcEnable = !stackNoGlobal;
13820-
const cabalEnable = !stackNoGlobal;
1382113866
const opts = {
1382213867
ghc: {
1382313868
raw: verInpt.ghc,
@@ -13842,7 +13887,7 @@ function getOpts({ ghc, cabal, stack }, os, inputs) {
1384213887
enable: stackEnable,
1384313888
setup: stackSetupGhc
1384413889
},
13845-
general: { matcher: { enable: !matcherDisable } }
13890+
general: { matcher: { enable: matcherEnable } }
1384613891
};
1384713892
core.debug(`Options are: ${JSON.stringify(opts)}`);
1384813893
return opts;

lib/opts.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export type Defaults = Record<Tool, Version> & {
6363
* },
6464
* 'enable-stack': {
6565
* required: false,
66-
* default: 'latest'
66+
* description: '...',
67+
* default: false
6768
* },
6869
* ...
6970
* }

0 commit comments

Comments
 (0)