Trying to use nix

👋 This page was last updated ~2 years ago. Just so you know.

Now that my website is deployed as a container image, I wanted to give nix a try. I'm still doing it the old-fashioned way right now: with a Dockerfile, running cargo in a "builder" image, copying stuff out of there into a slimmer image (that still has an Ubuntu base, even though distroless images are a thing now).

But why?

I was mostly interested in nix because some parts of my website have pretty big native dependencies. futile itself mostly relies on sqlite3 and some JS engine (used to be quickjs, currently duktape because MSVC Windows builds). But the asset processing pipeline, salvage (which I'd like to integrate with futile at some point) has a bunch more!

Not only does it need Google Chrome headless for diagrams (which are made in the excellent diagrams.net desktop application), it then exports those to PDF, which are converted to SVG (baking text into paths, so you don't need to have my fonts installed locally to view/print them), and then optimized further. That requires poppler and cairo, which requires glib, gobject, gio, etc.

And then there's webp processing, avif processing, etc.

In fact I wrote a whole series about this, how I got everything to link statically (except Google Chrome, which is a standalone executable) on both Linux and Windows, that involved the Meson build system, etc.

Nowadays I don't care so much about the Windows part, everything runs in a Linux VM anyway, but I figured nix would let me simplify a lot of this — no matter which distribution I chose to use day-to-day, all the dependencies would be statically defined in a single place, I could even apply my own patches if I wanted (which I do for the video processing pipeline — to get ffmpeg and libsvtav1 to play nice with each other), and I could link dynamically against libraries, which might speed up the link step and trim down the final executable quite a bit.

Also, apparently you can generate Docker images with nix, which sounded great.

Success (but at what cost?) with crane

So I started learning nix! I learned there's quite a few ways to build Rust code with nix, a lot of folks use naersk, but after a couple tries, I gravitated towards crane instead.

A still-experimental but not-so-new thing in nix are "flakes", I figured I'd make a flake to build futile (starting small), and got as far as this:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    crane = {
      # url = "github:ipetkov/crane";
      url = "path:/home/amos/bearcove/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          system = system;
          overlays = [ (import rust-overlay) ];
        };

        rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile
          ./rust-toolchain.toml;
        lib = crane.lib.${system}.overrideToolchain rustToolchain;

        shipyardToken = builtins.readFile ./secrets/shipyard-token;
        craneLib = lib.appendCrateRegistries [
          (lib.registryFromDownloadUrl {
            dl = "https://crates.shipyard.rs/api/v1/crates";
            indexUrl = "https://git.shipyard.rs/bearcove/crate-index.git";
            curlOptsList = [ "-H" "user-agent: shipyard ${shipyardToken}" ];
          })
        ];
        futile = craneLib.buildPackage {
          src = ./.;
          nativeBuildInputs = with pkgs; [ pkg-config protobuf clang_14 mold ];
          installCargoArtifactsMode = "use-symlink";
        };

      in { packages.default = futile; });
}
Cool bear

Wait, did you add nix syntax highlighting to futile just now for this?

Amos

...maybe? Most of the work was already done.

I have no idea if this is idiomatic, it's also complicated by the fact that I use a private crate registry, so I immediately had to go contribute to crane, and despite my best efforts, I still don't deem it usable for now.

Building and installing a futile binary via nix still takes a long time, and the main offender is... removing references to /nix/store paths! There's a long and interesting discussion going on about it right now, I might even get a chance to sneak some Rust in your Rust, but for now I've decided it's outside the scope of this article, and we'll revisit this all later.

It's time to tackle the one thing I actually meant to do with this series, which is: adding GitHub login.

Comment on /r/fasterthanlime

(JavaScript is required to see this. Or maybe my stuff broke)

Here's another article just for you:

Some mistakes Rust doesn't catch

I still get excited about programming languages. But these days, it's not so much because of what they let me do, but rather what they don't let me do.

Ultimately, what you can with a programming language is seldom limited by the language itself: there's nothing you can do in C++ that you can't do in C, given infinite time.

As long as a language is turing-complete and compiles down to assembly, no matter the interface, it's the same machine you're talking to. You're limited by... what your hardware can do, how much memory it has (and how fast it is), what kind of peripherals are plugged into it, and so on.