From 8e68a92ae2a6a6e457f5cb36aef82dbf9ba6a671 Mon Sep 17 00:00:00 2001 From: Andreas Abel Date: Sat, 6 May 2023 10:37:18 +0200 Subject: [PATCH] New outputs ghc/cabal/stack-version Fixes https://github.com/haskell/actions/issues/77 --- .github/workflows/workflow.yml | 13 +++++++++++-- setup/README.md | 30 +++++++++++++++++++----------- setup/action.yml | 18 ++++++++++++------ setup/dist/index.js | 26 ++++++++++++++++++++++---- setup/lib/installer.js | 5 +++-- setup/lib/opts.d.ts | 1 + setup/lib/opts.js | 21 +++++++++++++++++++-- setup/src/installer.ts | 4 +++- 8 files changed, 90 insertions(+), 28 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 8b82b504..0ed34245 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -140,6 +140,7 @@ jobs: - uses: actions/checkout@v3 - uses: ./setup + id: setup with: ghc-version: ${{ matrix.plan.ghc }} ghcup-release-channel: ${{ matrix.ghcup_release_channel }} @@ -152,6 +153,16 @@ jobs: ghc --version echo "$PATH" + - name: Confirm resolved and installed versions match + shell: bash + run: | + CABALVER="$(cabal --numeric-version)" + GHCVER="$(ghc --numeric-version)" + echo "CABALVER=${CABALVER}" >> "${GITHUB_ENV}" + echo "GHCVER=${GHCVER}" >> "${GITHUB_ENV}" + [[ "${CABALVER}" == "${{ steps.setup.outputs.cabal-version }}" ]] && \ + [[ "${GHCVER}" == "${{ steps.setup.outputs.ghc-version }}" ]] + - name: Test runghc run: | runghc --version @@ -185,8 +196,6 @@ jobs: # - ghc: major and minor version # pure bash startsWith run: | - CABALVER="$(cabal --numeric-version)" - GHCVER="$(ghc --numeric-version)" if [[ "${{ matrix.plan.cabal }}" =~ ^([0-9]+\.[0-9]+) ]]; then cabalmajor="${BASH_REMATCH[1]}"; fi if [[ "${{ matrix.plan.ghc }}" =~ ^([0-9]+\.[0-9]+) ]]; then ghcmajor="${BASH_REMATCH[1]}"; fi if [[ "${{ matrix.plan.ghc }}" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]]; then ghcver="${BASH_REMATCH[1]}"; fi diff --git a/setup/README.md b/setup/README.md index 511c2408..9f86df19 100644 --- a/setup/README.md +++ b/setup/README.md @@ -6,7 +6,7 @@ This action sets up a Haskell environment for use in actions by: - if requested, installing a version of [ghc](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/) and [cabal](https://www.haskell.org/cabal/) and adding them to `PATH`, - if requested, installing a version of [Stack](https://haskellstack.org) and adding it to the `PATH`, -- outputting of `ghc-exe/path`, `cabal-exe/path`, `stack-exe/path`, `stack-root` and `cabal-store` (for the requested components). +- outputting of `ghc-version/exe/path`, `cabal-version/exe/path`, `stack-version/exe/path`, `stack-root` and `cabal-store` (for the requested components). The GitHub runners come with [pre-installed versions of GHC and Cabal](https://github.com/actions/runner-images). Those will be used whenever possible. @@ -211,16 +211,24 @@ In contrast, a proper `boolean` input like `cabal-update` only accepts values `t ## 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 | -| `stack-root` | The path to the stack root (equal to the `STACK_ROOT` environment variable if it is set; otherwise an OS-specific default) | 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 | +The action outputs parameters for the components it installed. +E.g. if `ghc-version: 8.10` is requested, the action will output `ghc-version: 8.10.7` if installation succeeded, +and `ghc-exe` and `ghc-path` will be set accordingly. +(Details on version resolution see next section.) + +| Name | Description | Type | +| --------------- | -------------------------------------------------------------------------------------------------------------------------- | ------ | +| `ghc-version` | The resolved version of `ghc` | string | +| `cabal-version` | The resolved version of `cabal` | string | +| `stack-version` | The resolved version of `stack` | 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 | +| `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 | +| `stack-root` | The path to the stack root (equal to the `STACK_ROOT` environment variable if it is set; otherwise an OS-specific default) | string | ## Version Support diff --git a/setup/action.yml b/setup/action.yml index 805a6138..ba95487f 100644 --- a/setup/action.yml +++ b/setup/action.yml @@ -37,6 +37,18 @@ inputs: required: false description: 'If specified, disables match messages from GHC as GitHub CI annotations.' outputs: + ghc-version: + description: 'The resolved version of ghc' + cabal-version: + description: 'The resolved version of cabal' + stack-version: + description: 'The resolved version of stack' + 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_' ghc-path: description: 'The path of the ghc executable _directory_' cabal-path: @@ -47,12 +59,6 @@ outputs: description: 'The path to the cabal store' stack-root: description: 'The path to the stack root (equal to the STACK_ROOT environment variable if it is set; otherwise an OS-specific default)' - 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: using: 'node16' main: 'dist/index.js' diff --git a/setup/dist/index.js b/setup/dist/index.js index ab1427b0..35845341 100644 --- a/setup/dist/index.js +++ b/setup/dist/index.js @@ -13324,7 +13324,7 @@ const exec = async (cmd, args) => (0, exec_1.exec)(cmd, args, { ignoreReturnCode function failed(tool, version) { throw new Error(`All install methods for ${tool} ${version} failed`); } -async function configureOutputs(tool, path, os) { +async function configureOutputs(tool, version, path, os) { core.setOutput(`${tool}-path`, path); core.setOutput(`${tool}-exe`, await (0, io_1.which)(tool)); if (tool == 'stack') { @@ -13334,10 +13334,11 @@ async function configureOutputs(tool, path, os) { if (os === 'win32') core.exportVariable('STACK_ROOT', sr); } + core.setOutput(`${tool}-version`, version); } async function success(tool, version, path, os) { core.addPath(path); - await configureOutputs(tool, path, os); + await configureOutputs(tool, version, path, os); core.info(`Found ${tool} ${version} in cache at path ${path}. Setup successful.`); return true; } @@ -13688,8 +13689,9 @@ 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.parseURL = exports.parseYAMLBoolean = exports.releaseRevision = exports.resolveVersion = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; const core = __importStar(__nccwpck_require__(2186)); +const compare_versions_1 = __nccwpck_require__(4773); const fs_1 = __nccwpck_require__(7147); const js_yaml_1 = __nccwpck_require__(1917); const path_1 = __nccwpck_require__(1017); @@ -13743,13 +13745,29 @@ function getDefaults(os) { }; } exports.getDefaults = getDefaults; +function resolveVersion(pattern, versions) { + // Look for an exact hit first. + if (versions.includes(pattern)) + return pattern; + // Take the largest version that satisfies the pattern, e.g. '>9.4'. + let match = versions.filter(v => (0, compare_versions_1.satisfies)(v, pattern)).sort(compare_versions_1.compareVersions).at(-1); + if (match) + return match; + // Treat pattern as a prefix by extending it with '.*' + match = versions.filter(v => (0, compare_versions_1.satisfies)(v, pattern + '.*')).sort(compare_versions_1.compareVersions).at(-1); + if (match) + return match; + // Give up. + return ''; +} +exports.resolveVersion = resolveVersion; // E.g. resolve ghc latest to 9.4.2 // resolve ghc 8.1 to 8.10.7 (bug, https://github.com/haskell/actions/issues/248) function resolve(version, supported, tool, os, verbose // If resolution isn't the identity, print what resolved to what. ) { const result = version === 'latest' ? supported[0] - : supported.find(v => v.startsWith(version)) ?? version; + : resolveVersion(version, supported) ?? version; // Andreas 2022-12-29, issue #144: inform about resolution here where we can also output ${tool}. if (verbose === true && version !== result) core.info(`Resolved ${tool} ${version} to ${result}`); diff --git a/setup/lib/installer.js b/setup/lib/installer.js index 6f4baf03..70c94294 100644 --- a/setup/lib/installer.js +++ b/setup/lib/installer.js @@ -43,7 +43,7 @@ const exec = async (cmd, args) => (0, exec_1.exec)(cmd, args, { ignoreReturnCode function failed(tool, version) { throw new Error(`All install methods for ${tool} ${version} failed`); } -async function configureOutputs(tool, path, os) { +async function configureOutputs(tool, version, path, os) { core.setOutput(`${tool}-path`, path); core.setOutput(`${tool}-exe`, await (0, io_1.which)(tool)); if (tool == 'stack') { @@ -53,10 +53,11 @@ async function configureOutputs(tool, path, os) { if (os === 'win32') core.exportVariable('STACK_ROOT', sr); } + core.setOutput(`${tool}-version`, version); } async function success(tool, version, path, os) { core.addPath(path); - await configureOutputs(tool, path, os); + await configureOutputs(tool, version, path, os); core.info(`Found ${tool} ${version} in cache at path ${path}. Setup successful.`); return true; } diff --git a/setup/lib/opts.d.ts b/setup/lib/opts.d.ts index 5b5a3603..7853f366 100644 --- a/setup/lib/opts.d.ts +++ b/setup/lib/opts.d.ts @@ -73,6 +73,7 @@ export declare const yamlInputs: Record; export declare function getDefaults(os: OS): Defaults; +export declare function resolveVersion(pattern: string, versions: string[]): string; export declare function releaseRevision(version: string, tool: Tool, os: OS): string; /** * Convert a string input to a boolean according to the YAML 1.2 "core schema" specification. diff --git a/setup/lib/opts.js b/setup/lib/opts.js index 599f4ec0..a23d5c55 100644 --- a/setup/lib/opts.js +++ b/setup/lib/opts.js @@ -23,8 +23,9 @@ 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.parseURL = exports.parseYAMLBoolean = exports.releaseRevision = exports.resolveVersion = exports.getDefaults = exports.yamlInputs = exports.ghcup_version = exports.supported_versions = exports.release_revisions = void 0; const core = __importStar(require("@actions/core")); +const compare_versions_1 = require("compare-versions"); const fs_1 = require("fs"); const js_yaml_1 = require("js-yaml"); const path_1 = require("path"); @@ -78,13 +79,29 @@ function getDefaults(os) { }; } exports.getDefaults = getDefaults; +function resolveVersion(pattern, versions) { + // Look for an exact hit first. + if (versions.includes(pattern)) + return pattern; + // Take the largest version that satisfies the pattern, e.g. '>9.4'. + let match = versions.filter(v => (0, compare_versions_1.satisfies)(v, pattern)).sort(compare_versions_1.compareVersions).at(-1); + if (match) + return match; + // Treat pattern as a prefix by extending it with '.*' + match = versions.filter(v => (0, compare_versions_1.satisfies)(v, pattern + '.*')).sort(compare_versions_1.compareVersions).at(-1); + if (match) + return match; + // Give up. + return ''; +} +exports.resolveVersion = resolveVersion; // E.g. resolve ghc latest to 9.4.2 // resolve ghc 8.1 to 8.10.7 (bug, https://github.com/haskell/actions/issues/248) function resolve(version, supported, tool, os, verbose // If resolution isn't the identity, print what resolved to what. ) { const result = version === 'latest' ? supported[0] - : supported.find(v => v.startsWith(version)) ?? version; + : resolveVersion(version, supported) ?? version; // Andreas 2022-12-29, issue #144: inform about resolution here where we can also output ${tool}. if (verbose === true && version !== result) core.info(`Resolved ${tool} ${version} to ${result}`); diff --git a/setup/src/installer.ts b/setup/src/installer.ts index 72651a34..6e7f24f0 100644 --- a/setup/src/installer.ts +++ b/setup/src/installer.ts @@ -20,6 +20,7 @@ function failed(tool: Tool, version: string): void { async function configureOutputs( tool: Tool, + version: string, path: string, os: OS ): Promise { @@ -32,6 +33,7 @@ async function configureOutputs( core.setOutput('stack-root', sr); if (os === 'win32') core.exportVariable('STACK_ROOT', sr); } + core.setOutput(`${tool}-version`, version); } async function success( @@ -41,7 +43,7 @@ async function success( os: OS ): Promise { core.addPath(path); - await configureOutputs(tool, path, os); + await configureOutputs(tool, version, path, os); core.info( `Found ${tool} ${version} in cache at path ${path}. Setup successful.` );