getchoo/froyo
{ "createdAt": "2025-03-11T21:58:41Z", "defaultBranch": "main", "description": "đ¨ A tasteful way to organize your Nix code", "fullName": "getchoo/froyo", "homepage": "", "language": "Nix", "name": "froyo", "pushedAt": "2025-11-24T03:34:58Z", "stargazersCount": 33, "topics": [], "updatedAt": "2025-11-09T21:05:14Z", "url": "https://github.com/getchoo/froyo"}[!CAUTION] I made this on a whim in an afternoon. Itâs not production ready and may never be. Here be dragons! đ˛
What does it do?
Section titled âWhat does it do?âfroyo brings the module system from NixOS right into your stable Nix code. This comes with a few big advantages, like:
- No more manual importing of files
- Improved re-usability of code
- Self documenting interfaces
- Less boilerplate
- A lot of composability!
Quick Start
Section titled âQuick Startâ[!WARNING]
froyois best used with tools likenpinsandniv.
import <froyo> { } { outputs = { hello = "hi from froyo!"; };}$ nix-instantiate --eval --attr outputs.helloWith pinned sources
Section titled âWith pinned sourcesâBy default, froyo will use itâs own Nixpkgs when applicable. However, it is highly recommended to maintain your own lockfile with Nixpkgs and pass that to froyo directly.
let # In this example we use `npins`, but these could come from anything (even channels!) inputs = import ./npins;in
import inputs.froyo { inherit inputs; } { outputs = { hello = "hi from froyo!"; };
perTarget = { pkgs, ... }:
{ outputs = { inherit (pkgs) hello; }; };}You can then build this hello package:
$ nix-build --attr outputs.perTarget.x86_64-linux.helloConcepts
Section titled âConceptsâInputs and Outputs
Section titled âInputs and Outputsâfroyo projects can be boiled down to two things: inputs and outputs.
Inputs are sources (usually managed by tools like npins, niv, etc.) passed to froyo projects. They can be local (/nix/store) paths, instantiated package sets, or even flake inputs!
Outputs
Section titled âOutputsâOutputs are pieces of your Nix code meant to be used by the outside world. This is represented by the outputs config option, which (along with inputs) are the only fields exported by froyo.
âTargetsâ
Section titled ââTargetsââTo assist in both common and more exotic workflows, froyo introduces the concept of âtargetsâ. Targets are either:
- A string describing a system recognized by Nixpkgs (i.e.,
"x86_64-linux"or"aarch64-darwin") - An attribute set describing a system (such as those created with
lib.systems.elaborate) - An attribute set containing a
buildPlatformandhostPlatformattribute (with a value of one of the above) for cross compilation
[!TIP] Sound familiar?! This is analogous to the
systemvariable commonly used in flakes, but expanded upon to consider multiple systems
targets is the name-value pair option used to describe the targets froyo operates on by default, like so:
{ config, lib, ... }:
{ targets = { x86_64-linux = lib.systems.elaborate "x86_64-linux"; inherit (lib.systems.examples) aarch64-darwin; aarch64-cross = { buildPlatform = config.targets.x86_64-linux; hostPlatform = "aarch64-linux"; }; };}perTarget
Section titled âperTargetâperTarget is a small helper option defined in [modules/per-target.nix]!(./modules/per-target.nix). It allows you to define attributes that will be applied to each target defined by the aforementioned targets option.
{ perTarget = { pkgs, ... }: { # Similar to at the root level, you need to use `outputs` to export attributes outputs = { inherit (pkgs) hello; }; };}This is then available in the final project as outputs.perTarget.<target name>.hello.
Advanced Usage
Section titled âAdvanced UsageâCross compilation
Section titled âCross compilationâThe biggest advantage of using target over system is the ability for froyo to describe cross-compilation configurations. You can do this by defining a buildPlatform and hostPlatform.
import inputs.froyo { } { targets = { riscv-cross = { buildPlatform = "x86_64-linux"; hostPlatform = "riscv64-linux"; }; };
perTarget = { pkgs, ... }:
{ outputs = { inherit (pkgs) hello; }; };}Now, a cross-compiled hello will be available as outputs.perTarget.riscv-cross.hello.
Extending
Section titled âExtendingâfroyo project can be extended - similar to using extendModules in NixOS configurations
let # Assuming we're using the above example my-froyo-project = import ./my-froyo-project;
extended = my-froyo-project.extend { # This makes `perTarget` iterate over the new one defined here targets = { native-riscv = "riscv64-linux"; };
allSystems = { my-other-cool-target = { pkgs, ... }:
{ target = "powerpc64-linux";
outputs = { inherit (pkgs) hello-go; }; }; }; };in
{ # `native-riscv` is now in `perTarget`! inherit (extended.outputs.perTarget.native-riscv) hello; # `my-other-cool-target` now also exists, and has a special `hello-go` attribute exclusive to it inherit (extended.outputs.perTarget.my-other-cool-target) hello-go;}As someone who primarily uses Flakes, one of my favorite parts of them for a while has been flake-parts. There isnât much of an equivalent in stable Nix for its functions though, so after a couple social media posts and taking inspiration from previous work Iâve done, I came up with this