Table of Contents
Table of Contents
Hix is a toolkit for Haskell development that uses Nix to provide a unified, declarative interface for a range of build related tasks:
Reproducible environments and dependency overrides
Cabal file generation
Hackage upload
Rapid-recompilation testing with GHCid
Haskell Language Server
CTags generation
Virtual Machines for testing
Compatibility checks for multiple GHC versions
The following two sections explain the basics of Cabal builds and Nix flakes. If you’re already familiar with those, you can skip ahead to the section called “Package definitions”, but the later sections will build upon the examples introduced here.
Cabal is the basic build tool for Haskell projects.
It reads package configuration from a .cabal
file and performs compilation and execution of tests and applications.
A Cabal package consists of one or more components like libraries, test suites and executables.
The example project for this tutorial is called parser
and defines a library and an executable.
The library has a single module named Parser.hs
in the subdirectory lib/
with the content:
module Parser where
import Data.Aeson (encode, object, toJSON, (.=))
import Data.ByteString.Lazy.Char8 (unpack)
import Data.String (fromString)
import System.Environment (getArgs)
import Text.Read (readMaybe)
createJson :: Int -> String
createJson n = unpack (encode (object [fromString "number" .= toJSON n]))
parseNumber :: IO String
parseNumber = parse <$> getArgs
where
parse (input : _) = maybe ("Not a number: " ++ input) createJson (readMaybe input)
parse _ = "No argument given."
The function parseNumber
converts the first command line argument into an integer and returns a JSON string that
looks like {number:5}
, or an error message if the argument is not a number.
It uses the JSON library aeson and the GHC core library
bytestring, which have to be specified as dependencies in the Cabal
file like this:
library
exposed-modules:
Parser
hs-source-dirs:
lib
build-depends:
aeson ==2.0.*
, base ==4.*
, bytestring
We need to configure the source directory lib
, the module Parser
as well as the dependencies, including the
standard library base
.
The second file in this project is the executable’s entry point, located at app/Main.hs
:
module Main where
import Parser (parseNumber)
main :: IO ()
main = putStrLn =<< parseNumber
The main
function calls parseNumber
and prints the returned string to stdout.
It has no dependencies except for base
and the library component, therefore its Cabal section is:
executable parser
main-is: Main.hs
hs-source-dirs:
app
build-depends:
base ==4.*
, parser
The only difference here is that we need to specify the module that contains the main
function with the key
main-is
.
When these two fragments are written to parser.cabal
in the project root along with a bit of boilerplate, the Cabal
CLI tool be used to compile and run the application by invoking it as cabal build
and cabal run
.
In order for this to work, the developer has to ensure that GHC and Cabal are installed and use the right version and package set snapshot to be certain that the application is built the same way on different machines. Hix aims to reduce the overhead of this process to requiring only the presence of Nix, with the goal of replicating the development environment and all involved tools identically on any machine. While Cabal offers some mechanisms for this kind of reproducibility, Nix provides a unified and ergonomic interface to it on multiple levels of the build.
The build definition for a Nix project is declared in the file flake.nix
with the following
protocol:
{
inputs = {...};
outputs = inputs: {
packages = {...};
apps = {...};
...
};
}
The input set declares dependencies on other repositories, which are downloaded and passed to the output function as source directories when a Nix command is executed. Depending on the command, Nix chooses one of the entries in the output set and builds the requested package.
A basic example for an application that prints “Hello” could look like this:
{
inputs = { nixpkgs.url = "github:nixos/nixpkgs/b139e44d78c36c69bcbb825b20dbfa51e7738347"; };
outputs = {self, nixpkgs}: let
pkgs = import nixpkgs { system = "x86_64-linux"; };
drv = pkgs.writeScriptBin "greet" "echo Hello";
in {
apps.x86_64-linux.greet = {
type = "app";
program = "${drv}/bin/greet";
};
};
}
The single input is the nixpkgs repository at a specific commit, which contains both the build definitions for tens of thousands of available packages and many tools for creating new packages.
The single output is an app named greet
declared only for the architecture x86_64-linux
, which can be executed
with:
nix run .#greet
where the .
denotes the directory of the flake and the part after the #
is the name of the output, which Nix tries
to locate in different categories depending on the command – in the case of run
, it starts looking in apps
using
the current system’s architecture.
To gain access to nixpkgs’ functionality, we import the default.nix
(implicitly) at the root of the repository,
passing the identifier of the architecture we want to use as the system
argument.
The function writeScriptBin
is a so-called “trivial builder”, a function that produces a very simple package, like a
single shell script.
Under the hood, Nix wraps the builder in a data structure called
derivation that serves as the universal protocol
for the specification of dependencies, build steps and build outputs.
When Nix is instructed to process a derivation, it follows its build steps and writes the resulting files to a
directory in /nix/store
.
In this case, the text echo Hello
is written to /nix/store/bkz0kkv0hxhb5spcxw84aizcj5rm4qq9-greet/bin/greet
, where
the hash is calculated from the text and other environmental parameters.
When the value drv
is interpolated into the string in the declaration of the app output, Nix builds the derivation
and inserts the resulting store path (minus the bin/greet
).
Derivations are ubiquitous – when we build a Haskell application with Nix, it is represented as a derivation, just like all of its dependencies. For the Cabal project described in the previous section, we can create a derivation and an app with this flake:
{
inputs = { nixpkgs.url = "github:nixos/nixpkgs/b139e44d78c36c69bcbb825b20dbfa51e7738347"; };
outputs = {self, nixpkgs}: let
pkgs = import nixpkgs { system = "x86_64-linux"; };
parser = pkgs.haskell.packages.ghc925.callCabal2nix "parser" ./. {};
in {
packages.x86_64-linux.parser = parser;
apps.x86_64-linux.greet = {
type = "app";
program = "${parser}/bin/parser";
};
};
}
Now the app can be executed with:
$ nix run .#parser -- 63
{"number":63}
To create a derivation for the Haskell app, we select a package set for the GHC version 9.2.5 with
pkgs.haskell.packages.ghc92
and call the builder exposed by that set that wraps the tool
cabal2nix, which converts the Cabal file in the specified directory to a
derivation.
Cabal2nix reads the dependencies from the file and ensures that they are accessible during the build, in which Cabal
is invoked like in the previous section, though using a lower-level interface.
If this project were cloned on a different machine, like in a CI pipeline, the nixpkgs snapshot, and hence the entire
build environment, would be identical to that on your development machine – not just because the Git revision is
hardcoded in the flake input, but because Nix records the revision in the lockfile, flake.lock
.
If the revision were omitted, the latest commit at the time the project was first evaluated would be used, and updated
only when explicitly requested by executing either nix flake update
(to update all inputs) or nix flake lock --update-input nixpkgs
.
For a trivial Cabal project like this, the build definition may not look complicated enough to elicit the desire for higher abstractions. However, when the requirements become more elaborate, the basic tools provided by nixpkgs may prove inadequate, and the following sections attempt to illustrate some of the circumstances that warrant a more complex toolkit.
Table of Contents
Hix provides two fundamental services to combine Cabal and Nix:
Generating Cabal build files from configuration declared in Nix expressions.
Creating reproducible derivations from those files for different configurable environments that obtain dependencies from the Nix package set, which can be built and run via the flake interface.
The package from the tutorial section, consisting of an executable in app/
and a library in lib/
with dependencies
on the packages aeson
and bytestring
, can be declared in a flake like this:
{
description = "Example";
inputs.hix.url = "github:tek/hix?ref=0.8.0";
outputs = {hix, ...}: hix {
packages.parser = {
src = ./.;
library = {
enable = true;
dependencies = ["aeson ^>= 2.0" "bytestring"];
};
executable.enable = true;
};
};
}
In order to build the project, we first have to generate the Cabal file:
$ nix run .#gen-cabal
Flake commands ignore files that are not under version control when operating in a git repository.
If there are untracked files in your project, running nix build .#gen-cabal
might fail, so either git add
everything or run the command as nix run path:.#gen-cabal
, which bypasses this mechanism.
This command will create the file parser.cabal
in the project directory, with the following content:
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.35.0.
--
-- see: https://github.com/sol/hpack
name: parser
version: 0.1.0.0
description: See https://hackage.haskell.org/package/parser/docs/Parser.html
license: GPL-3
build-type: Simple
library
exposed-modules:
Parser
other-modules:
Paths_parser
hs-source-dirs:
lib
build-depends:
aeson ==2.0.*
, base ==4.*
, bytestring
default-language: Haskell2010
executable parser
main-is: Main.hs
other-modules:
Paths_parser
hs-source-dirs:
app
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
base ==4.*
, parser
default-language: Haskell2010
Using the package definition and the generated Cabal file, Hix creates flake outputs for building and running the
application with nix build
and nix run
:
$ nix run .#parser -- 63
{"number":63}
The generated flake outputs have roughly the following structure, analogous to the example in {#nix-flakes}:
{
outputs = let
parser = callCabal2nix "parser" ./. {};
in {
packages.x86_64-linux.parser = parser;
apps.x86_64-linux.parser = { type = "app"; program = "${parser}/bin/parser"; };
};
}
Rather than writing the boilerplate yourself, the Hix CLI application can generate it for you.
The CLI command new
will create a project skeleton with an executable and test suite in the current directory:
nix run 'github:tek/hix?ref=0.8.0#new' -- --name 'project-name' --author 'Your Name'
If you have an existing project with Cabal files in it, the bootstrap
command will create a flake that configures
the more basic components:
nix run 'github:tek/hix?ref=0.8.0#bootstrap'
There are three levels of generality at which Cabal options can be specified:
Global options in the module cabal
apply to all packages and components
Package-level options in the module packages.<name>.cabal
apply to all components in a package
Component-level options in all component modules
The structure looks like this:
{
cabal = {
dependencies = ["global-dep"];
};
packages = {
core = {
cabal = {
dependencies = ["core-dep"];
};
library = {
dependencies = ["core-lib-dep"];
};
executables.run = {
dependencies = ["core-exe-dep"];
};
};
api = {
cabal = {
dependencies = ["api-dep"];
};
library = {
dependencies = ["api-lib-dep"];
};
tests.unit = {
dependencies = ["api-test-dep"];
};
};
};
}
Each component gets global-dep
, while all components in core
get core-dep
.
Since dependencies
is a list option, the values are merged, so that the unit
component in api
will have the
dependencies ["global-dep" "api-dep" "api-test-dep"]
.
Verbatim configuration for individual components that have no specific option may be specified in the component
module:
{
packages = {
core = {
library = {
component = {
other-modules = ["Prelude"];
};
};
};
};
}
These are the Cabal options for packages and components that can be specified at any level.
enable
Whether to enable this <component type>.
Type: boolean
Default:
true
Example:
true
author
The author of the packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
null
base
The dependency spec for the base
package.
Type: string or (submodule)
Default:
"base >= 4 && < 5"
baseHide
The dependency spec for the base
package used when prelude
is set.
Type: string or (submodule)
Default:
{
mixin = [
"hiding (Prelude)"
];
name = "base";
version = ">= 4 && < 5";
}
benchSuffix
This string is appended to the package name to form the single benchmark component.
See testSuffix
for an example.
Type: string
Default:
"-bench"
build-type
The build type for the packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
"Simple"
component
Verbatim Cabal configuration in HPack format. Values defined here will be applied to components, not packages.
Cascades down into all components.
This unconditionally overrides all option definitions with the same keys if they are not mergeable (like lists and attrsets).
Type: attribute set of unspecified value
Default:
{ }
copyright
The copyright string for the packages in this option tree.
The default is to combine copyrightYear
and author
;
May be null
to omit it from the config.
Type: null or string
Default:
null
copyrightYear
The year for the copyright string.
Type: string
Default:
"2025"
default-extensions
GHC extensions for all components in this option tree.
Type: list of string
Default:
[ ]
Example:
["DataKinds" "FlexibleContexts" "OverloadedLists"]
dependOnLibrary
Convenience feature that automatically adds a dependency on the library component to all executable components, if the library exists.
Type: boolean
Default:
true
dependencies
Cabal dependencies used for all components in this option tree.
Type: list of package dependency in HPack format
Default:
[ ]
Example:
["aeson" "containers"]
env
The environment used when running GHCi with a module from this component.
Type: null or name of an environment defined in config.envs
Default:
null
exeSuffix
This string is appended to the package name to form the single executable component.
See testSuffix
for an example.
The default is to use no suffix, resulting in the same name as the package and library.
Type: string
Default:
""
ghc-options
GHC options for all components in this option tree.
Type: list of string
Default:
[ ]
Example:
["-Wunused-imports" "-j6"]
ghc-options-exe
GHC options for all executables in this option tree.
The purpose of this is to allow ghc-options
to use it as the default for executables without requiring
complicated overrides to disable it.
If you don’t want to use these options, set this option to []
instead of forcing other values in
ghc-options
.
These options are not used for benchmarks.
Type: list of string
Default:
[
"-threaded"
"-rtsopts"
"-with-rtsopts=-N"
]
internal.single
Whether this is the main component of its sort, declared as test
rather than tests.foo
.
Type: boolean (read only)
Default:
false
internal.sort
Sort of the component (test, executable etc)
Type: one of “library”, “executable”, “test”, “benchmark” (read only)
Default:
"<component type>"
language
The default extension set used for all components in this option tree.
It is set to GHC2021
if the GHC versions of all defined envs are 9.2 or greater, and Haskell2010
otherwise.
Type: string
Default:
"GHC2021"
license
The license for all packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
"GPL-3"
license-file
The name of the file containing the license text for all packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
null
meta
Verbatim top-level Cabal configuration in HPack format. Values defined here will not be applied to components, only packages.
Cascades down into all packages.
This should only be used for keys that have no corresponding module option, otherwise the values defined in a package might be overridden by option definitions in the global config.
Type: attribute set of unspecified value
Default:
{ }
name
The name of the <component type>, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"<name>"
paths
Cabal generates the module Paths_packagename
for each component, which provides access to data
files included in a package, but is rarely used.
This may cause trouble if prelude
is configured to use an alternative Prelude that does not export some
of the names used in this module.
Setting this option to false
prevents this module from being generated.
Type: boolean
Default:
true
prelude
Configure an alternative Prelude package.
Type: submodule
Default:
{ }
prelude.enable
Whether to enable an alternative Prelude.
Type: boolean
Default:
false
Example:
true
prelude.package
The package containing the alternative Prelude.
Type: string or (submodule)
Default:
"base"
Example:
"relude"
prelude.module
The module name of the alternative Prelude.
Type: string
Default:
"Prelude"
Example:
"Relude"
source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"<name>"
testSuffix
This string is appended to the package name to form the single test component. For example, given the config:
{
packages.spaceship = {
test.cabal.testSuffix = "-integration";
}
}
The name of the generated testsuite
will be spaceship-integration
.
Type: string
Default:
"-test"
version
The version for all packages in this option tree.
Type: string
Default:
"0.1.0.0"
Packages may contain multiple components: an optional library and any number of sublibraries, executables, test suites or benchmarks.
Each of those can be specified as the single component of its type using the singular-name option, like executable
,
or as a value in the plural-name submodule, or both:
{
packages.api = {
executable = { enable = true; };
executables = {
server = { enable = true; source-dirs = "api-server"; };
client = {};
debug = { enable = false; };
};
};
}
This configuration generates three Cabal executables:
The default executable
requires the option enable
to be set explicitly and uses the source directory app
unless specified otherwise.
The option server
configures a custom source directory
The option client
uses the default source directory, which is the same as the attribute name.
All components configured in the plural-name submodule are enabled by default, so this one is also generated.
The option debug
sets enable
to false
, so it is omitted from the configuration.
If no component was enabled, Hix defaults to enabling the default executable.
Multiple libraries are supported with the same syntax as other components.
You can depend on them with the dependency string <pkg>:<lib>
; when depending from another package, the library must
have public = true;
set.
benchmark
The single benchmark for this package.
To define multiple benchmarks, use benchmarks
.
Type: submodule of cabal-options and cabal-component
Default:
{ }
benchmark.name
The name of the benchmark, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"<name>-bench"
benchmark.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"benchmark"
benchmarks
Benchmarks for this package.
If benchmark
is defined, it will be added.
Type: attribute set of (submodule of cabal-options and cabal-component)
Default:
{ }
benchmarks.<name>.name
The name of the benchmark, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"‹name›"
benchmarks.<name>.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"‹name›"
buildInputs
Additional non-Haskell dependencies required by this package.
Type: (function that evaluates to a(n) list of package) or list of package
Default:
[ ]
cabal
Cabal options that are applied to all components.
Note: In order to enable cascading of these options, the definitions are not evaluated in-place, but when
evaluating components. Therefore, referring to these values with e.g.
config.packages.name.cabal.version
does not work as expected if the value uses an option property like mkIf
or
mkOverride
.
You can use cabal-config
for this purpose, though.
Type: module
Default:
{ }
cabal-config
Evaluated version of cabal
, for referencing in other config values.
May not be set by the user.
Type: submodule of cabal-options (read only)
Default:
{ }
dep.exact
Dependency string for referencing this package with its version from other Cabal package.
Like dep.minor
, but uses exact version equality, like core ==0.4.1.0
.
Type: package dependency in HPack format (read only)
dep.minor
Dependency string for referencing this package with its version from other Cabal package. Uses the minor version dependency bound, strictly greater than the precise version.
{config, ...}: {
packages = {
core = { version = "0.4.1.0"; };
api = {
dependencies = [config.packages.core.dep.minor];
};
}
}
This results in the dependency string core >= 0.4.1.0 && < 0.5
in the Cabal file.
Also works when using versionFile
.
Type: package dependency in HPack format (read only)
description
The Cabal description of this packages.
The default is a link to the rootModule
on Hackage, using the option
hackageRootLink
.
May be null
to omit it from the config.
Type: null or string
Default:
null
executable
The single executable for this package.
To define multiple executables, use executables
.
Type: submodule of cabal-options and cabal-component
Default:
{ }
executable.name
The name of the executable, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"<name>"
executable.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"app"
executables
Executables for this package.
If executable
is defined, it will be added.
Type: attribute set of (submodule of cabal-options and cabal-component)
Default:
{ }
executables.<name>.name
The name of the executable, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"‹name›"
executables.<name>.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"‹name›"
expose
The parts of this package that should be accessible as flake outputs, like being able to run
nix build .#<env>.<package>
.
If the value is boolean, all parts are affected.
If it is a set, submodule options configure the individual parts.
Type: boolean or (submodule)
Default:
true
hackageLink
A convenience option containing the URL to the Hackage page using the package name.
Type: string
hackageRootLink
A convenience option containing the URL to the root module’s documentation on Hackage using the package name and
rootModule
.
Type: string
libraries
The sublibraries of this package.
Unlike library
, these are treated specially by cabal.
To depend on them, use <pkg>:<lib>
.
If libraries.<name>.public
is set to false
, you can only depend on them from other components
in the same package (this is then called an internal library – default is true
).
Type: attribute set of (submodule of cabal-options and cabal-component)
Default:
{ }
libraries.<name>.enable
Whether to enable this library.
Type: boolean
Default:
true
Example:
true
libraries.<name>.author
The author of the packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
null
libraries.<name>.base
The dependency spec for the base
package.
Type: string or (submodule)
Default:
"base >= 4 && < 5"
libraries.<name>.baseHide
The dependency spec for the base
package used when prelude
is set.
Type: string or (submodule)
Default:
{
mixin = [
"hiding (Prelude)"
];
name = "base";
version = ">= 4 && < 5";
}
libraries.<name>.benchSuffix
This string is appended to the package name to form the single benchmark component.
See testSuffix
for an example.
Type: string
Default:
"-bench"
libraries.<name>.build-type
The build type for the packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
"Simple"
libraries.<name>.component
Verbatim Cabal configuration in HPack format. Values defined here will be applied to components, not packages.
Cascades down into all components.
This unconditionally overrides all option definitions with the same keys if they are not mergeable (like lists and attrsets).
Type: attribute set of unspecified value
Default:
{ }
libraries.<name>.copyright
The copyright string for the packages in this option tree.
The default is to combine copyrightYear
and author
;
May be null
to omit it from the config.
Type: null or string
Default:
null
libraries.<name>.copyrightYear
The year for the copyright string.
Type: string
Default:
"2025"
libraries.<name>.default-extensions
GHC extensions for all components in this option tree.
Type: list of string
Default:
[ ]
Example:
["DataKinds" "FlexibleContexts" "OverloadedLists"]
libraries.<name>.dep.exact
Dependency string for referencing this library with its version from other Cabal package.
Like libraries.<name>.dep.minor
, but uses exact version equality, like core ==0.4.1.0
.
Type: package dependency in HPack format (read only)
libraries.<name>.dep.minor
Dependency string for referencing this library with its version from other Cabal package.
Like dep.minor
, but for sublibraries.
Type: package dependency in HPack format (read only)
libraries.<name>.dependOnLibrary
Convenience feature that automatically adds a dependency on the library component to all executable components, if the library exists.
Type: boolean
Default:
true
libraries.<name>.dependencies
Cabal dependencies used for all components in this option tree.
Type: list of package dependency in HPack format
Default:
[ ]
Example:
["aeson" "containers"]
libraries.<name>.env
The environment used when running GHCi with a module from this component.
Type: null or name of an environment defined in config.envs
Default:
null
libraries.<name>.exeSuffix
This string is appended to the package name to form the single executable component.
See testSuffix
for an example.
The default is to use no suffix, resulting in the same name as the package and library.
Type: string
Default:
""
libraries.<name>.ghc-options
GHC options for all components in this option tree.
Type: list of string
Default:
[ ]
Example:
["-Wunused-imports" "-j6"]
libraries.<name>.ghc-options-exe
GHC options for all executables in this option tree.
The purpose of this is to allow ghc-options
to use it as the default for executables without requiring
complicated overrides to disable it.
If you don’t want to use these options, set this option to []
instead of forcing other values in
ghc-options
.
These options are not used for benchmarks.
Type: list of string
Default:
[
"-threaded"
"-rtsopts"
"-with-rtsopts=-N"
]
libraries.<name>.internal.single
Whether this is the main component of its sort, declared as test
rather than tests.foo
.
Type: boolean (read only)
Default:
false
libraries.<name>.internal.sort
Sort of the component (test, executable etc)
Type: one of “library”, “executable”, “test”, “benchmark” (read only)
Default:
"library"
libraries.<name>.language
The default extension set used for all components in this option tree.
It is set to GHC2021
if the GHC versions of all defined envs are 9.2 or greater, and Haskell2010
otherwise.
Type: string
Default:
"GHC2021"
libraries.<name>.license
The license for all packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
"GPL-3"
libraries.<name>.license-file
The name of the file containing the license text for all packages in this option tree.
May be null
to omit it from the config.
Type: null or string
Default:
null
libraries.<name>.meta
Verbatim top-level Cabal configuration in HPack format. Values defined here will not be applied to components, only packages.
Cascades down into all packages.
This should only be used for keys that have no corresponding module option, otherwise the values defined in a package might be overridden by option definitions in the global config.
Type: attribute set of unspecified value
Default:
{ }
libraries.<name>.name
The name of the library, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"‹name›"
libraries.<name>.paths
Cabal generates the module Paths_packagename
for each component, which provides access to data
files included in a package, but is rarely used.
This may cause trouble if prelude
is configured to use an alternative Prelude that does not export some
of the names used in this module.
Setting this option to false
prevents this module from being generated.
Type: boolean
Default:
true
libraries.<name>.prelude
Configure an alternative Prelude package.
Type: submodule
Default:
{ }
libraries.<name>.prelude.enable
Whether to enable an alternative Prelude.
Type: boolean
Default:
false
Example:
true
libraries.<name>.prelude.package
The package containing the alternative Prelude.
Type: string or (submodule)
Default:
"base"
Example:
"relude"
libraries.<name>.prelude.module
The module name of the alternative Prelude.
Type: string
Default:
"Prelude"
Example:
"Relude"
libraries.<name>.public
Whether to expose an internal library.
Type: boolean
Default:
true
libraries.<name>.reexported-modules
Modules from dependencies that this library exposes for downstream projects to import.
Type: list of string
Default:
[ ]
Example:
["Control.Concurrent.STM" "Data.Text"]
libraries.<name>.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"‹name›"
libraries.<name>.testSuffix
This string is appended to the package name to form the single test component. For example, given the config:
{
packages.spaceship = {
test.cabal.testSuffix = "-integration";
}
}
The name of the generated testsuite
will be spaceship-integration
.
Type: string
Default:
"-test"
libraries.<name>.version
The version for all packages in this option tree.
Type: string
Default:
"0.1.0.0"
library
The library for this package.
Type: submodule of cabal-options and cabal-component
Default:
{ }
library.name
The name of the library, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"<name>"
library.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"lib"
name
The name of the package, determined by the attribute name in the config.
Type: string (read only)
Default:
"<name>"
override
Manipulate the package’s derivation using the combinators described in the section called “Override combinators”.
Type: function that evaluates to a(n) function that evaluates to a(n) unspecified value
Default:
<function>
relativePath
A string representation of src
relative to the project root.
Its value is inferred if possible, but if src
is not a plain path, it must be set explicitly.
A common reason for this is when the path is constructed with a source filter, causing the creation a separate
store path for the subdirectory.
In the basic case, Hix infers the root directory (for base
) by taking the prefix /nix/store/*/
from one of the package paths, and stripping it from each package’s src
.
This works well for simple projects, but it helps to provide base
, as well as this option,
explicitly.
If the package is at the project root, this value should be "."
.
Type: null or string
Default:
null
rootModule
A convenience option that is used to generate a Hackage link. It should denote the module that represents the most high-level API of the package, if applicable. The default is to replace dashes in the name with dots.
Type: string
src
The root directory of the package.
Type: path
Example:
./packages/api
subpath
The computed relative path of the package root directory.
Type: string (read only)
test
The single test suite for this package.
To define multiple test suites, use tests
.
Type: submodule of cabal-options and cabal-component
Default:
{ }
test.name
The name of the test suite, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"<name>-test"
test.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"test"
tests
Test suites for this package.
If test
is defined, it will be added.
Type: attribute set of (submodule of cabal-options and cabal-component)
Default:
{ }
tests.<name>.name
The name of the test suite, defaulting to the attribute name in the config or the package name.
Type: string
Default:
"‹name›"
tests.<name>.source-dirs
Directories with Haskell sources.
Type: string or list of string
Default:
"‹name›"
versionFile
The version file for this package, defaulting to the global hackage.versionFile
if null
.
When generating Cabal files, the version field will be set to the content of this file, unless
version
is set explicitly.
When bumping the version of a package with nix run .#release
, this file is updated.
Should be relative to the project root.
Type: null or string
Default:
null
packages
The project’s Cabal packages, with Cabal package names as keys and package config as values. The config is processed with HPack.
Type: attribute set of (submodule)
Default:
{ }
Example:
{
core.src = ./.;
api = { src = ./api; cabal.dependencies = ["aeson"]; library.enable = true; };
}
base
The project’s base directory.
Will be inferred from package source directories if unset.
If the Hix project is a subdirectory of a git repository and flake.nix
isn’t tracked by git, this option has to
be set to ./.
explicitly.
Type: null or path
Default:
null
Example:
./.
buildInputs
Additional non-Haskell dependencies provided to all packages and environments.
Type: (function that evaluates to a(n) list of package) or list of package
Default:
[ ]
buildOutputsPrefix
Some of Hix’s features are exposed as top level outputs, like nix run .#release
.
Since these are quite numerous, it becomes increasingly likely that these clash with some of the project’s package
or command names, making them inaccessible.
For this reason, built-in Hix outputs are added to the outputs with lower precedence, to ensure that user-defined
outputs aren’t shadowed.
To keep the built-in outputs accessible as well, they are additionally exposed in a dedicated prefix set named
build
, as in nix run .#build.release
.
If you prefer a different prefix, set this option to the desired string.
Type: string
Default:
"build"
cabal
Cabal options that are applied to all packages and components.
If you define any options here, they will be merged with definitions that are set in packages or components.
This means that the default priority handling applies – definitions in components don’t automatically override
those in packages or the global config.
You will need to use mkDefault
or mkForce
, or even
mkOverride
if you define an option at all three levels.
In order to enable cascading for these options, the definitions are not evaluated in-place, but when evaluating
packages and components. Therefore, referring to these values with e.g. config.cabal.version
does not work as
expected if the value uses an option property like mkIf
or mkOverride
.
You can use cabal-config
for this purpose, though.
Type: module
Default:
{ }
cabal-config
Evaluated version of cabal
, for referencing in other config values.
May not be set by the user.
Type: submodule (read only)
Default:
{ }
commands
Commands are shell scripts associated with an environment that are exposed as flake apps.
All commands are accessible as attributes of .#cmd.<name>
, and those that set expose = true
are additionally
exposed at the top level.
Type: attribute set of (submodule)
Default:
{ }
compat.enable
Create derivations in outputs.checks
that build the packages with different GHC versions.
The set of versions is configured by compat.versions
.
Type: boolean
Default:
true
compat.ifd
Whether to allow IFD for compat checks.
Type: boolean
Default:
false
compat.versions
The GHC versions for which to create compat checks. Defaults to ghcVersions
.
There has to be an env in envs
with the version as its name for each of these.
Type: list of string
Default:
[
"ghc94"
"ghc96"
"ghc98"
"ghc910"
]
compiler
The GHC version used for internal tasks and for the default environment.
This is an attribute name in the nixpkgs set haskell.packages
, which is usually in the format ghc96
.
Type: string
Default:
"ghc98"
deps
Flake inputs containing hix projects whose overrides are merged into this project’s.
The local
overrides are ignored to prevent the dependencies’ project packages from being
injected into the compat checks.
Type: list of path
Default:
[ ]
depsFull
Flake inputs containing hix projects whose overrides are merged into this project’s.
Unlike deps
, this includes the local
overrides.
Type: list of path
Default:
[ ]
devGhc
Backwards-compat alias for envs.dev.ghc
.
Type: submodule (read only)
envs
All environments for this project.
Type: attribute set of (submodule)
Default:
{ }
exportedOverrides
These overrides are exposed from the flake for integration in downstream projects via the options
deps
and depsFull
.
This is an attrset whose keys indicate where to put the overrides in the dependent project – each version env and
the dev
env has their own, while the all
key is applied globally.
The special keys local
and localMin
contain the local packages and their minimal build variants, respectively.
Local packages are only propagated when depsFull
is used.
Type: lazy attribute set of Haskell package override function specified in the Hix DSL
Default:
{ }
forceCabal2nix
Whether to use cabal2nix even if there is no Cabal file.
Type: boolean
Default:
false
forceCabalGen
Whether to generate a Cabal file from Nix config even if there is one in the source directory.
Type: boolean
Default:
false
gen-overrides.enable
The flake app .#gen-overrides
collects all cabal2nix-based derivations from the overrides that would
require IFD when computed on the fly.
Setting this flag instructs Hix to read the generated derivations when building, and to abort the build when they are missing or outdated.
Type: boolean
Default:
false
gen-overrides.file
The relative path of the file in which the overrides are stored.
Type: string
Default:
"ops/overrides.nix"
gen-overrides.gitAdd
Git-add the overrides file after the first run. Since nix ignores untracked files in flakes, the next command would complain if the file wasn’t added.
Type: boolean
Default:
true
ghcVersions
The GHC versions for which to create envs, specified by their attribute names in pkgs.haskell.packages
.
Type: list of string
Default:
[
"ghc94"
"ghc96"
"ghc98"
"ghc910"
]
haskellTools
Function returning a list of names of Haskell packages that should be included in every environment’s $PATH
.
This is a convenience variant of buildInputs
that provides the environment’s GHC package set (without
overrides) as a function argument.
This is intended for tooling like fourmolu
.
The default consists of cabal-install
, since that’s a crucial tool most users would expect to be available.
If you want to provide a custom cabal-install
package, you’ll have to set haskellTools = lib.mkForce (...)
,
since the built-in definition doesn’t use mkDefault
to ensure that adding tools in your project won’t
mysteriously remove cabal-install
from all shells.
Type: function that evaluates to a(n) list of package
Example:
ghc: [ghc.fourmolu]
hls.genCabal
When running HLS with nix run .#hls
, the command first generates Cabal files from Hix config to ensure that HLS
works.
If that is not desirable, set this option to false
.
Type: boolean
Default:
true
ifd
Whether to use cabal2nix
, which uses Import From Derivation, or to generate simple derivations, for local
packages.
Type: boolean
Default:
false
inheritSystemDependentOverrides
Overrides can be exported without a dependency on a system, such that a dependent could use them even if the
dependency wasn’t declared for the desired system.
However, if ifd
is false
in the dependency, the local packages will need the system
option,
and therefore need to be imported from legacyPackages.<system>.overrides
.
Type: boolean
Default:
true
inputs
The inputs of the Hix flake.
Type: lazy attribute set of unspecified value (read only)
main
The name of a key in packages
that is considered to be the main package.
This package will be assigned to the defaultPackage
flake output that is built by a plain
nix build
.
If this option is undefined, Hix will choose one of the packages that are not in the dependencies of any other
package.
Type: string
manualCabal
Don’t use the options in packages
as Cabal configuration for the ghci preprocessor.
Type: boolean
Default:
false
name
The name of the project is used for some temporary directories and defaults to main
.
If no packages are defined, a dummy is used if possible.
Type: string
Default:
"hix-project"
output.expose.appimage
Include AppImage derivations for all executables in the outputs.
Type: boolean
Default:
true
output.expose.cross
Include full cross-compilation system sets in the outputs (like hix.cross.{mingw32,aarch64-android,...}
).
Type: boolean
Default:
true
output.expose.internals
Include the config set, GHC packages and other misc data in the outputs.
Type: boolean
Default:
true
output.expose.managed
Include apps for managed dependencies in the outputs, even as stubs if the feature is disabled.
Type: boolean
Default:
true
output.extraPackages
Names of packages that will be added to the flake outputs, despite not being declared in
packages
.
This may be a simple Hackage package like aeson
or a local package that is added in
overrides
due to the way its source is obtained.
Type: list of string
Default:
[ ]
output.final
The final flake outputs computed by Hix, defaulting to the set in outputs and its siblings. May be overriden for unusual customizations.
Type: raw value
outputs.packages
The flake output attribute packages
.
Type: lazy attribute set of package
outputs.apps
The flake output attribute apps
.
Type: lazy attribute set of flake output of type ‘app’
outputs.checks
The flake output attribute checks
.
Type: lazy attribute set of package
outputs.devShells
The flake output attribute devShells
.
Type: lazy attribute set of package
outputs.legacyPackages
The flake output attribute legacyPackages
.
Type: lazy attribute set of raw value
overrides
Cabal package specifications and overrides injected into GHC package sets. Each override spec is a function that takes a set of combinators and resources like nixpkgs and should return an attrset containing either derivations or a transformation built from those combinators.
The combinators are described in the section called “Override combinators”.
Type: Haskell package override function specified in the Hix DSL
Default:
[ ]
Example:
{hackage, fast, jailbreak, ...}: {
aeson = fast (hackage "2.0.0.0" "sha54321");
http-client = unbreak;
};
pkgs
The nixpkgs attrset used by the default GHC.
Type: nixpkgs attrset (read only)
services
Services are fragments of NixOS config that can be added to an environment to be started as virtual machines when the environment is used in a command or shell.
Type: attribute set of module
Default:
{ }
system
The system string like x86_64-linux
, set by iterating over systems
.
Type: string (read only)
systems
The systems for which to create outputs.
Type: list of string
Default:
[
"aarch64-linux"
"aarch64-darwin"
"i686-linux"
"x86_64-darwin"
"x86_64-linux"
]
A set of flake outputs is defined for a specific architecture/system combination like x86_64-linux
, which is the
second level key in the outputs
set.
When a flake command like nix build
is executed, Nix determines the system identifier matching the current machine
and looks up the target in that set, like packages.x86_64-linux.parser
.
This means that you have to define a (not necessarily) identical output set for each architecture and system you would
like to support for your project.
While Hix takes care of structuring outputs for all supported systems (via flake-utils), this feature comes with a significant caveat: When any part of an output set cannot be purely evaluated because it imports a file that was created by a derivation in the same evaluation (which is called Import From Derivation, IFD), evaluating this output will cause that derivation to be built.
This becomes a critical problem when running the command nix flake check
, which is a sort of test suite runner for
flakes, because it evaluates all outputs before running the checks.
Hix defines checks for all packages and GHC versions, so it is generally desirable to be able to run this command in
CI.
Unfortunately, cabal2nix
(which generates all Haskell derivations) uses IFD.
To mitigate this problem, Hix can generate derivations in a similar manner to cabal2nix
, controlled by the option
ifd
(on by default).
For local packages, this is rather straightforward – Hix can use the package config to determine the derivations
dependencies and synthesize a simple derivation.
It gets more difficult when overrides are used, since these are usually generated from Hackage or flake inputs, where the only information available is a Cabal file. In order to extract the dependencies, we would either have to run a subprocess (via IFD) or implement a Cabal config parser in Nix (which has also been done).
As a pragmatic compromise, Hix instead splits the override derivation synthesis into two separate passes:
The flake app gen-overrides
collects all cabal2nix
overrides and stores their derivations in a file in the
repository.
Whenever a build is performed afterwards, it loads the derivations from that file, avoiding the need for IFD!
In order to use this feature, the option gen-overrides.enable
must be set to true
.
The derivations can be written to the file configured by gen-overrides.file
with:
$ nix run .#gen-overrides
Of course this means that gen-overrides
will have to be executed every time an override is changed, but don’t worry
– Hix will complain when the metadata doesn’t match!
This feature is experimental.
If your configuration evaluates overridden packages unconditionally, the command will not work on the first execution,
since it will try to read the file and terminate with an error.
In that (unlikely) case, you’ll have to set gen-overrides.enable = false
before running it for the first time.
Another potential source for nix flake check
to fail is when an environment with a virtual machine is exposed as a
shell.
For that purpose, the option systems
can be used to define for which systems the environment should be
exposed.
Options for UI behavior:
ui.warnings.all
Whether to enable all warnings.
Set this to false
to suppress all warnings and use ui.warnings.keys
to enable them
selectively.
Type: boolean
Default:
true
ui.warnings.keys
The set of warnings that should be enabled or disabled individually.
Keys are warning IDs and values are booleans, where true
enables a warning and false
disables it.
If a key is not present in this set, the warning is printed if ui.warnings.all
is true
.
Type: attribute set of boolean
Default:
{ }
Table of Contents
Actions like building a derivation and running HLS or GHCi are executed in an environment, which is a set of configuration options consisting of:
A GHC at a specific version
A set of packages that include project dependencies and tools like Cabal and HLS
An optional set of services that may be run in a virtual machine, like database servers
Environment variables
This example configuration defines an environment that uses GHC 9.4, adds socat
to the packages in $PATH
and runs
a PostgreSQL server:
{
outputs = {hix, ...}: hix ({config, ...}: {
envs.example = {
ghc.compiler = "ghc94";
buildInputs = [config.pkgs.socat];
services.postgres = {
enable = true;
config = { name = "test-db"; };
};
};
});
}
An environment can be used in different ways: By a derivation, a devshell, or a command.
A derivation only uses the GHC, Cabal and the project dependencies as inputs, while a command execution, like GHCid,
is preceded by booting a VM if any services are configured.
For the latter, the environment provides a script that wraps the command with the startup/shutdown code for the VM and
adds all build inputs to the $PATH
.
The simplest way to use an environment is as a devshell:
$ nix develop .#example
>>> Starting VM with base port 20000
>>> Waiting 30 seconds for VM to boot...
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.4.3
$ psql "host=localhost port=25432 user=test-db password=test-db dbname=test-db" -c 'select 1'
?column?
----------
1
(1 row)
$ exit
>>> Killing VM
Using nix develop -c some-command
to execute a shell command in an environment will fail to stop the VM afterwards.
Use a command for one-shot executions.
The default environment is called dev
and is used for everything that doesn’t have a custom environment.
For instance, the default devshell uses this environment when entered with nix develop
without an explicit argument.
Running cabal build
in that shell will use the configured GHC.
The most important part of an environment is the associated GHC.
As demonstrated in the above example, the option ghc.compiler
in an environment is used to select the package set
corresponding to a version.
These package sets are predefined by nixpkgs – each snapshot has a certain number of GHC versions with packages
obtained from Stackage and Hackage.
The GHC executable provided by an environment comes preloaded with all dependencies declared in the project’s Cabal
files (which amounts to those declared in packages
).
When running ghci
, those dependencies are importable.
The default package set doesn’t always provide the version of a package that the project requires – for example, sometimes you want to use a newer version than the stable one in a Stackage snapshot. For this purpose, Hix offers a DSL for package overrides:
{
overrides = {hackage, ...}: {
streamly = hackage "0.9.0" "1ab5n253krgccc66j7ch1lx328b1d8mhkfz4szl913chr9pmbv3q";
};
}
Overrides are defined as a function that produces a set mapping package names to dependency specifications and takes
as its argument a set of combinators and metadata for declaring those specifications, like the hackage
combinator
used above that takes a version and Nix store hash to fetch a package directly from Hackage.
They can be specified at different levels, like dependencies: At the top level for all environments, in each
individual environment, and on the ghc
module in an environment (although the latter is populated by Hix with the
merged global and local overrides).
nixpkgs’ shipped GHC package sets come with a few special derivations whose attribute names are suffixed with a
concrete version, like Cabal_3_10_2_1
, to be used as overrides for packages with incompatible requirements.
If you create a Hackage override for such a package, there’s a chance that it will result in an infinite recursion
error.
The precise reason for this is not clear to me yet, but it can be avoided by using that special derivation, if it is
compatible with your dependency set:
{ overrides = {super, …}: { broken-dep = super.broken-dep_1_2_3; }; }
There are different classes of combinators. The most fundamental ones are used to declare package sources:
hackage
takes two arguments, version and hash, to pull a dependency directly from Hackage.
source.root
takes a path to a source directory (like a flake input) and builds the dependency from its contents.
source.sub
is the same as source.root
, but takes a second argument describing a subdirectory of the path.
source.package
is the same as source.sub
, but it prepends packages/
to the subdirectory.
hackageAt
is like hackage
, but takes an additional first argument specifying the address of the server.
hackageConf
is like hackageAt
, but instead of an address, it takes the name of an item in
hackage.repos
.
If a server with the key hix-override
is configured, the combinator hackage
will automatically use it instead of
nixpkgs’ built-in logic.
disable
or null
(the literal) results in the final derivation being assigned null
.
This may be useful to force the dependency by that name to be provided by some other mechanism; for example, boot
libraries are available in the compiler package and are set to null
in nixpkgs.
When an override is defined using source
combinators, the derivation is generated by cabal2nix
.
Consider the following override for a package named dep
that uses a flake input as the source path:
{
inputs = {
hix.url = "...";
dep.url = "...";
};
outputs = {hix, dep, ...}: hix {
overrides = {source, ...}: {
dep = source.root dep;
};
};
}
The implementation of source.root
roughly consists of the function call
haskellPackages.callCabal2nix "dep" dep { <optional overrides>; }
which invokes haskellPackages.haskellSrc2nix
,
which executes the program cabal2nix
, which in turn analyzes the dep.cabal
in the source tree of the flake input
and generates a function that looks like this:
{
cabal2nix_dep = {mkDerivation, aeson, base, pcre}: mkDerivation {
pname = "dep";
src = <path passed to source.root>;
libraryHaskellDepends = [aeson base];
librarySystemDepends = [pcre];
...
}
}
Its arguments consist of dep
’s Haskell and system dependencies as well as the mkDerivation
function.
All of these are ultimately provided by the GHC package set, and callCabal2nix
wraps this generated function with
the common
callPackage
pattern, which allows individual arguments to be overridden.
For example, to sketch a simplified representation of how our local package would be merged into the nixpkgs GHC
package set, the above function would be called like this:
let
callPackage = lib.callPackageWith ghc;
ghc = {
# These two are defined in `pkgs/development/haskell-modules/make-package-set.nix`
mkDerivation = pkgs.callPackage ./generic-builder.nix { ... };
aeson = callPackage aesonDrv {};
...
api = callPackage cabal2nix_dep {
aeson = someCustomAesonDerivation; # Haskell package override
pcre = pkgs.pcre; # System package override
};
};
in ghc
Now, this is already very convoluted, and the real thing is even worse; but you can see:
The callPackage
variant used for the Haskell derivations is created with lib.callPackageWith
by recursively
providing the set ghc
, which consists of all the lazy attributes that are themselves constructed with that same
function.
Just like most of nixpkgs, the fixpoint of this computation is the final value of ghc
, allowing each package to
depend on others from the same set without having to provide the deps directly.
mkDerivation
is also created with a callPackage
variant, but it’s the top-level nixpkgs set that provides the
values.
mkDerivation
will be passed to the calls in aeson
and dep
automatically.
The call to our cabal2nix
derivation created by source
would get the sibling attribute aeson
, but the second
argument to callPackage
here contains some custom override (as well as another for pcre
), which takes precedence
over the “default arguments” in the recursive set.
Now, of course, these overrides need to be specified somehow, and the mechanism for that is the third argument to
callCabal2nix
that is labeled with <overrides>
in the above example.
However, this argument is not directly exposed in source.root
.
Instead, another type of override combinator provides that functionality, described below (cabal2nixOverrides
).
The second class consists of “transformers”, which perform some modification on a derivation.
They can either be applied to a source combinator or used on their own, in which case they operate on whatever the
previous definition of the overridden package is (for example, the default package from nixpkgs, or an override from
another definition of overrides
).
Transformers also compose – when using multiple of them in succession, their effects accumulate:
{
overrides = {hackage, notest, nodoc, configure, jailbreak, nobench, ...}: {
# Fetch streamly from Hackage and disable tests and haddock
streamly = notest (nodoc (hackage "0.9.0" "1ab5n253krgccc66j7ch1lx328b1d8mhkfz4szl913chr9pmbv3q"));
# Use the default aeson package and disable tests
aeson = notest;
# Disable version bounds, tests, benchmarks and Haddock, and add a configure flag
parser = configure "-f debug" (jailbreak (notest (nobench nodoc)));
};
}
The available transformers are:
unbreak
– Override nixpkgs’ “broken” flag
jailbreak
– Disable Cabal version bounds for the package’s dependencies
notest
– Disable tests
nodoc
– Disable Haddock
bench
– Enable benchmarks
nobench
– Disable benchmarks
minimal
– Unbreak and disable profiling libraries, Haddock, benchmarks and tests
fast
– Disable profiling and Haddock
force
– Unbreak, jailbreak and disable Haddock, benchmarks and tests
noprofiling
– Disable profiling libraries for the package
profiling
– Enable executable profiling for the package
configure
– Add a Cabal configure CLI option
configures
– Add multiple Cabal configure CLI options (in a list)
enable
– Enable a Cabal flag (-f<flag>
)
disable
– Disable a Cabal flag (-f-<flag>
)
ghcOptions
– Pass GHC options to Cabal (--ghc-options
)
override
– Call overrideCabal
on the derivation, allowing arbitrary Cabal manipulation
overrideAttrs
– Call overrideAttrs
on the derivation
buildInputs
– Add Nix build inputs
The third class consists of “options”, which modify the behavior of other override combinators:
cabal2nixOverrides
– An attrset used as the third argument of callCabal2nix
when called by source
combinators,
as described in the example in the previous section, used as follows:
{
overrides = {cabal2nixOverrides, source, pkgs}: {
dep = cabal2nixOverrides { aeson = ...; pcre = pkgs.pcre.override {...}; } (source.root dep);
}
}
This would result in both aeson
and pcre
being overridden, for the derivation of dep
, by the packages supplied
explicitly.
cabal2nixArgs
– CLI options for cabal2nix
.
cabal2nix
doesn’t have many options that would make a lot of sense in this context, but one is particularly
useful:
Like cabal-install
, cabal2nix
recognizes the option -f
to enable or disable Cabal flags, which are often used
to configure optional dependencies.
The problem with passing these only to Cabal in the final derivation is that the function generated by cabal2nix
will require exactly those dependencies that were enabled when it was invoked.
Imagine that our example package has a Cabal flag large-records
that controls two effects: an additional
dependency on the package large-records
and some CPP-guarded code that uses this library.
Disabling a flag with disable "large-records" (source.root dep)
will ultimately cause Cabal to build dep
without
large-records
visible in the GHC package DB, as well as ignore the additional code, but the derivation will still
have the dependency on large-records
, since that is not interpreted by Cabal.
However, cabal2nixArgs "-f-large-records" (source.root dep)
will avoid the dependency entirely.
Finally, there are some special values that are injected into the override function:
self
and super
– The final and previous state of the package set, as is common for nixpkgs extension functions
pkgs
– The nixpkgs set
keep
– Reset the previous combinators and use the default package
hsLib
– The entire nixpkgs tool set haskell.lib
hsLibC
– The entire nixpkgs tool set haskell.lib.compose
.
This is preferable to hsLib
, but for historic reasons that one ended up in here first, and it’s gonna stay for
compatibility.
compilerName
– The name
attribute of the GHC package
compilerVersion
– The version
attribute of the GHC package
Overrides can be exported from the flake in order to reuse them in other projects.
When a downstream flake has project foo
as an input, setting deps = [foo]
will cause foo
’s overrides to be
incorporated into the local ones.
Furthermore, the option depsFull
will additionally include foo
’s local packages in the overrides:
{
inputs.dep1.url = github:me/dep1;
inputs.dep2.url = github:me/dep2;
outputs = { hix, dep1, dep2, ... }: hix {
deps = [dep1];
depsFull = [dep2];
};
}
The GHC package set uses the same nixpkgs snapshot that is also used for Hix internals, which is configured as a flake input of the Hix repository. You can override this globally:
{
inputs.hix.inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
}
In order to avoid incompatiblities with the Hix internals, it might be advisable to only override the nixpkgs used by GHC:
{
inputs.hix.url = "github:tek/hix";
inputs.ghc_nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
outputs = { hix, ghc_nixpkgs, ... }: hix {
envs.dev.ghc.nixpkgs = ghc_nixpkgs;
};
}
Since the usage of nixpkgs within the library is tightly interwoven with the GHC package set, this might have a slight potential for breakage, but (like the global variant) it should be minimal.
Services in an environment are relevant when executing a command, which consists of an arbitrary shell script
associated with an environment.
When the command is executed, the environment’s code
option will be run beforehand, which boots the VM with the
services and sets up an exit hook for shutting it down.
code
is a script assembled from other options, notably setup-pre
, setup
, exit-pre
and exit
.
A command can be defined as follows:
{
commands.integration-test = {
env = "example";
command = "cabal test api-integration";
};
}
This assumes that your project contains some Cabal component named api-integration
that needs a PostgreSQL server
running.
When executing this command, the setup code from the previously defined environment example
will be executed,
starting the virtual machine, before running the specified Cabal command line:
nix run .#cmd.integration-test
When the command option component
is set to true
, the command will take two argument lists, separated by a --
.
The first batch of arguments is passed to the Hix CLI to select the environment, the second one is assigned to the
environment variable $cmd_args
to be used by the command script.
This allows the user to select an environment dynamically from the command line, without having to statically associate it in the Nix config or use complex expressions with nested invocations of Nix, realized by encoding all components and environments as JSON and extracting the environment runner based on the CLI arguments. The primary use case for this is to use a different environment when running a GHCid test, like running a database server for integration tests.
There are three alternative selection methods, illustrated by this example:
{
description = "hix test project";
inputs.hix.url = "github:tek/hix?ref=0.8.0";
outputs = {hix, ...}: hix ({config, ...}: {
envs = {
one.env = { number = 1; };
two.env = { number = 2; };
three.env = { number = 3; };
};
packages.root = {
src = ./.;
executable.env = config.envs.two;
};
commands.number = {
env = config.envs.one;
command = ''
echo $number
'';
component = true;
};
});
}
The first method is to use the flake app path to select an environment by name, then append the command name:
$ nix run .#env.three.number
3
The second method is to select a component by its package name or directory and component name or source directory:
$ nix run .#cmd.number -- -p api -c server
$ nix run .#cmd.number -- -p packages/api -c app
2
The third method is to specify a Haskell source file and let Hix figure out which component it belongs to:
$ nix run .#cmd.number -- -f /path/to/packages/api/test/NumberTest.hs
2
This method needs to know the root directory of the project, which is determined by searching for flake.nix
with a
fallback to the current working directory.
The root directory may also be specified explicitly using the CLI option --root
.
If no selection arguments are given, the command’s default environment is used:
$ nix run .#cmd.number
1
Hix provides two special commands for executing a function in GHCi or GHCid.
The ghcid
command in particular is highly useful for repeatedly executing a test whenever a source file is written.
Compilation is very fast since GHCi doesn’t need to link the executable and doesn’t perform optimization in the
default mode, leading to sub-second feedback loops on small projects (or dependency closures).
nix run .#ghci -- -p root -r server
nix run .#ghcid -- -p root -t test_server -r hedgehog-unit
Their interface is mostly identical to the generic commands described above, while taking three additional, optional command line options:
The name of a module (-m
), defaulting to Main
.
This module is loaded in GHCi and hence only its dependencies are (re)compiled.
The name of a Haskell test function (-t
) that will be called after the module was loaded successfully.
See next bullet for defaults.
The name of an attribute in the Hix option ghci.run
(-r
).
This option contains a Haskell expression for each key which will be executed after the module was loaded
successfully.
If a test function was specified, this expression will be applied to it.
If the runner wasn’t specified, the test function will be run directly.
If neither this nor the test function was specified, the ghci
command will drop to interactive mode directly while
the ghcid
command defaults to main
.
For example, the built-in hedgehog-unit
entry in ghci.run
has the value check . withTests 1 . property . test
.
When specifying the name of a Hedgehog test like in the example above, the evaluated epression will be:
ghci> (check . withTests 1 . property . test) test_server
You can specify arbitrary additional command line arguments for GHCi and GHCid with --ghci-options
and
--ghcid-options
.
Another built-in command is run
, which executes an arbitrary shell command in an environment:
$ nix run .#env.ghc92.run -- "ghc --version"
The Glorious Glasgow Haskell Compilation System, version 9.2.4
If you depend on the GHC library package ghc
, you’ll have to set ghci.args = ["-package ghc"];
.
Otherwise it won’t be visible, due to a bug.
Services used in environments and commands can be user-defined in a flake:
{
services.http = {
nixos.services.nginx = {
enable = true;
virtualHosts.localhost.locations."/test".return = "200 Yay!";
};
ports.nginx = { host = 2000; guest = 80; };
};
envs.test = {
basePort = 10000;
services = { http = { enable = true; }; };
};
}
This defines a service named http
that runs an nginx with a single endpoint at /test
and exposes it in the host
system at port offset 2000 (relative to an environment’s basePort
).
The environment test
references the service, so when a command uses this environment, a VM will be started:
nix run .#env.test.ghcid -- -p root
Since the environment uses basePort = 10000
, the nginx server will listen on port 12000.
You can refer to the effective port from other options with config.envs.hostPorts.nginx
(the attribute name in
ports
).
Hix provides built-in services, like the previously mentioned PostgreSQL server, that have specialized configuration
options.
They can be configured in the same option as the definition of a new service, still allowing the specification of
additional NixOS config as described before, in services.<name>.nixos
.
Furthermore, an environment may provide Hix config overrides in envs.<name>.services.<name>.config
that is combined
with the config in services.<name>
.
{
outputs = {hix, ...}: hix ({config, ...}: {
services.postgres = {
# Add NixOS config to the default config computed by Hix
nixos.users.users.postgres.extraGroups = ["docker"];
# Configure PostgreSQL specifically, used by `services.postgres.nixos-base` internally
creds.user = "root";
};
envs.example = {
services.postgres = {
# Declare that this environment uses `config.services.postgres` above
enable = true;
# Add overrides for the configuration in `config.services.postgres`
config = { name = "test-db"; };
};
};
});
}
In order to define a service with specialized config, an entry in the option internal.services.myservice
must be
provided that contains a module with option declarations.
This option has type deferredModule
, which means that it’s not evaluated at the definition site, but used in a magic
way somewhere else to create a new combined module set consisting of all the configs described before.
You can log into a service VM via ssh.
The default configuration sets the root password to the empty string and exposes the ssh port in the host system at
basePort + 22
:
ssh -p 1022 root@localhost
A service can be defined in services
with plain NixOS config, but it is useful to allow the service
to be specially configurable.
For that purpose, the value assigned to the entry in services
should be a full module that defines
options as well as their translation to service config:
{
services.greet = ({config, lib, ...}: {
options.response = mkOption {
type = lib.types.str;
default = "Hello";
};
config = {
ports.greet = { guest = 80; host = 10; };
nixos-base.services.nginx = {
enable = true;
virtualHosts.localhost.locations."/greet".return = "200 ${config.response}";
};
};
});
envs.bye = {
services.greet = {
enable = true;
config.response = "Goodbye";
};
};
}
This defines a service running nginx that has a single endpoint at /greet
, which responds with whatever is
configured in the service option response
.
The environment bye
uses that service and overrides the response string.
Any option defined within services.greet.options
is configurable from within the environment, and the values in
services.greet.config
correspond to the regular service options.
enable
Whether to enable this environment.
Type: boolean
Default:
false
Example:
true
packages
The subset of local packages that should be built by this environment.
Entries must correspond to existing keys in packages
.
If the value is null
, all packages are included.
This is useful when the project contains multiple sets of packages that should have separate dependency trees with different versions.
Setting this has a variety of effects:
These packages will be exposed as outputs for this env
The GHC package db will not contain the excluded packages, so they won’t be built when entering a shell or
starting .#ghci
for this env
Managed dependencies envs use this to produce separate sets of bounds
Type: null or (list of name of a package defined in config.packages)
Default:
null
basePort
The number used as a base for ports in this env’s VM, like ssh getting basePort + 22
.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
20000
buildInputs
Additional system package dependencies for this environment.
These are only made available to shells and commands, not added to packages, like when they are set in overrides.
Type: (function that evaluates to a(n) list of package) or list of package
Default:
[ ]
code
The shell script code that starts this env’s services and sets its environment variables.
Type: string
Default:
''
_hix_unrestricted() {
[[ -z ''${DIRENV_IN_ENVRC-} ]] && [[ -z ''${HIX_ONLY_ENV-} ]]
}
quitting=0
quit() {
if [[ $quitting == 0 ]]
then
quitting=1
if [[ -n ''${1-} ]]
then
echo ">>> Terminated by signal $1" >&2
fi
# kill zombie GHCs
/nix/store/nyqwmqlnnhq3x5718309wrkif0wrppvp-procps-4.0.4/bin/pkill -9 -x -P 1 ghc || true
fi
if [[ -n ''${1-} ]]
then
exit 1
fi
}
if _hix_unrestricted
then
if [[ -z ''${_hix_is_shell-} ]]
then
trap "quit INT" INT
fi
trap "quit TERM" TERM
trap "quit KILL" KILL
trap quit EXIT
fi
export PATH="/nix/store/0px0carb0vl8bzjviz3siqa7asq97a5x-cabal-install-3.14.1.0/bin:/nix/store/x6zmmsvwyfa1yxgpzl0dlkdllcd8jckm-ghcid-0.8.9-bin/bin:/nix/store/d9v1lxs84wq29796v1s8b558g9kc5cf5-ghc-9.8.4/bin:$PATH"
export env_args
if _hix_unrestricted
then
:
fi
''
defaults
Whether to use the common NixOS options for VMs.
Type: boolean
Default:
true
env
Environment variables to set when running scripts in this environment.
Type: attribute set of (signed integer or string)
Default:
{ }
exit
Command to run when the env exits.
Type: string
Default:
""
exit-pre
Command to run before the service VM is shut down.
Type: string
Default:
""
expose
The parts of this environment that should be accessible as flake outputs, like being able to run
nix build .#<env>.<package>
.
If the value is boolean, all parts are affected.
If it is a set, submodule options configure the individual parts.
Type: boolean or (submodule)
Default:
false
ghc
The GHC configuration for this environment.
Type: submodule
Default:
{ }
ghcWithPackages
The fully configured GHC package exposing this environment’s dependencies.
Type: package (read only)
ghcWithPackagesArgs
Additional arguments to pass to ghcWithPackages
.
Type: attribute set of unspecified value
Default:
{ }
ghcid.enable
Whether to enable GHCid for this env.
Type: boolean
Default:
true
Example:
true
ghcid.package
The package for GHCid, defaulting to the one from the env’s GHC without overrides.
Type: package
Default:
<derivation ghcid-0.8.9>
haskellPackages
Names of Haskell packages that should be added to this environment’s GHC’s package db, making them available for import. These may include the local packages.
Type: (function that evaluates to a(n) list of package) or list of string
Default:
[ ]
haskellTools
Function returning a list of names of Haskell packages that should be included in the environment’s $PATH
.
This is a convenience variant of buildInputs
that provides the environment’s GHC package set (without
overrides) as a function argument.
This is intended for tooling like fourmolu
.
Type: function that evaluates to a(n) list of package
Default:
<function>
Example:
ghc: [ghc.fourmolu]
hls.enable
Whether to enable HLS for this env.
Type: boolean
Default:
false
Example:
true
hls.package
The package for HLS, defaulting to the one from the env’s GHC without overrides.
Type: package
Default:
<derivation haskell-language-server-2.9.0.0>
hoogle
Whether to enable Hoogle in this environment.
Type: boolean
Default:
false
hostPorts
The effective ports of the VM services in the host system.
Computed from basePort
and ports
.
Type: attribute set of 16 bit unsigned integer; between 0 and 65535 (both inclusive) (read only)
ifd
Whether to use cabal2nix, which uses Import From Derivation, or to generate simple derivations.
Type: boolean
Default:
false
libraryProfiling
Whether to build local libraries with profiling enabled. This is the default mode for Haskell derivations.
Type: boolean
Default:
true
localDeps
Whether to add the dependencies of the env’s local packages to GHC’s package db.
Type: boolean
Default:
true
localPackage
A function that takes override combinators and a derivation and returns a modified version of that derivation.
Called for each cabal2nix derivation of the local packages before inserting it into the overrides.
Like overrides
, but applies too all packages when building with this env.
Type: unspecified value
Default:
<function>
Example:
{ fast, nobench, ... }: pkg: nobench (fast pkg);
main
The name of the main package of this env, defaulting to main
if that is in packages
or one of those packages determined by the same method as described
for the global equivalent.
Type: null or string
Default:
null
managed
Whether this env’s dependencies are the section called “Automatic dependency management”.
This has the effect that its bounds and overrides are read from the managed state in
managed.file
.
Type: boolean
Default:
false
name
Name of this environment.
Type: string
Default:
"<name>"
overrides
Like overrides
, but used only when this environment is used to build packages.
Type: Haskell package override function specified in the Hix DSL
Default:
[ ]
profiling
Whether to build local libraries and executables with profiling enabled.
Type: boolean
Default:
false
runner
An executable script file that sets up the environment and executes its command line arguments.
Type: path
Default:
<derivation env--name--runner.bash>
services
Services for this environment.
Type: attribute set of (submodule)
Default:
{ }
setup
Commands to run after the service VM has started.
Type: string
Default:
""
setup-pre
Commands to run before the service VM has started.
Type: string
Default:
""
shell
The shell derivation for this environment, starting the service VM in the shellHook
.
If this shell is used with nix develop -c
, the exit hook will never be called and the VM will not be shut down.
Use a command instead for this purpose.
Type: package
systems
The architecture/system identifiers like x86_64-linux
for which this environment works.
This is used to exclude environments from being exposed as shells when they are system-specific, for example when
using a VM that only works with Linux.
If those shells were exposed, the command nix flake check
would fail while evaluating the devShells
outputs,
since that doesn’t only select the current system.
If set to null
(the default), all systems are accepted.
Type: null or (list of string)
Default:
null
vm.enable
Whether to enable the service VM for this env.
Type: boolean
Default:
false
Example:
true
vm.derivation
The VM derivation
Type: path
vm.dir
Type: string
Default:
"/tmp/hix-vm/app/<name>"
vm.exit
Commands for shutting down the VM.
Type: string
vm.headless
VMs are run without a graphical connection to their console. For debugging purposes, this option can be disabled to show the window.
Type: boolean
Default:
true
vm.image
The path to the image file.
Type: string
Default:
"/tmp/hix-vm/app/<name>/vm.qcow2"
vm.monitor
The monitor socket for the VM.
Type: string
Default:
"/tmp/hix-vm/app/<name>/monitor"
vm.name
Name of the VM, used in the directory housing the image file.
Type: string
Default:
"<name>"
vm.pidfile
The file storing the qemu process’ process ID.
Type: string
Default:
"/tmp/hix-vm/app/<name>/vm.pid"
vm.setup
Commands for starting the VM.
Type: string
vm.system
The system architecture string used for this VM, defaulting to system
.
Type: string
Default:
"x86_64-linux"
wait
Wait for the VM to complete startup within the given number of seconds. 0 disables the feature.
Type: signed integer
Default:
30
compiler
The attribute name for a GHC version in the set haskell.packages
.
Type: string
crossPkgs
This option can be used to override the pkgs set used for the Haskell package set, for example an element of
pkgsCross
: envs.dev.ghc.crossPkgs = config.envs.dev.ghc.pkgs.pkgsCross.musl64
Type: nixpkgs attrset
gen-overrides
Allow this GHC to use pregenerated overrides.
Has no effect when gen-overrides.enable
is false
.
Disabled by default, but enabled for GHCs that are defined in an environment.
Type: boolean
Default:
false
ghc
The package set with overrides.
Type: Haskell package set (read only)
name
A unique identifier of the package set.
Type: string
nixpkgs
The path to a nixpkgs source tree, used as the basis for the package set.
This can be a flake input or a regular type of path, like the result of fetchGit
.
Type: nixpkgs snapshot
nixpkgsOptions
Additional options to pass to nixpkgs when importing.
Type: attribute set of unspecified value
overlays
Additional nixpkgs overlays.
Type: list of (overlay)
overrides
The overrides used for this package set – see the section called “Configuring GHC” for an explanation.
This option is set by environments (see the section called “Environments”), but GHC modules can be used outside of environments, so this might be set by the user.
Type: Haskell package override function specified in the Hix DSL
Default:
[ ]
pkgs
The nixpkgs set used for this GHC.
Type: nixpkgs attrset
vanillaGhc
The package set without overrides.
Type: Haskell package set (read only)
version
The GHC version as a canonical string, like 9.2.5
, for use in conditions.
Type: string (read only)
command
The script executed by this command.
Type: string
component
Whether this command should determine the env based on a target component specified by command line arguments.
The component selector chooses a default component when no arguments are given. If that component has an explicit environment configured, it will be used instead of the one configured in this command.
Type: boolean
Default:
false
env
The default env for the command.
Type: name of an environment defined in config.envs
Default:
"dev"
expose
Whether this command should be a top-level flake app.
Type: boolean
Default:
false
ghci.enable
Create a command that runs GHCi (like the built-in command) with some static options.
For example, you can specify a runner, and the app will be equivalent to running
nix run .#ghci -r <runner>
.
Type: boolean
Default:
false
ghci.package
The name of the package passed to the GHCi runner with -p
.
Type: null or string
Default:
null
ghci.component
The name of the component passed to the GHCi runner with -c
.
Type: null or string
Default:
null
ghci.ghcid
Whether to run this command with GHCid instead of plain GHCi.
Type: boolean
Default:
false
ghci.module
The name of the module passed to the GHCi runner with -m
.
Type: null or string
Default:
null
ghci.runner
The name of a runner in ghci.run
and ghci.run
.
Type: null or string
Default:
null
name
Name
Type: string
Default:
"<name>"
ghci.args
The command line arguments passed to GHCi.
Setting this option appends to the defaults, so in order to replace them, use mkForce
.
To only override basic GHC options like -Werror
, use ghci.ghcOptions
.
Type: list of string
ghci.cores
The value for the GHC option -j
, specifying the number of system threads to use.
Type: signed integer or string
Default:
"\${NIX_BUILD_CORES-}"
ghci.ghcOptions
Command line arguments passed to GHCi that aren’t related to more complex Hix config like the preprocessor.
This option is initialized with values that use the Nix setting cores
to set the number of
threads GHCi should use. If you want to control this yourself, use mkForce
here.
Type: list of string
Default:
[ ]
ghci.preprocessor
The preprocessor script used to insert extensions and a custom Prelude into source files. This is generated by Hix, but may be overridden.
Type: path
ghci.run
Test functions for GHCi commands.
The attribute name is matched against the command line option -r
when running apps like nix run .#ghci
.
Type: attribute set of string
ghci.setup
Scripts that should be executed when starting a GHCi command, like imports.
The attribute name is matched against the command line option -r
when running apps like nix run .#ghci
.
Type: attribute set of string
enable
Enable this service
Type: boolean flag that uses conjunction for merging
Default:
true
messages
Informational messages that will be echoed when an environment starts this service.
Type: function that evaluates to a(n) list of string
Default:
<function>
nixos
NixOS config used for the service VM.
Type: module
Default:
{ }
nixos-base
NixOS base config used for the service VM.
Type: module
Default:
{ }
ports
Simple ports forwarded relative to the env’s basePort
.
Type: attribute set of (submodule)
Default:
{ }
ports.<name>.absolute
Whether the host port is an absolute number. If false
(default), the port is added to basePort
.
Type: boolean
Default:
false
ports.<name>.guest
Port used in the VM.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
ports.<name>.host
Port exposed in the system, relative to the env’s basePort
unless ports.<name>.absolute
is set.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Table of Contents
Hix provides a flake app for running HLS with the proper GHC (for the dev
env) and the project dependencies:
nix run .#hls
In order to use it with your IDE, you need to specify the command in the editor configuration as: nix run .#hls --
,
since nix
consumes all options until a --
is encountered, and only passes what comes afterwards to the program
(which in this case would be --lsp
).
This app corresponds to the command named hls
, which uses the dev
environment but gets the HLS
executable from a special environment named hls
.
This allows the HLS package and its dependencies to be configured separately from the project dependencies.
For example, to use the package exposed from the HLS flake:
{
inputs.hls.url = "github:haskell/haskell-language-server?ref=1.9.0.0";
outputs = {hix, hls, ...}: ({config, ...}: {
envs.hls.hls.package = hls.packages.${config.system}.haskell-language-server-925;
});
}
Additionally, all other environments can expose HLS as well:
{
envs.ghc94.hls.enable = true;
}
nix run .#env.ghc94.hls
This is disabled by default to avoid building HLS for environments whose GHCs don’t have its derivation in the Nix cache.
Since the dev environment exposes HLS by default, the executable (haskell-language-server
) is in $PATH
in the
default devshell, so it can also be run with nix develop -c haskell-language-server
.
The environments created for each entry in ghcVersions
are intended primarily as a CI tool to ensure
that the project builds with versions other than the main development GHC.
For that purpose, the checks
output contains all packages across those environments, which can be built with:
nix flake check
Hix provides flake apps that run the flake checks and upload package candidates, releases or docs to Hackage:
nix run .#candidates
nix run .#release
nix run .#docs
If versionFile
is set, the script will substitute the version:
line in that Cabal file after asking for
the next version.
If you use nix run .#gen-cabal
to maintain the Cabal files, this should be a .nix
file containing a string that’s
also used for version
:
{
packages.parser = {
cabal.version = import ./parser-version.nix;
versionFile = ./parser-version.nix;
};
}
The options hackage.versionFileExtract
and hackage.versionFileUpdate
can be
customized to allow for arbitrary other formats.
The command line option --version
/-v
may be used to specify the version noninteractively.
Furthermore, if a package name is specified as a positional argument, only that package will be uploaded.
For example, to publish parser
at version 2.5.0.1
:
nix run .#release -- parser -v 2.5.0.1
The upload command will use your global Cabal config to obtain credentials, please consult the Cabal docs for more.
The options module hackage.hooks
provides a way to execute scripts at certain points in the release process.
hackage.packages
The set of packages that will be published to Hackage when the release command is run without arguments.
If it is null
, all packages are published.
The items in the list should be Cabal package names as defined in options.packages
.
Type: null or (list of string)
Default:
null
hackage.add
When hackage.add
is set to false
, this option can be enabled to git-add the files but not
commit them.
Type: boolean
Default:
false
hackage.allPackages
There are two modes for versioning: Either all packages share the same version, in which case the release app will publish all packages at the same time, or each package has an individual version, in which case the release app expects the name of a package to be specified.
Type: boolean
Default:
true
hackage.askVersion
Whether to interactively query the user for a new version when releasing.
Type: boolean
Default:
true
hackage.cabalArgs
Extra global CLI arguments for cabal
.
Type: string
Default:
""
hackage.cabalUploadArgs
Extra CLI arguments for cabal upload
to use in hackage.uploadCommand
.
Type: string
Default:
""
hackage.check
Whether to run nix flake check
before the release process.
Type: boolean
Default:
true
hackage.commit
After successfully uploading a new release, the changes to the version file, cabal files and changelog will be
committed unless this is set to false
.
Type: boolean
Default:
true
hackage.commitExtraArgs
Extra CLI options for git commit
.
Type: string
Default:
""
hackage.confirm
Whether to ask for confirmation before uploading.
Type: boolean
Default:
true
hackage.formatTag
Function that creates a tag name from a version and an optional package name.
Type: function that evaluates to a(n) string
Default:
<function, args: {name, version}>
hackage.hooks.postCommitAll
Shell script lines (zsh) to run after commiting the version change after publishing all packages.
Type: strings concatenated with “\n”
Default:
""
hackage.hooks.postUploadAll
Shell script lines (zsh) to run after uploading all packages.
Value is a function that gets the set {source, publish}
, two booleans that indicate whether the sources (or
only docs) were uploaded, and whether the artifacts were published (or just candidates).
Type: function that evaluates to a(n) strings concatenated with “\n”
Default:
<function>
hackage.hooks.preCommitAll
Shell script lines (zsh) to run before commiting the version change after publishing all packages.
Type: strings concatenated with “\n”
Default:
""
hackage.repos
Hackage repos used by the CLI for several tasks, like resolving managed dependencies and publishing packages and
revisions.
The default config consists of the usual server at hackage.haskell.org
.
Type: attribute set of (submodule)
Default:
{ }
hackage.repos.<name>.enable
Whether to enable this Hackage server.
Type: boolean
Default:
true
hackage.repos.<name>.description
Arbitrary short text for presentation, like ‘local Hackage’.
Type: null or string
Default:
null
hackage.repos.<name>.indexState
When resolving, use the index at this time.
Type: null or string
Default:
null
Example:
"2024-01-01T00:00:00Z"
hackage.repos.<name>.keys
Security keys for this server.
Type: null or (non-empty (list of string))
Default:
null
hackage.repos.<name>.location
Server URL with scheme and optional port.
Type: string
Default:
"https://hackage.haskell.org"
Example:
"https://company-hackage.com:8080"
hackage.repos.<name>.name
Name used to refer to this server in other components. Uses the attribute name and cannot be changed.
Type: string (read only)
hackage.repos.<name>.password
Password for uploading.
Type: null or string or (submodule)
Default:
null
hackage.repos.<name>.publish
Publish packages to this server.
Type: boolean
Default:
false
hackage.repos.<name>.secure
Use the newer Cabal client that verifies index signatures via hackage-security
.
Type: null or boolean
Default:
true
hackage.repos.<name>.solver
Use this server for the Cabal resolver when managing dependency versions.
Type: boolean
Default:
true
hackage.repos.<name>.user
User name for uploading.
Type: null or string
Default:
null
hackage.setChangelogVersion
Whether to substitute the word ‘Unreleased’ with the new version in changelogs.
Type: boolean
Default:
false
hackage.tag
After successfully uploading a new release, a tag with the version name will be created unless this is set to
false
.
Type: boolean
Default:
true
hackage.tagExtraArgs
Extra CLI options for git tag
.
Type: string
Default:
""
hackage.uploadCommand
The command used to upload a tarball, specified as a function that takes a set as a parameter with the attributes:
{
publish = "Boolean indicating whether this is a candidate or release";
doc = "Boolean indicating whether this is a source or doc tarball";
path = "The tarball's file path";
}
Type: function that evaluates to a(n) string
hackage.versionFile
If multiple packages use the same file for the version (like when using shared hpack files) this option may
point to that file.
If hackage.allPackages
is true
and this option is null
, the version will not be modified by the release
app.
If the project uses the feature for hpack config synthesis from nix expressions, the version must be defined in
a nix file.
In that case, the simplest mechanism would be to use a separate file that only contains a string and is
integrated into the config with version = import ./version.nix;
.
The default version handlers make this assumption; if a different method is used, the options
hackage.versionFileExtract
and hackage.versionFileUpdate
must be adapted.
Type: null or string
Default:
null
hackage.versionFileExtract
A function that returns a shell script fragment that extracts the current version from a version file.
The default assumes hpack/cabal format, like version: 5
, unless the file has the extension
.nix
, in which case it is assumed the file only contains a string.
Type: function that evaluates to a(n) string
Default:
<function>
hackage.versionFileUpdate
A function that returns a shell script fragment that updates the current version in a version file.
The new version is stored in the environment variable $new_version
in the surrounding shell
script.
The default assumes hpack/cabal format, like version: 5
, unless the file has the extension
.nix
, in which case it is assumed the file only contains a string.
Type: function that evaluates to a(n) string
Default:
<function>
Hix exposes an app that runs thax to generate a CTags file covering all dependencies and the local packages:
nix run .#tags
This will result in the creation of the file .tags
.
All package outputs can be cross-compiled with the following syntax:
nix build .#parser.cross.musl64
In addition, the package may also be linked statically against its Haskell dependencies:
nix build .#parser.cross.musl64.static
Note that this does not result in a static binary – it will still be linked dynamically against libc.
For more elaborate cross-compilation setups, each GHC can be configured to use a cross pkgs set:
{
envs.dev.ghc.crossPkgs = config.envs.dev.ghc.pkgs.pkgsCross.musl64;
}
For musl
, there are two native package sets in nixpkgs that are supported by Hix:
nix build .#parser.musl
This will result in a binary that’s similar to .#parser.cross.musl64.static
.
For a fully static build, you can use the static
attribute:
$ nix build .#parser.static
$ file result/bin/parser
result/bin/parser: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
Hix can create more portable distributable bundles by using nix-appimage to generate AppImage executables, exposed in the following flake apps:
nix run '.#appimage' # The default executable from the default package
nix run '.#<pkgname>.appimage' # The default executable from the specified package
nix run '.#<exename>.appimage' # The specified executable from whatever package defines it
nix run '.#env.<envname>.[<pkg/exe>.]appimage' # The same as above, but from the specified env
This will print a store path:
>>> AppImage bundle for <exename> created at:
/nix/store/bxbcp9mk9rf0sjg88hxsjqzpql5is280-<exename>-0.1.0.0-x86_64.AppImage
Hix provides some functionality for adapting and verifying dependency bounds.
This feature is new, so there are likely many edge cases that have not been tested.
If the option managed.enable
is enabled, the flake will expose an environment named latest
and an app named bump
.
Running this app with nix run .#bump
will fetch the newest versions of all dependencies and create a file in the
project at the path configured by managed.file
, containing updated dependency version ranges
that include the new version.
For each dependency, this app will build all of the project’s packages and omit the version update if the build fails. When generating cabal files or derivations, the version ranges from this file will override those from the flake.
Additionally, the app will add overrides to the same file that select the newest version for the environment latest
,
so that the dev
environment (and all others) will still use the default versions from nixpkgs (plus regular
overrides), making the latest
environment the testing ground for bleeding-edge dependency versions.
You can change this behavior to apply to other environments by setting managed
to true
and running nix run .#env.<name>.bump
instead.
If managed.check
is true
(the default), the derivations with latest versions will be added
to the checks
output, so your CI may depend on them.
If the option managed.lower.enable
is enabled, the flake will expose an environment named lower
and
an app named lower
.
The app executes a subset of three stages based on the current state, init
, optimize
and stabilize
, that are
also available as subcommands of this app (by running e.g. nix run .#lower optimize
).
The mode of operation is similar to bump
, with the most crucial difference being that it manipulates the lower
bounds of dependencies.
The operational details of this app that are most important for a user are that optimize
determines the lowest
possible bounds with which the project builds, and that stabilize
attempts to repair them after the code was changed
in a way that invalidates them, e.g. by importing a name that wasn’t available in a dependency at the version in the
lower bound.
This allows the bounds validation to be integrated into the development process by running
nix run .#lower -- --stabilize
as a pre-commit hook or a pre-release CI job.
Since older versions require boot packages from older GHCs, it is advisable to use the oldest GHC available.
See the option managed.lower.compiler
for more information.
The default is to use the first version in ghcVersions
.
The first stage of lower
is called init
, and it attempts to determine initial lower bounds, which consist of the
lowest version in the highest major that builds successfully with the environment’s GHC.
For example, if a dependency has the majors 1.4, 1.5 and 1.6, and all versions greater than 1.5.4 fail to build, the
initial lower bound will be 1.5.0.
Repeatedly executing this stage will only compute initial bounds for dependencies that don’t have any yet (unless
--reset
is specified).
The purpose of this step is preparation for the other two apps: The initial bounds will be sufficiently recent that the project won’t break with these versions after changes with a high probability. The initial versions are stored in the managed state file along with the bounds.
Executing the app with a subcommand, nix run .#lower init
, will only run this stage; otherwise, the next stage will
depend on the final state and the outcome of init
.
If the specified dependencies have lower bounds configured in the flake, they will be ignored.
After the lower bounds have been initialized, or if the first stage is skipped due to all dependencies having bounds,
the app will build the current state to decide what to do next.
If the build succeeds, it will continue with the optimize
stage, otherwise it will run stabilize
, although this
requires the user to pass --stabilize
in the command line, since it is an expensive operation with a high failure
rate.
The optimize
stage will iterate over the majors of all dependencies, building all versions in each of them until it
finds a working one, and terminating when an entire majors fails after at least one working version had been found.
E.g. if a package has the majors 1.1, 1.2, 1.3, 1.4 and 1.5, and some versions in 1.3 and 1.4 build successfully, but
none in 1.3 do, then the lowest version in 1.4 will be the optimized lower bound.
Before the stabilize
stage is executed, the app will first build the project with the versions that form the initial
lower bounds.
If that fails, it will refuse to continue, and require the user to fix the situation manually (the easiest first step
would be to run nix run .#lower -- --reset
).
Otherwise, it will build the project with the initial lower versions and the optimized lower bound of the first
dependency, continuing upwards in the version sequence until a working version is found.
Then the same process is performed with the next dependency, keeping the stabilized bounds of all previously processed
dependencies.
In the default mode, the managed-deps apps operate on the entirety of local packages, finding new bounds that work for all packages. This might be undesirable – some of the packages might have stricter version requirements than others, or they might be completely independent from each other.
For that purpose, the option managed.sets
may be used to specify multiple sets of packages that are
processed independently.
The simplest variant, with the value managed.sets = "each";
, is to create one app and one env for each package, so
that you would run nix run .#bump.api
to bump only the package api
, with a flake check created as bump-api
.
More granular sets can be specified like this:
{
main = ["core" "api" "app"];
other = ["docs" "compat"];
}
Now the apps have paths like nix run .#lower.init.main
, while the checks use the schema lower-main-api
.
The default value for sets
is "all"
, which processes all packages at once as described above.
If your packages are dependent on each other, this might be more desirable, since it reduces the build time.
This feature is incredibly complicated and suffers from vulnerabilities to myriad failure scenarios, some of which might be mitigated by configuration.
The app uses the Cabal solver to optimize the selection of overrides, which requires two package indexes:
A source package database of available packages with their versions and dependencies, which is read from Hackage.
An installed package index of concrete versions of packages that are considered to be bundled with the compiler, which is provided as the executable of a nixpkgs GHC with a selection of packages.
The latter of those is a very difficult mechanism that serves multiple independent purposes.
In order to make installed packages visible to Cabal, the Nix GHC has to be outfitted with a package DB, which is the
same structure used for development shells with GHCi, obtained by calling ghcWithPackages
and specifying the
dependencies from the flake config as the working set.
This results in a GHC executable that has those dependencies “built in”, meaning that you can just import them without
any additional efforts, and the solver can query them by executing ghc-pkg list
.
Since we’re required to ultimately build the project with Nix, a major concern is to avoid rebuilding dependencies that are already available in the central cache. If a version selected by the solver matches the one present in the GHC set, we therefore don’t want to specify an override (to pull the version from Hackage and build it), since that version is very likely cached.
However, the GHC package set from nixpkgs always has the potential of containing broken packages, most often due to incompatible version bounds, or simply because a package fails to build with a bleeding-edge GHC (not to mention any requirements for custom build flags that your project might have). Even though this is a problem that the Cabal solver is intended to, uh, solve, the GHC set must be fully working before starting the app, since that is how Nix works – we pass a store path to the GHC with packages to the app as a CLI option (roughly).
Now, the set of installed packages should resemble the “vanilla” Nixpkgs as closely as possible because of the aforementioned benefit of avoiding builds of cached versions, but if a broken package requires on override, parts of the package set will differ from the vanilla state!
To complicate matters even more, the same issue arises twice when building the project with the computed overrides – once when the app tests the build, and again when the final state has been written and the environment is built by a flake check or manual invocation.
At least for test builds and bound mismatches there’s a partial mitigation in place, since the app forces a jailbreak (removal of most bounds) of all overridden dependencies, but this only covers a tiny part of the problem space.
For more direct control, you can specify overrides in the flake config. However, since the solver is supposed to work on vanilla package sets, most of the usual override sources are ignored, so there is a special option for this purpose. In addition, a project might contain several managed environments with different requirements, so each must be configurable individually, but we also don’t want to be forced to specify a common override multiple times.
To achieve this, the modules managed.envs
, managed.latest.envs
and
managed.lower.envs
allow you to specify configuration for all managed envs and all latest and lower
envs, respectively.
The option managed.envs.solverOverrides
is used only for the solver package set (with the usual
the section called “Override combinators” protocol).
The actual build overrides can be defined at managed.envs.verbatim
, which is equivalent to
specifying regular env-specific overrides for all managed (or latest/lower) environments individually.
Note that at the moment, the config in latest
/lower
completely overrides the more general one; they are not
combined.
For example, if your project depends on type-errors
, which has an insufficient upper bound on a dependency in the
current Nipxkgs set for the latest GHC that prevents the set from building, you might want to specify:
{
managed.latest.envs = {
solverOverrides = {jailbreak, ...}: { type-errors = jailbreak; };
verbatim.overrides = {jailbreak, ...}: { type-errors = jailbreak; };
};
};
This will only use those overrides for latest
envs used by .#bump
– if some overrides should be used for lower
bounds envs as well, you’d set this on managed.envs
instead of managed.latest.envs
.
The “verbatim” config is copied to all envs that are generated for the section called “Target sets”, so with the setup from that
section, this config would be equivalent to:
{
envs = {
latest-main.overrides = {jailbreak, ...}: { type-errors = jailbreak; };
latest-other.overrides = {jailbreak, ...}: { type-errors = jailbreak; };
};
};
managed.enable
Enable managed dependencies.
Type: boolean
Default:
false
managed.check
Add builds with latest versions and lower bounds to the flake checks.
Type: boolean
Default:
true
managed.debug
Print debug messages when managing dependencies.
Type: boolean
Default:
false
managed.envs
Options for environments generated for managed dependencies.
These apply to both latest
and lower
environments; the modules managed.latest.envs
and
managed.lower.envs
have precedence over them.
Type: submodule
Default:
{ }
managed.envs.solverOverrides
Dependency overrides for the package set used only by the solver while finding new versions. Specifying these should only be necessary if the vanilla package set contains broken packages that would prevent the managed apps from starting.
Type: Haskell package override function specified in the Hix DSL
managed.envs.verbatim
Default config for environments generated for managed dependencies.
These can be overriden per-environment by specifying envs.*.<attr>
like for any other environment.
Type: unspecified value
managed.file
Relative path to the file in which dependency versions should be stored.
Type: string
Default:
"ops/managed.nix"
managed.forceBounds
Concrete bounds that fully override those computed by the app when generating Cabal files.
This is useful to relax the bounds of packages that cannot be managed, like base
, for example when the GHC
used for the latest env isn’t the newest one because the dependencies are all broken right after release, but
you want it to build with that version anyway.
Type: attribute set of (submodule)
Default:
{ }
Example:
{
base = { upper = "4.21"; };
}
managed.forceBounds.<name>.lower
The lower bound, inclusive.
Type: null or string
Default:
null
Example:
"1.4.8"
managed.forceBounds.<name>.upper
The upper bound, exclusive.
Type: null or string
Default:
null
Example:
"1.7"
managed.generate
Whether to regenerate cabal files and override derivations after updating the project.
Type: boolean
Default:
true
managed.gitAdd
Git-add the managed deps after the first run. Since nix ignores untracked files in flakes, the state wouldn’t be loaded if you forgot to add the file yourself.
Type: boolean
Default:
true
managed.internal.localsInPackageDb
Whether to include local packages as source derivations in the package db used for the solver
Type: boolean
Default:
false
managed.latest.compiler
The GHC version (as the attribute name in haskell.packages
) that should be used for latest versions
environments.
The default is to use the last entry in ghcVersions
, or compiler
if the
former is empty.
It is advisable to use the latest GHC version that you want to support, since boot libraries will fail to
build with different GHCs.
Type: string
Default:
"ghc910"
managed.latest.envs
Options for environments generated for latest versions.
These default to the values in managed.envs
.
Type: submodule
Default:
{ }
managed.latest.envs.solverOverrides
Dependency overrides for the package set used only by the solver while finding new versions. Specifying these should only be necessary if the vanilla package set contains broken packages that would prevent the managed apps from starting.
Type: Haskell package override function specified in the Hix DSL
managed.latest.envs.verbatim
Default config for environments generated for managed dependencies.
These can be overriden per-environment by specifying envs.*.<attr>
like for any other environment.
Type: unspecified value
managed.latest.readFlakeBounds
Use the upper bounds from the flake for the first run.
Type: boolean
Default:
false
managed.lower.enable
Enable an environment for testing lower bounds.
Type: boolean
Default:
false
managed.lower.compiler
The GHC version (as the attribute name in haskell.packages
) that should be used for lower bounds
environments.
The default is to use the first entry in ghcVersions
, or compiler
if the
former is empty.
It is advisable to use the lowest GHC version that you want to support, since boot libraries will fail to
build with different GHCs.
Type: string
Default:
"ghc94"
managed.lower.envs
Options for environments generated for lower bounds.
These default to the values in managed.envs
.
Type: submodule
Default:
{ }
managed.lower.envs.solverOverrides
Dependency overrides for the package set used only by the solver while finding new versions. Specifying these should only be necessary if the vanilla package set contains broken packages that would prevent the managed apps from starting.
Type: Haskell package override function specified in the Hix DSL
managed.lower.envs.verbatim
Default config for environments generated for managed dependencies.
These can be overriden per-environment by specifying envs.*.<attr>
like for any other environment.
Type: unspecified value
managed.mergeBounds
Add the flake bounds to the managed bounds.
Aside from going in the Cabal file, they are added to Cabal’s dependency solver when finding new bounds.
This can be used to avoid problematic versions that have dependencies with a high tendency to break the build.
The ranges defined here are intersected with the managed bounds.
If you want to relax bounds, use managed.forceBounds
.
Type: boolean
Default:
false
managed.quiet
Suppress informational messages when managing dependencies.
Type: boolean
Default:
false
managed.sets
Select how to group packages for processing by the managed deps tool.
all
for a single set, each
for one set per package, and an attrset for custom grouping.
Type: one of “all”, “each” or attribute set of list of name of a package defined in config.packages
Default:
"all"
Example:
{
main = ["core" "api" "app"];
other = ["docs" "compat"];
}
managed.verbose
Print verbose messages when managing dependencies.
Type: boolean
Default:
false
nix run .#show-overrides
Prints all environments’ overrides.
nix run .#show-config
Prints the project’s entire configuration.
nix run .#dep-versions
nix run .#env.ghc96.dep-versions
Prints all components’ dependencies and their actual versions in the dev environment, or the named environment in the second variant.
Packages and environments are subjected to several stages of transformation in order to arrive at the final flake
outputs from the initial configuration obtained from module options.
In order to make this process more transparent and flexible, in particular for overriding outputs without having to
reimplement substantial parts of Hix’s internals, intermediate data is exposed in the module arguments project
,
build
, and outputs
.
This means that the module that constitutes the Hix flake config can access these arguments by listing them in its
parameter set:
{
outputs = {hix, ...}: hix ({config, project, build, outputs, ...}: {
packages.core.src = ./core;
outputs.packages.custom-build = build.packages.dev.core.static.overrideAttrs (...);
});
}
For now, these sets don’t have a stable API, but here is a brief overview:
project
contains values that are closely related to the config options they are computed from, to be used by a
wide spectrum of consumers:
project.base
contains the project’s base directory in the nix store, which is either base
if
that’s non-null, or the directory inferred from src
if only the package config contains paths.
project.packages.*.path
contains the relative path to a package, either given by relativePath
if that’s non-null, or the directory inferred from project.base
.
build
contains the full set of derivations for each package in each env.
Its API looks roughly like this:
{
packages = {
dev = {
core = {
package = <derivation>;
static = <derivation>;
musl = <derivation>;
cross = {
aarch64-android = <derivation>;
...
};
release = <derivation>;
ghc = { <package set used for this package> };
cabal = { <cabal config for this package> };
expose = true;
executables = {
<name> = {
package = <derivation>;
static = <derivation>;
musl = <derivation>;
app = <flake app>;
appimage = <derivation>;
};
...
};
};
api = {
...
};
...
};
ghc910 = {
...
};
...
};
envs = {
dev = {
# Like above, but only for the main package of the set
static = <derivation>;
musl = <derivation>;
cross = <attrs>;
release = <derivation>;
# Main executable of the main package
api = <derivation>;
# All executables of all packages, flattened
executables = <attrs>;
};
...
};
commands = {
default = {
# All built-in and custom commands using their default env env
ghci = <derivation>;
hls = <derivation>;
...
};
envs = {
dev = {
# All built-in and custom commands using this env
ghci = <derivation>;
hls = <derivation>;
...
};
};
};
}
outputs
contains most of what will eventually end up in the flake outputs, keyed by output type.
All of this data is also exposed in the flake outputs, and can therefore be inspected from nix repl
:
Welcome to Nix 2.18.5. Type :? for help.
nix-repl> :load-flake .
Added 19 variables.
nix-repl> legacyPackages.x86_64-linux.project
{
# Values from `project`
base = /nix/store/ga9ifpvqzqgi6sqcfqhdvhj0qmfms8hk-source;
packages = { ... };
# The sets `build` and `outputs`, as attributes of `project` for simpler scoping
build = { ... };
outputs = { ... };
# Other internal data
config = <full config>;
ghc = <dev ghc set>;
ghc0 = <dev ghc set without overrides>;
pkgs = <dev nixpkgs>;
show-config = <see above>;
}
nix-repl> legacyPackages.x86_64-linux.project.build.packages.dev.hix.package
«derivation /nix/store/qys3qc4kyvfx6wlsqkvjxk40dyq28gl3-hix-0.7.2.drv»
nix-repl> packages.x86_64-linux.hix
«derivation /nix/store/qys3qc4kyvfx6wlsqkvjxk40dyq28gl3-hix-0.7.2.drv»