mirror of
https://github.com/srid/haskell-flake.git
synced 2024-09-11 05:55:25 +03:00
Expose cabal executables as flake apps (#137)
Also, Add a corresponding `outputs.apps` option, while the `outputs.localPackages` option is renamed to `outputs.packages` (it now contains package metadata, including packages and its executables).
This commit is contained in:
parent
fe3d657ffc
commit
4e8e79b9b4
@ -5,6 +5,7 @@
|
|||||||
- #134: Add `autoWire` option to control generation of flake outputs
|
- #134: Add `autoWire` option to control generation of flake outputs
|
||||||
- #138: Add `checks` to `outputs` submodule
|
- #138: Add `checks` to `outputs` submodule
|
||||||
- #143: Changed `autoWire` to be an enum type, for granular controlling of which outputs to autowire.
|
- #143: Changed `autoWire` to be an enum type, for granular controlling of which outputs to autowire.
|
||||||
|
- #137: Expose cabal executables as flake apps. Add a corresponding `outputs.apps` option, while the `outputs.localPackages` option is renamed to `outputs.packages` (it now contains package metadata, including packages and its executables).
|
||||||
|
|
||||||
## 0.2.0 (Mar 13, 2023)
|
## 0.2.0 (Mar 13, 2023)
|
||||||
|
|
||||||
|
@ -35,10 +35,11 @@ When nixifying a Haskell project without flake-parts (thus without haskell-flake
|
|||||||
|
|
||||||
In addition, compared to using plain nixpkgs, haskell-flake supports:
|
In addition, compared to using plain nixpkgs, haskell-flake supports:
|
||||||
|
|
||||||
- Auto-detection of local packages based on `cabal.project` file (via [find-haskell-paths](https://github.com/srid/haskell-flake/tree/master/nix/find-haskell-paths))
|
- Auto-detection of local packages based on `cabal.project` file (via [haskell-parsers](https://github.com/srid/haskell-flake/tree/master/nix/haskell-parsers))
|
||||||
- Support for hpack's `package.yaml`
|
- Support for hpack's `package.yaml`
|
||||||
|
- Parse executables from `.cabal` file
|
||||||
- Composition of dependency overrides, and other project settings, via [[modules]]
|
- Composition of dependency overrides, and other project settings, via [[modules]]
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
Visit [[guide]] for more details, and [[ref]] for module options. If you are new to Nix, see [[basics]]. See [[howto]] for tangential topics.
|
Visit [[guide]] for more details, and [[ref]] for module options. If you are new to Nix, see [[basics]]. See [[howto]] for tangential topics.
|
||||||
|
0
doc/test.sh
Normal file → Executable file
0
doc/test.sh
Normal file → Executable file
30
nix/app-type.nix
Normal file
30
nix/app-type.nix
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Taken from https://github.com/hercules-ci/flake-parts/blob/dcc36e45d054d7bb554c9cdab69093debd91a0b5/modules/apps.nix#L11-L41
|
||||||
|
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
derivationType = lib.types.package // {
|
||||||
|
check = lib.isDerivation;
|
||||||
|
};
|
||||||
|
|
||||||
|
getExe = x:
|
||||||
|
"${lib.getBin x}/bin/${x.meta.mainProgram or (throw ''Package ${x.name or ""} does not have meta.mainProgram set, so I don't know how to find the main executable. You can set meta.mainProgram, or pass the full path to executable, e.g. program = "''${pkg}/bin/foo"'')}";
|
||||||
|
programType = lib.types.coercedTo derivationType getExe lib.types.str;
|
||||||
|
appType = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
type = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "app" ];
|
||||||
|
default = "app";
|
||||||
|
description = ''
|
||||||
|
A type tag for `apps` consumers.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
program = lib.mkOption {
|
||||||
|
type = programType;
|
||||||
|
description = ''
|
||||||
|
A path to an executable or a derivation with `meta.mainProgram`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
appType
|
@ -1,11 +0,0 @@
|
|||||||
# `find-haskell-paths`
|
|
||||||
|
|
||||||
`find-haskell-paths` is a superior alternative to nixpkgs' [`haskellPathsInDir`](https://github.com/NixOS/nixpkgs/blob/f991762ea1345d850c06cd9947700f3b08a12616/lib/filesystem.nix#L18).
|
|
||||||
|
|
||||||
- It locates packages based on the "packages" field of `cabal.project` file if it exists (otherwise it returns the top-level package).
|
|
||||||
- It supports `hpack`, thus works with `package.yaml` even if no `.cabal` file exists.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Glob patterns in `cabal.project` are not supported yet. Feel free to open a PR improving the parser to support them.
|
|
||||||
- https://github.com/srid/haskell-flake/issues/113
|
|
@ -1,79 +0,0 @@
|
|||||||
{ pkgs
|
|
||||||
, lib
|
|
||||||
, throwError ? msg: builtins.throw msg
|
|
||||||
, ...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
parser = import ./parser.nix { inherit pkgs lib; };
|
|
||||||
traversal = rec {
|
|
||||||
findSingleCabalFile = path:
|
|
||||||
let
|
|
||||||
cabalFiles = lib.filter (lib.strings.hasSuffix ".cabal") (builtins.attrNames (builtins.readDir path));
|
|
||||||
num = builtins.length cabalFiles;
|
|
||||||
in
|
|
||||||
if num == 0
|
|
||||||
then null
|
|
||||||
else if num == 1
|
|
||||||
then builtins.head cabalFiles
|
|
||||||
else throwError "Expected a single .cabal file, but found multiple: ${builtins.toJSON cabalFiles}";
|
|
||||||
findSinglePackageYamlFile = path:
|
|
||||||
let f = path + "/package.yaml";
|
|
||||||
in if builtins.pathExists f then f else null;
|
|
||||||
getCabalName = cabalFile:
|
|
||||||
lib.strings.removeSuffix ".cabal" cabalFile;
|
|
||||||
getPackageYamlName = fp:
|
|
||||||
let
|
|
||||||
name = parser.parsePackageYamlName (builtins.readFile fp);
|
|
||||||
in
|
|
||||||
if name.type == "success"
|
|
||||||
then name.value
|
|
||||||
else throwError ("Failed to parse ${fp}: ${builtins.toJSON name}");
|
|
||||||
findHaskellPackageNameOfDirectory = path:
|
|
||||||
let
|
|
||||||
cabalFile = findSingleCabalFile path;
|
|
||||||
packageYamlFile = findSinglePackageYamlFile path;
|
|
||||||
in
|
|
||||||
if cabalFile != null
|
|
||||||
then
|
|
||||||
getCabalName cabalFile
|
|
||||||
else if packageYamlFile != null
|
|
||||||
then
|
|
||||||
getPackageYamlName packageYamlFile
|
|
||||||
else
|
|
||||||
throwError "Neither a .cabal file nor a package.yaml found under ${path}";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
projectRoot:
|
|
||||||
let
|
|
||||||
cabalProjectFile = projectRoot + "/cabal.project";
|
|
||||||
packageDirs =
|
|
||||||
if builtins.pathExists cabalProjectFile
|
|
||||||
then
|
|
||||||
let
|
|
||||||
res = parser.parseCabalProjectPackages (builtins.readFile cabalProjectFile);
|
|
||||||
isSelfPath = path:
|
|
||||||
path == "." || path == "./" || path == "./.";
|
|
||||||
in
|
|
||||||
if res.type == "success"
|
|
||||||
then
|
|
||||||
map
|
|
||||||
(path:
|
|
||||||
if isSelfPath path
|
|
||||||
then projectRoot
|
|
||||||
else if lib.strings.hasInfix "*" path
|
|
||||||
then throwError "Found a path with glob (${path}) in ${cabalProjectFile}, which is not supported"
|
|
||||||
else if lib.strings.hasSuffix ".cabal" path
|
|
||||||
then throwError "Expected a directory but ${path} (in ${cabalProjectFile}) is a .cabal filepath"
|
|
||||||
else "${projectRoot}/${path}"
|
|
||||||
)
|
|
||||||
res.value
|
|
||||||
else throwError ("Failed to parse ${cabalProjectFile}: ${builtins.toJSON res}")
|
|
||||||
else
|
|
||||||
[ projectRoot ];
|
|
||||||
in
|
|
||||||
lib.listToAttrs
|
|
||||||
(map
|
|
||||||
(path:
|
|
||||||
lib.nameValuePair (traversal.findHaskellPackageNameOfDirectory path) path)
|
|
||||||
packageDirs)
|
|
@ -1,5 +1,5 @@
|
|||||||
# A flake-parts module for Haskell cabal projects.
|
# A flake-parts module for Haskell cabal projects.
|
||||||
{ self, config, lib, flake-parts-lib, withSystem, ... }:
|
{ self, lib, flake-parts-lib, withSystem, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (flake-parts-lib)
|
inherit (flake-parts-lib)
|
||||||
@ -16,6 +16,7 @@ in
|
|||||||
perSystem = mkPerSystemOption
|
perSystem = mkPerSystemOption
|
||||||
({ config, self', inputs', pkgs, system, ... }:
|
({ config, self', inputs', pkgs, system, ... }:
|
||||||
let
|
let
|
||||||
|
appType = import ./app-type.nix { inherit pkgs lib; };
|
||||||
hlsCheckSubmodule = types.submodule {
|
hlsCheckSubmodule = types.submodule {
|
||||||
options = {
|
options = {
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
@ -125,14 +126,21 @@ in
|
|||||||
overrides, on top of `basePackages`.
|
overrides, on top of `basePackages`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
localPackages = mkOption {
|
packages = mkOption {
|
||||||
type = types.attrsOf types.package;
|
type = types.attrsOf packageInfoSubmodule;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
description = ''
|
description = ''
|
||||||
The local Haskell packages in the project.
|
Package information for all local packages. Contains the following keys:
|
||||||
|
|
||||||
This is a subset of `finalPackages` containing only local
|
- `package`: The Haskell package derivation
|
||||||
packages excluding everything else.
|
- `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.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
devShell = mkOption {
|
devShell = mkOption {
|
||||||
@ -152,6 +160,28 @@ in
|
|||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The executables are accessed without any reference to the
|
||||||
|
Haskell library, using `justStaticExecutables`.
|
||||||
|
|
||||||
|
NOTE: Evaluating up to this option will involve IFD.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
projectSubmodule = types.submoduleWith {
|
projectSubmodule = types.submoduleWith {
|
||||||
specialArgs = { inherit pkgs self; };
|
specialArgs = { inherit pkgs self; };
|
||||||
modules = [
|
modules = [
|
||||||
@ -232,7 +262,7 @@ in
|
|||||||
'';
|
'';
|
||||||
default =
|
default =
|
||||||
let
|
let
|
||||||
find-haskell-paths = import ./find-haskell-paths {
|
haskell-parsers = import ./haskell-parsers {
|
||||||
inherit pkgs lib;
|
inherit pkgs lib;
|
||||||
throwError = msg: builtins.throw ''
|
throwError = msg: builtins.throw ''
|
||||||
haskell-flake: A default value for `packages` cannot be auto-determined:
|
haskell-flake: A default value for `packages` cannot be auto-determined:
|
||||||
@ -244,8 +274,8 @@ in
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib.mapAttrs
|
lib.mapAttrs
|
||||||
(_: value: { root = value; })
|
(_: path: { root = path; })
|
||||||
(find-haskell-paths config.projectRoot);
|
(haskell-parsers.findPackagesInCabalProject config.projectRoot);
|
||||||
defaultText = lib.literalMD "autodiscovered by reading `self` files.";
|
defaultText = lib.literalMD "autodiscovered by reading `self` files.";
|
||||||
};
|
};
|
||||||
devShell = mkOption {
|
devShell = mkOption {
|
||||||
@ -265,7 +295,7 @@ in
|
|||||||
};
|
};
|
||||||
autoWire =
|
autoWire =
|
||||||
let
|
let
|
||||||
outputTypes = [ "packages" "checks" "devShells" ];
|
outputTypes = [ "packages" "checks" "apps" "devShells" ];
|
||||||
in
|
in
|
||||||
mkOption {
|
mkOption {
|
||||||
type = types.listOf (types.enum outputTypes);
|
type = types.listOf (types.enum outputTypes);
|
||||||
@ -295,22 +325,26 @@ in
|
|||||||
let
|
let
|
||||||
# Like mapAttrs, but merges the values (also attrsets) of the resulting attrset.
|
# Like mapAttrs, but merges the values (also attrsets) of the resulting attrset.
|
||||||
mergeMapAttrs = f: attrs: lib.mkMerge (lib.mapAttrsToList f attrs);
|
mergeMapAttrs = f: attrs: lib.mkMerge (lib.mapAttrsToList f attrs);
|
||||||
|
mapKeys = f: attrs: lib.mapAttrs' (n: v: { name = f n; value = v; }) attrs;
|
||||||
|
|
||||||
contains = k: vs: lib.any (x: x == k) vs;
|
contains = k: vs: lib.any (x: x == k) vs;
|
||||||
|
|
||||||
|
# Prefix value with the project name (unless
|
||||||
|
# project is named `default`)
|
||||||
|
prefixUnlessDefault = projectName: value:
|
||||||
|
if projectName == "default"
|
||||||
|
then value
|
||||||
|
else "${projectName}-${value}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages =
|
packages =
|
||||||
mergeMapAttrs
|
mergeMapAttrs
|
||||||
(name: project:
|
(name: project:
|
||||||
let
|
let
|
||||||
mapKeys = f: attrs: lib.mapAttrs' (n: v: { name = f n; value = v; }) attrs;
|
packages = lib.mapAttrs (_: info: info.package) project.outputs.packages;
|
||||||
# Prefix package names with the project name (unless
|
|
||||||
# project is named `default`)
|
|
||||||
dropDefaultPrefix = packageName:
|
|
||||||
if name == "default"
|
|
||||||
then packageName
|
|
||||||
else "${name}-${packageName}";
|
|
||||||
in
|
in
|
||||||
lib.optionalAttrs (contains "packages" project.autoWire) (mapKeys dropDefaultPrefix project.outputs.localPackages))
|
lib.optionalAttrs (contains "packages" project.autoWire)
|
||||||
|
(mapKeys (prefixUnlessDefault name) packages))
|
||||||
config.haskellProjects;
|
config.haskellProjects;
|
||||||
devShells =
|
devShells =
|
||||||
mergeMapAttrs
|
mergeMapAttrs
|
||||||
@ -322,7 +356,15 @@ in
|
|||||||
checks =
|
checks =
|
||||||
mergeMapAttrs
|
mergeMapAttrs
|
||||||
(name: project:
|
(name: project:
|
||||||
lib.optionalAttrs (contains "checks" project.autoWire) project.outputs.checks
|
lib.optionalAttrs (contains "checks" project.autoWire)
|
||||||
|
project.outputs.checks
|
||||||
|
)
|
||||||
|
config.haskellProjects;
|
||||||
|
apps =
|
||||||
|
mergeMapAttrs
|
||||||
|
(name: project:
|
||||||
|
lib.optionalAttrs (contains "apps" project.autoWire)
|
||||||
|
(mapKeys (prefixUnlessDefault name) project.outputs.apps)
|
||||||
)
|
)
|
||||||
config.haskellProjects;
|
config.haskellProjects;
|
||||||
};
|
};
|
||||||
|
13
nix/haskell-parsers/README.md
Normal file
13
nix/haskell-parsers/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# `haskell-parsers`
|
||||||
|
|
||||||
|
`haskell-parsers` provides parsers for Haskell associated files: hpack, cabal and cabal.project. It provides:
|
||||||
|
|
||||||
|
- **`findPackagesInCabalProject`**: a superior alternative to nixpkgs' [`haskellPathsInDir`](https://github.com/NixOS/nixpkgs/blob/f991762ea1345d850c06cd9947700f3b08a12616/lib/filesystem.nix#L18).
|
||||||
|
- It locates packages based on the "packages" field of `cabal.project` file if it exists (otherwise it returns the top-level package).
|
||||||
|
- It supports `hpack`, thus works with `package.yaml` even if no `.cabal` file exists.
|
||||||
|
- **`getCabalExecutables`**: a function to extract executables from a `.cabal` file.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Glob patterns in `cabal.project` are not supported yet. Feel free to open a PR improving the parser to support them.
|
||||||
|
- https://github.com/srid/haskell-flake/issues/113
|
90
nix/haskell-parsers/default.nix
Normal file
90
nix/haskell-parsers/default.nix
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
{ pkgs
|
||||||
|
, lib
|
||||||
|
, throwError ? msg: builtins.throw msg
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
parser = import ./parser.nix { inherit pkgs lib; };
|
||||||
|
traversal = rec {
|
||||||
|
findSingleCabalFile = path:
|
||||||
|
let
|
||||||
|
cabalFiles = lib.filter (lib.strings.hasSuffix ".cabal") (builtins.attrNames (builtins.readDir path));
|
||||||
|
num = builtins.length cabalFiles;
|
||||||
|
in
|
||||||
|
if num == 0
|
||||||
|
then null
|
||||||
|
else if num == 1
|
||||||
|
then builtins.head cabalFiles
|
||||||
|
else throwError "Expected a single .cabal file, but found multiple: ${builtins.toJSON cabalFiles}";
|
||||||
|
findSinglePackageYamlFile = path:
|
||||||
|
let f = path + "/package.yaml";
|
||||||
|
in if builtins.pathExists f then f else null;
|
||||||
|
getCabalName = cabalFile:
|
||||||
|
lib.strings.removeSuffix ".cabal" cabalFile;
|
||||||
|
getPackageYamlName = fp:
|
||||||
|
let
|
||||||
|
name = parser.parsePackageYamlName (builtins.readFile fp);
|
||||||
|
in
|
||||||
|
if name.type == "success"
|
||||||
|
then name.value
|
||||||
|
else throwError ("Failed to parse ${fp}: ${builtins.toJSON name}");
|
||||||
|
findHaskellPackageNameOfDirectory = path:
|
||||||
|
let
|
||||||
|
cabalFile = findSingleCabalFile path;
|
||||||
|
packageYamlFile = findSinglePackageYamlFile path;
|
||||||
|
in
|
||||||
|
if cabalFile != null
|
||||||
|
then
|
||||||
|
getCabalName cabalFile
|
||||||
|
else if packageYamlFile != null
|
||||||
|
then
|
||||||
|
getPackageYamlName packageYamlFile
|
||||||
|
else
|
||||||
|
throwError "Neither a .cabal file nor a package.yaml found under ${path}";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
findPackagesInCabalProject = projectRoot:
|
||||||
|
let
|
||||||
|
cabalProjectFile = projectRoot + "/cabal.project";
|
||||||
|
packageDirs =
|
||||||
|
if builtins.pathExists cabalProjectFile
|
||||||
|
then
|
||||||
|
let
|
||||||
|
res = parser.parseCabalProjectPackages (builtins.readFile cabalProjectFile);
|
||||||
|
isSelfPath = path:
|
||||||
|
path == "." || path == "./" || path == "./.";
|
||||||
|
in
|
||||||
|
if res.type == "success"
|
||||||
|
then
|
||||||
|
map
|
||||||
|
(path:
|
||||||
|
if isSelfPath path
|
||||||
|
then projectRoot
|
||||||
|
else if lib.strings.hasInfix "*" path
|
||||||
|
then throwError "Found a path with glob (${path}) in ${cabalProjectFile}, which is not supported"
|
||||||
|
else if lib.strings.hasSuffix ".cabal" path
|
||||||
|
then throwError "Expected a directory but ${path} (in ${cabalProjectFile}) is a .cabal filepath"
|
||||||
|
else "${projectRoot}/${path}"
|
||||||
|
)
|
||||||
|
res.value
|
||||||
|
else throwError ("Failed to parse ${cabalProjectFile}: ${builtins.toJSON res}")
|
||||||
|
else
|
||||||
|
[ projectRoot ];
|
||||||
|
in
|
||||||
|
lib.listToAttrs
|
||||||
|
(map
|
||||||
|
(path:
|
||||||
|
lib.nameValuePair (traversal.findHaskellPackageNameOfDirectory path) path)
|
||||||
|
packageDirs);
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
@ -44,4 +44,20 @@ in
|
|||||||
val;
|
val;
|
||||||
in
|
in
|
||||||
parsec.runParser parser packageYamlFile;
|
parsec.runParser parser packageYamlFile;
|
||||||
|
|
||||||
|
# Extract all the executables from a .cabal file
|
||||||
|
parseCabalExecutableNames = cabalFile:
|
||||||
|
with parsec;
|
||||||
|
let
|
||||||
|
# Skip empty lines and lines that don't start with 'executable'
|
||||||
|
skipLines =
|
||||||
|
skipTill
|
||||||
|
(sequence [ (skipWhile (x: x != "\n")) anyChar ])
|
||||||
|
(parsec.string "executable ");
|
||||||
|
val = (parsec.fmap lib.concatStrings (parsec.many1 (parsec.anyCharBut "\n")));
|
||||||
|
parser = parsec.many (parsec.skipThen
|
||||||
|
skipLines
|
||||||
|
val);
|
||||||
|
in
|
||||||
|
parsec.runParser parser cabalFile;
|
||||||
}
|
}
|
@ -32,6 +32,31 @@ let
|
|||||||
expected = "foo";
|
expected = "foo";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
cabalExecutableTests =
|
||||||
|
let
|
||||||
|
eval = s:
|
||||||
|
let res = parser.parseCabalExecutableNames s; in
|
||||||
|
if res.type == "success" then res.value else res;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
testSimple = {
|
||||||
|
expr = eval ''
|
||||||
|
cabal-version: 3.0
|
||||||
|
name: test-package
|
||||||
|
version: 0.1
|
||||||
|
|
||||||
|
executable foo-exec
|
||||||
|
main-is: foo.hs
|
||||||
|
|
||||||
|
library test
|
||||||
|
exposed-modules: Test.Types
|
||||||
|
|
||||||
|
executable bar-exec
|
||||||
|
main-is: bar.hs
|
||||||
|
'';
|
||||||
|
expected = [ "foo-exec" "bar-exec" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
# Like lib.runTests, but actually fails if any test fails.
|
# Like lib.runTests, but actually fails if any test fails.
|
||||||
runTestsFailing = tests:
|
runTestsFailing = tests:
|
||||||
let
|
let
|
||||||
@ -44,4 +69,5 @@ in
|
|||||||
{
|
{
|
||||||
"cabal.project" = runTestsFailing cabalProjectTests;
|
"cabal.project" = runTestsFailing cabalProjectTests;
|
||||||
"package.yaml" = runTestsFailing packageYamlTests;
|
"package.yaml" = runTestsFailing packageYamlTests;
|
||||||
|
"foo-bar.cabal" = runTestsFailing cabalExecutableTests;
|
||||||
}
|
}
|
@ -23,12 +23,13 @@ let
|
|||||||
${command}
|
${command}
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
haskell-parsers = pkgs.callPackage ./haskell-parsers { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
||||||
config =
|
config =
|
||||||
let
|
let
|
||||||
inherit (config.outputs) finalPackages finalOverlay;
|
inherit (config.outputs) finalPackages finalOverlay packages;
|
||||||
|
|
||||||
projectKey = name;
|
projectKey = name;
|
||||||
|
|
||||||
@ -62,6 +63,25 @@ in
|
|||||||
++ builtins.attrValues (config.devShell.extraLibraries p);
|
++ builtins.attrValues (config.devShell.extraLibraries p);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
buildPackageInfo = name: value: {
|
||||||
|
package = finalPackages.${name};
|
||||||
|
exes =
|
||||||
|
let
|
||||||
|
exeNames = haskell-parsers.getCabalExecutables value.root;
|
||||||
|
staticPackage = pkgs.haskell.lib.justStaticExecutables finalPackages.${name};
|
||||||
|
in
|
||||||
|
lib.listToAttrs
|
||||||
|
(map
|
||||||
|
(exe:
|
||||||
|
lib.nameValuePair exe ({
|
||||||
|
program = "${staticPackage}/bin/${exe}";
|
||||||
|
})
|
||||||
|
)
|
||||||
|
exeNames
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
hlsCheck =
|
hlsCheck =
|
||||||
runCommandInSimulatedShell
|
runCommandInSimulatedShell
|
||||||
devShell
|
devShell
|
||||||
@ -85,9 +105,11 @@ in
|
|||||||
|
|
||||||
finalPackages = config.basePackages.extend finalOverlay;
|
finalPackages = config.basePackages.extend finalOverlay;
|
||||||
|
|
||||||
localPackages = lib.mapAttrs
|
packages = lib.mapAttrs buildPackageInfo config.packages;
|
||||||
(name: _: finalPackages."${name}")
|
|
||||||
config.packages;
|
apps =
|
||||||
|
lib.mkMerge
|
||||||
|
(lib.mapAttrsToList (_: packageInfo: packageInfo.exes) packages);
|
||||||
|
|
||||||
checks = lib.filterAttrs (_: v: v != null) {
|
checks = lib.filterAttrs (_: v: v != null) {
|
||||||
"${name}-hls" = if (config.devShell.enable && config.devShell.hlsCheck.enable) then hlsCheck else null;
|
"${name}-hls" = if (config.devShell.enable && config.devShell.hlsCheck.enable) then hlsCheck else null;
|
||||||
|
@ -8,7 +8,7 @@ nix --version
|
|||||||
# The test directory must contain a 'test.sh' file that will be run in that
|
# The test directory must contain a 'test.sh' file that will be run in that
|
||||||
# directory.
|
# directory.
|
||||||
TESTS=(
|
TESTS=(
|
||||||
./nix/find-haskell-paths
|
./nix/haskell-parsers
|
||||||
./example
|
./example
|
||||||
./test/simple
|
./test/simple
|
||||||
./test/hpack
|
./test/hpack
|
||||||
@ -25,4 +25,4 @@ do
|
|||||||
popd
|
popd
|
||||||
done
|
done
|
||||||
|
|
||||||
logHeader "All tests passed!"
|
logHeader "All tests passed!"
|
||||||
|
@ -57,8 +57,11 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# haskell-flake doesn't set the default package, but you can do it here.
|
|
||||||
packages.default = self'.packages.haskell-flake-test;
|
packages.default = self'.packages.haskell-flake-test;
|
||||||
|
|
||||||
|
# An explicit app to test `nix run .#test` (*without* falling back to
|
||||||
|
# using self.packages.test)
|
||||||
|
apps.app1 = self'.apps.haskell-flake-test;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ nix build ${OVERRIDE_ALL}
|
|||||||
# Run the devshell test script in a nix develop shell.
|
# Run the devshell test script in a nix develop shell.
|
||||||
logHeader "Testing nix devshell"
|
logHeader "Testing nix devshell"
|
||||||
nix develop ${OVERRIDE_ALL} -c ./test-in-devshell.sh
|
nix develop ${OVERRIDE_ALL} -c ./test-in-devshell.sh
|
||||||
|
# Run the cabal executable as flake app
|
||||||
|
logHeader "Testing nix flake app (cabal exe)"
|
||||||
|
nix run ${OVERRIDE_ALL} .#app1
|
||||||
# Test non-devshell features:
|
# Test non-devshell features:
|
||||||
# Checks
|
# Checks
|
||||||
logHeader "Testing nix flake checks"
|
logHeader "Testing nix flake checks"
|
||||||
|
Loading…
Reference in New Issue
Block a user