Every project depends on existing software that is beyond your control. Nix DSL enables you to declaratively specify your projects dependencies, a repo or a tar-ball down to the file digest of its content, which is what gives nix superpowers of being a deterministic and reproducible package manager. This means that if your inputs stays the same, nix guarantees that it produces the exact same output regardless of when and where. Below is a flake that pulls in latest version of Clojure into your project.
{
# optional attribute
description = "My awesome Clojure/ClojureScript project";
# required attribute
inputs = {
# nix dsl fns useful for writing flakes
flake-utils.url = "github:numtide/flake-utils/v1.0.0";
# Pins state of the packages to a specific commit sha
pinnedPkgs.url = "github:NixOS/nixpkgs/c46290747b2aaf090f48a478270feb858837bf11";
};
# required attribute
outputs = { self, flake-utils, pinnedPkgs }@inputs :
flake-utils.lib.eachDefaultSystem (system:
let pinnedSysPkgs = inputs.pinnedPkgs.legacyPackages.${system};
in
{
devShells.default = pinnedSysPkgs.mkShell {
packages = [
pinnedSysPkgs.clojure
];
# commands to run in the development interactive shell
shellHook = ''
echo To get Clojure REPL, Run:
echo clojure
echo To get ClojureScript REPL, Run:
echo clj -Sdeps \'{:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}}\' -M -m cljs.main --repl
'';
};
packages = {
docker = pinnedSysPkgs.dockerTools.buildLayeredImage {
name = "My awesome Clj docker image built by nix";
tag = "latest";
contents = [pinnedSysPkgs.clojure];
};
};
});
}
Do not worry too much about not understanding above nix dsl code. The most important thing to know is that it is nix dsl referred to as a flake that specifies its inputs and outputs declaratively. Save above code as `flake.nix`, which is a convention, then run `nix develop` to get an interactive shell with Clojure in your path. Nix can do way more than this. However, I recommend you just start with solving project dependencies problem. Above flake gives you following benefits:
- Ability to pin the exact versions of your project dependencies.
- Cross platform development environment that works both in MacOS and various flavors of Linux.
- Determinate and reproducible development environment that eliminates "it works on my machine" tooling issues.
One important thing to notice here is the way I chose to reference the url inputs of the flake. I deliberately used tags or commit sha to prevent the state of the urls (thus the state of the nix DSL) change under me, which defeats the purpose of having a determinate and reproducible way to get a development environment. I have following bash script that prints available tags and corresponding commit hash:
git_tag_sha () {
repo="$1"
echo "********************************************************"
echo "Available release and commit sha for pinning are:"
echo "********************************************************"
printf "\033[1m%-12s %s\033[0m\n" "release" "commit sha"
curl -s https://github.com/$repo/tags | grep -oP 'href="\K[^"]*(releases/tag|nixpkgs/commit)[^"]*' | awk -F '/' 'NR%2{tag=$NF; next} {printf "%-12s %s\n", tag, $NF}'
echo
echo "****************************************************************************"
echo "Please replace the commit sha of following line to pin pkgs to a commit sha: "
echo "pinnedPkgs.url = github:$repo/<commit>"
echo "****************************************************************************"
echo
}
# You can run it like this:
git_tag_sha "NixOS/nixpkgs"