Skip to content

Updates to the cabal files created by cabal init #11231

@Kleidukos

Description

@Kleidukos

I'd like to suggest some updates to the cabal file template used by cabal init. We can discuss them separately.

Motivation

I believe that cabal should guide new projects towards writing the best Haskell code possible, without taking principled stances on things that are based in personal taste.

Disclaimer: My background is as a professional backend developer using Haskell, and I mentor new users in community spaces through their first cabal projects.

Current state of things

For the purpose of this ticket I created a new cabal project:

cabal-version:      3.4
name:               lol
version:            0.1.0.0
-- synopsis:
-- description:
license:            BSD-3-Clause
license-file:       LICENSE
-- author:
-- maintainer:
-- copyright:
build-type:         Simple
extra-doc-files:    CHANGELOG.md
-- extra-source-files:

common warnings
    ghc-options: -Wall

library
    import:           warnings
    exposed-modules:  MyLib
    -- other-modules:
    -- other-extensions:
    build-depends:    base ^>=4.18.3.0
    hs-source-dirs:   src
    default-language: GHC2024

executable lol
    import:           warnings
    main-is:          Main.hs
    -- other-modules:
    -- other-extensions:
    build-depends:
        base ^>=4.18.3.0,
        lol

    hs-source-dirs:   app
    default-language: GHC2024

test-suite lol-test
    import:           warnings
    default-language: GHC2024
    -- other-modules:
    -- other-extensions:
    type:             exitcode-stdio-1.0
    hs-source-dirs:   test
    main-is:          Main.hs
    build-depends:
        base ^>=4.18.3.0,
        lol

Changes

What does the project build?

$ cabal init
What does the package build:
   1) Library
 * 2) Executable
   3) Library and Executable
   4) Test suite
Your choice? [default: Executable] 

A. Executable without library: The current community status quo encourages cabal projects to put most of their business logic in a library component and put program initialisation in the executable's Main.hs. As such, the executable-only option does not reflect the best practices promoted by the community.
The fact that it is the default option makes it even worse.

My recommendation is to remove it.

B. Test suite: I can find a reasoning as to why some projects may want to have a separate package for their test suite that gets picked up by cabal test, but highly it's unclear to beginners why they should care for such a thing. Moreover, the presence of Test suite below Library and Executable implies that the interactive wizard will not create a test suite component (since it's a different option).

My recommendation is to put an in-line explanation like (for projects who wish to separate their test suite) if we are keeping it. I could see it removed, as it does not reflect a frequently used pattern for new projects.

Common stanzas

In the last industrial & hobby projects I have worked on, the default-language is shared by all components. In fact, the current warnings name is misleading because it incurs an artificial distinction between flags that provide warnings and those that do not. I would like to suggest (based on experience) a new nomenclature:

  • extensions: Used by all components
  • `ghc-options: Used by all components
  • rts-options: Used by all executable, test and benchmark components

In practice this looks like the following snippet (NOTE: The extensions and flags themselves are not suggested for inclusion in the template, they are just here as an example that I have picked verbatim from my projects):

common extensions
  default-extensions:
    DataKinds
    DeepSubsumption
    DeriveAnyClass
    DerivingStrategies
    DerivingVia
    DuplicateRecordFields
    GADTs
    LambdaCase
    NoStarIsType
    NumericUnderscores
    OverloadedLabels
    OverloadedRecordDot
    OverloadedStrings
    PackageImports
    PolyKinds
    StrictData
    TypeFamilies
    UndecidableInstances
    ViewPatterns

  default-language: GHC2021

common ghc-options
  ghc-options:
    -Wall
    -Wcompat
    -Widentities
    -Wincomplete-record-updates
    -Wincomplete-uni-patterns
    -Wpartial-fields
    -fhide-source-paths
    -Wno-unused-do-bind
    -fshow-hole-constraints
    -Wno-unticked-promoted-constructors
    -Werror=unused-imports
    -fdicts-strict
    -fmax-worker-args=16
    -fspec-constr-recursive=16
    -Wunused-packages
    -flate-specialise
    -funbox-strict-fields
    -finline-generics-aggressively
    -fexpose-all-unfoldings

common rts-options
  ghc-options:
    -rtsopts
    -threaded
    "-with-rtsopts=-N -T"

You can see that GHC options used for both emitting warnings and controlling behaviour like strict dicts, unboxing, inlining, and UX improvements like -fhide-source-paths are shared within one stanza, whereas the rtsopts relevant only for runtime are located elsewhere.

extra-doc-files, extra-source-files

It's good that CHANGELOG.md is included in extra-doc-files, but I'd like to have a README.md generated and added to this stanza.

Bounds for GHC when selecting GHC language editions

I selected the GHC2024 language edition, which I'm told needs GHC 9.10 at least. However when generating the cabal file, the "default" GHC in my path (GHC 9.6) is used to generate the bounds of base, leading to a cabal-GHC error when I type cabal build.

Since I (and most users) use GHCup to handle our toolchains, we are expected to use cabal.project to give the appropriate GHC executable name. I'm not sure what the ideal workflow looks like here.

The cabal.project limbos

Ok I'm a GHCup user, I have asked on Discord how to make cabal pick my compiler of choice, I have learned that there exists a cabal.project file that I can create (but never seen mentioned once in the guide or in the interactive wizard).

Turns out writing packages: ./ and with-compiler: ghc-9.10.1 is not enough because my test suite does not build when I run cabal build or even cabal build all. That's a big UX problem that is almost inexplicable because while the default is documented as False, it's overridden when calling cabal test.

My recommendations are:

  1. Either we enable tests: True by default, which is the usually desired behaviour: If you have created a test suite and written tests, you usually want to build it with the rest, so that cabal test does not surprise you with a second build step just for your tests.

  2. We create a cabal.project file with packages: ./ at least, and commented keys like tests, documentation and optimization. I'm specifically mentioning optimization because we want to discourage project authors to put optimisation levels in their package file, so the sooner we redirect them to cabal.project, the better it will be.


I can do the implementation but please do judge these suggestions on their own merit.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions