Froyo: A tasteful way to organize your Nix code

This is a tidied up version of some work I did at the beginning of the year, based around the idea of bringing a flake-parts-like experience to stable Nix code. It has a pretty basic API right now, but I’ve had a lot of fun playing around with the possibilities - even in flakes!

Feel free to give any feedback or ask questions :slight_smile:

How do I use it?

A minimal use case would probably look something like this

let
  inputs = {
    nixpkgs = <nixpkgs>;
    froyo = builtins.fetchTarball "https://github.com/getchoo/froyo/archive/main.tar.gz";
  };
in

import inputs.froyo { inherit inputs; } {
  outputs = {
    hello = "hi from froyo!";
  };

  perTarget =
    { pkgs, ... }:

    {
      outputs = { inherit (pkgs) hello; };
    };
}

which will evaluate to

{
  extend = «lambda extend @ /nix/store/m2nirx1bm7bh3riq0darc9g82hm38yb8-source/default.nix:31:16»;
  inputs = {
    froyo = "/nix/store/m2nirx1bm7bh3riq0darc9g82hm38yb8-source";
    nixpkgs = /nix/store/nbbcr40rk108j1pckbn3g09php4j1r32-source;
  };
  outputs = {
    hello = "hi from froyo!";
    perTarget = {
      aarch64-darwin = {
        hello = «derivation /nix/store/qd76lndmahkn1m3bicpwv7kgk5rwqhjx-hello-2.12.2.drv»;
      };
      x86_64-linux = {
        hello = «derivation /nix/store/8zbcxlfgg2cdngx0v4blrcs33kl6dpdv-hello-2.12.2.drv»;
      };
      # ...
    };
  };
}

This is inspired by the basic inputs/outputs schema of flakes, but froyo doesn’t make any hard assumptions (even on using per-system-esc evaluation like this!) on how you want to organize things past that

Why would I use it?

The most obvious reason is if you want a flake-parts-like experience in stable Nix, similar to myself. I think the module system provides a nice workflow (and fixed point) for mid to large sized projects, as well as a very nice way for consumers to understand, reuse, and extend your work. I think I’ve done a decent job at implementing that here, as well as hopefully expanded on the concept outside of the flakes world

You may also just want a more ergonomic experience for stable Nix code as to stay away from flakes and some of their issues. This project may offer that, in the same spirit as some other projects that have cropped up recently like sprinkles or Nilla

For advanced users (or just those with specific needs or more exotic configurations), froyo also introduces a few cool new things:

Targets

If you’re familiar with flake-parts and read the above example, you may have noticed the similarity between perTarget and perSystem. But why the name change?

Well, as opposed to flakes and their system-centric approach, froyo introduces the new concept of targets!

Targets describe both a host and build platform, enabling for fine grained control of what you’re building with and for - which is basically impossible with flakes. With a bit of module system magic, this also comes at zero real cost to usability, since similar to Nixpkgs, froyo is pretty good at figuring out what you want based on what “system” you pass to it

In practice, this is all managed through the targets attribute set. Most people won’t need to worry about this as it will default to the traditional [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ], but for those with more exotic setups, it’s pretty easy to configure:

{ config, ... }:

{
  targets = {
    # Basically `pkgsStatic` as a target
    # Will be available as `outputs.perTarget.x86_64-linux-static`
    x86_64-linux-static = {
      inherit (config.targets.x86_64-linux) buildPlatform;
      hostPlatform = {
        config = "x86_64-unknown-linux-musl";
        isStatic = true;
      };
    };

    # Single strings can be passed, similar to `system` in flakes
    riscv-native = "riscv64-linux";

    riscv-cross = {
      inherit (config.targets.x86_64-linux) buildPlatform;
      inherit (config.targets.riscv-native) hostPlatform;
    };
  };
}

You may also want to check out the cross-compilation section of the README

extend

This function is explained a bit more in-depth here, but it’s basically just a nice wrapper around lib.evalModule’s extendModules - similar to the extendModules attribute exported by NixOS configurations. It attempts to tackle another problem of flakes, which is composability

As an example, now you can “extend” a project to support your system/cross-compilation setup if it currently doesn’t. And override Nixpkgs while you’re at it!

let
  a-froyo-project = import ./froyo-project.nix;
in

a-froyo-project.extend (
  { config, ... }:

  {
    inputs = {
      nixpkgs = <nixpkgs>;
    };

    targets = {
      powerpc64-linux = "powerpc64-linux";

      powerpc64-cross = {
        inherit (config.targets.x86_64-linux) buildPlatform;
        inherit (config.targets.powerpc64-linux) hostPlatform;
      };
    };
  }
)     

What’s next?

I’m not sure really. I prototyped this quickly a while ago, and got the itch recently to make it something I could properly show off and document so people might find use out of it lol

I don’t want to make any hard set promises, as life has been busy lately. But I think I will start making this more of a stable, production-ish ready Thing soon

I am also interested in making this project more usable with flakes, which I’ve seen some early success with. It might be cool to have some kind of independent library like this that can just use flakes as a locking mechanism, and hopefully still expose some of the new functionality like extend

Past that, I want to keep this API/default module set simple, and I think the core of what’s here is all that’s necessary from a project like this. I don’t plan to add much more, but let the module system and its extensibility let people fill in gaps they might want

27 Likes

looking quite nice!
I’ve recently wondered about the recursive caching of flakes as enabled by their fixpoint, and ways to enable those in such a way one could run with or without flakes, getting the caching when one does.
might be cool to maybe see such an angle explored as well, tho tbf i haven’t really looked for prior art.

2 Likes

Can’t wait to test it, looks clean and useful !!!

1 Like