Mana locks and injects Nix dependencies without flakes.
- A few lines of Nix β‘οΈ bash
- No flake dependency
nix run github:hsjobeki/mana init
nix run github:hsjobeki/mana updateThis creates a lock.json that pins all dependencies. Build with:
nix build -f default.nix hellomana init generates four files:
mana.nixβ the manifest (dependencies, shares, pins, groups)entrypoint.nixβ receives injected dependenciesdefault.nixβ evaluation entry pointnix/importer.nixβ runtime shim that wires everything together
fetchTree(the fetcher inside flakes) limits sources to those flakes supports.- Verbose lockfile format.
- Requires the
importer.nixshim. Flakes hides this internally. - Nix commands require the
-fflag or aflake.nixcompat shim (see Nix commands).
| Command | Description |
|---|---|
mana init [--force] |
Initialize a new project (creates mana.nix, entrypoint.nix, default.nix, nix/importer.nix) |
mana update [dep1 dep2 ...] |
Update locked dependencies (all or specific ones) |
mana sync |
Sync nix/importer.nix with the current mana version (useful after upgrading) |
# 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 downloaded when it belongs to an enabled group. Without groups, everything goes into eval by default.
Here nixpkgs is in eval (always fetched), while treefmt-nix is in dev (only fetched when requested).
default.nix enables only eval. To also fetch 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
By default, mana respects upstream manifests but re-locks all dependencies locally.
You often want to reduce nixpkgs downloads by forcing dependencies to use your pinned version.
share lists dependencies shared with all transitive dependencies:
# 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) will use YOUR nixpkgs
share = [ "nixpkgs" ];
}This overrides nixpkgs in:
- treefmt-nix's dependencies
- Any transitive dependencies (dependencies of dependencies)
It does not override your root-level nixpkgs.
pins protects specific dependencies from being overridden by parent share declarations:
# mana.nix for a library that needs its own specific nixpkgs
{
name = "my-library";
entrypoint = ./entrypoint.nix;
dependencies = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
};
# Even if a consumer shares nixpkgs, this library keeps its own version
pins = [ "nixpkgs" ];
}When a dependency declares pins, those pinned dependencies are immune to share overrides from parent projects. This is useful for libraries that depend on a specific version for correctness.
comming soon!
By default, mana imports each dependency's entrypoint (from its mana.nix) or falls back to default.nix. You can override this per-dependency.
Set entrypoint = null to disable the import:
{
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 a path to import a specific file instead of the default:
{
dependencies = {
some-lib.url = "github:someone/some-lib";
some-lib.entrypoint = "./lib/special.nix";
};
}Experimental nix commands (nix build, nix run) only work natively with flakes. They require a flake.nix. With other files, you must pass -f <filename> attrName.
To get native nix run support, create a flake.nix shim that re-exposes your packages:
# 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
);
};
}Comming soon!
Cheers