Skip to content

Commit

Permalink
Merge PR #88 feat/stack-test-ci
Browse files Browse the repository at this point in the history
  • Loading branch information
ulidtko authored Nov 22, 2024
2 parents 987e467 + 6b66e96 commit ac00851
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.github/workflows/haskell-ci.yml linguist-generated=true
stack.yaml.lock linguist-generated=true
51 changes: 51 additions & 0 deletions .github/scripts/stackage-lts-map.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env cabal
{- cabal:
build-depends: base, aeson, yaml, http-client, http-client-tls, http-types
-}
{-# LANGUAGE GHC2021, OverloadedStrings #-}

{- | Fetch Stackage LTS -> GHC version map, print as YAML. -}
module Main where

import Control.Monad ((<=<), unless, forM, forM_)
import Data.Bifunctor (first)
import Data.Function (on)
import Data.Functor ((<&>))
import System.Exit (die)

import Data.Aeson
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KM
import Network.HTTP.Client
import Network.HTTP.Client.TLS (newTlsManager)
import Network.HTTP.Types (status200)

newtype LtsEntries = LtsEntries [(String, String)] -- {"lts-20": "lts-20.26"}
deriving Show
instance FromJSON LtsEntries where
parseJSON = withObject "LtsEntries" $
pure . LtsEntries . map (first Key.toString) . KM.toList <=< mapM parseJSON

ghcVersionEndpoint :: String -> Request
ghcVersionEndpoint tag = parseRequest_ $
"https://www.stackage.org/" <> tag <> "/ghc-major-version"

main :: IO ()
main = do
gConnPool <- newTlsManager -- reuses one connection for all requests
-- fetch list of LTS snapshots
req <- parseUrlThrow "https://www.stackage.org/download/lts-snapshots.json"
resp <- httpLbs req gConnPool
unless (responseStatus resp == status200) . die $
"Fetch of Stackage lts-snapshots.json gave " ++ show resp
LtsEntries entries <- either fail pure $ eitherDecode (responseBody resp)
-- for each snapshot, call endpoint telling GHC version
ghcVersions <- forM entries $ \(_ltsMajor, ltsFull) -> do
let req2 = ghcVersionEndpoint ltsFull
resp <- httpLbs req2 gConnPool <&> responseBody
ghcVer <- maybe (fail $ "non-utf8 response " <> show resp) pure
$ decode ("\"" <> resp <> "\"") -- reuse aeson to avoid a dependency for utf8
pure (ltsFull, ghcVer)
-- print out
forM_ ghcVersions $ \(lts, ghc) -> putStrLn $
ghc <> ": " <> lts
26 changes: 26 additions & 0 deletions .github/scripts/stackage-lts-map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python

"""
Translation of stackage-lts-map.hs into Python.
(Because Cabal makes it too hard to run.)
Fetches Stackage LTS -> GHC version map, prints as YAML.
"""

import http.client
import json

endpoint1 = "/download/lts-snapshots.json"
endpoint2 = lambda tag: "/" + tag + "/ghc-major-version"

#-- reuse one connection for all requests
http = http.client.HTTPSConnection('www.stackage.org')

http.request('GET', endpoint1)
resp = json.load(http.getresponse())
ltslist = dict(resp).values()
for lts_tag in ltslist:
http.request('GET', endpoint2(lts_tag))
ghc_ver = http.getresponse().read().decode('ascii')
print(f"{ghc_ver}: {lts_tag}")
138 changes: 138 additions & 0 deletions .github/workflows/stack-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: stack test

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

permissions:
contents: read

env:
#-- increment this to force-rebuild the cache of dependency packages
MANUAL_CACHE_RESET_TESTDEPS: r0
#-- increment this to force-rebuild the cache of cabal hackage index
MANUAL_CACHE_RESET_HACKAGE: r0

jobs:
stack-test:

runs-on: ubuntu-latest

strategy:
matrix:
ghc:
# - 9.10.1 # LTS absent yet
# - 9.8.3 # not in ghcup yet; LTS nightly available
- 9.6.6
- 9.4.8
- 9.2.8
- 9.0.2
- 8.10.7
- 8.8.4
- 8.6.5
- 8.4.4
#-- Stack no longer supports Cabal <2.2.
#- 8.2.2
#- 8.0.2

name: with GHC ${{ matrix.ghc }}

steps:
- uses: actions/checkout@v4

#-- ghcup, cached

- name: Cache GHC installation
uses: actions/cache@v4
id: ghcup
with:
path: |
~/.ghcup/bin/*
~/.ghcup/cache/*
~/.ghcup/config.yaml
~/.ghcup/ghc/${{ matrix.ghc }}
key: CI-ghcup-${{ matrix.ghc }}

- name: ghcup
uses: haskell-actions/setup@v2
if: steps.ghcup.outputs.cache-hit != 'true'
with:
ghc-version: ${{ matrix.ghc }}
enable-stack: true
stack-version: latest

- name: GHCup diagnostics
run: |
ghcup list
du -csh ~/.ghcup/*
ls -ld ~/.ghcup/*/*
#-- stack pantry, cached

- name: Cache Pantry (Stackage package index)
id: pantry
uses: actions/cache@v4
with:
path: ~/.stack/pantry
key: CI-pantry-${{ env.STACK_LTS }}

- name: Recompute Stackage package index
if: steps.pantry.outputs.cache-hit != 'true'
run: stack update # populates ~/.stack/pantry

- name: ~/.stack cache diagnostics
run: du -csh -t 64k ~/.stack/*

#-- for the matrix, we'll need a map GHC version -> Stack LTS resolver
- name: Cached LTS-GHC map
id: ltsmap
uses: actions/cache@v4
with:
path: lts-resolver-map.yaml
key: lts-resolver-map
- name: Regenerate LTS-GHC map
if: steps.ltsmap.outputs.cache-hit != 'true'
run: |
timeout 50 .github/scripts/stackage-lts-map.py \
| sort -n | tee lts-resolver-map.yaml
- name: Configure Stack
run: |
#-- set resolver tag based on GHC version from the matrix
selected="$(yq '.["${{ matrix.ghc }}"]' lts-resolver-map.yaml)"
test -z $selected -o $selected == null && {
echo "Could not find LTS snapshot for GHC version ${{ matrix.ghc }}"
echo "Version map:"; cat lts-resolver-map.yaml
exit 1
} >&2
stack config set resolver $selected
stack config set system-ghc true --global
stack config set install-ghc false --global
#-- not much deps in this package; however Cabal is a large one. cache, too

- name: Cache Haskell dependencies
uses: actions/cache@v4
env:
MANUAL_RESET: ${{ env.MANUAL_CACHE_RESET_TESTDEPS }}
with:
path: |
~/.stack/stack.sqlite3
~/.stack/snapshots
key: CI-testdeps-${{ env.MANUAL_RESET }}-lock${{ hashFiles('**/stack.yaml.lock') }}

- name: ~/.stack cache diagnostics
run: du -csh -t 64k ~/.stack/*

#-- compiling time!

- name: Build dependencies
run: stack build --test --only-dependencies

- name: Compile project & examples
run: stack build --test --no-run-tests

- name: Run tests
run: stack test
2 changes: 1 addition & 1 deletion cabal-doctest.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ library
-- In any case, revisions may set tighter bounds afterwards, if exceptional
-- circumstances would warrant that.
base >=4.9 && <5
, Cabal >=1.10 && <3.16
, Cabal >=1.24 && <3.16
, directory >=1.3 && <2
, filepath >=1.4 && <2

Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 1.0.11 -- unreleased

* Support Cabal 3.14.0.0. [cabal-doctest#85][].
* Fix `stack test` of examples.

[cabal-doctest#85]: https://github.com/ulidtko/cabal-doctest/issues/85

Expand Down
47 changes: 47 additions & 0 deletions hie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#--
#-- https://github.com/haskell/hie-bios/issues/350
#--
cradle:
multi:
- path: src
config:
cradle:
cabal:
- path: simple-example/src
config:
cradle:
cabal:
- path: simple-example/tests
config:
cradle:
cabal:
- path: multiple-components-example/src
config:
cradle:
cabal:
- path: multiple-components-example/tests
config:
cradle:
cabal:
- path: multiple-components-example/exe
config:
cradle:
cabal:

- path: simple-example/Setup.hs
config:
cradle:
direct:
arguments: ["-package Cabal", "-package base"]

- path: multiple-components-example/Setup.hs
config:
cradle:
direct:
arguments: ["-package Cabal", "-package base"]

- path: .github/scripts/stackage-lts-map.hs
config:
cradle:
cabal:
dependencies: [ .github/scripts/stackage-lts-map.hs ]
21 changes: 19 additions & 2 deletions multiple-components-example/exe/Something.hs
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
module Main where

import OtherModule
import Data.Version (showVersion)

import OtherModule
import qualified Paths_multiple_components_example as Paths

-- $setup
--
-- This is a special block to run before each doctest (group).
--
-- >>> import Data.Version.Compat (makeVersion)
-- >>> let setupDummy = [1, 2, 3]

-- | An example 'CBool'.
--
-- >>> toBool myCBool
-- True
myCBool :: CBool
myCBool = fromBool True

-- | Example of paths
-- | Doctest examples.
--
-- We can assert a thing about our package version, obtainable from the special
-- cabal-generated Paths module:
--
-- >>> showVersion Paths.version
-- "1"
--
-- We can assert a thing using names from the $setup block:
--
-- >>> showVersion (makeVersion setupDummy)
-- "1.2.3"
--
main :: IO ()
main = do
print (showVersion Paths.version)
Expand Down
15 changes: 14 additions & 1 deletion multiple-components-example/multiple-components-example.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ custom-setup
base < 5
, cabal-doctest >=1.0.9 && <1.1

-- Declaring this setup-dep fixes `stack build`. NOTE: Cabal API is not used directly
-- in the examples' Setup.hs! Instead, indirectly through wrappings of cabal-doctest.
--
-- When copying the example, you may of course delete this note, and start importing
-- Cabal stuff directly from Setup.hs.
-- ! Make sure to set a tighter bound, e.g. Cabal <3.16, here in setup-depends !
-- as soon as you do that.
, Cabal >= 1.24 && <4
-- > Warning: [...] Each major release of the 'Cabal' package changes the API
-- > in various ways and most packages will need some changes to compile with it.
-- > If you are not sure what upper bound to use then use the next major version.

library
exposed-modules: Example
default-extensions:
Expand All @@ -51,6 +63,7 @@ executable my-exe
Paths_multiple_components_example
build-depends:
base
, base-compat >=0.10.5 && <1
, multiple-components-example

ghc-options: -Wall -threaded
Expand All @@ -66,7 +79,7 @@ test-suite doctests
, base-compat >=0.10.5 && <1
, doctest >=0.15 && <1
, multiple-components-example
, QuickCheck >=2.12 && <3
, QuickCheck >=2.10 && <3
, template-haskell

ghc-options: -Wall -threaded
Expand Down
14 changes: 13 additions & 1 deletion simple-example/simple-example.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ custom-setup
base < 5
, cabal-doctest >=1.0.9 && <1.1

-- Declaring this setup-dep fixes `stack build`. NOTE: Cabal API is not used directly
-- in the examples' Setup.hs! Instead, indirectly through wrappings of cabal-doctest.
--
-- When copying the example, you may of course delete this note, and start importing
-- Cabal stuff directly from Setup.hs.
-- ! Make sure to set a tighter bound, e.g. Cabal <3.16, here in setup-depends !
-- as soon as you do that.
, Cabal >= 1.24 && <4
-- > Warning: [...] Each major release of the 'Cabal' package changes the API
-- > in various ways and most packages will need some changes to compile with it.
-- > If you are not sure what upper bound to use then use the next major version.

library
exposed-modules: Example
default-extensions:
Expand All @@ -51,7 +63,7 @@ test-suite doctests
base
, base-compat >=0.10.5 && <1
, doctest >=0.15 && <1
, QuickCheck >=2.12 && <3
, QuickCheck >=2.10 && <3
, simple-example
, template-haskell

Expand Down
Loading

0 comments on commit ac00851

Please sign in to comment.