Haskell application and library templates for Buildkite, Gitlab or GitHub CI using haskell.nix.
-
The recommended way to use these templates (if you have nix flakes enabled):
nix flake init -t github:serokell/templates#haskell-applicationfor application ornix flake init -t github:serokell/templates#haskell-libraryfor library. -
Otherwise:
-
Copy
flake.nixfile to your repository, it contains all nix dependencies and definitions. -
Copy
default.nixandshell.nixfiles which provide compatibility with non-flake nix interface. -
Get nix with
nix-command flakesexperimental features enabled, and runnix flake updateto generateflake.lockfile. This file pins the latest versions of the nix dependencies. -
Copy pipeline configuration:
.gitlab-ci.ymlfor Gitlab,.buildkite/pipeline.ymlfor Buildkite or.github/workflows/check.ymlfor GitHub.
-
-
Afterwards, adjust the template for your project:
-
If the root of your haskell project (directory containing
stack.yamlorcabal.project) is not the same as the directory whereflake.nixis located, you need to setsrc = ./.;fromflake.nixto the root of your haskell project.- FOR LIBRARY: List the GHC versions that will be used to build and test your library in the
tested-withstanza of the.cabalfiles, alternatively you can set them with theghcVersionsargument. If your project contains multiplestack.yamlfiles, or if you want to test your library with different stack resolvers, you can set them instackFilesandresolversrespectively. If you are using GitHub actions, uncommentinherit (ci) build-matrixinflake.nix, otherwise changematrixin the CI pipeline to the list of GHC versions,stack.yamlfiles, and resolvers.
- FOR LIBRARY: List the GHC versions that will be used to build and test your library in the
-
If your project contains bash scripts, uncomment related lines in
flake.nixand in the pipeline configuration. If you don't needshellcheck, remove those lines. -
If you're using
hpackorstack2cabalin your project, make sure to uncomment the related lines in bothflake.nixand pipeline configuration files. To avoid version mismatches, usenix develop .#ci -c hpackornix develop .#ci -c stack2cabal. -
You may want to add scheduled
cabal outdatedorstack buildagainst latest nigthly checks to be notified of new dependency versions. You need to provide aSLACK_TOKENto your CI environment to report failures in the slack channel, please request one from the SRE team.- Copy
./scripts/slack-send-message.shto your repository. - For GitHub, copy
.github/workflows/check-outdated.ymlor.github/workflows/check-nightly.ymlto your library repository. - For Gitlab, configure the schedule in the repository settings and uncomment
check-outdatedandreport-outdatedorcheck-nightlyandreport-nightly-failurein.gitlab-ci.yml. For nightly checks, you will also need to enable gitlab hosted runners in the repository settings.
- Copy
-
You might want to add a
weedercheck to check for dead code in your application. To do this uncomment the related lines in bothflake.nix(do not forget to uncommentwriteHieFiles = true;) and pipeline configuration files, then copyweeder.toml(for more advanced setup, please refer to the corresponding section of theweederREADME) to your repository. For a library, code is usually considered dead unless it is exported; such cases are already detected by GHC warnings, so noweederis required. -
Make sure to clean up your
flake.nixand pipeline configuration files by removing any optional code that is left commented out.
-
-
Enjoy working CI!
By default, these templates run Haskell tests in a pure nix builds, so it is not possible to access external resources such as a database or docker socket.
To get around this, you first need to provide a nix shell with the test executable along with other packages needed for the test environment.
Here, as an example, we provide a nix shell with the pataq-package-test executable, as well as two additional packages: ephemeralpg, used to create the ephemeral database, and postgresql, used to create the database schema:
devShells = {
impure-tests = pkgs.mkShell {
buildInputs = [
hs-pkg.components.tests.pataq-package-test
pkgs.ephemeralpg # we need pg_tmp to create an ephemeral db
pkgs.postgresql # we need psql to create the db schema
];
};
};We then create a bash script that sets up the required environment and runs the pataq-package-test:
#!/usr/bin/env bash
# Set up the necessary environment
set -euo pipefail
export LANG="C.UTF-8"
export LC_ALL="C.UTF-8"
PSQL_CONNECTION_STRING=$(pg_tmp -t)
echo "Ephemeral PostgreSQL connection string: $PSQL_CONNECTION_STRING"
psql "$PSQL_CONNECTION_STRING" -f ./pataq-schema.sql
# some explanation for the env var magic below https://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash
portAndDb=${PSQL_CONNECTION_STRING##*:}
# Environment variables used by the test suite
export POSTGRES_PORT_TEST=${portAndDb%%/*}
export POSTGRES_USER_TEST=$(whoami)
export POSTGRES_DATABASE_TEST=test
# Run tests
pataq-package-testAnd finally, we can run our impure tests from CI:
nix develop .#impure-tests -c ./impure-tests.sh
As an alternative solution, you can also use the make-test-python module, which uses a nixos virtual machine:
impure-tests = import "${nixpkgs}/nixos/tests/make-test-python.nix" ({ ... }:
{
name = "${hs-package-name}-tests";
nodes = {
# Define nixos VM
testvm = { ... }: let
dbUser = "postgres";
dbName = "postgres";
dbPort = 5342;
in {
virtualisation.memorySize = 1024;
virtualisation.diskSize = 1024;
# Set up the necessary environment
services.postgresql = {
enable = true;
port = dbPort;
initialScript = ./pataq-schema.sql;
ensureDatabases = [ dbName ];
ensureUsers = [
{ name = dbUser;
ensurePermissions = {
"DATABASE \"${dbName}\"" = "ALL PRIVILEGES";
};
}
];
authentication = ''
host all ${dbUser} localhost trust
'';
};
# Environment variables used by the test suite
environment.sessionVariables = {
POSTGRES_PORT_TEST = builtins.toString dbPort;
POSTGRES_USER_TEST = dbUser;
POSTGRES_DATABASE_TEST = dbName;
};
};
};
testScript = ''
#Strat VM
start_all()
# Wait until the postgres service is up and the port is open
testvm.wait_for_unit("postgresql.service")
testvm.wait_for_open_port(5432)
# Run tests
testvm.succeed("${hs-pkg.components.tests.pataq-package-test}/bin/pataq-package-test")
# Shutdown VM
testvm.shutdown()
'';
}) { inherit pkgs system; };