omniflake

Unification

omniflake.flakes.<name> substitutes five inputs into the flake, matched by input name, at every depth of its input graph:

nixpkgs  flake-utils  systems  flake-parts  flake-compat

The substituted values are omniflake's own inputs. With

inputs.omniflake.inputs.nixpkgs.follows = "nixpkgs";

in your flake, every indexed flake's nixpkgs is your nixpkgs, including one reached through another flake, such as agenixhome-managernixpkgs.

Matching is on the exact input name. nixpkgs-stable, nixpkgs-unstable and similar names are left on the revision in the flake's own lock.

pinned

omniflake.pinned.<name> substitutes nothing. Each input is the revision in the flake's lock file. Use it when a package must be built against the nixpkgs its author locked.

$ nix run 'github:fzakaria/omniflake#pinned.nh.packages.x86_64-linux.default'

unified

omniflake.unified.<name> substitutes on every name the index knows, where flakes substitutes on five. A graph reaches one home-manager and one treefmt-nix, both at the revision the pipeline pinned, rather than the revision each author happened to lock.

$ nix eval --raw 'github:fzakaria/omniflake#unified.devenv.inputs.cachix.inputs.git-hooks.rev'
4f3bdeba21c84f5664887d0451e435e786fd7723
$ nix eval --raw 'github:fzakaria/omniflake#flakes.devenv.inputs.cachix.inputs.git-hooks.rev'
9f7e99119ece7705299595299f3b031f39356de1

Substitution is a fixed point: the flake substituted in is itself unified, so the unified line above reaches git-hooks through a cachix that had already been overridden. Nothing is fetched until an attribute is forced, so an override set naming the whole index costs nothing until one of its names is matched.

The five foundations still win over the index, which carries entries called nixpkgs, flake-utils, systems, flake-parts and flake-compat too. They have to: follows reaches a declared input and nothing else, so taking those five from the index would cut the one line a consumer uses to unify omniflake with their own tree.

unified is a stronger claim than flakes, and it is wrong more often. An author who locked an older home-manager may have done it because the newer one broke them. Use it when a single graph matters more to you than each flake working the way its author tested it.

Matching is still on the exact name. An input called utils rather than flake-utils is untouched, and agenix's darwin input stays pinned because the index calls that flake nix-darwin.

A name cycle resolves only because evaluation is lazy: ihp depends on ihp-boilerplate, which depends on ihp. A flake whose outputs forced its way around such a cycle would report infinite recursion for that attribute rather than for the whole set.

omniflake.lib.unifyAll { } is unified. Anything passed to it wins over both the index and the foundations:

omniflake.lib.unifyAll { home-manager = home-manager; }

Your own policy

lib.withOverrides returns every flake under an override set of your choice; lib.load does the same for one flake.

let
  mine = omniflake.lib.withOverrides {
    nixpkgs = nixpkgs;
    nixpkgs-stable = nixpkgs-stable;
    nixpkgs-unstable = nixpkgs;
  };
in
mine.sops-nix.nixosModules.sops
omniflake.lib.load "nh" { nixpkgs = nixpkgs-stable; }

An override replaces every input of that name wherever it appears in the graph. When the replaced input is declared with flake = false, the override's source tree (sourceInfo) is passed instead of its outputs.

Scope

Substitution happens during evaluation, in the loader. No lock file is modified, and a consumer's nix flake lock is unaffected. Two consumers with different override sets evaluate different graphs from the same index.

Each flake still evaluates import nixpkgs { … } for itself. Substitution shares the source revision, not the evaluated package set.

Edit this page on GitHub