Description
I have a flake where one output embeds self.sourceInfo
(writeJSON
behaves in a surprising way if outPath
is present, hence the removeAttrs
call):
packages.x86_64-linux.sourceInfo = pkgs.writers.writeJSON "sourceInfo.json" (builtins.removeAttrs self.sourceInfo [ "outPath" ]);
If I build and print this output, I get all the metadata I expected
$ nix build .#sourceInfo
$ cat result
{
"dirtyRev": "c8bec8697f5dda3eb27a8703ea1004ba6bac1c8a-dirty",
"dirtyShortRev": "c8bec86-dirty",
"lastModified": 1738331265,
"lastModifiedDate": "20250131134745",
"narHash": "sha256-MIrEQ686AL6QdvsU+guE3Zh5UnL9V7mu0MZAu413R38=",
"submodules": false
}
I now want to have a second output of my flake that is a script which when run, evaluates and builds the first attribute.
I can get a basic version of this by doing nix build .#sourceInfo
packages.x86_64-linux.print-sourceinfo = pkgs.writeShellScriptBin "print-sourceinfo" ''
cat $(nix build --no-link --print-out-paths .#sourceInfo)
'';
The above works, but only if the current working directory is the actual flake that I want to run. I would like to be able to run this script from any source, using something as nix run github:org/repo#print-sourceinfo
.
I can get pretty close to this by using self
as the flake ref in the script:
outputs = { self, nixpkgs }:
let pkgs = nixpkgs.legacyPackages.x86_64-linux; in {
packages.x86_64-linux.sourceInfo =
pkgs.writers.writeJSON "sourceInfo.json" (builtins.removeAttrs self.sourceInfo [ "outPath" ]);
packages.x86_64-linux.print-sourceinfo = pkgs.writeShellScriptBin "print-metadata" ''
cat $(nix build --no-link --print-out-paths ${self}#sourceInfo)
'';
};
Unfortunately, because ${self}
is just a store path, this setup loses any of the Git metadata that is associated with the flake.
$ nix run .#print-sourceinfo
{
"lastModified": 0,
"lastModifiedDate": "19700101000000",
"narHash": "sha256-Ze0b5N+a3RklxbLX7bEFVCOv+PSo2rr2Zq83Yd1bbhM="
}
I'd like a way to replicate this setup, but while preserving the sourceInfo
metadata.
Proposed solution
Effectively I need to be able to get a reference to the current flake as a string that can be embedded into a script.
There should be a function that can get take a sourceInfo
and produce a flake ref that is locked to that source, including all the associated metadata.
The CLI has the nix flake metadata
command that prints a "locked URL". If this URL was exposed inside the flake, then my script could embed it and be certain to be re-evaluating itself with the same source and metadata by using nix build ${self.lockedUrl}
. It's worth noting that even in the CLI, the "Locked URL" is not printed if the local Git tree is dirty.
Alternative solutions
I can actually get some of the way there by using path:${self}?rev=${self.sourceInfo.rev}
as the flake reference in my script. Somehow the path
flake scheme allows a ref
to be specified. It does not however allow a dirtyRev
to be used, which means that I cannot use this solution in the case where the Git tree has local modifications.
Additional context
My use case of having a flake output evaluate the flake itself might seemed a bit contrived. In pratice this script is used to deploy a NixOS configuration from the flake to a machine. It accepts a hostname as an argument, and when run evalutes ${self}#nixosConfigurations.$hostname
and deploys it to the machine.
The overall workflow should be to run nix run github:org/repo#deploy <hostname>
, which would use the deploy script and NixOS configuration found in the flake.
I do not want to embed the built system configuration into the deploy script, since we have many machines and they should be all be evaluated and built lazily when needed by the script, and not eagerly when the script is being built.
Add 👍 to issues you find important.