an alternative dependency resolver & runtime for Nix flakes
  • Nix 81.2%
  • Python 18.8%
Find a file
Max Siling cd4807d720
All checks were successful
/ test (pull_request) Successful in 2m32s
/ test (push) Successful in 1m56s
only keep one of rev / rev for github inputs when applying registry
2026-02-15 22:57:48 +03:00
.forgejo/workflows upgrade npins version 2026-02-14 22:29:08 +03:00
tests ignore overrides on attr-like flakerefs 2026-02-14 22:41:32 +03:00
.coverage a job to build test container 2025-11-30 01:06:16 +03:00
.envrc use unflake to build unflake 2025-12-20 23:15:53 +03:00
.gitignore use unflake to build unflake 2025-12-20 23:15:53 +03:00
COPYING asyncify (gotta go fast), better logging, license 2025-11-27 14:45:39 +03:00
default.nix upgrade npins version 2026-02-14 22:29:08 +03:00
flake.lock first actually working version 2025-11-27 14:45:37 +03:00
flake.nix use unflake to build unflake 2025-12-20 23:15:53 +03:00
inputs.nix use unflake to build unflake 2025-12-20 23:15:53 +03:00
README.md add flake-file link 2026-02-14 17:49:19 +03:00
unflake.nix use unflake to build unflake 2025-12-20 23:15:53 +03:00
unflake.py only keep one of rev / rev for github inputs when applying registry 2026-02-15 22:57:48 +03:00

unflake

unflake is an alternative implementation of a flake dependency resolver and runtime. it allows non-flake projects to depend on flake inputs while unifying (transitive) dependencies (with customizable deduplication rules). it can also integrate with npins to allow for granular updates.

what does it mean in practical terms?

you can:

  1. use flake dependencies in your project / system configuration
  2. without having to write inputs.foo.inputs.nixpkgs.follows = "nixpkgs" all the time
  3. and without dealing with a billion duplicate nixpkgs and flake-utils in your deps
  4. or other flake idiosyncrasies like copying your whole dir into the store or requiring flake.nix to be committed

sounds cool, how do I use it?

you run unflake. it reads your flake.nix (or inputs.nix, more on that later) and generates unflake.nix, which evaluates to the same attrset that would be provided to the flake outputs.

i.e. instead of

# flake.nix
{
  inputs = ...;
  outputs = inputs: # your code
}

you write

# default.nix or actually any file
let inputs = import ./unflake.nix; in
# your code

or

import ./unflake.nix (inputs:
  # your code
)

(the latter form also provides inputs.self)

how do I install it though?

you can just download unflake.py and plop it anywhere on your system. alternatively, you can use Nix:

nix run -f https://codeberg.org/goldstein/unflake/archive/main.tar.gz unflake

any of these will do too:

# shortened link
nix run -f https://ln-s.sh/unflake unflake
# start a shell with unflake
nix shell -f https://ln-s.sh/unflake unflake
# ironically using flakes
nix run https://ln-s.sh/unflake
# using old CLI
nix-shell https://ln-s.sh/unflake -A unflake-shell --run unflake

(any of the commands can be used with full URL too)

input formats

unflake can read your regular flake.nix files, but as unflake doesn’t actually use the flake outputs, you can also create an inputs.nix file that contains just the inputs attrset. as a bonus — you can use any Nix expressions in your inputs, not just the limited set available to the actual flakes. we can have nice things.

unflake will use inputs.nix if it’s available, or flake.nix otherwise. you can pass the -f / --flake option to force reading flake.nix. you can use the -i / --inputs option to specify the input file name. additionally, to migrate from flake.nix to inputs.nix, you can pass --convert-flake, and unflake will copy your inputs from flake.nix to inputs.nix (or the file specified with -i). you’ll want to run the formatter on inputs.nix after that though.

output formats / “backends”

in addition to generating unflake.nix, unflake can also integrate with npins for more granular dependency management. if your project has an npins/ directory, it will add all the (transitive) dependencies into your npins/sources.json, so you can update them via npins. if you use npins, but don’t want to use npins with unflake, you can suppress this by passing -b nix.

the npins backend requires sequentially running a bunch of npins add commands, which is unfortunately rather slow right now. the good news: it only needs to do this once per dependency, so if you run unflake again (e.g. to add more inputs), it will only run npins add for new dependencies (and won’t update or otherwise touch any existing ones).

if you’re not using npins, you can update all your dependencies by running unflake again. unflake fetches dependencies via Nix, which by default caches downloads for an hour, so you might need to set tarball-ttl if you want to update more often than that.

deduplication rules

sometimes flakes specify inputs that are technically different, but can be unified in practice: e.g. some flakes depend on nixos-unstable nixpkgs, but some depend on nixpkgs-unstable.

you can use the _unflake.dedupRules key in your inputs.nix to unify them:

_unflake.dedupRules = [
  ({
    when = { type = "github"; owner = "nixos"; repo = "nixpkgs"; ref = "nixpkgs-unstable"; };
    set = { ref = "nixos-unstable"; };
  })
];

when resolving a dependency, unflake will try to apply every rule in order. if the value for every attr specified in .when matches the dependency specification, it will update it with values specified in .set.

you can match absent attrs by passing null, e.g. rev = null matches dependencies without an explicit commit specified. you can remove attrs by passing null in set.

if you want to completely replace dependency with a new one, you can use .replace instead of .set. .replace takes any flakeref, e.g. replace = "github:nixos/nixpkgs/nixos-unstable".

deduplication rules are matched against the attrs format of a flake reference. unfortunately, there’re no good docs on what a flake reference is. I linked to the best source I know, but you can also grep for FLAKEREF_FIELDS in unflake.py, where I use data from the actual CppNix and Lix source code.

known limitations

  • unflake relies on the idea that we don’t actually need to lock transitive dependencies. this idea is already heavily relied upon in the ecosystem (a lot of popular packages use .follows or suggest you use it), but unflake takes it to its logical conclusion: using unflake is like specifying .follows for every input.

  • we only parse inputs that are actually specified in .inputs. magical inputs that are only specified as arguments to .outputs are not supported. this shouldn’t be a problem because any public flake already shouldn’t do that.

  • unfortunately, flakes are somewhat poorly specified. while I’ve made an effort to match native flakes’ behaviour, there might be cases where unflake diverges from the actual flakes. please, open an issue if you encounter any: compatibility with any flakes found in the wild is an explicit goal of this project. if a proper specification appears one day, unflake will be modified to adhere to it.

  • for tarball-based inputs (including GitHub) in npins backend, we set less flake input attributes than Nix specifies. I’ve never seen missing attributes used in the wild, but please comment on #1 if you encounter this problem.

known bugs

  • sometimes while running unflake the underlying evaluator crashes with “SQL fetcher cache is busy”. it’s solved by just re-running the command. the underlying cause is probably some Nix implementation bug triggered by running a lot of fetches concurrently.

contributing

PRs and bug reports are welcome. if you’re planning on working on something big, please create an issue first.

all the code is intentionally kept in a single file to simplify installation. it is grouped in logical modules though. each logical module starts with === name === {{{ and ends with }}} (curly braces are Vim fold markers, you can check if your editor supports them). when reading the code for the first time, you probably should start with data model, then resolver, then backend-generic code. the rest of the modules are not essential for understanding the common high-level logic, but you might need them to work on particular features. if you have any questions, don’t hesitate to reach out.

if you want to help, but don’t know where to start, you can take a look at the open issues.

llm policy

don’t.

every line of code submitted to the project should be produced by a human mind.

user projects

(want yours added? create an issue/PR!)