Compiler :: Compiler -> Spec m ()compilerSet the GHC compiler, e.g.
compiler "ghc-9.10.1".On :: Base -> Spec m ()onWhat the set is built on, exactly once:
on (toolchain (forge "nixlab.openfp.netpackage-setshaskellhorizon-build-packages") (branch "ltsghc-10.0.x"))for a set built from scratch with a toolchain's compiler and build tools, oron (set "horizon-core" (forge …) (branch …))for an overlay on a lower package set, which the set takes its compiler from and whose packages it solves around at the versions that set holds. The base is the generatedflake.nix's one real input; the branch is what it follows, and the flake lock records the commit. A config with no base is an overlay for an application, which writes no flake.Platform :: Platform -> Spec m ()platformThe platform the set is rendered for, as cabal names it: its packages'
arch(…)andos(…)conditionals are resolved for this target rather than for the machine that runs the generator, e.g.platform "javascript-ghcjs"for a set built by the JavaScript backend. Undeclared, a set is rendered forx86_64-linux, whichever machine generates it.IndexState :: IndexState -> Spec m ()indexStateThe Hackage index state the set is read at, e.g.
indexState "2025-03-01T00:00:00Z": each Hackage pin's revision is the last one published by then, and a Solve againsthackagechooses among the releases Hackage had then, so nothing published later changes the generated set. Undeclared, the newest the index the generator reads holds.Name :: Text -> Spec m ()nameThe set's name (used for the generated module + flake description), e.g.
name "horizon-build-essentials".Description :: Text -> Spec m ()descriptionA one-line description of the set.
License :: Spdx -> Spec m ()licenseThe licence, as an SPDX identifier:
license.MITWarnings :: Warnings -> Spec m ()warningsThe warnings contract:
warnings "werrorwolf", every package also built as a check with-Werror;warnings "none"for none.Language :: Language -> Spec m ()languageThe default language of the local packages:
language.GHC2024Extension :: Extension -> Spec m ()extensionA default extension of the local packages, once per verb:
extension.OverloadedStringsNixConfig :: Text -> Text -> Spec m ()nixConfigA
nixConfigsetting of the generated flake, key and value:nixConfig "extra-substituters" "https://cache.openfp.net".DoBenchmark :: Bool -> Spec m ()doBenchmarkDoCheck :: Bool -> Spec m ()doCheckDoHaddock :: Bool -> Spec m ()doHaddockDoJailbreak :: Bool -> Spec m ()doJailbreakEnableProfiling :: Bool -> Spec m ()enableProfilingIncludeBenchmarks :: Bool -> Spec m ()includeBenchmarksIncludeExecutables :: Bool -> Spec m ()includeExecutablesIncludeTests :: Bool -> Spec m ()includeTestsHackage :: PackageName -> Version -> Spec m ()hackageA package from Hackage at a pinned version.
Repository :: RepositoryName -> Url -> Int -> [RootKey] -> Spec m ()repositoryDeclare a secure cabal repository the set pins packages from, as a cabal
repositorystanza does: its name, its URL, how many of the root keys must sign its root metadata, and the root key ids, e.g.repository "nixlab" "https://nixlab.openfp.net/hackage" 2 ["…", "…", "…"]. The generator fetches its index with cabal, which checks the repository's signatures against these keys. A repository is declared before the pins that name it.HackageFrom :: RepositoryName -> PackageName -> Version -> Spec m ()hackageFromA package at a pinned version from a declared repository instead of Hackage: repository, name, version, e.g.
hackageFrom "nixlab" "vortex-trace" "0.3.0".Git :: PackageName -> Url -> Revision -> Spec m ()gitA package from a git repository at a revision.
GitSubdir :: PackageName -> Url -> Revision -> Subdir -> Spec m ()gitSubdirThe same, from a subdirectory of the repository: name, url, revision, subdirectory.
Tarball :: PackageName -> Url -> Spec m ()tarballA package from a tarball URL: name, url.
TarballSubdir :: PackageName -> Url -> Subdir -> Spec m ()tarballSubdirThe same, from a subdirectory of the unpacked tarball.
Local :: PackageName -> Subdir -> Spec m ()localA LOCAL package: its source is a subdirectory of this config's own repo, not a fetched artifact — so a set can include a project developed alongside it (the code you are writing here): name, subdirectory.
EnableFlag :: PackageName -> FlagName -> Spec m ()enableFlagTurn a cabal flag on for a declared package: package, flag.
DisableFlag :: PackageName -> FlagName -> Spec m ()disableFlagTurn a cabal flag off for a declared package: package, flag.
Patch :: PackageName -> Text -> Spec m ()patchApply a source patch to a declared package: package, path to the
.patch/.difffile (which stays raw in the repo). A genuinely per-package fact — the diff is specific to that package's source.SystemDeps :: PackageName -> Text -> Spec m ()systemDepsSystemDepFrom :: PackageName -> Text -> Text -> Spec m ()systemDepFromA native library whose
callPackageargument is named differently from the nixpkgs attribute that supplies it: package, argument, attribute, e.g.systemDepFrom "cardano-crypto-class" "libblst" "blst".Exposed :: PackageName -> ModuleName -> Spec m ()exposedA module a local package exposes: package, module, e.g.
exposed "tint"; once per module.Tint.DiffInternal :: PackageName -> ModuleName -> Spec m ()internalA module a local package keeps internal (
other-modules).Suite :: PackageName -> SuiteName -> MainIs -> Spec m ()suiteA test suite of a local package: package, suite, the file it runs, e.g.
suite "tint" "laws" "Laws.hs".Backend :: PackageName -> Backend -> Spec m ()backendA backend a local package builds for, once per backend:
backend "tint" "javascript". Undeclared, the native one.Missing :: PackageName -> Spec m ()missingAssert that a package is intentionally NOT provided by this set — an architecture gap, no source, or deliberately unavailable. This is the ONLY named absence (never a bare
= null): the generator records it, and if a root transitively needs amissingpackage that is a hard error rather than a silent hole. (Packages that are merely unreached under the set's policy are pruned, not declared missing.)Overlay :: Text -> Spec m ()overlayExport an overlay of the declared packages to this file, e.g.
overlay "overlay.nix". The default: what a library or an application declares are the packages its base set is missing.PackageSet :: Text -> Spec m ()packageSetExport a whole package set to this file instead, e.g.
packageSet "initial-packages.nix", for a repository that builds a package set from scratch.Root :: PackageName -> Spec m ()rootKeep a package in the set for its own sake: the set exists to provide it, not because something else in the set needs it. The roots and everything they depend on are what the set must carry; any other declaration is a pin nothing reaches, which
horizon-config --prunereports so it can be removed.Index :: Text -> Spec m ()indexDeclare a cabal index to resolve against, e.g.
index "hackage"orindex "head.hackage". Declaration order is precedence (earlier = higher): when a package is in several indices the resolved one comes from the highest-precedence index carrying a usable version. Named indices resolve to known repositories (hackage, head.hackage, and chap for Cardano Haskell Packages) or to a repository the config declares with Repository. Only meaningful with Solve.AllowNewer :: PackageName -> Spec m ()allowNewerRelax the upper version bounds on a package during the solve, e.g.
allowNewer "base"— the solve-time counterpart of a build-time jailbreak. Needed when a bleeding-edge compiler ships a boot library (base, template-haskell) newer than dependants' stated bounds. Changes what the solver may pick, so it is declared, not assumed. Only meaningful with Solve.Solve :: Spec m ()solveExpand the declared roots into the full transitive closure by running the solver against the declared indices, writing the resolved closure to the lock. Explicit: a config without
solveuses its literalhackage/gitpins as written. A set has one closure, so there is onesolve.
Modulehorizon-config-0.8.0GHC2024
Horizon.Config.Effects
The package-set schema, as an effect row. Each operation is one kind of declaration a config may make. The row has no IO and no lifting — a config script is capability-free by construction, so the type checker guarantees it can only describe a package set, never perform an effect. This is what makes it a safe config language.
Each pin argument carries a validating newtype from
Horizon.Config.Types rather than a bare Text, so a malformed
literal (a revision that is not a hash, a version that is not numeric)
is a fold-time error. With OverloadedStrings a config still writes
bare string literals — GHC picks the right fromString per argument —
so the .vx surface is unchanged.
- 2 types
- 43 values
- Packagehorizon-config-0.8.0
- Exports45
- LanguageGHC2024
- LicenceBSD-3-Clause
- SourceEffects.hs
What a config is: one expression in a pure declaration row. No IO appears in the type, so a config cannot escape into the world; the host folds it into a value.
Set the GHC compiler, e.g. compiler "ghc-9.10.1".
What the set is built on, exactly once:
on (toolchain (forge "nixlab.openfp.netpackage-setshaskellhorizon-build-packages") (branch "ltsghc-10.0.x"))
for a set built from scratch with a toolchain's compiler and build
tools, or on (set "horizon-core" (forge …) (branch …)) for an overlay
on a lower package set, which the set takes its compiler from and whose
packages it solves around at the versions that set holds. The base is
the generated flake.nix's one real input; the branch is what it
follows, and the flake lock records the commit. A config with no base
is an overlay for an application, which writes no flake.
The platform the set is rendered for, as cabal names it: its packages'
arch(…) and os(…) conditionals are resolved for this target rather
than for the machine that runs the generator, e.g.
platform "javascript-ghcjs" for a set built by the JavaScript backend.
Undeclared, a set is rendered for x86_64-linux, whichever machine
generates it.
The Hackage index state the set is read at, e.g.
indexState "2025-03-01T00:00:00Z": each Hackage pin's revision is the
last one published by then, and a Solve against hackage chooses
among the releases Hackage had then, so nothing published later changes
the generated set. Undeclared, the newest the index the generator reads
holds.
The set's name (used for the generated module + flake description),
e.g. name "horizon-build-essentials".
A one-line description of the set.
The licence, as an SPDX identifier: license .MIT
The warnings contract: warnings "werrorwolf", every package also
built as a check with -Werror; warnings "none" for none.
The default language of the local packages: language .GHC2024
A default extension of the local packages, once per verb:
extension .OverloadedStrings
A nixConfig setting of the generated flake, key and value:
nixConfig "extra-substituters" "https://cache.openfp.net".
from DoBenchmark
from DoCheck
from DoHaddock
from DoJailbreak
from EnableProfiling
from IncludeBenchmarks
from IncludeExecutables
from IncludeTests
A package from Hackage at a pinned version.
Declare a secure cabal repository the set pins packages from, as a
cabal repository stanza does: its name, its URL, how many of the
root keys must sign its root metadata, and the root key ids, e.g.
repository "nixlab" "https://nixlab.openfp.net/hackage" 2 ["…", "…", "…"].
The generator fetches its index with cabal, which checks the
repository's signatures against these keys. A repository is declared
before the pins that name it.
A package at a pinned version from a declared repository instead of
Hackage: repository, name, version, e.g.
hackageFrom "nixlab" "vortex-trace" "0.3.0".
A package from a git repository at a revision.
The same, from a subdirectory of the repository: name, url, revision, subdirectory.
A package from a tarball URL: name, url.
The same, from a subdirectory of the unpacked tarball.
A LOCAL package: its source is a subdirectory of this config's own repo, not a fetched artifact — so a set can include a project developed alongside it (the code you are writing here): name, subdirectory.
Turn a cabal flag on for a declared package: package, flag.
Turn a cabal flag off for a declared package: package, flag.
Apply a source patch to a declared package: package, path to the
.patch/.diff file (which stays raw in the repo). A genuinely
per-package fact — the diff is specific to that package's source.
A native library whose callPackage argument is named differently
from the nixpkgs attribute that supplies it: package, argument,
attribute, e.g. systemDepFrom "cardano-crypto-class" "libblst" "blst".
A module a local package exposes: package, module, e.g.
exposed "tint" ; once per module.Tint.Diff
A module a local package keeps internal (other-modules).
A test suite of a local package: package, suite, the file it runs,
e.g. suite "tint" "laws" "Laws.hs".
A backend a local package builds for, once per backend:
backend "tint" "javascript". Undeclared, the native one.
Assert that a package is intentionally NOT provided by this set —
an architecture gap, no source, or deliberately unavailable. This is
the ONLY named absence (never a bare = null): the generator records
it, and if a root transitively needs a missing package that is a
hard error rather than a silent hole. (Packages that are merely
unreached under the set's policy are pruned, not declared missing.)
Export an overlay of the declared packages to this file, e.g.
overlay "overlay.nix". The default: what a library or an
application declares are the packages its base set is missing.
Export a whole package set to this file instead, e.g.
packageSet "initial-packages.nix", for a repository that builds
a package set from scratch.
Keep a package in the set for its own sake: the set exists to
provide it, not because something else in the set needs it. The
roots and everything they depend on are what the set must carry;
any other declaration is a pin nothing reaches, which
horizon-config --prune reports so it can be removed.
Declare a cabal index to resolve against, e.g. index "hackage" or
index "head.hackage". Declaration order is precedence (earlier =
higher): when a package is in several indices the resolved one comes
from the highest-precedence index carrying a usable version. Named
indices resolve to known repositories (hackage, head.hackage, and chap
for Cardano Haskell Packages) or to a repository the config declares
with Repository. Only meaningful with Solve.
Relax the upper version bounds on a package during the solve, e.g.
allowNewer "base" — the solve-time counterpart of a build-time
jailbreak. Needed when a bleeding-edge compiler ships a boot library
(base, template-haskell) newer than dependants' stated bounds. Changes
what the solver may pick, so it is declared, not assumed. Only
meaningful with Solve.
Expand the declared roots into the full transitive closure by
running the solver against the declared indices, writing the resolved
closure to the lock. Explicit: a config without solve uses its
literal hackage/git pins as written. A set has one closure, so
there is one solve.