Modular overrides (#162)

**Completely new way to override Haskell packages**: removed `overrides` and `source-overrides`. Use `packages` to specify your source overrides; use `settings` to override individual packages in modular fashion (like NixOS modules). Additional changes include:
  - Add `package.<name>.cabal.executables` referring to the executables in a package. This is auto-detected by parsing the Cabal file.
  - Add `packages.<name>.local.*` to determine of a package is a local package or not.
  - Add `projectFlakeName` option (useful in debug logging prefix)
  - `flake.haskellFlakeProjectModules`: Dropped all defaults, except the `output` module, which now exports `packages` and `settings`. Added a `defaults.projectModules.output` option that allows the user to override this module, or directly access the generated module.
  - Add `project.config.defaults.settings.default` defining sensible defaults for local packages.
  - Add `project.config.defaults.enable` to turn off all default settings en masse.

Also, disable docs test due to https://github.com/hercules-ci/flake.parts-website/issues/332
This commit is contained in:
Sridhar Ratnakumar 2023-05-30 13:55:26 -04:00 committed by GitHub
parent 74210fa80a
commit 996f5c2cdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1070 additions and 445 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vscode
.direnv
result
result-*
dist-newstyle

View File

@ -1,5 +1,15 @@
# Revision history for haskell-flake
## `master`
- #162: **Completely new way to override Haskell packages**: removed `overrides` and `source-overrides`. Use `packages` to specify your source overrides; use `settings` to override individual packages in modular fashion (like NixOS modules). Additional changes include:
- Add `package.<name>.cabal.executables` referring to the executables in a package. This is auto-detected by parsing the Cabal file.
- Add `packages.<name>.local.*` to determine of a package is a local package or not.
- Add `projectFlakeName` option (useful in debug logging prefix)
- `flake.haskellFlakeProjectModules`: Dropped all defaults, except the `output` module, which now exports `packages` and `settings`. Added a `defaults.projectModules.output` option that allows the user to override this module, or directly access the generated module.
- Add `project.config.defaults.settings.default` defining sensible defaults for local packages.
- Add `project.config.defaults.enable` to turn off all default settings en masse.
## 0.3.0 (May 22, 2023)
- #134: Add `autoWire` option to control generation of flake outputs

View File

@ -4,13 +4,13 @@ slug: dependency
# Overriding dependencies
Haskell libraries ultimately come from [Hackage](https://hackage.haskell.org/), and [nixpkgs] contains [most of these](https://nixpkgs.haskell.page/). Adding a library to your project is done as follows (requiring no change to Nix!):
Haskell libraries ultimately come from [Hackage](https://hackage.haskell.org/), and [nixpkgs] contains [most of these](https://nixpkgs.haskell.page/). Adding a library to your project usually involves modifying the `.cabal` file and restart the nix shell:
1. Identify the package name from Hackage. Let's say you want to use [`ema`](https://hackage.haskell.org/package/ema)
2. Add the package, `ema`, to the `.cabal` file under [the `build-depends` section](https://cabal.readthedocs.io/en/3.4/cabal-package.html#pkg-field-build-depends).
3. Exit and restart the nix shell (`nix develop`).
Step (3) above will try to fetch the package from the Haskell package set in [nixpkgs] (the one that is pinned in `flake.lock`). For various reasons, this package may be either broken or does not exist. In such cases, you will have to override the package in the `overrides` argument (see the next section).
Step (3) above will try to fetch the package from the Haskell package set in [nixpkgs] (the one that is pinned in `flake.lock`). For various reasons, this package may be either missing or marked as broken. In such cases, you will have to override the package locally in the project (see the next section).
## Overriding a Haskell package in Nix
@ -25,61 +25,46 @@ In Nix, it is possible to use an exact package built from an arbitrary source (G
};
}
```
1. Build it using `callCabal2nix` and assign it to the `ema` name in the Haskell package set by adding it to the `overrides` argument of your `flake.nix` that is using haskell-flake:
1. Build it using `callCabal2nix` and assign it to the `ema` name in the Haskell package set by adding it to the `packages` argument of your `flake.nix` that is using haskell-flake:
```nix
{
perSystem = { self', config, pkgs, ... }: {
haskellProjects.default = {
overrides = self: super: with pkgs.haskell.lib; {
ema = dontCheck (self.callCabal2nix "ema" inputs.ema {});
packages = {
ema.source = inputs.ema;
};
settings = {
ema = { # This module can take `{self, super, ...}` args, optionally.
check = false;
};
};
};
};
}
```
We use `dontCheck` here to disable running tests.
We use `check = false` here to disable running tests.
1. Re-run the nix shell (`nix develop`).
### [nixpkgs] functions
- The `pkgs.haskell.lib` module provides various utility functions (like `dontCheck` above, to disable running tests) that you can use to override Haskell packages. The canonical place to find documentation on these is [the source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix).
- `self.callHackage` - Build a library from Hackage given its version. You can also do this with `source-overrides` (see below).
- [Artyom's tutorial](https://tek.brick.do/how-to-override-dependency-versions-when-building-a-haskell-project-with-nix-K3VXJd8mEKO7)
- The `pkgs.haskell.lib` module provides various utility functions that you can use to override Haskell packages. The canonical place to find documentation on these is [the source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix). haskell-flake provides a `settings` submodule for convienience; for eg., the `dontCheck` function translates to `settings.<name>.check`.
## Using `source-overrides`
## Using Hackage versions
If you are only specifying the source of the Haskell package, but are not overriding anything else, you may use the simpler `source-overrides` option instead. The above example would look like:
`packages.<name>.source` also supports Hackage versions. So the following works to pull [ema 0.8.2.0](https://hackage.haskell.org/package/ema-0.8.2.0):
```nix
{
perSystem = { self', config, pkgs, ... }: {
haskellProjects.default = {
source-overrides = {
ema = inputs.ema;
packages = {
ema.source = "0.8.2.0";
};
};
};
}
```
`source-overrides` also supports specifying referring directly to a Hackage version. So the following works to pull [ema 0.8.2.0](https://hackage.haskell.org/package/ema-0.8.2.0):
```nix
{
perSystem = { self', config, pkgs, ... }: {
haskellProjects.default = {
source-overrides = {
ema = "0.8.2.0";
};
};
};
}
```
If you are using both the options, `overrides` take priority over `source-overrides`.
## See also
- [Setting environment variables for build](https://github.com/srid/haskell-flake/discussions/159#discussioncomment-5840505)
[[project-modules]] export both `packages` and `settings` options for reuse in downstream Haskell projects.
[nixpkgs]: https://zero-to-nix.com/concepts/nixpkgs

View File

@ -18,8 +18,8 @@ Let's say you have two repositories -- `common` and `myapp`. The `common` reposi
cabal-fmt
ormolu;
};
source-overrides = {
mylib = inputs.mylib;
packages = {
mylib.source = inputs.mylib;
};
};
}
@ -58,11 +58,9 @@ By default, haskell-flake will generate the following modules for the "default"
| Module | Contents |
| -- | -- |
| `haskellFlakeProjectModules.input` | Dependency overrides only |
| `haskellFlakeProjectModules.local` | Local packages only |
| `haskellFlakeProjectModules.output` | Local packages & dependency overrides |
The idea here being that you can "connect" two Haskell projects such that they depend on one another while reusing the overrides from one place. For example, if you have a project "foo" that depends on "bar" and if "foo"'s flake.nix has "bar" as its input, then in "foo"'s `haskellProject.default` entry you can import "bar" as follows:
The idea here being that you can "connect" two Haskell projects such that they depend on one another while reusing the overrides (`packages` and `settings`) from one place. For example, if you have a project "foo" that depends on "bar" and if "foo"'s flake.nix has "bar" as its input, then in "foo"'s `haskellProject.default` entry you can import "bar" as follows:
```nix
# foo's flake.nix's perSystem
@ -78,13 +76,9 @@ The idea here being that you can "connect" two Haskell projects such that they d
}
```
By importing "bar"'s `output` project module, you automatically get the overrides from "bar" (unless you use the `local` module) as well as the local packages[^bar]. This way you don't have to duplicate the `overrides` and manually specify the `source-overrides` in "foo"'s flake.nix.
By importing "bar"'s `output` project module, you automatically get the overrides from "bar" as well as the local packages. This way you don't have to duplicate the `settings` and manually specify the `packages.<name>.source` in "foo"'s flake.nix.
[^bar]: Local packages come from the `packages` option. So this is typically the "bar" package itself for single-package projects; or all the local projects if it is a multi-package project.
## Examples
- https://github.com/srid/nixpkgs-140774-workaround
- [shared-kernel](https://github.com/nammayatri/shared-kernel/blob/591bdc1c87b3f80b57a3c3849414bd106a1f8365/flake.nix#L24-L26) importing [euler-hs overrides](https://github.com/juspay/euler-hs/blob/168dc51f8a68e4bf52de6c691343afa594f933a9/flake.nix#L31-L52) and local packages.
- https://github.com/juspay/prometheus-haskell/pull/3
- https://github.com/nammayatri/nammayatri (imports `shared-kernel` which in turn imports `euler-hs`)

View File

@ -11,9 +11,9 @@ A "project" in haskell-flake primarily serves the purpose of developing Haskell
```nix
{
haskellProjects.ghc810 = {
packages = {}; # No local packages
devShell.enable = false;
autoWire = [ ]; # Don't wire any flake outputs
defaults.packages = {}; # Disable scanning for local package
devShell.enable = false; # Disable devShells
autoWire = [ ]; # Don't wire any flake outputs
# Start from nixpkgs's ghc8107 package set
basePackages = pkgs.haskell.packages.ghc8107;
@ -26,41 +26,41 @@ You can access this package set as `config.haskellProjects.ghc810.outputs.finalP
```nix
{
haskellProjects.ghc810 = {
packages = {}; # No local packages
defaults.packages = {}; # No local packages
devShell.enable = false;
basePackages = pkgs.haskell.packages.ghc8107;
source-overrides = {
packages = {
# New packages from flake inputs
mylib = inputs.mylib;
mylib.source = inputs.mylib;
# Dependencies from Hackage
aeson = "1.5.6.0";
dhall = "1.35.0";
aeson.source = "1.5.6.0";
dhall.source = "1.35.0";
};
overrides = self: super: with pkgs.haskell.lib; {
aeson = doJailbreak super.aeson;
settings = {
aeson.jailbreak = true;
};
};
}
```
This will create a package set that overrides the `aeson` and `dhall` packages using the specified versions from Hackage, but with the `aeson` package having the `doJailbreak` flag set (which relaxes its Cabal constraints). It also adds the `mylib` package which exists neither in nixpkgs nor in Hackage, but comes from somewhere arbitrary and specified as flake input.
This will create a package set that overrides the `aeson` and `dhall` packages using the specified versions from Hackage, but with the `aeson` package having the `jailbreak` flag set (which relaxes its Cabal constraints). It also adds the `mylib` package which exists neither in nixpkgs nor in Hackage, but comes from somewhere arbitrary and specified as flake input.
In your *actual* haskell project, you can use this package set (`config.haskellProjects.ghc810.outputs.finalPackages`) as its base package set:
```nix
{
haskellProjects.myproject = {
packages.mypackage = ./.;
packages.mypackage.source = ./.;
basePackages = config.haskellProjects.ghc810.outputs.finalPackages;
};
}
```
Finally you can externalized this `ghc810` package set as either a flake-parts module or as a [[modules|haskell-flake module]], and import it in multiple repositories.
Finally, you can externalize this `ghc810` package set as either a flake-parts module or as a [[modules|haskell-flake module]], and thereon import it from multiple repositories.
## Examples
- https://github.com/nammayatri/common/pull/3/files
- https://github.com/nammayatri/common/pull/11/files

View File

@ -37,6 +37,7 @@ In addition, compared to using plain nixpkgs, haskell-flake supports:
- Auto-detection of local packages based on `cabal.project` file (via [haskell-parsers](https://github.com/srid/haskell-flake/tree/master/nix/haskell-parsers))
- Parse executables from `.cabal` file
- Modular interface to `pkgs.haskell.lib.compose.*` (via `packages` and `settings` submodules)
- Composition of dependency overrides, and other project settings, via [[modules]]
## Next steps

View File

@ -14,18 +14,29 @@
# Typically, you just want a single project named "default". But
# multiple projects are also possible, each using different GHC version.
haskellProjects.default = {
# If you have a .cabal file in the root, this option is determined
# automatically. Otherwise, specify all your local packages here.
# packages.example.root = ./.;
# The base package set representing a specific GHC version.
# By default, this is pkgs.haskellPackages.
# You may also create your own. See https://haskell.flake.page/package-set
# basePackages = pkgs.haskellPackages;
# Dependency overrides go here. See https://haskell.flake.page/dependency
# source-overrides = { };
# overrides = self: super: { };
# Extra package information. See https://haskell.flake.page/dependency
#
# Note that local packages are automatically included in `packages`
# (defined by `defaults.packages` option).
#
# packages = {
# aeson.source = "1.5.0.0"; # Hackage version override
# shower.source = inputs.shower;
# };
# settings = {
# aeson = {
# check = false;
# };
# relude = {
# haddock = false;
# broken = false;
# };
# };
# devShell = {
# # Enabled by default

View File

@ -1,7 +1,10 @@
# Like callCabal2nix, but does more:
# - Source filtering (to prevent parent content changes causing rebuilds)
# - Always build from cabal's sdist for release-worthiness
{ pkgs, lib, self, log, ... }:
#
# This function can only be called from inside a Haskell overlay, whose 'self'
# and 'super' are accessible in args here.
{ pkgs, lib, self, super, log, ... }:
let
fromSdist = self.buildFromCabalSdist or
@ -19,8 +22,8 @@ let
};
in
name: pkgCfg:
lib.pipe pkgCfg.root
name: root:
lib.pipe root
[
# Avoid rebuilding because of changes in parent directories
(mkNewStorePath "source-${name}")

View File

@ -68,9 +68,13 @@ in
getCabalExecutables = path:
let
cabalFile = traversal.findSingleCabalFile path;
res = parser.parseCabalExecutableNames (builtins.readFile (lib.concatStrings [ path "/" cabalFile ]));
in
if res.type == "success"
then res.value
else throwError "Failed to parse ${cabalFile}: ${builtins.toJSON res}";
if cabalFile != null then
let res = parser.parseCabalExecutableNames (builtins.readFile (lib.concatStrings [ path "/" cabalFile ]));
in
if res.type == "success"
then res.value
else throwError "Failed to parse ${cabalFile}: ${builtins.toJSON res}"
else
throwError "No .cabal file found under ${path}";
}

View File

@ -1,16 +1,16 @@
{ debug ? false, ... }:
{ name, debug ? false, ... }:
{
traceDebug = msg:
if debug then
builtins.trace ("DEBUG[haskell-flake]: " + msg)
builtins.trace ("DEBUG[haskell-flake] [${name}]: " + msg)
else
x: x;
traceWarning = msg:
builtins.trace ("WARNING[haskell-flake]: " + msg);
builtins.trace ("WARNING[haskell-flake] [${name}]: " + msg);
throwError = msg: builtins.throw ''
ERROR[haskell-flake]: ${msg}
ERROR[haskell-flake] [${name}]: ${msg}
'';
}

View File

@ -16,44 +16,21 @@ in
A lazy attrset of `haskellProjects.<name>` modules that can be
imported in other flakes.
'';
defaultText = ''
defaultText = lib.literalMD ''
Package and dependency information for this project exposed for reuse
in another flake, when using this project as a Haskell dependency.
Typically the consumer of this flake will want to use one of the
following modules:
- output: provides both local package and dependency overrides.
- local: provides only local package overrides (ignores dependency
overrides in this flake)
These default modules are always available.
The 'output' module of the default project is included by default,
returning `defaults.projectModules.output`.
'';
default = { }; # Set in config (see ./default-project-modules.nix)
default = { };
};
config.haskellFlakeProjectModules =
let
defaults = rec {
# The 'output' module provides both local package and dependency
# overrides.
output = {
imports = [ input local ];
};
# The 'local' module provides only local package overrides.
local = { pkgs, lib, ... }: withSystem pkgs.system ({ config, ... }: {
source-overrides =
lib.mapAttrs (_: v: v.root)
config.haskellProjects.default.packages;
});
# The 'input' module contains only dependency overrides.
input = { pkgs, ... }: withSystem pkgs.system ({ config, ... }: {
inherit (config.haskellProjects.default)
source-overrides overrides;
});
};
in
defaults;
config.haskellFlakeProjectModules = {
output = { pkgs, lib, ... }: withSystem pkgs.system ({ config, ... }:
config.haskellProjects."default".defaults.projectModules.output
);
};
}];
};
};

View File

@ -1,243 +0,0 @@
# Definition of the `haskellProjects.${name}` submodule's `config`
{ self, config, lib, pkgs, ... }:
let
inherit (lib)
mkOption
types;
inherit (types)
raw;
appType = import ../types/app-type.nix { inherit pkgs lib; };
haskellOverlayType = import ../types/haskell-overlay-type.nix { inherit lib; };
outputsSubmodule = types.submodule {
options = {
finalOverlay = mkOption {
type = types.raw;
readOnly = true;
internal = true;
};
finalPackages = mkOption {
# This must be raw because the Haskell package set also contains functions.
type = types.attrsOf types.raw;
readOnly = true;
description = ''
The final Haskell package set including local packages and any
overrides, on top of `basePackages`.
'';
};
packages = mkOption {
type = types.attrsOf packageInfoSubmodule;
readOnly = true;
description = ''
Package information for all local packages. Contains the following keys:
- `package`: The Haskell package derivation
- `executables`: Attrset of executables found in the .cabal file
'';
};
apps = mkOption {
type = types.attrsOf appType;
readOnly = true;
description = ''
Flake apps for each Cabal executable in the project.
'';
};
};
};
packageInfoSubmodule = types.submodule {
options = {
package = mkOption {
type = types.package;
description = ''
The local package derivation.
'';
};
exes = mkOption {
type = types.attrsOf appType;
description = ''
Attrset of executables from `.cabal` file.
If the associated Haskell project has a separate bin output
(cf. `enableSeparateBinOutput`), then this exes will refer
only to the bin output.
NOTE: Evaluating up to this option will involve IFD.
'';
};
};
};
in
{
imports = [
./project/defaults.nix
./project/packages.nix
./project/devshell.nix
];
options = {
projectRoot = mkOption {
type = types.path;
description = ''
Path to the root of the project directory.
Chaning this affects certain functionality, like where to
look for the 'cabal.project' file.
'';
default = self;
defaultText = "Top-level directory of the flake";
};
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable verbose trace output from haskell-flake.
Useful for debugging.
'';
};
log = mkOption {
type = types.attrsOf (types.functionTo types.raw);
default = import ../logging.nix { inherit (config) debug; };
internal = true;
readOnly = true;
description = ''
Internal logging module
'';
};
basePackages = mkOption {
type = types.attrsOf raw;
description = ''
Which Haskell package set / compiler to use.
You can effectively select the GHC version here.
To get the appropriate value, run:
nix-env -f "<nixpkgs>" -qaP -A haskell.compiler
And then, use that in `pkgs.haskell.packages.ghc<version>`
'';
example = "pkgs.haskell.packages.ghc924";
default = pkgs.haskellPackages;
defaultText = lib.literalExpression "pkgs.haskellPackages";
};
source-overrides = mkOption {
type = types.attrsOf (types.oneOf [ types.path types.str ]);
description = ''
Source overrides for Haskell packages
You can either assign a path to the source, or Hackage
version string.
'';
default = { };
};
overrides = mkOption {
type = haskellOverlayType;
description = ''
Cabal package overrides for this Haskell project
For handy functions, see
<https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix>
**WARNING**: When using `imports`, multiple overlays
will be merged using `lib.composeManyExtensions`.
However the order the overlays are applied can be
arbitrary (albeit deterministic, based on module system
implementation). Thus, the use of `overrides` via
`imports` is not officiallly supported. If you'd like
to see proper support, add your thumbs up to
<https://github.com/NixOS/nixpkgs/issues/215486>.
'';
default = self: super: { };
defaultText = lib.literalExpression "self: super: { }";
};
outputs = mkOption {
type = outputsSubmodule;
description = ''
The flake outputs generated for this project.
This is an internal option, not meant to be set by the user.
'';
};
autoWire =
let
outputTypes = [ "packages" "checks" "apps" "devShells" ];
in
mkOption {
type = types.listOf (types.enum outputTypes);
description = ''
List of flake output types to autowire.
Using an empty list will disable autowiring entirely,
enabling you to manually wire them using
`config.haskellProjects.<name>.outputs`.
'';
default = outputTypes;
};
};
config =
let
inherit (config.outputs) finalPackages packages;
localPackagesOverlay = self: _:
let
build-haskell-package = import ../build-haskell-package.nix {
inherit pkgs lib self;
inherit (config) log;
};
in
lib.mapAttrs build-haskell-package config.packages;
finalOverlay = lib.composeManyExtensions [
# The order here matters.
#
# User's overrides (cfg.overrides) is applied **last** so
# as to give them maximum control over the final package
# set used.
localPackagesOverlay
(pkgs.haskell.lib.packageSourceOverrides config.source-overrides)
config.overrides
];
buildPackageInfo = name: value: {
package = finalPackages.${name};
exes =
let
haskell-parsers = import ../haskell-parsers {
inherit pkgs lib;
throwError = msg: config.log.throwError ''
Unable to determine executable names for package ${name}:
${msg}
'';
};
exeNames = haskell-parsers.getCabalExecutables value.root;
in
lib.listToAttrs
(map
(exe:
lib.nameValuePair exe {
program = "${lib.getBin finalPackages.${name}}/bin/${exe}";
}
)
exeNames
);
};
in
{
outputs = {
inherit finalOverlay;
finalPackages = config.basePackages.extend finalOverlay;
packages = lib.mapAttrs buildPackageInfo config.packages;
apps =
lib.mkMerge
(lib.mapAttrsToList (_: packageInfo: packageInfo.exes) packages);
};
};
}

View File

@ -0,0 +1,98 @@
# Definition of the `haskellProjects.${name}` submodule's `config`
{ self, name, config, lib, pkgs, ... }:
let
inherit (lib)
mkOption
types;
inherit (types)
raw;
in
{
imports = [
./defaults.nix
./packages
./settings
./devshell.nix
./outputs.nix
];
options = {
projectRoot = mkOption {
type = types.path;
description = ''
Path to the root of the project directory.
Chaning this affects certain functionality, like where to
look for the 'cabal.project' file.
'';
default = self;
defaultText = "Top-level directory of the flake";
};
projectFlakeName = mkOption {
type = types.nullOr types.str;
description = ''
A descriptive name for the flake in which this project resides.
If unspecified, the Nix store path's basename will be used.
'';
default = null;
apply = cls:
if cls == null
then builtins.baseNameOf config.projectRoot
else cls;
};
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable verbose trace output from haskell-flake.
Useful for debugging.
'';
};
log = mkOption {
type = types.attrsOf (types.functionTo types.raw);
default = import ../../logging.nix {
name = config.projectFlakeName + "#haskellProjects." + name;
inherit (config) debug;
};
internal = true;
readOnly = true;
description = ''
Internal logging module
'';
};
basePackages = mkOption {
type = types.attrsOf raw;
description = ''
Which Haskell package set / compiler to use.
You can effectively select the GHC version here.
To get the appropriate value, run:
nix-env -f "<nixpkgs>" -qaP -A haskell.compiler
And then, use that in `pkgs.haskell.packages.ghc<version>`
'';
example = "pkgs.haskell.packages.ghc924";
default = pkgs.haskellPackages;
defaultText = lib.literalExpression "pkgs.haskellPackages";
};
autoWire =
let
outputTypes = [ "packages" "checks" "apps" "devShells" ];
in
mkOption {
type = types.listOf (types.enum outputTypes);
description = ''
List of flake output types to autowire.
Using an empty list will disable autowiring entirely,
enabling you to manually wire them using
`config.haskellProjects.<name>.outputs`.
'';
default = outputTypes;
};
};
}

View File

@ -1,5 +1,5 @@
# A module representing the default values used internally by haskell-flake.
{ lib, ... }:
{ lib, pkgs, config, ... }:
let
inherit (lib)
mkOption
@ -9,10 +9,18 @@ let
in
{
options.defaults = {
enable = mkOption {
type = types.bool;
description = ''
Whether to enable haskell-flake's default settings for this project.
'';
default = true;
};
devShell.tools = mkOption {
type = functionTo (types.attrsOf (types.nullOr types.package));
description = ''Build tools always included in devShell'';
default = hp: with hp; {
default = hp: with hp; lib.optionalAttrs config.defaults.enable {
inherit
cabal-install
haskell-language-server
@ -20,5 +28,89 @@ in
hlint;
};
};
packages = mkOption {
type = types.lazyAttrsOf types.deferredModule;
description = ''Local packages scanned from projectRoot'';
default =
let
haskell-parsers = import ../../haskell-parsers {
inherit pkgs lib;
throwError = msg: config.log.throwError ''
A default value for `packages` cannot be auto-determined:
${msg}
Please specify the `packages` option manually or change your project configuration (cabal.project).
'';
};
localPackages = lib.pipe config.projectRoot [
haskell-parsers.findPackagesInCabalProject
(lib.mapAttrs (_: path: {
# The rest of the module options are not defined, because we'll use
# the submodule defaults.
source = path;
}))
];
in
lib.optionalAttrs config.defaults.enable localPackages;
apply = x:
config.log.traceDebug "defaults.packages = ${builtins.toJSON x}" x;
defaultText = lib.literalMD ''
If you have a `cabal.project` file (under `projectRoot`), those packages
are automatically discovered. Otherwise, the top-level .cabal file is
used to discover the only local package.
haskell-flake currently supports a limited range of syntax for
`cabal.project`. Specifically it requires an explicit list of package
directories under the "packages" option.
'';
};
settings.default = mkOption {
type = types.deferredModule;
description = ''
Default settings for all packages in `packages` option.
'';
defaultText = ''
- Speed up builds by disabling haddock and library profiling.
- Separate bin output (for reduced closure size when using `getBin` in apps)
This uses `local.toDefinedProject` option to determine which packages to
override. Thus, it applies to both local packages as well as
transitively imported packags that are local to that flake (managed by
haskell-flake). The goal being to use the same configuration
consistently for all packages using haskell-flake.
'';
default =
let
localSettings = { name, package, config, ... }:
lib.optionalAttrs (package.local.toDefinedProject or false) {
# Disabling haddock and profiling is mainly to speed up Nix builds.
haddock = lib.mkDefault false; # Because, this is end-user software. No need for library docs.
libraryProfiling = lib.mkDefault false; # Avoid double-compilation.
separateBinOutput = lib.mkDefault (
# Reduce closure size
if package.cabal.executables == [ ]
then null
else true
);
};
in
if config.defaults.enable then localSettings else { };
};
projectModules.output = mkOption {
type = types.deferredModule;
description = ''
A haskell-flake project module that exports the `packages` and
`settings` options to the consuming flake. This enables the use of this
flake's Haskell package as a dependency, re-using its overrides.
'';
default = lib.optionalAttrs config.defaults.enable {
inherit (config)
packages settings;
};
};
};
}

View File

@ -93,9 +93,12 @@ in
};
devShell = finalPackages.shellFor (mkShellArgs // {
packages = p:
let
localPackages = (lib.filterAttrs (k: v: v.local.toCurrentProject) config.packages);
in
map
(name: p."${name}")
(lib.attrNames config.packages);
(lib.attrNames localPackages);
withHoogle = true;
extraDependencies = p:
let o = mkShellArgs.extraDependencies or (_: { }) p;

View File

@ -0,0 +1,120 @@
# haskellProjects.<name>.outputs module.
{ config, lib, pkgs, ... }:
let
inherit (lib)
mkOption
types;
appType = import ../../types/app-type.nix { inherit pkgs lib; };
outputsSubmodule = types.submodule {
options = {
finalOverlay = mkOption {
type = types.raw;
readOnly = true;
internal = true;
};
finalPackages = mkOption {
# This must be raw because the Haskell package set also contains functions.
type = types.attrsOf types.raw;
readOnly = true;
description = ''
The final Haskell package set including local packages and any
overrides, on top of `basePackages`.
'';
};
packages = mkOption {
type = types.attrsOf packageInfoSubmodule;
readOnly = true;
description = ''
Package information for all local packages. Contains the following keys:
- `package`: The Haskell package derivation
- `exes`: Attrset of executables found in the .cabal file
'';
};
apps = mkOption {
type = types.attrsOf appType;
readOnly = true;
description = ''
Flake apps for each Cabal executable in the project.
'';
};
};
};
packageInfoSubmodule = types.submodule {
options = {
package = mkOption {
type = types.package;
description = ''
The local package derivation.
'';
};
exes = mkOption {
type = types.attrsOf appType;
description = ''
Attrset of executables from `.cabal` file.
If the associated Haskell project has a separate bin output
(cf. `enableSeparateBinOutput`), then this exe will refer
only to the bin output.
NOTE: Evaluating up to this option will involve IFD.
'';
};
};
};
in
{
options = {
outputs = mkOption {
type = outputsSubmodule;
description = ''
The flake outputs generated for this project.
This is an internal option, not meant to be set by the user.
'';
};
};
config =
let
# Subet of config.packages that are local to the project.
localPackages =
lib.filterAttrs (_: cfg: cfg.local.toCurrentProject) config.packages;
finalOverlay = lib.composeManyExtensions [
config.packagesOverlay
config.settingsOverlay
];
finalPackages = config.basePackages.extend finalOverlay;
buildPackageInfo = name: value: {
package = finalPackages.${name};
exes =
lib.listToAttrs
(map
(exe:
lib.nameValuePair exe {
program = "${lib.getBin finalPackages.${name}}/bin/${exe}";
}
)
value.cabal.executables
);
};
in
{
outputs = {
inherit finalOverlay finalPackages;
packages = lib.mapAttrs buildPackageInfo localPackages;
apps =
lib.mkMerge
(lib.mapAttrsToList (_: packageInfo: packageInfo.exes) config.outputs.packages);
};
};
}

View File

@ -1,56 +0,0 @@
# Definition of the `haskellProjects.${name}` submodule's `config`
{ name, config, lib, pkgs, ... }:
let
inherit (lib)
mkOption
types;
haskell-parsers = import ../../haskell-parsers {
inherit pkgs lib;
throwError = msg: config.log.throwError ''
A default value for `packages` cannot be auto-determined:
${msg}
Please specify the `packages` option manually or change your project configuration (cabal.project).
'';
};
packageSubmodule = with types; submodule {
options = {
root = mkOption {
type = path;
description = ''
Path containing the Haskell package's `.cabal` file.
'';
};
};
};
in
{
options = {
packages = mkOption {
type = types.lazyAttrsOf packageSubmodule;
description = ''
Set of local packages in the project repository.
If you have a `cabal.project` file (under `projectRoot`), those packages
are automatically discovered. Otherwise, the top-level .cabal file is
used to discover the only local package.
haskell-flake currently supports a limited range of syntax for
`cabal.project`. Specifically it requires an explicit list of package
directories under the "packages" option.
'';
default =
lib.pipe config.projectRoot [
haskell-parsers.findPackagesInCabalProject
(x: config.log.traceDebug "config.haskellProjects.${name}.packages = ${builtins.toJSON x}" x)
(lib.mapAttrs (_: path: { root = path; }))
];
defaultText = lib.literalMD "autodiscovered by reading `self` files.";
};
};
}

View File

@ -0,0 +1,74 @@
# Definition of the `haskellProjects.${name}` submodule's `config`
project@{ name, lib, pkgs, ... }:
let
inherit (lib)
types;
packageSubmodule = import ./package.nix { inherit project lib pkgs; };
# Merge the list of attrset of modules.
mergeModuleAttrs =
lib.zipAttrsWith (k: vs: { imports = vs; });
tracePackages = k: x:
project.config.log.traceDebug "${k} ${builtins.toJSON x}" x;
in
{
options = {
packages = lib.mkOption {
type = types.lazyAttrsOf types.deferredModule;
default = { };
apply = packages:
let
packages' =
# Merge user-provided 'packages' with 'defaults.packages'.
#
# Note that the user can override the latter too if they wish.
mergeModuleAttrs
[ project.config.defaults.packages packages ];
in
tracePackages "${name}.packages:apply" (
lib.mapAttrs
(name: v:
(lib.evalModules {
modules = [ packageSubmodule v ];
specialArgs = { inherit name pkgs; };
}).config
)
packages');
description = ''
Additional packages to add to `basePackages`.
Local packages are added automatically (see `config.defaults.packages`):
You can also override the source for existing packages here.
'';
};
packagesOverlay = lib.mkOption {
type = import ../../../types/haskell-overlay-type.nix { inherit lib; };
description = ''
The Haskell overlay computed from `packages` modules.
'';
internal = true;
default = self: super:
let
inherit (project.config) log;
isPathUnderNixStore = path: builtins.hasContext (builtins.toString path);
build-haskell-package = import ../../../build-haskell-package.nix {
inherit pkgs lib self super log;
};
getOrMkPackage = name: cfg:
if isPathUnderNixStore cfg.source
then
log.traceDebug "${name}.callCabal2nix ${cfg.source}"
(build-haskell-package name cfg.source)
else
log.traceDebug "${name}.callHackage ${cfg.source}"
(self.callHackage name cfg.source { });
in
lib.mapAttrs getOrMkPackage project.config.packages;
};
};
}

View File

@ -0,0 +1,82 @@
# haskellProjects.<name>.packages.<name> module.
{ project, lib, pkgs, ... }:
let
inherit (lib)
mkOption
types;
# TODO: DRY
isPathUnderNixStore = path: builtins.hasContext (builtins.toString path);
# Whether the 'path' is local to `project.config.projectRoot`
localToProject = path:
path != null &&
isPathUnderNixStore path &&
lib.strings.hasPrefix "${project.config.projectRoot}" "${path}";
in
{ name, config, ... }: {
options = {
source = mkOption {
type = import ../../../types/haskell-source-type.nix { inherit lib; };
description = ''
Source refers to a Haskell package defined by one of the following:
- Path containing the Haskell package's `.cabal` file.
- Hackage version string
'';
};
cabal.executables = mkOption {
type = types.nullOr (types.listOf types.string);
description = ''
List of executable names found in the cabal file of the package.
The value is null if 'source' option is Hackage version.
'';
default =
let
haskell-parsers = import ../../../haskell-parsers {
inherit pkgs lib;
throwError = msg: project.config.log.throwError ''
Unable to determine executable names for package ${name}:
${msg}
'';
};
in
if isPathUnderNixStore config.source
then haskell-parsers.getCabalExecutables config.source
else null; # cfg.source is Hackage version; nothing to do.
};
local.toCurrentProject = mkOption {
type = types.bool;
description = ''
Whether this package is local to the project that is importing it.
'';
internal = true;
readOnly = true;
# We use 'apply' rather than 'default' to make this evaluation lazy at
# call site (which could be different projectRoot)
apply = _:
localToProject config.source;
defaultText = ''
Computed automatically if package 'source' is under 'projectRoot' of the
importing project.
'';
};
local.toDefinedProject = mkOption {
type = types.bool;
description = ''
Whether this package is local to the project it is defined in.
'';
internal = true;
default =
localToProject config.source;
defaultText = ''
Computed automatically if package 'source' is under 'projectRoot' of the
defining project.
'';
};
};
}

View File

@ -0,0 +1,323 @@
{ pkgs, lib, config, ... }:
let
inherit (lib) types;
inherit (import ./lib.nix {
inherit lib config;
}) mkCabalSettingOptions;
# Convenient way to create multiple settings using `mkCabalSettingOptions`
cabalSettingsFrom =
lib.mapAttrsToList (name: args: {
options = mkCabalSettingOptions (args // {
inherit name;
});
});
in
{
# NOTE: These settings are based on the functions in:
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix
#
# Some functions (like checkUnusedPackages) are not included here, but we
# probably should, especially if there is demand.
imports = with pkgs.haskell.lib.compose; cabalSettingsFrom {
check = {
type = types.bool;
description = ''
Whether to run cabal tests as part of the nix build
'';
impl = enable:
if enable then doCheck else dontCheck;
};
jailbreak = {
type = types.bool;
description = ''
Remove version bounds from this package's cabal file.
'';
impl = enable:
if enable then doJailbreak else dontJailbreak;
};
broken = {
type = types.bool;
description = ''
Whether to mark the package as broken
'';
impl = enable:
if enable then markBroken else unmarkBroken;
};
brokenVersions = {
type = types.attrsOf types.string;
description = ''
List of versions that are known to be broken.
'';
impl = versions:
let
markBrokenVersions = vs: drv:
builtins.foldl' markBrokenVersion drv vs;
in
markBrokenVersions versions;
};
haddock = {
type = types.bool;
description = ''
Whether to build the haddock documentation.
'';
impl = enable:
if enable then doHaddock else dontHaddock;
};
coverage = {
type = types.bool;
description = ''
Modifies thae haskell package to disable the generation
and installation of a coverage report.
'';
impl = enable:
if enable then doCoverage else dontCoverage;
};
benchmark = {
type = types.bool;
description = ''
Enables dependency checking and compilation
for benchmarks listed in the package description file.
Benchmarks are, however, not executed at the moment.
'';
impl = enable:
if enable then doBenchmark else dontBenchmark;
};
libraryProfiling = {
type = types.bool;
description = ''
Build the library for profiling by default.
'';
impl = enable:
if enable then enableLibraryProfiling else disableLibraryProfiling;
};
executableProfiling = {
type = types.bool;
description = ''
Build the executable with profiling enabled.
'';
impl = enable:
if enable then enableExecutableProfiling else disableExecutableProfiling;
};
sharedExecutables = {
type = types.bool;
description = ''
Build the executables as shared libraries.
'';
impl = enable:
if enable then enableSharedExecutables else disableSharedExecutables;
};
sharedLibraries = {
type = types.bool;
description = ''
Build the libraries as shared libraries.
'';
impl = enable:
if enable then enableSharedLibraries else disableSharedLibraries;
};
deadCodeElimination = {
type = types.bool;
description = ''
Enable dead code elimination.
'';
impl = enable:
if enable then enableDeadCodeElimination else disableDeadCodeElimination;
};
staticLibraries = {
type = types.bool;
description = ''
Build the libraries as static libraries.
'';
impl = enable:
if enable then enableStaticLibraries else disableStaticLibraries;
};
extraBuildDepends = {
type = types.listOf types.package;
description = ''
Extra build dependencies for the package.
'';
impl = addBuildDepends;
};
extraBuildTools = {
type = types.listOf types.package;
description = ''
Extra build tools for the package.
'';
impl = addBuildTools;
};
extraTestToolDepends = {
type = types.listOf types.package;
description = ''
Extra test tool dependencies for the package.
'';
impl = addTestToolDepends;
};
extraPkgconfigDepends = {
type = types.listOf types.package;
description = ''
Extra pkgconfig dependencies for the package.
'';
impl = addPkgconfigDepends;
};
extraSetupDepends = {
type = types.listOf types.package;
description = ''
Extra setup dependencies for the package.
'';
impl = addSetupDepends;
};
extraConfigureFlags = {
type = types.listOf types.string;
description = ''
Extra flags to pass to 'cabal configure'
'';
impl = appendConfigureFlags;
};
extraBuildFlags = {
type = types.listOf types.string;
description = ''
Extra flags to pass to 'cabal build'
'';
impl = appendBuildFlags;
};
removeConfigureFlags = {
type = types.listOf types.string;
description = ''
Flags to remove from the default flags passed to 'cabal configure'
'';
impl =
let
removeConfigureFlags = flags: drv:
builtins.foldl' removeConfigureFlag drv flags;
in
removeConfigureFlags;
};
cabalFlags = {
type = types.attrsOf types.bool;
description = ''
Cabal flags to enable or disable explicitly.
'';
impl = flags: drv:
let
enabled = lib.filterAttrs (_: v: v) flags;
disabled = lib.filterAttrs (_: v: !v) flags;
enableCabalFlags = fs: drv: builtins.foldl' enableCabalFlag drv fs;
disableCabalFlags = fs: drv: builtins.foldl' disableCabalFlag drv fs;
in
lib.pipe drv [enableCabalFlag disableCabalFlag];
};
patches = {
type = types.listOf types.path;
description = ''
A list of patches to apply to the package.
'';
impl = appendPatches;
};
justStaticExecutables = {
type = types.bool;
description = ''
Link executables statically against haskell libs to reduce closure size
'';
impl = enable:
if enable then justStaticExecutables else x: x;
};
separateBinOutput = {
type = types.bool;
description = ''
Create two outputs for this Haskell package -- 'out' and 'bin'. This is
useful to separate out the binary with a reduced closure size.
'';
impl = enable:
let
disableSeparateBinOutput =
overrideCabal (drv: { enableSeparateBinOutput = false; });
in
if enable then enableSeparateBinOutput else disableSeparateBinOutput;
};
buildTargets = {
type = types.listOf types.string;
description = ''
A list of targets to build.
By default all cabal executable targets are built.
'';
impl = setBuildTargets;
};
hyperlinkSource = {
type = types.bool;
description = ''
Whether to hyperlink the source code in the generated documentation.
'';
impl = enable:
if enable then doHyperlinkSource else dontHyperlinkSource;
};
disableHardening = {
type = types.bool;
description = ''
Disable hardening flags for the package.
'';
impl = enable:
if enable then disableHardening else x: x;
};
strip = {
type = types.bool;
description = ''
Let Nix strip the binary files.
This removes debugging symbols.
'';
impl = enable:
if enable then doStrip else dontStrip;
};
enableDWARFDebugging = {
type = types.bool;
description = ''
Enable DWARF debugging.
'';
impl = enable:
if enable then enableDWARFDebugging else x: x;
};
disableOptimization = {
type = types.bool;
description = ''
Disable core optimizations, significantly speeds up build time
'';
impl = enable:
if enable then disableOptimization else x: x;
};
failOnAllWarnings = {
type = types.bool;
description = ''
Turn on most of the compiler warnings and fail the build if any of them occur
'';
impl = enable:
if enable then failOnAllWarnings else x: x;
};
triggerRebuild = {
type = types.raw;
description = ''
Add a dummy command to trigger a build despite an equivalent earlier
build that is present in the store or cache.
'';
impl = triggerRebuild;
};
# When none of the above settings is suitable:
custom = {
type = types.functionTo types.package;
description = ''
A custom funtion to apply on the Haskell package.
Use this only if none of the existing settings are suitable.
The function must take three arguments: self, super and the package being
applied to.
Example:
custom = pkg: builtins.trace pkg.version pkg;
'';
impl = f: f;
};
};
}

View File

@ -0,0 +1,81 @@
project@{ name, pkgs, lib, ... }:
let
inherit (lib)
types;
traceSettings = hpkg: x:
let
# Convert the settings config (x) to be stripped of functions, so we can
# convert it to JSON for logging.
xSanitized = lib.filterAttrs (s: v:
!(builtins.isFunction v) && !(s == "impl") && v != null) x;
in
project.config.log.traceDebug "settings.${hpkg} ${builtins.toJSON xSanitized}" x;
in
{
options.settings = lib.mkOption {
type = types.lazyAttrsOf types.deferredModule;
default = { };
apply = settings:
# Polyfill 'packages'; because overlay's defaults setting merge requires it.
let
packagesEmptySettings =
lib.mapAttrs (_: _: {}) project.config.packages;
in packagesEmptySettings // settings;
description = ''
Overrides for packages in `basePackages` and `packages`.
Attr values are submodules that take the following arguments:
- `name`: Package name
- `package`: The reference to the package in `packages` option if it exists, null otherwise.
- `self`/`super`: The 'self' and 'super' (aka. 'final' and 'prev') used in the Haskell overlay.
- `pkgs`: Nixpkgs instance of the module user (import'er).
Default settings are defined in `project.config.defaults.settings` which can be overriden.
'';
};
options.settingsOverlay = lib.mkOption {
type = import ../../../types/haskell-overlay-type.nix { inherit lib; };
description = ''
The Haskell overlay computed from `settings` modules, as well as
`defaults.settings.default` module.
'';
internal = true;
default = self: super:
let
applySettingsFor = name: mod:
let
cfg = (lib.evalModules {
modules = [
# Settings spec
./all.nix
# Default settings
project.config.defaults.settings.default
# User module
mod
];
specialArgs = {
inherit name pkgs self super;
package = project.config.packages.${name} or null;
} // (import ./lib.nix {
inherit lib;
# NOTE: Recursively referring generated config in lib.nix.
config = cfg;
});
}).config;
in
lib.pipe super.${name} (
# TODO: Do we care about the *order* of overrides?
# Might be relevant for the 'custom' option.
lib.concatMap
(impl: impl)
(lib.attrValues (traceSettings name cfg).impl)
);
in
lib.mapAttrs applySettingsFor project.config.settings;
};
}

View File

@ -0,0 +1,45 @@
# Provides the `mkCabalSettingOptions` helper for defining settings.<name>.???.
{ lib, config, ... }:
let
inherit (lib)
mkOption
types;
inherit (types)
functionTo listOf;
mkImplOption = name: f: mkOption {
# [ pkg -> pkg ]
type = listOf (functionTo types.package);
description = ''
Implementation for settings.${name}
'';
default =
let
cfg = config.${name};
in
lib.optional (cfg != null)
(f cfg);
};
mkNullableOption = attrs:
mkOption (attrs // {
type = types.nullOr attrs.type;
default = null;
});
# This creates `options.${name}` and `options.impl.${name}`.
#
# The user sets the former, whereas the latter provides the list of functions
# to apply on the package (as implementation for this setting).
mkCabalSettingOptions = { name, type, description, impl }: {
"${name}" = mkNullableOption {
inherit type description;
};
impl."${name}" = mkImplOption name impl;
};
in
{
inherit mkCabalSettingOptions;
}

View File

@ -15,7 +15,7 @@ in
type = types.attrsOf (types.submoduleWith {
specialArgs = { inherit pkgs self; };
modules = [
./project.nix
./project
];
});
};

View File

@ -1,28 +1,13 @@
{ lib, ... }:
let
log = import ../logging.nix { };
in
# WARNING: While the order is deterministic, it is not
# determined by the user. Thus overlays may be applied in
# an unexpected order.
# We need: https://github.com/NixOS/nixpkgs/issues/215486
lib.types.mkOptionType {
name = "haskellOverlay";
description = "An Haskell overlay function";
# Use this instead of types.functionTo, because Haskell overlay functions cannot
# be merged (whereas functionTo's can).
lib.mkOptionType {
name = "haskell-overlay";
description = ''
A Haskell overlay function taking 'self' and 'super' args.
'';
descriptionClass = "noun";
# NOTE: This check is not exhaustive, as there is no way
# to check that the function takes two arguments, and
# returns an attrset.
check = lib.isFunction;
merge = _loc: defs':
let
defs =
if builtins.length defs' > 1
then log.traceWarning "Multiple haskell overlays are applied in arbitrary order" defs'
else defs';
overlays =
map (x: x.value) defs;
in
lib.composeManyExtensions overlays;
merge = lib.mergeOneOption;
}

View File

@ -0,0 +1,15 @@
{ lib, ... }:
let
isPathUnderNixStore = path: builtins.hasContext (builtins.toString path);
in
lib.mkOptionType {
name = "haskell-source";
description = ''
Path to Haskell package source, or version from Hackage.
'';
descriptionClass = "noun";
check = path:
isPathUnderNixStore path || builtins.isString path;
merge = lib.mergeOneOption;
}

View File

@ -13,7 +13,8 @@ TESTS=(
./test/simple
./test/with-subdir
./test/project-module
./doc
# Disabled due to https://github.com/hercules-ci/flake.parts-website/issues/332
# ./doc
)
for testDir in "${TESTS[@]}"

View File

@ -19,11 +19,19 @@
inputs.haskell-flake.flakeModule
inputs.check-flake.flakeModule
];
flake.haskellFlakeProjectModules.default = { pkgs, ... }: {
overrides = self: super: {
flake.haskellFlakeProjectModules.default = { pkgs, lib, ... }: {
packages = {
# This is purposefully incorrect (pointing to ./.) because we
# expect it to be overriden in perSystem below.
foo = self.callCabal2nix "foo" ./. { };
foo.source = ./.;
};
settings = {
# Test that self and super are passed
foo = { self, super, ... }: {
custom = _: builtins.seq
(lib.assertMsg (lib.hasAttr "ghc" self) "self is bad")
super.foo;
};
};
devShell = {
tools = hp: {
@ -33,15 +41,16 @@
hlsCheck.enable = true;
};
};
perSystem = { self', pkgs, ... }: {
perSystem = { self', pkgs, lib, ... }: {
haskellProjects.default = {
# Multiple modules should be merged correctly.
imports = [ self.haskellFlakeProjectModules.default ];
overrides = self: super: {
# This overrides the overlay above (in `flake.*`), because the
# module system merges them in such order. cf. the WARNING in option
# docs.
foo = self.callCabal2nix "foo" (inputs.haskell-multi-nix + /foo) { };
# Debug logging should work.
debug = true;
packages = {
# Because the module being imported above also defines a root for
# the 'foo' package, we must override it here using `lib.mkForce`.
foo.source = lib.mkForce (inputs.haskell-multi-nix + /foo);
};
devShell = {
tools = hp: {

View File

@ -1,9 +1,18 @@
source ../common.sh
set -euxo pipefail
rm -f result result-bin
# First, build the flake
logHeader "Testing nix build"
nix build ${OVERRIDE_ALL}
# Test defaults.settings module behaviour, viz: separateBinOutput
test -f result-bin/bin/haskell-flake-test || {
echo "ERROR: separateBinOutput (from defaults.settings) not in effect"
exit 1
}
# Run the devshell test script in a nix develop shell.
logHeader "Testing nix devshell"
nix develop ${OVERRIDE_ALL} -c ./test-in-devshell.sh
@ -13,5 +22,6 @@ nix run ${OVERRIDE_ALL} .#app1
# Test non-devshell features:
# Checks
logHeader "Testing nix flake checks"
nix --option sandbox false \
build ${OVERRIDE_ALL} -L .#check
nix --option sandbox false build \
--no-link --print-out-paths \
${OVERRIDE_ALL} -L .#check