Skip to content

Commit

Permalink
Add parseFlakeRef and flakeRefToString builtins (NixOS#8670)
Browse files Browse the repository at this point in the history
Over the last year or so I've run into several use cases where I need to
parse and/or serialize URLs for use by `builtins.fetchTree` or
`builtins.getFlake`, largely in order to produce _lockfile-like_ files
for lang2nix frameworks or tools which use `nix` internally to drive
builds.

I've gone through the painstaking process of emulating
`nix::FlakeRef::fromAttrs` and `nix::parseFlakeRef` several times with
mixed success; but these are difficult to create and even harder to
maintain if I hope to stay aligned with changes to the real
parser/serializer.

I understand why adding new `builtins` isn't something we want to do
flagrantly. I'm recommending this addition simply because I keep
encountering use cases where I need to parse/serialize these URIs in
`nix` expressions, and I want a reliable solution.

Co-authored-by: Eelco Dolstra <[email protected]>
Co-authored-by: John Ericson <[email protected]>
  • Loading branch information
3 people authored Jul 25, 2023
1 parent 484c820 commit 2d1d811
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 0 deletions.
7 changes: 7 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# Release X.Y (202?-??-??)

- Two new builtin functions,
[`builtins.parseFlakeRef`](@docroot@/language/builtins.md#builtins-parseFlakeRef)
and
[`builtins.flakeRefToString`](@docroot@/language/builtins.md#builtins-flakeRefToString),
have been added.
These functions are useful for converting between flake references encoded as attribute sets and URLs.
95 changes: 95 additions & 0 deletions src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,101 @@ static RegisterPrimOp r2({
.experimentalFeature = Xp::Flakes,
});

static void prim_parseFlakeRef(
EvalState & state,
const PosIdx pos,
Value * * args,
Value & v)
{
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos,
"while evaluating the argument passed to builtins.parseFlakeRef"));
auto attrs = parseFlakeRef(flakeRefS, {}, true).toAttrs();
auto binds = state.buildBindings(attrs.size());
for (const auto & [key, value] : attrs) {
auto s = state.symbols.create(key);
auto & vv = binds.alloc(s);
std::visit(overloaded {
[&vv](const std::string & value) { vv.mkString(value); },
[&vv](const uint64_t & value) { vv.mkInt(value); },
[&vv](const Explicit<bool> & value) { vv.mkBool(value.t); }
}, value);
}
v.mkAttrs(binds);
}

static RegisterPrimOp r3({
.name = "__parseFlakeRef",
.args = {"flake-ref"},
.doc = R"(
Parse a flake reference, and return its exploded form.
For example:
```nix
builtins.parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib"
```
evaluates to:
```nix
{ dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; }
```
)",
.fun = prim_parseFlakeRef,
.experimentalFeature = Xp::Flakes,
});


static void prim_flakeRefToString(
EvalState & state,
const PosIdx pos,
Value * * args,
Value & v)
{
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs) {
auto t = attr.value->type();
if (t == nInt) {
attrs.emplace(state.symbols[attr.name],
(uint64_t) attr.value->integer);
} else if (t == nBool) {
attrs.emplace(state.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->str()));
} else {
state.error(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.symbols[attr.name],
showType(*attr.value)).debugThrow<EvalError>();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);
v.mkString(flakeRef.to_string());
}

static RegisterPrimOp r4({
.name = "__flakeRefToString",
.args = {"attrs"},
.doc = R"(
Convert a flake reference from attribute set format to URL format.
For example:
```nix
builtins.flakeRefToString {
dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github";
}
```
evaluates to
```nix
"github:NixOS/nixpkgs/23.05?dir=lib"
```
)",
.fun = prim_flakeRefToString,
.experimentalFeature = Xp::Flakes,
});

}

Fingerprint LockedFlake::getFingerprint() const
Expand Down
1 change: 1 addition & 0 deletions tests/lang/eval-okay-flake-ref-to-string.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"github:NixOS/nixpkgs/23.05?dir=lib"
7 changes: 7 additions & 0 deletions tests/lang/eval-okay-flake-ref-to-string.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
builtins.flakeRefToString {
type = "github";
owner = "NixOS";
repo = "nixpkgs";
ref = "23.05";
dir = "lib";
}
1 change: 1 addition & 0 deletions tests/lang/eval-okay-parse-flake-ref.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; }
1 change: 1 addition & 0 deletions tests/lang/eval-okay-parse-flake-ref.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
builtins.parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib"

0 comments on commit 2d1d811

Please sign in to comment.