Implement fallback install and outputs (#2)
* Add better version support. Restructure code. Implement OS agnostic fallback * Support latest for cabal and stack. Add enable-stack option. * Add outputs * Update documentation. Change default for GHC to latest.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
dist/
|
dist/
|
||||||
lib/
|
.out/
|
||||||
|
__tests__/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
19
.github/workflows/workflow.yml
vendored
19
.github/workflows/workflow.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
node-version: 12
|
node-version: 12
|
||||||
# http://www.tiernok.com/posts/2019/faster-npm-installs-during-ci/
|
# http://www.tiernok.com/posts/2019/faster-npm-installs-during-ci/
|
||||||
- run: npm ci --prefer-offline --no-audit --progress=false
|
- run: npm ci --prefer-offline --no-audit --progress=false
|
||||||
- run: npm run pre-push
|
- run: npm test
|
||||||
|
|
||||||
install-haskell:
|
install-haskell:
|
||||||
name: GHC ${{ matrix.ghc }}, Cabal ${{ matrix.cabal }} - ${{ matrix.os }}
|
name: GHC ${{ matrix.ghc }}, Cabal ${{ matrix.cabal }} - ${{ matrix.os }}
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
ghc: ['8.8.3', '8.4.4']
|
ghc: ['8.8', '8.4.4']
|
||||||
cabal: ['3.0.0.0']
|
cabal: ['3.0.0.0']
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
@@ -55,14 +55,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ghc-version: ${{ matrix.ghc }}
|
ghc-version: ${{ matrix.ghc }}
|
||||||
cabal-version: ${{ matrix.cabal }}
|
cabal-version: ${{ matrix.cabal }}
|
||||||
- run: runhaskell __tests__/hello.hs
|
- run: |
|
||||||
|
runhaskell --version
|
||||||
|
runhaskell __tests__/hello.hs
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: cd __tests__/project && cabal build && cabal run
|
run: cd __tests__/project && cabal build && cabal run
|
||||||
|
- run: |
|
||||||
|
cabal --version
|
||||||
|
ghc --version
|
||||||
|
|
||||||
install-stack:
|
install-stack:
|
||||||
name: Stack ${{ matrix.stack }} ${{ matrix.os }}
|
name: Stack ${{ matrix.stack }} ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
stack: ['latest', '1.9.1']
|
stack: ['latest', '1.9.1']
|
||||||
@@ -72,6 +78,9 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./
|
- uses: ./
|
||||||
with:
|
with:
|
||||||
stack-version: ${{ matrix.stack }}
|
enable-stack: true
|
||||||
stack-no-global: true
|
stack-no-global: true
|
||||||
- run: stack
|
stack-version: ${{ matrix.stack }}
|
||||||
|
- run: |
|
||||||
|
stack --version
|
||||||
|
stack
|
||||||
|
|||||||
101
.gitignore
vendored
101
.gitignore
vendored
@@ -1,101 +1,4 @@
|
|||||||
# Dependency directory
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# next.js build output
|
|
||||||
.next
|
|
||||||
|
|
||||||
# nuxt.js build output
|
|
||||||
.nuxt
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# OS metadata
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Ignore built ts files
|
|
||||||
__tests__/runner/*
|
|
||||||
lib/**/*
|
|
||||||
|
|
||||||
dist-newstyle
|
dist-newstyle
|
||||||
|
.out
|
||||||
|
.eslintcache
|
||||||
|
|||||||
6
.lintstagedrc.js
Normal file
6
.lintstagedrc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
'!(*test).{js,ts}': 'eslint --cache --fix',
|
||||||
|
'!(*test).ts': () => ['ncc build', 'git add dist'],
|
||||||
|
'src/**/*.ts': () => 'tsc -p tsconfig.json',
|
||||||
|
'*.{js,ts,json,md}': 'prettier --write'
|
||||||
|
};
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
dist/
|
dist/
|
||||||
lib/
|
.out/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
80
README.md
80
README.md
@@ -6,9 +6,10 @@ This action sets up a Haskell environment for use in actions by:
|
|||||||
|
|
||||||
- optionally installing a version of [ghc](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/) and [cabal](https://www.haskell.org/cabal/) and adding to PATH.
|
- optionally installing a version of [ghc](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/) and [cabal](https://www.haskell.org/cabal/) and adding to PATH.
|
||||||
- optionally installing a version of [Stack](https://haskellstack.org) and adding to PATH.
|
- optionally installing a version of [Stack](https://haskellstack.org) and adding to PATH.
|
||||||
|
- setting the outputs of `ghc-path`, `cabal-path`, `stack-path`, and `cabal-store` when necessary.
|
||||||
|
|
||||||
The GitHub runners come with [pre-installed versions of GHC and Cabal](https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners). Those will be used whenever possible.
|
The GitHub runners come with [pre-installed versions of GHC and Cabal](https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners). Those will be used whenever possible.
|
||||||
For all other versions, this action utilizes [`ppa:hvr/ghc`](https://launchpad.net/~hvr/+archive/ubuntu/ghc), [`ghcup`](https://gitlab.haskell.com/ghcup), and [`chocolatey`](https://chocolatey.org/packages/ghc).
|
For all other versions, this action utilizes [`ppa:hvr/ghc`](https://launchpad.net/~hvr/+archive/ubuntu/ghc), [`ghcup`](https://gitlab.haskell.org/haskell/ghcup-hs), and [`chocolatey`](https://chocolatey.org/packages/ghc).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -27,8 +28,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-haskell@v1
|
- uses: actions/setup-haskell@v1
|
||||||
with:
|
with:
|
||||||
ghc-version: '8.8.3' # Exact version of ghc to use
|
ghc-version: '8.8' # Resolves to the latest point release of GHC 8.8
|
||||||
cabal-version: '3.0.0.0'
|
cabal-version: '3.0.0.0' # Exact version of Cabal
|
||||||
- run: runhaskell Hello.hs
|
- run: runhaskell Hello.hs
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ jobs:
|
|||||||
- uses: actions/setup-haskell@v1
|
- uses: actions/setup-haskell@v1
|
||||||
with:
|
with:
|
||||||
ghc-version: '8.8.3' # Exact version of ghc to use
|
ghc-version: '8.8.3' # Exact version of ghc to use
|
||||||
cabal-version: '3.0.0.0'
|
# cabal-version: 'latest'. Omitted, but defalts to 'latest'
|
||||||
stack-version: 'latest'
|
stack-version: 'latest'
|
||||||
- run: runhaskell Hello.hs
|
- run: runhaskell Hello.hs
|
||||||
```
|
```
|
||||||
@@ -81,62 +82,85 @@ jobs:
|
|||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
| Name | Required | Description | Type | Default |
|
| Name | Required | Description | Type | Default |
|
||||||
| ----------------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
|
| ----------------- | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
|
||||||
| `ghc-version` | | GHC version to use, ex. `8.8.3` | string | 8.8.3 |
|
| `ghc-version` | | GHC version to use, ex. `latest` | string | latest |
|
||||||
| `cabal-version` | | Cabal version to use, ex. `3.0.0.0` | string | 3.0.0.0 |
|
| `cabal-version` | | Cabal version to use, ex. `3.2` | string | latest |
|
||||||
| `stack-version` | | Stack version to use, ex. `latest`. Stack will only be installed if this option is set | string | |
|
| `stack-version` | | Stack version to use, ex. `latest`. Stack will only be installed if enable-stack is set. | string | latest |
|
||||||
| `stack-no-global` | | If specified, stack-version must be set. Prevents installing GHC and Cabal globally | "boolean" | |
|
| `enable-stack` | | If specified, will setup Stack. | "boolean" | false |
|
||||||
| `stack-setup-ghc` | | If specified, stack-version must be set. Runs stack setup to install the specified GHC. (Note: setting this does _not_ imply `stack-no-global`) | "boolean" | 3.0.0.0 |
|
| `stack-no-global` | | If specified, enable-stack must be set. Prevents installing GHC and Cabal globally | "boolean" | false |
|
||||||
|
| `stack-setup-ghc` | | If specified, enable-stack must be set. Runs stack setup to install the specified GHC. (Note: setting this does _not_ imply `stack-no-global`) | "boolean" | false |
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
| Name | Description | Type |
|
||||||
|
| ------------- | -------------------------------------------- | ------ |
|
||||||
|
| `ghc-path` | The path of the ghc executable _directory_ | string |
|
||||||
|
| `cabal-path` | The path of the cabal executable _directory_ | string |
|
||||||
|
| `stack-path` | The path of the stack executable _directory_ | string |
|
||||||
|
| `cabal-store` | The path to the cabal store | string |
|
||||||
|
| `ghc-exe` | The path of the ghc _executable_ | string |
|
||||||
|
| `cabal-exe` | The path of the cabal _executable_ | string |
|
||||||
|
| `stack-exe` | The path of the stack _executable_ | string |
|
||||||
|
|
||||||
## Version Support
|
## Version Support
|
||||||
|
|
||||||
**GHC:**
|
**GHC:**
|
||||||
|
|
||||||
- `8.8.3` (default)
|
- `latest` (default, recommended)
|
||||||
|
- `8.10.1` `8.10`
|
||||||
|
- `8.8.3` `8.8`
|
||||||
- `8.8.2`
|
- `8.8.2`
|
||||||
- `8.8.1`
|
- `8.8.1`
|
||||||
- `8.6.5`
|
- `8.6.5` `8.6`
|
||||||
- `8.6.4`
|
- `8.6.4`
|
||||||
- `8.6.3`
|
- `8.6.3`
|
||||||
- `8.6.2`
|
- `8.6.2`
|
||||||
- `8.6.1`
|
- `8.6.1`
|
||||||
- `8.4.4`
|
- `8.4.4` `8.4`
|
||||||
- `8.4.3`
|
- `8.4.3`
|
||||||
- `8.4.2`
|
- `8.4.2`
|
||||||
- `8.4.1`
|
- `8.4.1`
|
||||||
- `8.2.2`
|
- `8.2.2` `8.2`
|
||||||
- `8.0.2`
|
- `8.0.2` `8.0`
|
||||||
- `7.10.3`
|
- `7.10.3` `7.10`
|
||||||
|
|
||||||
|
Suggestion: Try to support the three latest major versions of GHC.
|
||||||
|
|
||||||
**Cabal:**
|
**Cabal:**
|
||||||
|
|
||||||
- `3.0.0.0` (default)
|
- `latest` (default, recommended)
|
||||||
- `2.4.1.0`
|
- `3.2.0.0` `3.2`
|
||||||
|
- `3.0.0.0` `3.0`
|
||||||
|
- `2.4.1.0` `2.4`
|
||||||
- `2.4.0.0`
|
- `2.4.0.0`
|
||||||
- `2.2.0.0`
|
- `2.2.0.0` `2.2`
|
||||||
|
|
||||||
Recommendation: Cabal is almost always fully backwards compatible and so for most purposes, using the latest available version is sufficient
|
Recommendation: Use the latest available version if possible.
|
||||||
|
|
||||||
**Stack:**
|
**Stack:**
|
||||||
|
|
||||||
- `latest` (recommended) -- follows the latest release automatically.
|
- `latest` (recommended) -- follows the latest release automatically.
|
||||||
- `2.1.3`
|
- `2.1.3` `2.1`
|
||||||
- `2.1.1`
|
- `2.1.1`
|
||||||
- `1.9.3.1`
|
- `1.9.3.1` `1.9`
|
||||||
- `1.9.1.1`
|
- `1.9.1.1`
|
||||||
- `1.7.1`
|
- `1.7.1` `1.7`
|
||||||
- `1.6.5`
|
- `1.6.5` `1.6`
|
||||||
- `1.6.3.1`
|
- `1.6.3.1`
|
||||||
- `1.6.1.1`
|
- `1.6.1.1`
|
||||||
- `1.5.1`
|
- `1.5.1` `1.5`
|
||||||
- `1.5.0`
|
- `1.5.0`
|
||||||
|
- `1.4.0` `1.4`
|
||||||
|
- `1.3.2` `1.3`
|
||||||
|
- `1.3.0`
|
||||||
|
- `1.2.0` `1.2`
|
||||||
|
|
||||||
Recommendation: Stack follows SemVer, and additionally attempts to remain backwards compatible across major versions whenever reasonable. Using the latest available version is almost always sufficient
|
Recommendation: Use the latest available version if possible.
|
||||||
|
|
||||||
The full list of available versions of GHC, Cabal, and Stack are as follows:
|
The full list of available versions of GHC, Cabal, and Stack are as follows:
|
||||||
|
|
||||||
- [Linux/macOS - Cabal and GHC](https://gitlab.haskell.org/haskell/ghcup/blob/master/.available-versions)
|
- [Linux/macOS - Cabal and GHC](https://www.haskell.org/ghc/download.html)
|
||||||
- [Windows - Cabal](https://chocolatey.org/packages/cabal#versionhistory).
|
- [Windows - Cabal](https://chocolatey.org/packages/cabal#versionhistory).
|
||||||
- [Windows - GHC](https://chocolatey.org/packages/ghc#versionhistory)
|
- [Windows - GHC](https://chocolatey.org/packages/ghc#versionhistory)
|
||||||
- [Linux/macOS/Windows - Stack](https://github.com/commercialhaskell/stack/tags)
|
- [Linux/macOS/Windows - Stack](https://github.com/commercialhaskell/stack/tags)
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import {getOpts, getDefaults} from '../src/installer';
|
import {getOpts, getDefaults, Tool} from '../src/opts';
|
||||||
import {getInput} from '@actions/core';
|
import {getInput} from '@actions/core';
|
||||||
|
import * as supported_versions from '../src/versions.json';
|
||||||
|
|
||||||
const def = getDefaults();
|
const def = getDefaults();
|
||||||
|
|
||||||
const environments = {
|
|
||||||
empty: {'ghc-version': null, 'cabal-version': null},
|
|
||||||
stack: {'stack-version': '2.1.3'},
|
|
||||||
stacklatest: {'stack-version': 'latest'},
|
|
||||||
stackOnly: {'stack-version': 'latest', 'stack-no-global': 'true'},
|
|
||||||
stackOnlyWrong: {'stack-no-global': 'true'},
|
|
||||||
stackOnlyWrong2: {'stack-setup-ghc': 'true'}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mkName = (s: string): string =>
|
const mkName = (s: string): string =>
|
||||||
`INPUT_${s.replace(/ /g, '_').toUpperCase()}`;
|
`INPUT_${s.replace(/ /g, '_').toUpperCase()}`;
|
||||||
|
|
||||||
const setupEnv = (o: Record<string, unknown>): void =>
|
const setupEnv = (o: Record<string, unknown>): void =>
|
||||||
Object.entries(o).forEach(([k, v]) => v && (process.env[mkName(k)] = `${v}`));
|
Object.entries(o).forEach(([k, v]) => v && (process.env[mkName(k)] = `${v}`));
|
||||||
|
|
||||||
|
const forAll = (fn: (t: Tool) => any) =>
|
||||||
|
(['ghc', 'cabal', 'stack'] as const).forEach(fn);
|
||||||
|
|
||||||
describe('actions/setup-haskell', () => {
|
describe('actions/setup-haskell', () => {
|
||||||
const OLD_ENV = process.env;
|
const OLD_ENV = process.env;
|
||||||
|
|
||||||
@@ -29,13 +24,13 @@ describe('actions/setup-haskell', () => {
|
|||||||
|
|
||||||
afterEach(() => (process.env = OLD_ENV));
|
afterEach(() => (process.env = OLD_ENV));
|
||||||
|
|
||||||
it('Parses action.yml to get correct default GHC', () => {
|
it('Parses action.yml to get correct default versions', () => {
|
||||||
expect(def.ghc.version).toBe('8.8.3');
|
const defs = {ghc: '8.10.1', cabal: '3.2.0.0', stack: '2.1.3'};
|
||||||
|
forAll(t => expect(def[t].version).toBe(defs[t]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Parses action.yml to get correct default Cabal', () => {
|
it('Supported versions are parsed from JSON correctly', () =>
|
||||||
expect(def.cabal.version).toBe('3.0.0.0');
|
forAll(t => expect(def[t].supported).toBe(supported_versions[t])));
|
||||||
});
|
|
||||||
|
|
||||||
it('[meta] Setup Env works', () => {
|
it('[meta] Setup Env works', () => {
|
||||||
setupEnv({input: 'value'});
|
setupEnv({input: 'value'});
|
||||||
@@ -44,36 +39,60 @@ describe('actions/setup-haskell', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getOpts grabs defaults correctly from environment', () => {
|
it('getOpts grabs defaults correctly from environment', () => {
|
||||||
setupEnv(environments.empty);
|
setupEnv({});
|
||||||
const options = getOpts(def);
|
const options = getOpts(def);
|
||||||
expect(options.ghc.version).toBe(def.ghc.version);
|
forAll(t => expect(options[t].raw).toBe(def[t].version));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Enabling stack does not disable GHC', () => {
|
it('Versions resolve correctly', () => {
|
||||||
setupEnv(environments.stack);
|
const v = {ghc: '8.6.5', cabal: '2.4.1.0', stack: '2.1.3'};
|
||||||
const {ghc, stack} = getOpts(def);
|
setupEnv({
|
||||||
expect({
|
'stack-version': '2.1',
|
||||||
ghc: ghc.enable,
|
'ghc-version': '8.6',
|
||||||
stack: stack.enable
|
'cabal-version': '2.4'
|
||||||
}).toStrictEqual({ghc: true, stack: true});
|
});
|
||||||
|
const options = getOpts(def);
|
||||||
|
forAll(t => expect(options[t].resolved).toBe(v[t]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Enabling stack-no-global does disable GHC and Cabal', () => {
|
it('"latest" Versions resolve correctly', () => {
|
||||||
setupEnv(environments.stackOnly);
|
const v = {ghc: '8.6.5', cabal: '2.4.1.0', stack: '2.1.3'};
|
||||||
const {ghc, cabal} = getOpts(def);
|
setupEnv({
|
||||||
|
'stack-version': '2.1',
|
||||||
|
'ghc-version': '8.6',
|
||||||
|
'cabal-version': '2.4'
|
||||||
|
});
|
||||||
|
const options = getOpts(def);
|
||||||
|
forAll(t => expect(options[t].resolved).toBe(v[t]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Enabling stack does not disable GHC or Cabal', () => {
|
||||||
|
setupEnv({'enable-stack': 'true'});
|
||||||
|
const {ghc, cabal, stack} = getOpts(def);
|
||||||
expect({
|
expect({
|
||||||
ghc: ghc.enable,
|
ghc: ghc.enable,
|
||||||
|
stack: stack.enable,
|
||||||
cabal: cabal.enable
|
cabal: cabal.enable
|
||||||
}).toStrictEqual({ghc: false, cabal: false});
|
}).toStrictEqual({ghc: true, cabal: true, stack: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Enabling stack-no-global without setting stack-version errors', () => {
|
it('Enabling stack-no-global disables GHC and Cabal', () => {
|
||||||
setupEnv(environments.stackOnlyWrong);
|
setupEnv({'enable-stack': 'true', 'stack-no-global': 'true'});
|
||||||
|
const {ghc, cabal, stack} = getOpts(def);
|
||||||
|
expect({
|
||||||
|
ghc: ghc.enable,
|
||||||
|
cabal: cabal.enable,
|
||||||
|
stack: stack.enable
|
||||||
|
}).toStrictEqual({ghc: false, cabal: false, stack: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Enabling stack-no-global without setting enable-stack errors', () => {
|
||||||
|
setupEnv({'stack-no-global': 'true'});
|
||||||
expect(() => getOpts(def)).toThrow();
|
expect(() => getOpts(def)).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Enabling stack-setup-ghc without setting stack-version errors', () => {
|
it('Enabling stack-setup-ghc without setting enable-stack errors', () => {
|
||||||
setupEnv(environments.stackOnlyWrong2);
|
setupEnv({'stack-setup-ghc': 'true'});
|
||||||
expect(() => getOpts(def)).toThrow();
|
expect(() => getOpts(def)).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
33
action.yml
33
action.yml
@@ -4,21 +4,40 @@ author: 'GitHub'
|
|||||||
inputs:
|
inputs:
|
||||||
ghc-version:
|
ghc-version:
|
||||||
required: false
|
required: false
|
||||||
description: 'Exact version of ghc to use.'
|
description: 'Version of GHC to use. If set to "latest", it will always get the latest stable version.'
|
||||||
default: '8.8.3'
|
default: 'latest'
|
||||||
cabal-version:
|
cabal-version:
|
||||||
required: false
|
required: false
|
||||||
description: 'Exact version of cabal to use.'
|
description: 'Version of Cabal to use. If set to "latest", it will always get the latest stable version.'
|
||||||
default: '3.0.0.0'
|
default: 'latest'
|
||||||
stack-version:
|
stack-version:
|
||||||
required: false
|
required: false
|
||||||
description: 'If specified, will download the given version of stack. If set to "latest", it will always get the latest stable version of stack.'
|
description: 'Version of Stack to use. If set to "latest", it will always get the latest stable version.'
|
||||||
|
default: 'latest'
|
||||||
|
enable-stack:
|
||||||
|
required: false
|
||||||
|
description: 'If specified, will setup Stack'
|
||||||
stack-no-global:
|
stack-no-global:
|
||||||
required: false
|
required: false
|
||||||
description: 'If specified, stack-version must be set. Prevents installing GHC and Cabal globally'
|
description: 'If specified, enable-stack must be set. Prevents installing GHC and Cabal globally'
|
||||||
stack-setup-ghc:
|
stack-setup-ghc:
|
||||||
required: false
|
required: false
|
||||||
description: 'If specified, will run stack setup to install the specified GHC'
|
description: 'If specified, enable-stack must be set. Will run stack setup to install the specified GHC'
|
||||||
|
outputs:
|
||||||
|
ghc-path:
|
||||||
|
description: 'The path of the ghc executable _directory_'
|
||||||
|
cabal-path:
|
||||||
|
description: 'The path of the cabal executable _directory_'
|
||||||
|
stack-path:
|
||||||
|
description: 'The path of the stack executable _directory_'
|
||||||
|
cabal-store:
|
||||||
|
description: 'The path to the cabal store'
|
||||||
|
ghc-exe:
|
||||||
|
description: 'The path of the ghc _executable_'
|
||||||
|
cabal-exe:
|
||||||
|
description: 'The path of the cabal _executable_'
|
||||||
|
stack-exe:
|
||||||
|
description: 'The path of the stack _executable_'
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
21
dist/action.yml
vendored
21
dist/action.yml
vendored
@@ -4,12 +4,25 @@ author: 'GitHub'
|
|||||||
inputs:
|
inputs:
|
||||||
ghc-version:
|
ghc-version:
|
||||||
required: false
|
required: false
|
||||||
description: 'Exact version of ghc to use.'
|
description: 'Version of ghc to use.'
|
||||||
default: '8.8.3'
|
default: '8.8'
|
||||||
cabal-version:
|
cabal-version:
|
||||||
required: false
|
required: false
|
||||||
description: 'Exact version of cabal to use.'
|
description: 'Version of cabal to use. If set to "latest", it will always get the latest stable version.'
|
||||||
default: '3.0.0.0'
|
default: 'latest'
|
||||||
|
stack-version:
|
||||||
|
required: false
|
||||||
|
description: 'Version of stack to use. If set to "latest", it will always get the latest stable version.'
|
||||||
|
default: 'latest'
|
||||||
|
enable-stack:
|
||||||
|
required: false
|
||||||
|
description: 'If specified, will setup stack'
|
||||||
|
stack-no-global:
|
||||||
|
required: false
|
||||||
|
description: 'If specified, enable-stack must be set. Prevents installing GHC and Cabal globally'
|
||||||
|
stack-setup-ghc:
|
||||||
|
required: false
|
||||||
|
description: 'If specified, enable-stack must be set. Will run stack setup to install the specified GHC'
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
4155
dist/index.js
vendored
4155
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -18,3 +18,9 @@ git commit -m "Informative commit message" # Commit. This will run Husky
|
|||||||
|
|
||||||
During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier). It will also bundle the code into a single `dist/index.js` file.
|
During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier). It will also bundle the code into a single `dist/index.js` file.
|
||||||
Finally, it will make sure these changes are appropriately included in your commit--no further work is needed.
|
Finally, it will make sure these changes are appropriately included in your commit--no further work is needed.
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
Cabal does not follow SemVer and Stack has both SemVer compatible version numbers as well as PVP-style versions; due to this, support for "resolving" a version like `X.X` into the latest `X.X.Y` or `X.X.Y.Y` version is tricky.
|
||||||
|
To avoid complications, all recognized versions of GHC, Cabal, and Stack are in `src/versions.json`; these versions are supported across all three operating systems.
|
||||||
|
When a new release of GHC, Cabal, or Stack comes out, the `src/versions.json` file will need to be updated accordingly.
|
||||||
|
|||||||
87
env.d.ts
vendored
Normal file
87
env.d.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
export interface ProcessEnv {
|
||||||
|
/**
|
||||||
|
* The path to the GitHub home directory used to store user data.
|
||||||
|
*
|
||||||
|
* Example: /github/home.
|
||||||
|
*/
|
||||||
|
HOME: string;
|
||||||
|
|
||||||
|
/** The name of the workflow. */
|
||||||
|
GITHUB_WORKFLOW: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique number for each run within a repository. This number does not
|
||||||
|
* change if you re-run the workflow run.
|
||||||
|
*/
|
||||||
|
GITHUB_RUN_ID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique number for each run of a particular workflow in a repository.
|
||||||
|
* This number begins at 1 for the workflow's first run, and increments with
|
||||||
|
* each new run. This number does not change if you re-run the workflow run.
|
||||||
|
* */
|
||||||
|
GITHUB_RUN_NUMBER: string;
|
||||||
|
|
||||||
|
/** The unique identifier (id) of the action. */
|
||||||
|
GITHUB_ACTION: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always set to true when GitHub Actions is running the workflow. You can
|
||||||
|
* use this variable to differentiate when tests are being run locally or
|
||||||
|
* by GitHub Actions.
|
||||||
|
*/
|
||||||
|
GITHUB_ACTIONS: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the person or app that initiated the workflow.
|
||||||
|
*
|
||||||
|
* Example: octocat.
|
||||||
|
*/
|
||||||
|
GITHUB_ACTOR: string;
|
||||||
|
|
||||||
|
/** The owner and repository name. For example, octocat/Hello-World. */
|
||||||
|
GITHUB_REPOSITORY: string;
|
||||||
|
|
||||||
|
/** The name of the webhook event that triggered the workflow. */
|
||||||
|
GITHUB_EVENT_NAME: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the file with the complete webhook event payload.
|
||||||
|
*
|
||||||
|
* Example: /github/workflow/event.json.
|
||||||
|
*/
|
||||||
|
GITHUB_EVENT_PATH: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GitHub workspace directory path. The workspace directory contains a
|
||||||
|
* subdirectory with a copy of your repository if your workflow uses the
|
||||||
|
* actions/checkout action. If you don't use the actions/checkout action,
|
||||||
|
* the directory will be empty.
|
||||||
|
*
|
||||||
|
* Example: /home/runner/work/my-repo-name/my-repo-name.
|
||||||
|
*/
|
||||||
|
GITHUB_WORKSPACE: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The commit SHA that triggered the workflow.
|
||||||
|
*
|
||||||
|
* Example: ffac537e6cbbf934b08745a378932722df287a53.
|
||||||
|
*/
|
||||||
|
GITHUB_SHA: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The branch or tag ref that triggered the workflow. If neither a branch
|
||||||
|
* or tag is available for the event type, the variable will not exist.
|
||||||
|
*
|
||||||
|
* Example: refs/heads/feature-branch-1.
|
||||||
|
*/
|
||||||
|
GITHUB_REF: string;
|
||||||
|
|
||||||
|
/** Only set for forked repositories. The branch of the head repository. */
|
||||||
|
GITHUB_HEAD_REF: string;
|
||||||
|
|
||||||
|
/** Only set for forked repositories. The branch of the base repository. */
|
||||||
|
GITHUB_BASE_REF: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
2252
package-lock.json
generated
2252
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -5,15 +5,7 @@
|
|||||||
"description": "setup haskell action",
|
"description": "setup haskell action",
|
||||||
"main": "src/setup-haskell.ts",
|
"main": "src/setup-haskell.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check:type": "tsc --noEmit",
|
"test": "jest"
|
||||||
"check:format": "prettier --check **/*.{ts,json}",
|
|
||||||
"check:lint": "eslint src/**/*.ts",
|
|
||||||
"test": "jest",
|
|
||||||
"format": "prettier --write **/*.{ts,json}",
|
|
||||||
"pack": "ncc build",
|
|
||||||
"_git-add": "git add dist",
|
|
||||||
"pre-push": "run-p check:* test",
|
|
||||||
"pre-commit": "npm-run-all format pack _git-add"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -30,31 +22,34 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.3",
|
"@actions/core": "^1.2.3",
|
||||||
|
"@actions/exec": "^1.0.3",
|
||||||
|
"@actions/glob": "^0.1.0",
|
||||||
"@actions/io": "^1.0.2",
|
"@actions/io": "^1.0.2",
|
||||||
"@actions/tool-cache": "^1.3.3",
|
"@actions/tool-cache": "^1.3.3",
|
||||||
"js-yaml": "^3.13.1"
|
"js-yaml": "^3.13.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^25.1.4",
|
"@types/jest": "^25.2.1",
|
||||||
"@types/js-yaml": "^3.12.2",
|
"@types/js-yaml": "^3.12.3",
|
||||||
"@types/node": "^13.9.1",
|
"@types/node": "^13.13.2",
|
||||||
"@typescript-eslint/parser": "^2.24.0",
|
"@typescript-eslint/parser": "^2.29.0",
|
||||||
"@zeit/ncc": "^0.21.1",
|
"@typescript-eslint/eslint-plugin": "^2.29.0",
|
||||||
|
"@zeit/ncc": "^0.22.1",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-github": "^3.4.1",
|
"eslint-plugin-github": "^3.4.1",
|
||||||
"eslint-plugin-jest": "^23.8.2",
|
"eslint-plugin-jest": "^23.8.2",
|
||||||
"husky": "^4.2.3",
|
"husky": "^4.2.5",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.4.0",
|
||||||
"jest-circus": "^25.1.0",
|
"jest-circus": "^25.4.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"lint-staged": "^10.1.7",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^2.0.5",
|
||||||
"ts-jest": "^25.2.1",
|
"ts-jest": "^25.4.0",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "npm run pre-commit",
|
"pre-commit": "lint-staged",
|
||||||
"pre-push": "npm run pre-push"
|
"pre-push": "npm test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
311
src/installer.ts
311
src/installer.ts
@@ -1,181 +1,190 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import {exec} from '@actions/exec';
|
import {exec} from '@actions/exec';
|
||||||
import * as io from '@actions/io';
|
import {which} from '@actions/io';
|
||||||
|
import {create as glob} from '@actions/glob';
|
||||||
import * as tc from '@actions/tool-cache';
|
import * as tc from '@actions/tool-cache';
|
||||||
import {promises as fs, readFileSync} from 'fs';
|
import {promises as fs} from 'fs';
|
||||||
import {safeLoad} from 'js-yaml';
|
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
import type {OS, Tool} from './opts';
|
||||||
|
|
||||||
export interface ProgramOpt {
|
function failed(tool: Tool, version: string): void {
|
||||||
enable: boolean;
|
throw new Error(`All install methods for ${tool} ${version} failed`);
|
||||||
version: string;
|
|
||||||
install: (version: string) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Options {
|
async function success(
|
||||||
ghc: ProgramOpt;
|
tool: Tool,
|
||||||
cabal: ProgramOpt;
|
version: string,
|
||||||
stack: ProgramOpt & {setup: boolean};
|
path: string
|
||||||
}
|
): Promise<true> {
|
||||||
|
core.addPath(path);
|
||||||
export interface Defaults {
|
core.setOutput(`${tool}-path`, path);
|
||||||
ghc: {version: string};
|
core.setOutput(`${tool}-exe`, await which(tool));
|
||||||
cabal: {version: string};
|
core.info(
|
||||||
}
|
`Found ${tool} ${version} in cache at path ${path}. Setup successful.`
|
||||||
|
|
||||||
export function getDefaults(): Defaults {
|
|
||||||
const actionYml = safeLoad(
|
|
||||||
readFileSync(join(__dirname, '..', 'action.yml'), 'utf8')
|
|
||||||
);
|
);
|
||||||
return {
|
return true;
|
||||||
ghc: {version: actionYml.inputs['ghc-version'].default},
|
|
||||||
cabal: {version: actionYml.inputs['cabal-version'].default}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOpts(def: Defaults): Options {
|
function warn(tool: Tool, version: string): void {
|
||||||
const stackNoGlobal = core.getInput('stack-no-global') !== '';
|
const policy = {
|
||||||
const stackVersion = core.getInput('stack-version');
|
cabal: `the two latest major releases of ${tool} are commonly supported.`,
|
||||||
const stackSetupGhc = core.getInput('stack-setup-ghc') !== '';
|
ghc: `the three latest major releases of ${tool} are commonly supported.`,
|
||||||
|
stack: `the latest release of ${tool} is commonly supported.`
|
||||||
|
}[tool];
|
||||||
|
|
||||||
const errors = [];
|
core.warning(
|
||||||
if (stackNoGlobal && stackVersion === '') {
|
`${tool} ${version} was not found in the cache. It will be downloaded.\n` +
|
||||||
errors.push('stack-version is required if stack-no-global is set');
|
`If this is unexpected, please check if version ${version} is pre-installed.\n` +
|
||||||
}
|
`The list of pre-installed versions is available here: https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners\n` +
|
||||||
|
`The above list follows a common haskell convention that ${policy}\n` +
|
||||||
|
'If the list is outdated, please file an issue here: https://github.com/actions/virtual-environments\n' +
|
||||||
|
'by using the appropriate tool request template: https://github.com/actions/virtual-environments/issues/new/choose'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (stackSetupGhc && stackVersion === '') {
|
async function isInstalled(
|
||||||
errors.push('stack-version is required if stack-setup-ghc is set');
|
tool: Tool,
|
||||||
}
|
version: string,
|
||||||
|
os: OS
|
||||||
|
): Promise<boolean> {
|
||||||
|
const toolPath = tc.find(tool, version);
|
||||||
|
if (toolPath) return success(tool, version, toolPath);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
const ghcupPath = `${process.env.HOME}/.ghcup${
|
||||||
throw new Error(errors.join('\n'));
|
tool === 'ghc' ? `/ghc/${version}` : ''
|
||||||
}
|
}/bin`;
|
||||||
|
const v = tool === 'cabal' ? version.slice(0, 3) : version;
|
||||||
|
const aptPath = `/opt/${tool}/${v}/bin`;
|
||||||
|
|
||||||
return {
|
const chocoPath = join(
|
||||||
ghc: {
|
`${process.env.ChocolateyInstall}`,
|
||||||
version: core.getInput('ghc-version') || def.ghc.version,
|
'lib',
|
||||||
enable: !stackNoGlobal,
|
`${tool}.${version}`,
|
||||||
install: installGHC
|
'tools',
|
||||||
},
|
`${tool}-${version}`,
|
||||||
|
tool === 'ghc' ? 'bin' : ''
|
||||||
|
);
|
||||||
|
|
||||||
|
const locations = {
|
||||||
|
stack: [], // Always installed into the tool cache
|
||||||
cabal: {
|
cabal: {
|
||||||
version: core.getInput('cabal-version') || def.cabal.version,
|
win32: [chocoPath],
|
||||||
enable: !stackNoGlobal,
|
linux: [aptPath],
|
||||||
install: installCabal
|
darwin: []
|
||||||
},
|
}[os],
|
||||||
stack: {
|
ghc: {
|
||||||
version: stackVersion,
|
win32: [chocoPath],
|
||||||
enable: stackVersion !== '',
|
linux: [aptPath, ghcupPath],
|
||||||
install: installStack,
|
darwin: [ghcupPath]
|
||||||
setup: core.getInput('stack-setup-ghc') !== ''
|
}[os]
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const p of locations[tool]) {
|
||||||
|
const installedPath = await fs
|
||||||
|
.access(p)
|
||||||
|
.then(() => p)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
if (installedPath) return success(tool, version, installedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool === 'cabal' && os !== 'win32') {
|
||||||
|
const installedPath = await fs
|
||||||
|
.access(`${ghcupPath}/cabal`)
|
||||||
|
.then(() => ghcupPath)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
if (installedPath) return success(tool, version, installedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const installCabal = async (version: string): Promise<void> =>
|
export async function installTool(
|
||||||
installTool('cabal', version);
|
tool: Tool,
|
||||||
|
version: string,
|
||||||
|
os: OS
|
||||||
|
): Promise<void> {
|
||||||
|
if (await isInstalled(tool, version, os)) return;
|
||||||
|
warn(tool, version);
|
||||||
|
|
||||||
export const installGHC = async (version: string): Promise<void> =>
|
if (tool === 'stack') {
|
||||||
installTool('ghc', version);
|
await stack(version, os);
|
||||||
|
if (await isInstalled(tool, version, os)) return;
|
||||||
|
return failed(tool, version);
|
||||||
|
}
|
||||||
|
|
||||||
export async function installStack(version: string): Promise<void> {
|
switch (os) {
|
||||||
const info =
|
case 'linux':
|
||||||
version === 'latest'
|
await apt(tool, version);
|
||||||
? 'Installing the latest version'
|
if (await isInstalled(tool, version, os)) return;
|
||||||
: `Installing version ${version}`;
|
await ghcup(tool, version, os);
|
||||||
core.startGroup(`${info} of stack`);
|
break;
|
||||||
const platformMap = ({
|
case 'win32':
|
||||||
|
await choco(tool, version);
|
||||||
|
break;
|
||||||
|
case 'darwin':
|
||||||
|
await ghcup(tool, version, os);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await isInstalled(tool, version, os)) return;
|
||||||
|
return failed(tool, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stack(version: string, os: OS): Promise<void> {
|
||||||
|
core.info(`Attempting to install stack ${version}`);
|
||||||
|
const build = {
|
||||||
linux: 'linux-x86_64-static',
|
linux: 'linux-x86_64-static',
|
||||||
darwin: 'osx-x86_64',
|
darwin: 'osx-x86_64',
|
||||||
win32: 'windows-x86_64'
|
win32: 'windows-x86_64'
|
||||||
} as unknown) as Record<NodeJS.Platform, string>;
|
}[os];
|
||||||
|
|
||||||
const name = `stack-${version}-${platformMap[process.platform]}`;
|
|
||||||
const url =
|
const url =
|
||||||
version === 'latest'
|
version === 'latest'
|
||||||
? `get.haskellstack.org/stable/${platformMap[process.platform]}`
|
? `https://get.haskellstack.org/stable/${build}.tar.gz`
|
||||||
: `github.com/commercialhaskell/stack/releases/download/v${version}/${name}`;
|
: `https://github.com/commercialhaskell/stack/releases/download/v${version}/stack-${version}-${build}.tar.gz`;
|
||||||
|
const p = await tc.downloadTool(`${url}`).then(tc.extractTar);
|
||||||
|
const [stackPath] = await glob(`${p}/stack*`, {
|
||||||
|
implicitDescendants: false
|
||||||
|
}).then(async g => g.glob());
|
||||||
|
await tc.cacheDir(stackPath, 'stack', version);
|
||||||
|
|
||||||
const stack = await tc.downloadTool(`https://${url}.tar.gz`);
|
if (os === 'win32') core.exportVariable('STACK_ROOT', 'C:\\sr');
|
||||||
const p = await tc.extractTar(stack);
|
|
||||||
|
|
||||||
// Less janky than figuring out how to ./p/*/stack. (Not by much)
|
|
||||||
const stackPath =
|
|
||||||
(await fs.readdir(p, {withFileTypes: true}))
|
|
||||||
.flatMap(d => (d.isDirectory() ? [d.name] : []))
|
|
||||||
.find(f => f.startsWith('stack')) ?? '';
|
|
||||||
|
|
||||||
const cachedTool = await tc.cacheDir(join(p, stackPath), 'stack', version);
|
|
||||||
core.addPath(cachedTool);
|
|
||||||
|
|
||||||
core.endGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tool = 'cabal' | 'ghc';
|
async function apt(tool: Tool, version: string): Promise<void> {
|
||||||
async function installTool(tool: Tool, version: string): Promise<void> {
|
const toolName = tool === 'ghc' ? 'ghc' : 'cabal-install';
|
||||||
core.startGroup(`Installing ${tool}`);
|
const v = tool === 'cabal' ? version.slice(0, 3) : version;
|
||||||
// Linux comes pre-installed with some versions of GHC and supports older
|
core.info(`Attempting to install ${toolName} ${v} using apt-get`);
|
||||||
// versions through hvr's PPA.
|
await exec(`sudo -- sh -c "apt-get -y install ${toolName}-${v}"`);
|
||||||
if (process.platform === 'linux') {
|
}
|
||||||
// Cabal is installed to /opt/cabal/x.x but cabal's full version is X.X.Y.Z
|
|
||||||
const v = tool === 'cabal' ? version.slice(0, 3) : version;
|
async function choco(tool: Tool, version: string): Promise<void> {
|
||||||
|
core.info(`Attempting to install ${tool} ${version} using chocolatey`);
|
||||||
const p = join('/opt', tool, v, 'bin');
|
|
||||||
const installed = await fs
|
await exec('powershell', [
|
||||||
.access(p)
|
'choco',
|
||||||
.then(() => true)
|
'install',
|
||||||
.catch(() => false);
|
tool,
|
||||||
|
'--version',
|
||||||
if (tool === 'ghc' && !installed) {
|
version,
|
||||||
try {
|
'-m',
|
||||||
// hvr's PPA has better support for GHC < 8.0
|
'--no-progress',
|
||||||
await exec(`sudo -- sh -c "apt-get -y install ghc-${v}"`);
|
'-r'
|
||||||
} catch {
|
]);
|
||||||
// oh well, we tried
|
}
|
||||||
}
|
|
||||||
}
|
async function ghcup(tool: Tool, version: string, os: OS): Promise<void> {
|
||||||
|
core.info(`Attempting to install ${tool} ${version} using ghcup`);
|
||||||
try {
|
|
||||||
await fs.access(p);
|
const bin = await tc.downloadTool(
|
||||||
core.addPath(p);
|
`https://downloads.haskell.org/~ghcup/x86_64-${
|
||||||
core.endGroup();
|
os === 'darwin' ? 'apple-darwin' : 'linux'
|
||||||
return;
|
}-ghcup`
|
||||||
} catch {
|
);
|
||||||
// ok, let's try the generic install now
|
await fs.chmod(bin, 0o755);
|
||||||
}
|
|
||||||
}
|
await exec(bin, [tool === 'ghc' ? 'install' : 'install-cabal', version]);
|
||||||
|
if (tool === 'ghc') await exec(bin, ['set', version]);
|
||||||
if (process.platform === 'win32') {
|
|
||||||
await exec('powershell', [
|
|
||||||
'choco',
|
|
||||||
'install',
|
|
||||||
tool,
|
|
||||||
'--version',
|
|
||||||
version,
|
|
||||||
'-m',
|
|
||||||
'--no-progress',
|
|
||||||
'-r'
|
|
||||||
]);
|
|
||||||
|
|
||||||
core.addPath(
|
|
||||||
join(
|
|
||||||
process.env.ChocolateyInstall || '',
|
|
||||||
'lib',
|
|
||||||
`${tool}.${version}`,
|
|
||||||
'tools',
|
|
||||||
`${tool}-${version}`,
|
|
||||||
tool === 'ghc' ? 'bin' : ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const ghcup = await tc.downloadTool(
|
|
||||||
'https://raw.githubusercontent.com/haskell/ghcup/master/ghcup'
|
|
||||||
);
|
|
||||||
await fs.chmod(ghcup, 0o755);
|
|
||||||
await io.mkdirP(join(process.env.HOME || '', '.ghcup', 'bin'));
|
|
||||||
await exec(ghcup, [tool === 'ghc' ? 'install' : 'install-cabal', version]);
|
|
||||||
|
|
||||||
const p = tool === 'ghc' ? ['ghc', version] : [];
|
|
||||||
core.addPath(join(process.env.HOME || '', '.ghcup', ...p, 'bin'));
|
|
||||||
}
|
|
||||||
core.endGroup();
|
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/opts.ts
Normal file
97
src/opts.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import * as core from '@actions/core';
|
||||||
|
import {readFileSync} from 'fs';
|
||||||
|
import {safeLoad} from 'js-yaml';
|
||||||
|
import {join} from 'path';
|
||||||
|
import * as supported_versions from './versions.json';
|
||||||
|
|
||||||
|
export type OS = 'linux' | 'darwin' | 'win32';
|
||||||
|
export type Tool = 'cabal' | 'ghc' | 'stack';
|
||||||
|
|
||||||
|
export interface ProgramOpt {
|
||||||
|
enable: boolean;
|
||||||
|
raw: string;
|
||||||
|
resolved: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
ghc: ProgramOpt;
|
||||||
|
cabal: ProgramOpt;
|
||||||
|
stack: ProgramOpt & {setup: boolean};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Version = {version: string; supported: string[]};
|
||||||
|
export type Defaults = Record<Tool, Version>;
|
||||||
|
|
||||||
|
export function getDefaults(): Defaults {
|
||||||
|
const inpts = safeLoad(
|
||||||
|
readFileSync(join(__dirname, '..', 'action.yml'), 'utf8')
|
||||||
|
).inputs;
|
||||||
|
|
||||||
|
const mkVersion = (v: string, vs: string[]): Version => ({
|
||||||
|
version: resolve(inpts[v].default, vs),
|
||||||
|
supported: vs
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ghc: mkVersion('ghc-version', supported_versions.ghc),
|
||||||
|
cabal: mkVersion('cabal-version', supported_versions.cabal),
|
||||||
|
stack: mkVersion('stack-version', supported_versions.stack)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(version: string, supported: string[]): string {
|
||||||
|
return version === 'latest'
|
||||||
|
? supported[0]
|
||||||
|
: supported.find(v => v.startsWith(version)) ?? version;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOpts({ghc, cabal, stack}: Defaults): Options {
|
||||||
|
const stackNoGlobal = core.getInput('stack-no-global') !== '';
|
||||||
|
const stackSetupGhc = core.getInput('stack-setup-ghc') !== '';
|
||||||
|
const stackEnable = core.getInput('enable-stack') !== '';
|
||||||
|
const verInpt = {
|
||||||
|
ghc: core.getInput('ghc-version') || ghc.version,
|
||||||
|
cabal: core.getInput('cabal-version') || cabal.version,
|
||||||
|
stack: core.getInput('stack-version') || stack.version
|
||||||
|
};
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
if (stackNoGlobal && !stackEnable) {
|
||||||
|
errors.push('enable-stack is required if stack-no-global is set');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackSetupGhc && !stackEnable) {
|
||||||
|
errors.push('enable-stack is required if stack-setup-ghc is set');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(errors.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts: Options = {
|
||||||
|
ghc: {
|
||||||
|
raw: verInpt.ghc,
|
||||||
|
resolved: resolve(verInpt.ghc, ghc.supported),
|
||||||
|
enable: !stackNoGlobal
|
||||||
|
},
|
||||||
|
cabal: {
|
||||||
|
raw: verInpt.cabal,
|
||||||
|
resolved: resolve(verInpt.cabal, cabal.supported),
|
||||||
|
enable: !stackNoGlobal
|
||||||
|
},
|
||||||
|
stack: {
|
||||||
|
raw: verInpt.stack,
|
||||||
|
resolved: resolve(verInpt.stack, stack.supported),
|
||||||
|
enable: stackEnable,
|
||||||
|
setup: core.getInput('stack-setup-ghc') !== ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line github/array-foreach
|
||||||
|
Object.values(opts)
|
||||||
|
.filter(t => t.enable && t.raw !== t.resolved)
|
||||||
|
.forEach(t => core.info(`Resolved ${t.raw} to ${t.resolved}`));
|
||||||
|
|
||||||
|
core.debug(`Options are: ${JSON.stringify(opts)}`);
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
@@ -1,38 +1,37 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import {getOpts, getDefaults} from './installer';
|
import {getOpts, getDefaults, Tool} from './opts';
|
||||||
|
import {installTool} from './installer';
|
||||||
|
import type {OS} from './opts';
|
||||||
import {exec} from '@actions/exec';
|
import {exec} from '@actions/exec';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const opts = getOpts(getDefaults());
|
|
||||||
core.info('Preparing to setup a Haskell environment');
|
core.info('Preparing to setup a Haskell environment');
|
||||||
core.debug(`Options are: ${JSON.stringify(opts)}`);
|
const opts = getOpts(getDefaults());
|
||||||
|
|
||||||
for (const [tool, o] of Object.entries(opts)) {
|
for (const [t, {resolved}] of Object.entries(opts).filter(o => o[1].enable))
|
||||||
if (o.enable) {
|
await core.group(`Installing ${t} version ${resolved}`, async () =>
|
||||||
core.info(`Installing ${tool} version ${o.version}`);
|
installTool(t as Tool, resolved, process.platform as OS)
|
||||||
await o.install(o.version);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.stack.setup) {
|
if (opts.stack.setup)
|
||||||
core.startGroup('Pre-installing GHC with stack');
|
await core.group('Pre-installing GHC with stack', async () =>
|
||||||
await exec('stack', ['setup', opts.ghc.version]);
|
exec('stack', ['setup', opts.ghc.resolved])
|
||||||
core.endGroup();
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.cabal.enable) {
|
if (opts.cabal.enable)
|
||||||
core.startGroup('Setting up cabal');
|
await core.group('Setting up cabal', async () => {
|
||||||
await exec('cabal', [
|
await exec(
|
||||||
'user-config',
|
'cabal user-config update -a "http-transport: plain-http" -v3'
|
||||||
'update',
|
);
|
||||||
'-a',
|
await exec('cabal', ['update']);
|
||||||
'http-transport: plain-http',
|
if (process.platform === 'win32') {
|
||||||
'-v3'
|
await exec('cabal user-config update -a "store-dir: C:\\sr" -v3');
|
||||||
]);
|
core.setOutput('cabal-store', 'C:\\sr');
|
||||||
await exec('cabal', ['update']);
|
} else {
|
||||||
core.endGroup();
|
core.setOutput('cabal-store', `${process.env.HOME}/.cabal/store`);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/versions.json
Normal file
37
src/versions.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"ghc": [
|
||||||
|
"8.10.1",
|
||||||
|
"8.8.3",
|
||||||
|
"8.8.2",
|
||||||
|
"8.8.1",
|
||||||
|
"8.6.5",
|
||||||
|
"8.6.4",
|
||||||
|
"8.6.3",
|
||||||
|
"8.6.2",
|
||||||
|
"8.6.1",
|
||||||
|
"8.4.4",
|
||||||
|
"8.4.3",
|
||||||
|
"8.4.2",
|
||||||
|
"8.4.1",
|
||||||
|
"8.2.2",
|
||||||
|
"8.0.2",
|
||||||
|
"7.10.3"
|
||||||
|
],
|
||||||
|
"cabal": ["3.2.0.0", "3.0.0.0", "2.4.1.0", "2.4.0.0", "2.2.0.0"],
|
||||||
|
"stack": [
|
||||||
|
"2.1.3",
|
||||||
|
"2.1.1",
|
||||||
|
"1.9.3",
|
||||||
|
"1.9.1",
|
||||||
|
"1.7.1",
|
||||||
|
"1.6.5",
|
||||||
|
"1.6.3",
|
||||||
|
"1.6.1",
|
||||||
|
"1.5.1",
|
||||||
|
"1.5.0",
|
||||||
|
"1.4.0",
|
||||||
|
"1.3.2",
|
||||||
|
"1.3.0",
|
||||||
|
"1.2.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,12 +8,15 @@
|
|||||||
"es2020.symbol.wellknown"
|
"es2020.symbol.wellknown"
|
||||||
],
|
],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"outDir": "./lib",
|
"outDir": ".out",
|
||||||
|
"tsBuildInfoFile": ".out/tsbuildinfo",
|
||||||
|
"incremental": true,
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "**/*.test.ts"]
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user