Using Nix Flakes with Bazel
Nix Flakes are a new feature of Nix 2.4 (still prerelease as of this writing) to make Nix derivations composable and can be used to distribute derivations to other people.
This is not a post about what Nix Flakes are though and I recommend reading the series from Tweag (first here) to get up to speed if you haven’t already.
Support for flakes is of course not very widespread. This is a very short explanation of how I bridged the gap between Nix Flakes and Bazel on a project of mine. It’s not complicated but not exactly obvious at first glance.
A simple development environment
First, we create a simple flake.nix
at the root of the project. It uses
flake-utils
to easily create environments for various systems, e.g.
x86_64-linux
. We take Bazel from a different version of Nixpkgs since it was
broken on nixpkgs-unstable
last time I tried.
flake.nix
{
description = "Development environment";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable;
inputs.nixpkgs-bazel.url = github:NixOS/nixpkgs/20.09;
inputs.flake-utils.url = github:numtide/flake-utils;
outputs = { self, nixpkgs, nixpkgs-bazel, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = {
haskell = import ./nix/haskell/overlay.nix;
};
pkgs = builtins.foldl'
(acc: overlay: acc.extend overlay)
nixpkgs.legacyPackages.${system}
(builtins.attrValues overlays);
in
{
inherit overlays; # Needed by `bazel-nixpkgs.nix` later on.
devShell = import ./nix/shell.nix {
inherit pkgs;
nixpkgs-bazel = nixpkgs-bazel.legacyPackages.${system};
};
}
);
}
The "old" nixpkgs (as in import <nixpkgs> {}
) is available at attributes such
as nixpkgs.legacyPackages.x86_64-linux
where nixpkgs
here is a flake used as
an input to our flake.
Just for the sake of demonstration, we define a simple Nix overlay which
overrides ghc
from nixpkgs
with a different derivation.
nix/haskell/overlay.nix
nixpkgsSelf: nixpkgsSuper:
{
ghc = nixpkgsSuper.ghcWithPackages (hsPkgs: with hsPkgs; [
aeson
]);
}
In order let Bazel know that the Nix environment depends on this file, we can
create BUILD.bazel
files in nix/haskell/
with the following rule.
This is used later on in WORKSPACE
.
nix/haskell/BUILD.bazel
exports_files(glob(["**"]))
Next, we can define our development environment as before. I like to put it in
nix/shell.nix
so (almost) all Nix code ends up in nix/
.
nix/shell.nix
{ pkgs, nixpkgs-bazel }:
pkgs.mkShell {
buildInputs = [
nixpkgs-bazel.bazel
pkgs.python3
# Just to show overlays.
pkgs.ghc
];
}
Running nix develop
should drop you into a shell with Bazel and Python
available. Since it’s the first time it runs, another file flake.lock
should
be generated with the pinned versions of inputs.
The bridge
rules_nixpkgs doesn’t have native
support for Nix Flakes so we need to bridge the gap with a simple file that
exposes the same nixpkgs
as in the flake. We also want any overlay to be
applied so Bazel uses the correct version of dependencies.
bazel-nixpkgs.nix
{ ... }:
let
flake = builtins.getFlake (toString ./.);
baseNixpkgs = flake.inputs.nixpkgs.legacyPackages.${builtins.currentSystem};
overlays = flake.overlays.${builtins.currentSystem};
nixpkgs = builtins.foldl'
(acc: overlay: acc.extend overlay)
baseNixpkgs
(builtins.attrValues overlays);
in
nixpkgs
Bazel workspace
Finally, we can use rules_nixpkgs
with our bridge as if we were not using
flakes at all.
WORKSPACE
workspace(name = "myproject")
load(
"@bazel_tools//tools/build_defs/repo:http.bzl",
"http_archive",
)
http_archive(
name = "rules_nixpkgs",
sha256 = "7aee35c95251c1751e765f7da09c3bb096d41e6d6dca3c72544781a5573be4aa",
strip_prefix = "rules_nixpkgs-0.8.0",
urls = ["https://github.com/tweag/rules_nixpkgs/archive/v0.8.0.tar.gz"],
)
load("@rules_nixpkgs//nixpkgs:repositories.bzl", "rules_nixpkgs_dependencies")
rules_nixpkgs_dependencies()
http_archive(
name = "rules_haskell",
sha256 = "b4e2c00da9bc6668fa0404275fecfdb31beb700abdba0e029e74cacc388d94d6",
strip_prefix = "rules_haskell-0.13",
urls = ["https://github.com/tweag/rules_haskell/archive/v0.13.tar.gz"],
)
load(
"@rules_haskell//haskell:repositories.bzl",
"rules_haskell_dependencies",
)
rules_haskell_dependencies()
load(
"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl",
"nixpkgs_local_repository",
"nixpkgs_python_configure",
)
# This loads Nixpkgs from `bazel-nixpkgs.nix` so it is the same version as the
# input of the flake with overlays applied.
nixpkgs_local_repository(
name = "nixpkgs",
nix_file = "//:bazel-nixpkgs.nix",
nix_file_deps = [
"//:flake.nix",
"//:flake.lock",
"//nix/haskell:overlay.nix",
],
)
# The Python executable used by Bazel is built by Nix and is therefore pinned.
nixpkgs_python_configure(
repository = "@nixpkgs",
)
# Same goes for the C compiler.
nixpkgs_cc_configure(
repository = "@nixpkgs",
)
# Rules for Haskell libraries and executables will be compiled with GHC from our
# Nix overlay.
haskell_register_ghc_nixpkgs(
attribute_path = "ghc",
repository = "@nixpkgs",
version = "8.10.4",
)
As you can see, the key is just that file bazel-nixpkgs.nix
which is pretty
straightforward. One nice side effect of having this file is also being able to
load Nixpkgs in the nix repl
by simply typing :l ./bazel-nixpkgs.nix
. This
trick turned out to be very useful for debugging.