Skip to content

tonyday567/checklist

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 

Repository files navigation

[C]hecklist2024

A Haskell [C]hecklist. For starting off, rebooting, or just freshening up.

Checklist2024

A new GHC and a new GHC2024 needs a refresh of the Haskell [C]hecklist. Highlights include:

  • GHC2024 is here and a GHC2024 cabal stanza backport is included.
  • Given the spread of the community, CI recommendations cover ghc-8.6 to ghc-8.12.
  • cabal-gild, a cabal file formatter, gets included in the checklist.

The checklist has been tested with:

ghcup list -c set -r

Quick Start 🛼

A very, very short list for creating and maintaining a Haskell project.

  • [ ] Install Haskell via ghcup. Install everything.
  • [ ] Create a new directory and run cabal init. Follow the prompts.
  • [ ] (optional) Install a few more tools.
  • [ ] Pick an IDE; vscode, neo-vim, emacs or unix command line. It all works.
  • [ ] (optional) read through the extras section.
  • [ ] (optional) read through the cabal stanzas section.
  • [ ] (required) hack happy Haskell for your new pet project.
  • [ ] follow the checklist when you want to tidy up.

Welcome to Haskell2024

Checklist

  • [ ] version bump
  • [ ] ghcup upgrades

    Check if tooling is current, and upgrade with ghcup as needed.

    ghcup list -c set -r
        
  • [ ] cabal.project check

    Is the cabal.project & cabal.project.local files clean?

    cat cabal.project
        
    cat cabal.project.local
        
  • [ ] upstream publishings

    Have upstream dependencies been published on Hackage?

  • [ ] cabal update
    cabal update
        
  • [ ] cabal outdated
    cabal outdated
        
  • [ ] cabal gen-bounds
    cabal gen-bounds
        
  • [ ] cabal build –ghc-options=-Wunused-packages
    cabal clean && cabal build --ghc-options=-Wunused-packages
        
  • [ ] cabal formatter
    cabal-gild -m check --input=xyzzy.cabal
        
    cabal-gild --io=xyzzy.cabal
        
  • [ ] pragma cleanup

    Consider a backported GHC2024.

  • [ ] doctests
    cabal-docspec
        
    cabal run doctests
        
  • [ ] cabal install
    cabal install
        
  • [ ] cabal bench
    cabal bench
        
  • [ ] ormolu
    ormolu --mode check $(git ls-files '*.hs')
        
    ormolu --mode inplace $(git ls-files '*.hs')
        
  • [ ] CI upgrade
    • check tested-with line in cabal file
    • check ./.github/workflow/haskell-ci.yaml actions for updates
  • [ ] ChangeLog
  • [ ] Versioning

    Check current hackage version and bump the package version in the cabal file, based on PvP.

    https://pvp.haskell.org/

  • [ ] branch, push & check CI
  • [ ] stack checks

    https://github.com/commercialhaskell/stackage/blob/master/verify-package

    stack init --resolver nightly --ignore-subdirs
    stack build --resolver nightly --haddock --test --bench --no-run-benchmarks
        
  • [ ] haddock
    cabal haddock
        
  • [ ] readme
  • [ ] PR to main
  • [ ] merge PR
  • [ ] immediate checkout and pull main
  • [ ] final check
    cabal clean && cabal build && cabal-docspec
        
  • [ ] hkgr tagdist
    hkgr tagdist
        
  • [ ] hkgr publish
    hkgr publish
        

    This won’t work if there are cabal.project specifications. So, something like:

    cabal upload .hkgr/prettychart-0.2.0.0.tar.gz --publish
        
  • [ ] check Hackage

    Sometimes haddocks don’t build on Hackage. Here’s a recipe for uploading your own docs.

    cabal haddock --builddir=docs --haddock-for-hackage --enable-doc
    cabal upload -d --publish docs/*-docs.tar.gz
        

cabal init

To quickly create a new Haskell project, run `cabal init` interactively or look through the cabal docs and use the command line eg

mkdir minimal && cd minimal && cabal init --minimal --simple --overwrite --lib --tests --language=GHC2024 --license=BSD-2-Clause -p minimal
[Info] Using cabal specification: 3.0
[Info] Creating fresh file LICENSE...
[Info] Creating fresh file CHANGELOG.md...
[Info] Creating fresh directory ./src...
[Info] Creating fresh file src/MyLib.hs...
[Info] Creating fresh directory ./test...
[Info] Creating fresh file test/Main.hs...
[Info] Creating fresh file minimal.cabal...
[Warn] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.

A quick test of these installations is to compile and test the project using cabal:

cd minimal && cabal build && cabal test

Tooling

Setup of a modern Haskell environment is straight forward. ghcup takes care of ghc, cabal, stack & the haskell-language-server. cabal can then be used to install other tools.

ghcup list -c set -r

ghcup places everything in ~/.ghcup/bin

which cabal
/Users/tonyday567/.ghcup/bin/cabal

Haskell-language-server versions matching older GHC versions are also installed, and selected automatically.

haskell-language-server-wrapper --version
haskell-language-server version: 2.5.0.0 (GHC: 9.2.8) (PATH: /Users/tonyday567/.ghcup/hls/2.5.0.0/lib/haskell-language-server-2.5.0.0/bin/haskell-language-server-wrapper)

extra tooling

This guide uses the following tools, which, when used together, provide the modern Haskell experience:

cabal installations

Most of the tools can be installed via cabal:

cabal install ormolu hlint hkgr cabal-gild --allow-newer --overwrite-policy=always

ghciwatch is via our cousins at rust:

cargo install ghciwatch

cabal stores executables in ​~​/.cabal/bin, stack in ​~​/.local/bin.

which hlint
/Users/tonyday567/.cabal/bin/hlint

cabal-docspec

cabal-docspec is a doctest runner that exists as a process outside the specification of a cabal project, acting more like hlint then a separate cabal stanza. The project is not available on hackage and needs to be installed manually:

git clone https://github.com/phadej/cabal-extras
cd cabal-extras/cabal-docspec
cabal install cabal-docspec:exe:cabal-docspec --overwrite-policy=always

Extras

A project typically needs a few more files that cabal init doesn’t cover.

readme.md

Practice varies widely, from saying nothing to all documentation being in the readme. This readme.md template:

  • adds some badges for Hackage & CI.
  • Includes a short description and basic Usage example, which in many cases should be exactly repeated in the cabal file as synopsis and description stanzas.
{{{name}}}
===

[![Hackage](https://img.shields.io/hackage/v/{{{name}}}.svg)](https://hackage.haskell.org/package/{{{name}}})
[![Build Status](https://github.com/{{{github-username}}}/{{{name}}}/workflows/haskell-ci/badge.svg)](https://github.com/{{{github-username}}}/{{{name}}}/actions?query=workflow%3Ahaskell-ci)

`{{{name}}}` is a new package.

Usage
==

``` haskell
import {{{lib-name}}}
```

readme.org

An alternative readme approach.

* {{{name}}}

[[https://hackage.haskell.org/package/{{{name}}}][https://img.shields.io/hackage/v/{{{name}}}.svg]]
[[https://github.com/{{{github-username}}}/{{{name}}}/actions?query=workflow%3Ahaskell-ci][https://github.com/{{{github-username}}}/{{{name}}}/workflows/haskell-ci/badge.svg]]

~{{{name}}}~ is a new package.

* Usage

#+begin_src haskell :results output
import {{{lib-name}}}
#+end_src

* Development

#+begin_src haskell :results output
:set -Wno-type-defaults
:set -Wno-name-shadowing
:set -XOverloadedStrings
#+end_src

check

#+begin_src haskell :results output :export both
let x = "ok"
putStrLn x
#+end_src

.hlint.yaml

- ignore: {name: Use if}
- ignore: {name: Use bimap}
- ignore: {name: Eta reduce}

.ghci

:set -Wno-type-defaults

.gitignore

/.stack-work/
/dist-newstyle/
stack.yaml.lock
**/.DS_Store
cabal.project.local*
/.hie/
.ghc.environment.*
/.hkgr/

