Mana locks and injects Nix dependencies without flakes.
- Pure Nix + bash. No compiled dependencies.
- No flake required.
npins and lon are flat source pinners. You pin each URL, import sources manually, and wire everything together yourself. They have no transitive dependency resolution.
Mana is a dependency manager:
| npins / lon | flakes | mana | |
|---|---|---|---|
| Fetcher | fetchTarball / fetchGit | fetchTree (experimental) | fetchTree (experimental) |
| Transitive dependencies | ❌ Manual | ✅ | ✅ |
| Dependency injection | ❌ Manual wiring | ✅ inputs |
✅ entrypoints |
| Version sharing across tree | ❌ | follows |
✅ shares |
| Protect deps from overrides | ❌ | ❌ | ✅ pins |
| Dev dependencies | ❌ | ❌ | ✅ groups |
| Deduplication | ❌ | ✅ | ✅ |
add command |
✅ | ❌ Manual | |
| Workspaces / monorepo | ❌ | ❌ | |
| Offline vendoring | ❌ | ❌ | |
| Implementation | Compiled Rust binary | Built into Nix | Pure Nix + shell script |
Mana has zero dependencies. It only needs nix-instantiate and nix eval -- tools already present on any system with Nix installed.
nix run github:hsjobeki/mana init
nix run github:hsjobeki/mana updateThis creates a lock.json that pins all dependencies.
nix build -f default.nix hellomana init generates four files:
mana.nix-- manifest (dependencies, shares, pins, groups)entrypoint.nix-- receives injected dependenciesdefault.nix-- evaluation entry pointnix/importer.nix-- shim that resolves and injects dependencies
| Command | Description |
|---|---|
mana init [--force] |
Create a new project |
mana update [dep1 dep2 ...] |
Update locked dependencies |
mana upgrade |
Upgrade nix/importer.nix to the current mana version |
mana check |
Validate mana.nix |
fetchTreelimits sources to what flakes support.- Lockfile format is verbose.
- Requires the
importer.nixshim. Flakes handle this internally. - Nix commands need the
-fflag or aflake.nixcompat shim (see Nix commands).
# mana.nix
{
name = "my-project";
description = "My project using mana";
entrypoint = ./entrypoint.nix;
dependencies = {
nixpkgs.url = "github:nixos/nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
groups = {
eval = {
nixpkgs = [ ];
};
dev = {
treefmt-nix = [ ];
};
};
}groups control which dependencies are fetched. A dependency is only fetched when it belongs to an enabled group. Without groups, everything defaults to eval.
Here nixpkgs is in eval (always fetched). treefmt-nix is in dev (fetched on request).
default.nix enables only eval. To include dev dependencies:
# ci.nix
(import ./nix/importer.nix) { groups = [ "eval" "dev" ]; }One entrypoint.nix serves all groups. Disabled dependencies throw on access:
# entrypoint.nix
{ nixpkgs, treefmt-nix }:
{ system ? builtins.currentSystem }:
let
pkgs = nixpkgs { inherit system; };
in
{
packages.x = pkgs.callPackage ./. { };
checks.formatting = pkgs.callPackage ./. { inherit treefmt-nix; };
}With default.nix: accessing treefmt-nix throws an error.
With ci.nix: treefmt-nix is available.
graph TD
default.nix -->|eval| entrypoint.nix
dev.nix -->|eval, dev| entrypoint.nix
doc.nix -->|eval, doc| entrypoint.nix
entrypoint.nix --> Packages
entrypoint.nix --> Modules
entrypoint.nix --> Documentation
Mana re-locks all dependencies locally by default. You often want all transitive deps to share a single nixpkgs. This avoids redundant downloads.
shares propagates your version of a dependency to the entire tree:
# mana.nix
{
name = "my-project";
entrypoint = ./entrypoint.nix;
dependencies = {
nixpkgs.url = "github:nixos/nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
# treefmt-nix (and any deeper deps) use YOUR nixpkgs
shares = [ "nixpkgs" ];
}This overrides nixpkgs in:
- treefmt-nix's dependencies
- All transitive dependencies (dependencies of dependencies)
It does not override your root-level nixpkgs.
pins protects a dependency from being overridden by a parent's shares:
# mana.nix for a library that needs its own nixpkgs
{
name = "my-library";
entrypoint = ./entrypoint.nix;
dependencies = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
};
# Keeps this version even if a consumer shares nixpkgs
pins = [ "nixpkgs" ];
}Pinned dependencies are immune to shares from parent projects. Use this for libraries that depend on a specific version for correctness.
Coming soon!
By default, mana imports each dependency's entrypoint from its mana.nix. If none exists, it falls back to default.nix. You can override this per-dependency.
Set entrypoint = null to get the raw source path:
{
dependencies = {
nixpkgs.url = "github:nixos/nixpkgs";
nixpkgs.entrypoint = null; # raw source path
};
}# entrypoint.nix
{ nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
in
pkgs.helloSet entrypoint to import a specific file:
{
dependencies = {
some-lib.url = "github:someone/some-lib";
some-lib.entrypoint = "./lib/special.nix";
};
}Set debug = true in your root mana.nix:
# mana.nix
{
name = "my-project";
entrypoint = ./entrypoint.nix;
debug = true;
# ...
}This traces the dependency tree during import:
trace: [mana] <root>
groups: eval, dev
deps: nixpkgs, treefmt-nix
trace: [mana] <root>/nixpkgs [raw]
trace: [mana] <root>/treefmt-nix [entrypoint: entrypoint.nix]
trace: [mana] /treefmt-nix
groups: eval
deps: nixpkgs
trace: [mana] /treefmt-nix/nixpkgs [raw]
Resolution types:
[raw]--entrypoint = null, returns the source path without importing[custom: path]-- parent overrides the entrypoint (dependencies.foo.entrypoint = "path")[entrypoint: path]-- dependency's own entrypoint from itsmana.nix[default.nix]-- nomana.nixfound, falls back todefault.nix
Only the root manifest's debug flag takes effect. debug in dependency manifests is ignored.
nix build and nix run only work natively with flakes. Without a flake.nix, you must pass -f <file> <attr>.
To get nix run support, create a flake.nix shim:
# flake.nix
# shim for nix run compat
{
outputs =
_:
let
systems = [
"aarch64-linux"
"x86_64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
in
{
packages = builtins.listToAttrs (
map (system: {
name = system;
value =
let
self = import ./default.nix { inherit system; };
in
self
// {
# The default package
# for 'nix run'
default = self.hello-world;
};
}) systems
);
};
}Coming soon!
Cheers