nova-nix

nova-nix is an implementation of Nix for Windows, written
in Haskell with a C99 data layer. It also builds and runs on macOS and Linux.
It has its own parser, evaluator, store, builder and binary-cache substituter,
and does not need an existing Nix installation.
nova-nix is experimental. Read Limitations before relying on
it.
Status
- Evaluation matches upstream Nix on the cases CI checks. On a pinned
nixpkgs 24.11 revision,
hello.drvPath and a dependent derivation evaluate
to the same store paths as Nix 2.24.9, and 23 smaller evaluation cases
produce the same output. CI runs this comparison on every change. A matching
drvPath means the whole build-time closure behind it matches too. How much
of the rest of nixpkgs evaluates has not been measured yet (#29).
- Windows builds run natively. CI builds GNU Hello on a Windows runner
through a stage-1 stdenv. Its toolchain is 17 MinGW-w64 packages and 22
MSYS2 packages, each fetched as a fixed-output derivation pinned by SHA-256
and unpacked into the store. sed, zlib and a small library-linking example
also have recipes, but CI does not build them. Replacing these binary seeds
with a toolchain built from source is tracked in #26.
- Binary caches work in both directions.
nova-nix push uploads a
closure, and build --substituter downloads signed NARs instead of
building. CI checks the round trip on Linux against a local nova-cache
server.
Limitations
- No flakes, no
nix-shell or nix develop, and no daemon or multi-user mode.
- No garbage collection yet (#24).
nova-nix store delete removes
individual paths.
- On Windows a build's process tree runs in a job object, so stopping a build
stops everything it started. There is no filesystem or network isolation
yet (#25).
- nova-nix itself needs neither WSL nor Cygwin, but the current Windows stdenv
runs its build scripts with MSYS2's bash.
- Store paths that contain symlinks need Windows Developer Mode or an elevated
shell. nova-nix creates native symlinks and fails rather than falling back
to a copy.
Install
Each release has an
archive per platform:
Each archive unpacks to a single directory holding bin/, share/nova-nix/
and pkgs/. share/nova-nix/ provides the <nix/*> search path, so the
binary works wherever the directory is unpacked, and pkgs/ holds the package
recipes. Check the download against the SHA256SUMS file attached to the same
release.
In a GitHub Actions workflow:
- uses: Novavero-AI/install-nova-nix@v1
- run: nova-nix eval --expr '1 + 2'
Usage
$ nova-nix eval --expr '1 + 2'
3
$ nova-nix eval --strict --expr 'builtins.map (x: x * x) [ 1 2 3 4 5 ]'
[ 1 4 9 16 25 ]
$ nova-nix eval FILE.nix # evaluate a file
$ nova-nix build FILE.nix -A ATTR # build an attribute of it
$ nova-nix build FILE.nix --substituter URL --trusted-key KEY # try a cache first
$ nova-nix push --cache URL --key-file KEY --all # upload the store to a cache
$ nova-nix --help
Evaluating a package from nixpkgs, on the 24.11 revision CI pins:
$ NIX_PATH=nixpkgs=/path/to/nixpkgs nova-nix eval --expr \
'(import <nixpkgs> { system = "x86_64-linux"; config = {}; overlays = []; }).hello.drvPath'
"/nix/store/gciipqhqkdlqqn803zd4a389v86ran45-hello-2.12.1.drv"
Building GNU Hello on Windows, from the unpacked release directory:
> bin\nova-nix build pkgs\windows\hello.nix
...
C:\nix\store\<hash>-hello
> C:\nix\store\<hash>-hello\bin\hello.exe
Hello, world!
The first build fetches 40 pinned archives (the 39 toolchain packages and the
Hello source), then builds the MSYS2 seed, the MinGW-w64 seed and Hello.
How it works
- Parser (
Nix.Parser): a hand-written recursive-descent parser for the
Nix 2.24 language.
- Evaluator (
Nix.Eval): the AST compiles to a small bytecode that a lazy
evaluator runs, memoizing thunks and detecting infinite recursion. The
evaluator is generic over MonadEval, with a pure instance for tests and an
IO instance for real evaluation. derivation is a lazy wrapper over the
strict derivationStrict primop, as in upstream's
src/libexpr/primops/derivation.nix, so referring to a package does not
force its build closure.
- Data layer (
cbits/): attribute sets, lists, thunks, environments,
symbols, context strings, lambdas and bytecode are C99 structures outside
the GHC heap, freed together when an evaluation session ends. Haskell calls
into C, and C never calls back.
- Store (
Nix.Store): a content-addressed store at /nix/store, or
C:\nix\store on Windows, with SQLite metadata, reference scanning and
per-path locks that follow upstream's protocol.
- Builder (
Nix.Builder): orders derivations by their dependencies, tries
substitution first, and runs each builder in a scrubbed environment.
- Substituter (
Nix.Substituter): the HTTP binary-cache protocol, with
Ed25519 signature checks and xz, zstd and bzip2 decompression, built on
nova-cache.
On Windows, derivations keep the canonical /nix/store spelling that hashes
are computed over, and it is mapped to the real store directory only when a
builder is started. NTFS has no executable bit, so nova-nix keeps it in an
alternate data stream. A store path therefore serializes to the same NAR, with
the same hash, as it does on other platforms.
Library
{-# LANGUAGE OverloadedStrings #-}
import Control.Exception (bracket_)
import Nix.Builtins (builtinEnv)
import Nix.Eval (eval, runPureEval)
import Nix.Eval.Arena (arenaDestroy, arenaInit)
import Nix.Parser (parseNix)
main :: IO ()
main = bracket_ arenaInit arenaDestroy $
-- The first argument is what relative path literals resolve against.
case parseNix "/tmp" "<expr>" "let x = 5; in x * 2 + 1" of
Left err -> print err
Right expr -> print (runPureEval (eval (builtinEnv 0 []) expr))
This prints Right (VInt 11). Evaluation needs the C data layer, so it must
run between arenaInit and arenaDestroy.
Building from source
Tested with GHC 9.14.1. CI uses the latest cabal-install release. You only
need this to work on nova-nix itself, not to use a release.
git clone https://github.com/Novavero-AI/nova-nix.git
cd nova-nix
cabal update
cabal build
cabal test
Contributing
See CONTRIBUTING.md. Planned work is on the
Nova roadmap project. Please
report security issues privately through
GitHub's vulnerability reporting
rather than in a public issue.
License
Apache-2.0. See LICENSE and NOTICE.