-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathsetup-haskell.ts
More file actions
143 lines (132 loc) · 5.67 KB
/
setup-haskell.ts
File metadata and controls
143 lines (132 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import * as core from '@actions/core';
import * as io from '@actions/io';
import ensureError from 'ensure-error';
import * as fs from 'fs';
import * as path from 'path';
import {EOL} from 'os';
import {getOpts, getDefaults, Tool} from './opts';
import {addGhcupReleaseChannel, installTool, resetTool} from './installer';
import type {Arch, OS} from './opts';
import {exec} from '@actions/exec';
async function cabalConfig(): Promise<string> {
let out = Buffer.from('');
const append = (b: Buffer): Buffer => (out = Buffer.concat([out, b]));
await exec('cabal', ['--help'], {
silent: true,
listeners: {stdout: append, stderr: append}
});
// The last line of the cabal help text is printing the config file, e.g.:
//
// > You can edit the cabal configuration file to set defaults:
// > <<HOME>>/.cabal/config
//
// So trimming the last line will give us the name of the config file.
//
// Needless to say this is very brittle, but we secure this by a test
// in Cabal's testsuite: https://github.com/haskell/cabal/pull/9614
return out.toString().trim().split('\n').slice(-1)[0].trim();
}
export default async function run(
inputs: Record<string, string>
): Promise<void> {
try {
core.info('Preparing to setup a Haskell environment');
const os = process.platform as OS;
const arch = process.arch as Arch;
const opts = getOpts(getDefaults(os), os, inputs);
core.debug(`run: inputs = ${JSON.stringify(inputs)}`);
core.debug(`run: os = ${JSON.stringify(os)}`);
core.debug(`run: opts = ${JSON.stringify(opts)}`);
await core.group(`Preparing ghcup environment`, async () => {
// Andreas Abel, 2026-01-01, https://github.com/haskell-actions/setup/issues/78
// Add ghcup vanilla and prereleases channels by default.
// Andreas Abel, 2026-01-11, https://github.com/haskell-actions/setup/issues/136
// There are reasons not to include the vanilla channel by default:
// mpickering write:
// the "vanilla" channel is the bindists "as released by GHC upstream",
// which is a change from the default, which provides curated bindists.
// In particular on the cabal repo this meant a 9.0.2 bindist without
// profiling libraries was downloaded by CI and caused a test to fail.
// await addGhcupReleaseChannel('vanilla', os, arch);
await addGhcupReleaseChannel('prereleases', os, arch);
if (opts.ghcup.releaseChannel)
await addGhcupReleaseChannel(
opts.ghcup.releaseChannel.toString(),
os,
arch
);
});
for (const [t, {resolved}] of Object.entries(opts).filter(
o => o[1].enable
)) {
await core.group(`Preparing ${t} environment`, async () =>
resetTool(t as Tool, resolved, os, arch)
);
await core.group(`Installing ${t} version ${resolved}`, async () =>
installTool(t as Tool, resolved, os, arch)
);
}
if (opts.stack.setup)
await core.group('Pre-installing GHC with stack', async () =>
exec('stack', ['setup', opts.ghc.resolved])
);
if (opts.cabal.enable)
await core.group('Setting up cabal', async () => {
// Andreas, 2023-03-16, issue #210.
// Create .cabal/bin to activate non-XDG mode of cabal.
if (process.platform !== 'win32')
io.mkdirP(`${process.env.HOME}/.cabal/bin`);
// Create config only if it doesn't exist.
await exec('cabal', ['user-config', 'init'], {
silent: true,
ignoreReturnCode: true
});
// Set the 'store-dir' in the cabal configuration.
// Blindly appending is fine.
// Cabal merges these and picks the last defined option.
const configFile = await cabalConfig();
const storeDir =
process.platform === 'win32'
? 'C:\\sr'
: `${process.env.HOME}/.cabal/store`;
fs.appendFileSync(configFile, `store-dir: ${storeDir}${EOL}`);
core.setOutput('cabal-store', storeDir);
if (process.platform === 'win32') {
// Some Windows version cannot symlink, so we need to switch to 'install-method: copy'.
// Choco does this for us, but not GHCup: https://github.com/haskell/ghcup-hs/issues/808
// However, here we do not know whether we installed with choco or not, so do it always:
fs.appendFileSync(configFile, `install-method: copy${EOL}`);
fs.appendFileSync(configFile, `overwrite-policy: always${EOL}`);
} else {
// Issue #130: for non-choco installs, add ~/.cabal/bin to PATH
const installdir = `${process.env.HOME}/.cabal/bin`;
core.info(`Adding ${installdir} to PATH`);
core.addPath(installdir);
}
// Workaround the GHC nopie linking errors for ancient GHC versions
// NB: Is this _just_ for GHC 7.10.3?
if (opts.ghc.resolved === '7.10.3' && os !== 'win32') {
fs.appendFileSync(
configFile,
['program-default-options', ' ghc-options: -optl-no-pie'].join(
EOL
) + EOL
);
// We cannot use cabal user-config to normalize the config because of:
// https://github.com/haskell/cabal/issues/6823
// await exec('cabal user-config update');
}
if (opts.cabal.update && !opts.stack.enable) await exec('cabal update');
});
core.info(`##[add-matcher]${path.join(__dirname, '..', 'matcher.json')}`);
} catch (_error) {
const error = ensureError(_error);
if (core.isDebug()) {
// we don't fail here so that the error path can be tested in CI
core.setOutput('failed', true);
core.debug(error.message);
} else {
core.setFailed(error.message);
}
}
}