nova-nix-0.8.0.0: README.md
# nova-nix
[](https://github.com/Novavero-AI/nova-nix/actions/workflows/ci.yml)
[](https://hackage.haskell.org/package/nova-nix)
[](LICENSE)
nova-nix is an implementation of [Nix](https://nixos.org) 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](#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](https://github.com/Novavero-AI/nova-nix/releases/latest) has an
archive per platform:
| Platform | Archive |
| --- | --- |
| Linux x86_64 | [`nova-nix-linux-x64.tar.gz`](https://github.com/Novavero-AI/nova-nix/releases/latest/download/nova-nix-linux-x64.tar.gz) |
| macOS arm64 | [`nova-nix-macos-arm64.tar.gz`](https://github.com/Novavero-AI/nova-nix/releases/latest/download/nova-nix-macos-arm64.tar.gz) |
| Windows x86_64 | [`nova-nix-windows-x64.zip`](https://github.com/Novavero-AI/nova-nix/releases/latest/download/nova-nix-windows-x64.zip) |
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:
```yaml
- uses: Novavero-AI/install-nova-nix@v1
- run: nova-nix eval --expr '1 + 2'
```
## Usage
```console
$ 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 ]
```
```console
$ 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:
```console
$ 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:
```console
> 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](https://github.com/Novavero-AI/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
```haskell
{-# 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.
```bash
git clone https://github.com/Novavero-AI/nova-nix.git
cd nova-nix
cabal update
cabal build
cabal test
```
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Planned work is on the
[Nova roadmap](https://github.com/orgs/Novavero-AI/projects/1) project. Please
report security issues privately through
[GitHub's vulnerability reporting](https://github.com/Novavero-AI/nova-nix/security/advisories/new)
rather than in a public issue.
## License
Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
[#24]: https://github.com/Novavero-AI/nova-nix/issues/24
[#25]: https://github.com/Novavero-AI/nova-nix/issues/25
[#26]: https://github.com/Novavero-AI/nova-nix/issues/26
[#29]: https://github.com/Novavero-AI/nova-nix/issues/29