diff --git a/.github/workflows/example.yml b/.github/workflows/example.yml index c1162e1..c34f0f0 100644 --- a/.github/workflows/example.yml +++ b/.github/workflows/example.yml @@ -30,6 +30,11 @@ jobs: - resolver: lts-12.26 ghc: "8.4.4" + exclude: + # Binary distributions for older GHCs don't exist on macOS-latest + - runner: macOS-latest + stack: {ghc: "8.4.4"} + fail-fast: false runs-on: ${{ matrix.runner }} diff --git a/README.md b/README.md index ce1226e..70f834f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ jobs: | `stack-build-arguments-test` |

Additional arguments passed after stack-build-arguments in stack build invocations on the Test step.

| `false` | `""` | | `cache-prefix` |

Prefix applied to all cache keys. This can be any value you like, but teams often use v{N} and bump it to v{N+1} when/if they need to explicitly bust caches.

| `false` | `""` | | `cache-save-always` |

Save artifacts to the cache even if the build fails. This may speed up builds in subsequent runs at the expense of slightly-longer builds when a full cache-hit occurs. Since @v4.2.0.

| `false` | `false` | +| `install-stack` |

Install stack, if necessary

| `false` | `true` | | `upgrade-stack` |

Upgrade stack

| `false` | `true` | | `compiler-tools` |

A list of packages to install as compiler tools, one per line. This is useful to do here rather than separate run commands so that their installation is incorporated in the dependency cache. Since @v5.2.0.

| `false` | `""` | | `stack-yaml` |

Deprecated use env.STACK_YAML or stack-arguments instead.

| `false` | `""` | diff --git a/action.yml b/action.yml index 80d4bb6..32b8022 100644 --- a/action.yml +++ b/action.yml @@ -48,6 +48,10 @@ inputs: builds in subsequent runs at the expense of slightly-longer builds when a full cache-hit occurs. Since `@v4.2.0`. default: false + install-stack: + description: | + Install stack, if necessary + default: true upgrade-stack: description: | Upgrade stack diff --git a/dist/index.js b/dist/index.js index 43a4060..bb5ba60 100644 --- a/dist/index.js +++ b/dist/index.js @@ -136,6 +136,7 @@ function getInputs() { stackBuildArgumentsTest: getBuildArguments("test"), cachePrefix: core.getInput("cache-prefix"), cacheSaveAlways: core.getBooleanInput("cache-save-always"), + installStack: core.getBooleanInput("install-stack"), upgradeStack: core.getBooleanInput("upgrade-stack"), compilerTools: core.getMultilineInput("compiler-tools"), stackYaml: getInputDefault("stack-yaml", null), @@ -203,6 +204,28 @@ async function run() { inputs.stackArguments.unshift("--stack-yaml"); } const stack = new stack_cli_1.StackCLI(inputs.stackArguments, core.isDebug()); + await core.group("Install/upgrade stack", async () => { + const installed = await stack.installed(); + if (installed) { + if (inputs.upgradeStack) { + core.info("Upgrading stack"); + await stack.upgrade(); + } + } + else { + if (inputs.installStack) { + core.info("Installing stack"); + await stack.install(); + } + else { + throw new Error([ + "The executable stack is not present on $PATH", + "Make sure it is installed in a preceding step, or use", + "`install-stack: true` to have it installed for you.", + ].join("\n")); + } + } + }); const hashes = await core.group("Calculate hashes", async () => { const hashes = await (0, hash_project_1.hashProject)(stack.config); core.info(`Snapshot: ${hashes.snapshot}`); @@ -210,11 +233,6 @@ async function run() { core.info(`Sources: ${hashes.sources}`); return hashes; }); - if (inputs.upgradeStack) { - await core.group("Upgrade stack", async () => { - await stack.upgrade(); - }); - } const { stackYaml, stackDirectories } = await core.group("Determine stack directories", async () => { const stackYaml = (0, stack_yaml_1.readStackYamlSync)(stack.config); const stackDirectories = await (0, stack_yaml_1.getStackDirectories)(stackYaml, stack); @@ -428,6 +446,20 @@ class StackCLI { this.globalArgs.push("nightly"); } } + async installed() { + const ec = await exec.exec("which", ["stack"], { + silent: true, + ignoreReturnCode: true, + }); + return ec == 0; + } + async install() { + const url = "https://get.haskellstack.org"; + const tmp = "install-stack.sh"; + await exec.exec("curl", ["-sSL", "-o", tmp, url]); + await exec.exec("sh", [tmp]); + fs.rmSync(tmp); + } async upgrade() { return await exec.exec("stack", ["upgrade"]); } diff --git a/src/inputs.ts b/src/inputs.ts index 43a8995..3820cac 100644 --- a/src/inputs.ts +++ b/src/inputs.ts @@ -13,6 +13,7 @@ export type Inputs = { stackBuildArgumentsTest: string[]; cachePrefix: string; cacheSaveAlways: boolean; + installStack: boolean; upgradeStack: boolean; compilerTools: string[]; @@ -38,6 +39,7 @@ export function getInputs(): Inputs { stackBuildArgumentsTest: getBuildArguments("test"), cachePrefix: core.getInput("cache-prefix"), cacheSaveAlways: core.getBooleanInput("cache-save-always"), + installStack: core.getBooleanInput("install-stack"), upgradeStack: core.getBooleanInput("upgrade-stack"), compilerTools: core.getMultilineInput("compiler-tools"), stackYaml: getInputDefault("stack-yaml", null), diff --git a/src/main.ts b/src/main.ts index acd41dd..2310973 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,6 +28,30 @@ async function run() { const stack = new StackCLI(inputs.stackArguments, core.isDebug()); + await core.group("Install/upgrade stack", async () => { + const installed = await stack.installed(); + + if (installed) { + if (inputs.upgradeStack) { + core.info("Upgrading stack"); + await stack.upgrade(); + } + } else { + if (inputs.installStack) { + core.info("Installing stack"); + await stack.install(); + } else { + throw new Error( + [ + "The executable stack is not present on $PATH", + "Make sure it is installed in a preceding step, or use", + "`install-stack: true` to have it installed for you.", + ].join("\n"), + ); + } + } + }); + const hashes = await core.group("Calculate hashes", async () => { const hashes = await hashProject(stack.config); core.info(`Snapshot: ${hashes.snapshot}`); @@ -36,12 +60,6 @@ async function run() { return hashes; }); - if (inputs.upgradeStack) { - await core.group("Upgrade stack", async () => { - await stack.upgrade(); - }); - } - const { stackYaml, stackDirectories } = await core.group( "Determine stack directories", async () => { diff --git a/src/stack-cli.ts b/src/stack-cli.ts index d7999a5..57041db 100644 --- a/src/stack-cli.ts +++ b/src/stack-cli.ts @@ -48,6 +48,22 @@ export class StackCLI { } } + async installed(): Promise { + const ec = await exec.exec("which", ["stack"], { + silent: true, + ignoreReturnCode: true, + }); + return ec == 0; + } + + async install(): Promise { + const url = "https://get.haskellstack.org"; + const tmp = "install-stack.sh"; + await exec.exec("curl", ["-sSL", "-o", tmp, url]); + await exec.exec("sh", [tmp]); + fs.rmSync(tmp); + } + async upgrade(): Promise { // Avoid this.exec because we don't need/want globalArgs return await exec.exec("stack", ["upgrade"]);