.github/workflows/haskell-ci.yml

GitHub actions are the current and common practice for continuous integration of projects. The CI file below uses actions from haskell-actions. It includes tests for ormolu, hlint, cabal-doctest and the usual cabal checks across a wide GHC range.

GitHub Actions Documentation - GitHub Docs

As at publication, the latest actions are:

  • actions/checkout@v4
  • actions/cache/restore@v4
  • haskell-actions/setup@v2
  • haskell-actions/hlint-setup@v2
  • haskell-actions/run-ormolu@v17
  • haskell-actions/setup@v2
name: build
on: [push]

# INFO: The following configuration block ensures that only one build runs per branch,
# which may be desirable for projects with a costly build process.
# Remove this block from the CI workflow to let each CI job run to completion.
concurrency:
  group: build-${{ github.ref }}
  cancel-in-progress: true

jobs:
  hlint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: haskell-actions/hlint-setup@v2
    - uses: haskell-actions/hlint-run@v2
      with:
        path: .
        fail-on: warning
  ormolu:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: haskell-actions/run-ormolu@v17
  build:
    name: GHC ${{ matrix.ghc-version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        ghc-version: ['9.12', '9.10', '9.8', '9.6']

        include:
          - os: windows-latest
            ghc-version: '9.12'
          - os: macos-latest
            ghc-version: '9.12'

    steps:
      - uses: actions/checkout@v4

      - name: Set up GHC ${{ matrix.ghc-version }}
        uses: haskell-actions/setup@v2
        id: setup
        with:
          ghc-version: ${{ matrix.ghc-version }}

      - name: Configure the build
        run: |
          cabal configure --enable-tests --enable-benchmarks --disable-documentation
          cabal build --dry-run
        # The last step generates dist-newstyle/cache/plan.json for the cache key.

      - name: Restore cached dependencies
        uses: actions/cache/restore@v4
        id: cache
        env:
          key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-cabal-${{ steps.setup.outputs.cabal-version }}
        with:
          path: ${{ steps.setup.outputs.cabal-store }}
          key: ${{ env.key }}-plan-${{ hashFiles('**/plan.json') }}
          restore-keys: ${{ env.key }}-

      - name: Install dependencies
        # If we had an exact cache hit, the dependencies will be up to date.
        if: steps.cache.outputs.cache-hit != 'true'
        run: cabal build all --only-dependencies

      # Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail.
      - name: Save cached dependencies
        uses: actions/cache/save@v4
        # If we had an exact cache hit, trying to save the cache would error because of key clash.
        if: steps.cache.outputs.cache-hit != 'true'
        with:
          path: ${{ steps.setup.outputs.cabal-store }}
          key: ${{ steps.cache.outputs.cache-primary-key }}

      - name: Build
        run: cabal build all

      - if: ${{ matrix.os == 'ubuntu-latest' && matrix.ghc-version == '9.12'}}
        name: doctests
        run: cabal run doctests

      - name: Check cabal file
        run: cabal check

test/doctests.hs

module Main where

import System.Environment (getArgs)
import Test.DocTest (mainFromCabal)
import Prelude (IO, (=<<))

main :: IO ()
main = mainFromCabal "{{{name}}}" =<< getArgs

cabal stanzas

cabal docs have gotten very good of late, and these recommended stanzas should be read with those docs handy.

Stanzas are used like so:

library
  import: ghc2024-stanza
  import: ghc-options-stanza

ghc2024-stanza

GHC2024 is the future. For the past, these stanzas reproduce the GHC2024 extensions for ghc’s between 9.2 and 9.10.

common ghc2024-additions
  default-extensions:
    DataKinds
    DerivingStrategies
    DisambiguateRecordFields
    ExplicitNamespaces
    GADTs
    LambdaCase
    MonoLocalBinds
    RoleAnnotations

common ghc2024-stanza
  if impl(ghc >=9.10)
    default-language:
      GHC2024
  else
    import: ghc2024-additions
    default-language:
      GHC2021

ghc2024-wide-stanza

The stanzas below faithfully extend GHC2024 back into the ghc-8 series:

common ghc2021-additions
  default-extensions:
    BangPatterns
    BinaryLiterals
    ConstrainedClassMethods
    ConstraintKinds
    DeriveDataTypeable
    DeriveFoldable
    DeriveFunctor
    DeriveGeneric
    DeriveLift
    DeriveTraversable
    DoAndIfThenElse
    EmptyCase
    EmptyDataDecls
    EmptyDataDeriving
    ExistentialQuantification
    ExplicitForAll
    FlexibleContexts
    FlexibleInstances
    ForeignFunctionInterface
    GADTSyntax
    GeneralisedNewtypeDeriving
    HexFloatLiterals
    ImplicitPrelude
    InstanceSigs
    KindSignatures
    MonomorphismRestriction
    MultiParamTypeClasses
    NamedFieldPuns
    NamedWildCards
    NumericUnderscores
    PatternGuards
    PolyKinds
    PostfixOperators
    RankNTypes
    RelaxedPolyRec
    ScopedTypeVariables
    StandaloneDeriving
    StarIsType
    TraditionalRecordSyntax
    TupleSections
    TypeApplications
    TypeOperators
    TypeSynonymInstances

  if impl(ghc <9.2) && impl(ghc >=8.10)
    default-extensions:
      ImportQualifiedPost
      StandaloneKindSignatures

common ghc2024-additions
  default-extensions:
    DataKinds
    DerivingStrategies
    DisambiguateRecordFields
    ExplicitNamespaces
    GADTs
    LambdaCase
    MonoLocalBinds
    RoleAnnotations

common ghc2024-stanza
  if impl(ghc >=9.10)
    default-language:
      GHC2024
  elif impl(ghc >=9.2)
    import: ghc2024-additions
    default-language:
      GHC2021
  else
    import: ghc2021-additions
    import: ghc2024-additions
    default-language:
      Haskell2010

ghc-options-stanza

Best-practice ghc-options:

common ghc-options-stanza
  ghc-options:
    -Wall
    -Wcompat
    -Wincomplete-record-updates
    -Wincomplete-uni-patterns
    -Wredundant-constraints

ghc-options-exe-stanza

Best-practice exe ghc-options:

common ghc-options-exe-stanza
    ghc-options:
        -fforce-recomp
        -funbox-strict-fields
        -rtsopts
        -threaded
        -with-rtsopts=-N

doctest-parallel

https://github.com/martijnbastiaan/doctest-parallel

test-suite doctests
    import: ghc2021-stanza
    main-is: doctests.hs
    hs-source-dirs: test
    build-depends:
        , base             >=4.14 && <5
        , doctest-parallel >=0.3 && <0.4
    ghc-options: -threaded
    type: exitcode-stdio-1.0

extras-doc-files

readmes can be included as documentation within a cabal file like so:

extra-doc-files:
    ChangeLog.md
    readme.md

About

How I start Haskell.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •