Updates to the cabal files created by cabal init
- Dominant language
- Haskell
- Stars
- 1.7k
- Forks
- 750
- Avg merge
- 4d 3h
- Merged PRs (30d)
- 28
Description
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
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):**
```cabal
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](https://cabal.readthedocs.io/en/stable/getting-started.html#creating-a-new-application) 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.
Contributor guide
Research direction
Start with the cabal init entry point and the generated .cabal template, then compare its prompts and output with the getting-started guide and the proposed cabal.project workflow. Done means the agreed template, prompt, compiler-bound, test, and project-file changes are implemented consistently and the resulting new project follows the documented workflow.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- build-system, cli, developer-experience
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100