Awesome
hpack: A modern format for Haskell packages
Hpack is a format for Haskell packages. It is a modern alternative to the Cabal package format and follows different design principles.
Design principles
The guiding design principles for Hpack are:
- Don't require the user to state the obvious, make sensible assumptions by default
- Give the user 100% control when needed
- Don't require the user to repeat things, facilitate DRYness
Tool integration
Hpack packages are described in a file named package.yaml
. Both
cabal2nix
and
stack
support package.yaml
natively. For
other build tools the hpack
executable can be used to generate a .cabal
file from package.yaml
.
There is no user guide
There is reference documentation below, but introductory documentation is still lacking. For the time being, take a look at the slides from my talk about Hpack at the Singapore Haskell meetup: http://typeful.net/talks/hpack
Examples
- Given this package.yaml running
hpack
will generate hpack.cabal - Given this package.yaml running
hpack
will generate getopt-generics.cabal - Given this package.yaml running
hpack
will generate sensei.cabal - Given this package.yaml running
hpack
will generate base-orphans.cabal
Documentation
<!--ts-->- hpack: A modern format for Haskell packages
Handling of Paths_
modules
Cabal generates a Paths_
module for every package. How exactly Hpack behaves
in regards to that module depends on the value of the spec-version
field.
If the spec-version
is explicitly specified and at least 0.36.0
the modern
behavior is used, otherwise Hpack falls back to the legacy behavior.
To use the modern behavior, require at least
spec-version: 0.36.0
in your package.yaml
.
Modern behavior
If you want to use the Paths_
module for a component, you have to explicitly
specify it under generated-other-modules
.
Example:
library:
source-dirs: src
generated-other-modules: Paths_name # substitute name with the package name
Legacy behavior
For historic reasons Hpack adds the Paths_
module to other-modules
when
generating a .cabal
file.
To prevent Hpack from adding the Paths_
module to other-modules
add the
following to package.yaml
:
library:
when:
- condition: false
other-modules: Paths_name # substitute name with the package name
Quick-reference
Top-level fields
Hpack | Cabal | Default | Notes | Example | Since |
---|---|---|---|---|---|
spec-version | The minimum version of hpack that is required to parse this package description. | spec-version: 0.30.0 | 0.30.0 | ||
name | · | ||||
version | · | 0.0.0 | |||
synopsis | · | ||||
description | · | ||||
category | · | ||||
stability | · | ||||
homepage | · | If github given, <repo>#readme | |||
bug-reports | · | If github given, <repo>/issues | |||
author | · | May be a list | |||
maintainer | · | author | May be a list | ||
copyright | · | May be a list | |||
license | · | Inferred from license-file | Both SPDX license expressions and traditional Cabal license identifiers are accepted. | license: MIT | SPDX: 0.29.0 |
license-file | license-file or license-files | LICENSE if file exists | May be a list | ||
tested-with | · | May be a list (since 0.34.3 ) | |||
build-type | · | Simple , or Custom if custom-setup exists | Must be Simple , Configure , Make , or Custom | ||
extra-source-files | · | Accepts glob patterns | |||
extra-doc-files | · | Accepts glob patterns | 0.21.2 | ||
data-files | · | Accepts glob patterns | |||
data-dir | · | ||||
github | source-repository head | Accepts owner/repo or owner/repo/subdir | github: foo/bar | ||
git | source-repository head | No effect if github given | git: https://my.repo.com/foo | ||
custom-setup | · | See Custom setup | |||
flags | flag <name> | Map from flag name to flag (see Flags) | |||
library | · | See Library fields | |||
internal-libraries | library <name> | Map from internal library name to a dict of library fields and global top-level fields. | 0.21.0 | ||
executables | executable <name> | Map from executable name to executable (see Executable fields) | |||
executable | executable <package-name> | Shortcut for executables: { package-name: ... } | 0.18.0 | ||
tests | test-suite <name> | Map from test name to test (see Test fields) | |||
benchmarks | benchmark <name> | Map from benchmark name to benchmark (see Benchmark fields) | |||
defaults | See Defaults, may be a list |
cabal-version
Hpack does not require you to specify a cabal-version
manually. When
generating a .cabal
file, Hpack sets the cabal-version
automatically based
on the features that are used.
If you want to override this behavior you can use verbatim
to set
cabal-version
manually, e.g.:
verbatim:
cabal-version: 2.2
Defaults
Hpack allows the inclusion of common fields from a file on GitHub or a local file.
To use this feature a user must specify a GitHub repository, Git reference and a path to a file within that repository; alternatively, a path to the local file must be given.
Example:
defaults:
github: sol/hpack-template
ref: 2017
path: defaults.yaml
This will include all common fields from https://github.com/sol/hpack-template/blob/2017/defaults.yaml into the package specification.
Field | Default | Notes | Example |
---|---|---|---|
github | For github defaults. | Accepts <owner>/<repo> | github: sol/hpack-template |
ref | For github defaults. | ref: 2017 | |
path | .hpack/defaults.yaml | For github defaults. A relative path to a file within the repository, path segments are separated by / and must not contain : and \ . | path: defaults.yaml |
local | For local defaults. New in 0.26.0 . |
Exactly one of github
and local
must be given in a defaults
section.
Hpack supports shorthand syntax for specifying github
and ref
as a string:
defaults: sol/hpack-template@2017
This is equivalent to:
defaults:
github: sol/hpack-template
ref: 2017
Note: Hpack caches downloaded files under
~/.hpack/defaults/<owner>/<repo>/<path>
. Once downloaded, a file is reused
from the cache. If the content on GitHub changes the file is not updated. For
this reason it is recommended to only use tags as Git references.
-
If a defaults file has changed on GitHub and you want to use the latest version, then you have to delete that file from the cache manually.
-
If you want to prevent Hpack from accessing the network to download a defaults file, then you can achieve this by adding that file to the cache manually.
Custom setup
Hpack | Cabal | Default | Notes | Example |
---|---|---|---|---|
dependencies | setup-depends | Implies build-type: Custom |
Common fields
These fields can be specified top-level or on a per section basis; top-level values are merged with per section values.
Hpack | Cabal | Default | Notes |
---|---|---|---|
buildable | · | Per section takes precedence over top-level | |
source-dirs | hs-source-dirs | ||
default-extensions | · | ||
language | default-language | Haskell2010 | Also accepts Haskell98 , GHC2021 or GHC2024 . Per section takes precedence over top-level |
other-extensions | · | ||
ghc-options | · | ||
ghc-prof-options | · | ||
ghc-shared-options | · | ||
ghcjs-options | · | ||
cpp-options | · | ||
asm-options | · | ||
asm-sources | · | Accepts glob patterns | |
cc-options | · | ||
c-sources | · | Accepts glob patterns | |
cxx-options | · | ||
cxx-sources | · | Accepts glob patterns | |
js-sources | · | Accepts glob patterns | |
extra-lib-dirs | · | ||
extra-libraries | · | ||
include-dirs | · | ||
install-includes | · | ||
frameworks | · | ||
extra-frameworks-dirs | · | ||
ld-options | · | ||
dependencies | build-depends | See Dependencies | |
pkg-config-dependencies | pkgconfig-depends | ||
build-tools | build-tools and/or build-tool-depends | ||
system-build-tools | build-tools | A set of system executables that have to be on the PATH to build this component | |
when | Accepts a list of conditionals (see Conditionals) |
build-tools
: A set of Haskell executables that are needed to build this component
Each element consists of a name and an optional version constraint.
The name can be specified in two ways:
- Qualified:
<package>:<executable>
- Unqualified:
<executable>
A qualified name refers to an executable named <executable>
from a
package named <package>
.
An unqualified name either refers to an executables in the same package, or if
no such executable exists it is desugared to <executable>:<executable>
.
build-tools
can be specified as a list or a mapping.
Examples:
build-tools:
- alex
- happy:happy
- hspec-discover == 2.*
build-tools:
alex: 3.2.*
happy:happy: 1.19.*
hspec-discover: 2.*
When generating a .cabal
file each element of build-tools
is either added
to build-tools
or build-tool-depends
.
If the name refers to one of alex
, c2hs
, cpphs
, greencard
, haddock
,
happy
, hsc2hs
or hscolour
then the element is added to build-tools
,
otherwise it is added to build-tool-depends
.
This is done to allow compatibility with a wider range of Cabal
versions.
Note: Unlike Cabal
, Hpack does not accept system executables as
build-tools
. Use system-build-tools
if you need this.
Library fields
Hpack | Cabal | Default | Notes |
---|---|---|---|
exposed | · | ||
visibility | · | ||
exposed-modules | · | All modules in source-dirs less other-modules less any modules mentioned in when | |
generated-exposed-modules | Added to exposed-modules and autogen-modules . Since 0.23.0 . | ||
other-modules | · | Outside conditionals: All modules in source-dirs less exposed-modules less any modules mentioned in when . Inside conditionals, and only if exposed-modules is not specified inside the conditional: All modules in source-dirs of the conditional less any modules mentioned in when of the conditional | |
generated-other-modules | Added to other-modules and autogen-modules . Since 0.23.0 . | ||
reexported-modules | · | ||
signatures | · |
Executable fields
Hpack | Cabal | Default | Notes |
---|---|---|---|
main | main-is | ||
other-modules | · | All modules in source-dirs less main less any modules mentioned in when | |
generated-other-modules | Added to other-modules and autogen-modules . Since 0.23.0 . |
Test fields
Hpack | Cabal | Default | Notes |
---|---|---|---|
type | exitcode-stdio-1.0 | ||
main | main-is | ||
other-modules | · | All modules in source-dirs less main less any modules mentioned in when | |
generated-other-modules | Added to other-modules and autogen-modules . Since 0.23.0 . |
Benchmark fields
Hpack | Cabal | Default | Notes |
---|---|---|---|
type | exitcode-stdio-1.0 | ||
main | main-is | ||
other-modules | · | All modules in source-dirs less main less any modules mentioned in when | |
generated-other-modules | Added to other-modules and autogen-modules . Since 0.23.0 . |
Flags
Hpack | Cabal | Default | Notes |
---|---|---|---|
description | · | Optional | |
manual | · | Required (unlike Cabal) | |
default | · | Required (unlike Cabal) |
Dependencies
Dependencies can be specified as either a list or an object. These are equivalent:
dependencies:
- base >= 4.10.1.0
- containers >= 5.10
dependencies:
base: ">= 4.10.1.0"
containers: ">= 5.10"
The individual dependencies can also be specified as an object:
dependencies:
- name: base
version: ">= 4.10.1.0"
- name: containers
You can use objects at both levels, or have a mix of valid ways to specify the individual dependencies:
dependencies:
base:
version: ">= 4.10.1.0"
# If you don't give a version, it defaults to 'any version'.
containers: {}
transformers: ">= 0.5.5.0 && < 5.6"
Individual dependencies as objects are only supported from version
0.31.0
.
When a dependency is specified as an object, you can use the mixin
field to control what modules from the dependency your program will
see and how its signatures are filled in:
dependencies:
# This gives you a shorter name to import from, and hides the other modules.
- name: containers
mixin:
- (Data.Map.Lazy as Map)
# This hides the System.IO.Unsafe module, and leaves the other modules unchanged.
- name: base
mixin:
- hiding (System.IO.Unsafe)
# This exposes only the listed modules - you won't be able to import the others!
- name: lens
mixin:
- (Control.Lens, Data.Set.Lens, Data.Map.Lens as MapL)
# This will rename the module, and expose the others.
- name: transformers
mixin:
- hiding (Control.Monad.Trans.State.Lazy)
- (Control.Monad.Trans.State.Lazy as State)
For more information, see the Cabal documentation.
Hint: you can hide the Prelude
module from base
, and then rename
an alternative prelude to Prelude
so that it doesn't need to be
imported!
mixin
was added in version 0.31.0
.
Conditionals
Conditionals with no else branch:
- Must have a
condition
field - May have any number of other fields
For example,
when:
- condition: os(darwin)
extra-lib-dirs: lib/darwin
becomes
if os(darwin)
extra-lib-dirs:
lib/darwin
Conditionals with an else branch:
- Must have a
condition
field - Must have a
then
field, itself an object containing any number of other fields - Must have a
else
field, itself an object containing any number of other fields
For example,
when:
- condition: flag(fast)
then:
ghc-options: -O2
else:
ghc-options: -O0
becomes
if flag(fast)
ghc-options: -O2
else
ghc-options: -O0
Note: Conditionals with condition: false
are omitted from the generated
.cabal
file.
File globbing
At place where you can specify a list of files you can also use glob patterns. Glob patterns and ordinary file names can be freely mixed, e.g.:
extra-source-files:
- static/*.js
- static/site.css
Glob patterns are expanded according to the following rules:
?
and*
are expanded according to POSIX (they match arbitrary characters, except for directory separators)**
is expanded in azsh
-like fashion (matching across directory separators)?
,*
and**
do not match a.
at the beginning of a file/directory
Passing things to Cabal verbatim
(since hpack-0.24.0
)
In cases where Hpack does not (yet!) support what you want to do, you can use
the verbatim
field to pass things to Cabal verbatim.
It is recognized top-level, in sections, and in conditionals.
verbatim
accepts an object or a string (or a list of objects and strings).
Disclaimer: The content of verbatim
fields are merged into the
generated .cabal
file as a final step, after Hpack is done with most of its
work. Before that final step Hpack does not look at any verbatim
fields.
Consequently, the content of a verbatim
field does not affect any other
fields that are populated by Hpack. As an example, if you use verbatim
to
override hs-source-dirs
, the overridden information will not be used when
Hpack infers exposed-modules
or other-modules
.
Objects
When an object is used:
- field values can be strings, numbers, booleans, or
null
- existing
.cabal
fields can be overridden - existing
.cabal
fields can be removed by overriding withnull
- additional
.cabal
fields can be added
Example:
tests:
spec:
main: Spec.hs
source-dirs: test
verbatim:
type: detailed-0.9 # change type from exitcode-stdio-1.0
default-language: null # remove default-language
Strings
When a string is used:
- it will be added verbatim, indented to match the indentation of the surrounding context.
- all existing
.cabal
fields are left untouched
Example:
verbatim: |
build-tool-depends:
hspec-discover:hspec-discover == 2.*
Lists of objects and strings
You can combine the use of objects and strings to gain more fine-grained control, e.g. you can remove an existing field with an object and then include it with a string so that you have 100% control over the layout.
verbatim:
- build-depends: null
- |
-- let's use Cabal 5.0 dependency syntax
build-depends:
hspec: [2-3[
Not repeating yourself
It is possible to use YAML anchors (&
), aliases
(*
) and merge keys (<<
) to define fields and reference them
later.
executables:
my-exe-1: &my-exe
main: my-exe-1.hs
dependencies: [base, my-lib]
ghc-options: [-threaded]
my-exe-2:
<<: *my-exe
main: my-exe-2.hs
Fields that start with an underscore are ignored by hpack
, so they can be
used to declare aliases:
_exe-ghc-options: &exe-ghc-options
- -threaded
- -rtsopts
executables:
my-exe-1:
ghc-options: *exe-ghc-options
It is also possible to use the !include
directive:
# ...
tests:
hlint: !include "../common/hlint.yaml"
hlint.yaml
:
source-dirs: test
main: hlint.hs
dependencies: [base, hlint]
This can also be used to provide entire libraries of snippets:
_common/lib: !include "../common/lib.yaml"
name: example1
version: '0.1.0.0'
synopsis: Example
<<: *legal
<<: *defaults
library:
source-dirs: src
tests:
hlint: *test_hlint
lib.yaml:
- &legal
maintainer: Some One <someone@example.com>
copyright: (c) 2017 Some One
license: BSD3
- &defaults
dependencies:
- base
- containers
ghc-options:
- -Wall
- -Werror
- &test_hlint
source-dirs: test
main: hlint.hs
dependencies: [hlint]
The hpack executable
If the hpack
executable is on the PATH
, to obtain help about its usage,
command hpack --help
. In addition to its main use, hpack
can also be used as
follows:
hpack --version
Output information about the version ofhpack
to the standard output channel, in the formathpack version x.y.z
.hpack --numeric-version
Output information about the version ofhpack
to the standard output channel, in the formatx.y.z
.hpack --help
Output information about the usage ofhpack
to the standard error channel.hpack list
(undocumented) Output the names of the package's exposed modules to the standard output channel, in the format of one name on each line.
In respect of its main use, hpack
has the following optional flags:
--silent
Output no information other than error messages.--canonical
By default,hpack
takes into account aspects of the format of an existing Cabal file when generating a new Cabal file. Pass this flag to causehpack
to ignore the format of an existing Cabal file when generating a new one.--force
or-f
By default,hpack
will not generate a Cabal file unnecessarily. Pass this flag to force the generation of a new Cabal file.--[no-]hash
Enable/disable the inclusion of a SHA-256 hash of the other content of the generated Cabal file in the header comment added byhpack
to the generated Cabal file. (default: disabled)-
Output the generated Cabal file contents to the standard output channel.
By default, hpack
will assume the package description in the Hpack format is
in file package.yaml
in the current working directory. Alternatively, a
relative or absolute path to a file can be specified.
Vim integration
To run hpack
automatically on modifications to package.yaml
add the
following to your ~/.vimrc
:
autocmd BufWritePost package.yaml call Hpack()
function Hpack()
let err = system('hpack ' . expand('%'))
if v:shell_error
echo err
endif
endfunction
Stack support
Stack has built-in support for Hpack.
If you are using Stack you can use package.yaml
instead of a .cabal
file. No additional
steps are required.
Binaries for use on CI
You can get binaries for use on CI with:
curl -sSL https://raw.githubusercontent.com/sol/hpack/main/get-hpack.sh | bash
hpack && git diff --exit-code
(both Linux and OS X are supported)