mirror of
https://github.com/NixOS/nix.git
synced 2025-12-07 09:31:01 +01:00
Move /src to /subprojects
This will facilitate breaking up Nix into multiple packages for each component with Meson.
This commit is contained in:
parent
4db9487823
commit
84e2963f8e
737 changed files with 504 additions and 505 deletions
332
subprojects/libexpr/primops/context.cc
Normal file
332
subprojects/libexpr/primops/context.cc
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
||||
v.mkString(*s);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_unsafeDiscardStringContext({
|
||||
.name = "__unsafeDiscardStringContext",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Discard the [string context](@docroot@/language/string-context.md) from a value that can be coerced to a string.
|
||||
)",
|
||||
.fun = prim_unsafeDiscardStringContext,
|
||||
});
|
||||
|
||||
|
||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
||||
v.mkBool(!context.empty());
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_hasContext({
|
||||
.name = "__hasContext",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Return `true` if string *s* has a non-empty context.
|
||||
The context can be obtained with
|
||||
[`getContext`](#builtins-getContext).
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Many operations require a string context to be empty because they are intended only to work with "regular" strings, and also to help users avoid unintentionally loosing track of string context elements.
|
||||
> `builtins.hasContext` can help create better domain-specific errors in those case.
|
||||
>
|
||||
> ```nix
|
||||
> name: meta:
|
||||
>
|
||||
> if builtins.hasContext name
|
||||
> then throw "package name cannot contain string context"
|
||||
> else { ${name} = meta; }
|
||||
> ```
|
||||
)",
|
||||
.fun = prim_hasContext
|
||||
});
|
||||
|
||||
|
||||
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
||||
|
||||
NixStringContext context2;
|
||||
for (auto && c : context) {
|
||||
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) {
|
||||
context2.emplace(NixStringContextElem::Opaque {
|
||||
.path = ptr->drvPath
|
||||
});
|
||||
} else {
|
||||
/* Can reuse original item */
|
||||
context2.emplace(std::move(c).raw);
|
||||
}
|
||||
}
|
||||
|
||||
v.mkString(*s, context2);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_unsafeDiscardOutputDependency({
|
||||
.name = "__unsafeDiscardOutputDependency",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Create a copy of the given string where every
|
||||
[derivation deep](@docroot@/language/string-context.md#string-context-element-derivation-deep)
|
||||
string context element is turned into a
|
||||
[constant](@docroot@/language/string-context.md#string-context-element-constant)
|
||||
string context element.
|
||||
|
||||
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
|
||||
|
||||
This is unsafe because it allows us to "forget" store objects we would have otherwise referred to with the string context,
|
||||
whereas Nix normally tracks all dependencies consistently.
|
||||
Safe operations "grow" but never "shrink" string contexts.
|
||||
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).
|
||||
Replacing a constant string context element with a "derivation deep" element is a safe operation that just enlargens the string context without forgetting anything.
|
||||
|
||||
[`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies
|
||||
)",
|
||||
.fun = prim_unsafeDiscardOutputDependency
|
||||
});
|
||||
|
||||
|
||||
static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
|
||||
|
||||
auto contextSize = context.size();
|
||||
if (contextSize != 1) {
|
||||
state.error<EvalError>(
|
||||
"context of string '%s' must have exactly one element, but has %d",
|
||||
*s,
|
||||
contextSize
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
NixStringContext context2 {
|
||||
(NixStringContextElem { std::visit(overloaded {
|
||||
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
|
||||
if (!c.path.isDerivation()) {
|
||||
state.error<EvalError>(
|
||||
"path '%s' is not a derivation",
|
||||
state.store->printStorePath(c.path)
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
return NixStringContextElem::DrvDeep {
|
||||
.drvPath = c.path,
|
||||
};
|
||||
},
|
||||
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
|
||||
state.error<EvalError>(
|
||||
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
|
||||
c.output
|
||||
).atPos(pos).debugThrow();
|
||||
},
|
||||
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
|
||||
/* Reuse original item because we want this to be idempotent. */
|
||||
return std::move(c);
|
||||
},
|
||||
}, context.begin()->raw) }),
|
||||
};
|
||||
|
||||
v.mkString(*s, context2);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_addDrvOutputDependencies({
|
||||
.name = "__addDrvOutputDependencies",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Create a copy of the given string where a single
|
||||
[constant](@docroot@/language/string-context.md#string-context-element-constant)
|
||||
string context element is turned into a
|
||||
[derivation deep](@docroot@/language/string-context.md#string-context-element-derivation-deep)
|
||||
string context element.
|
||||
|
||||
The store path that is the constant string context element should point to a valid derivation, and end in `.drv`.
|
||||
|
||||
The original string context element must not be empty or have multiple elements, and it must not have any other type of element other than a constant or derivation deep element.
|
||||
The latter is supported so this function is idempotent.
|
||||
|
||||
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
|
||||
)",
|
||||
.fun = prim_addDrvOutputDependencies
|
||||
});
|
||||
|
||||
|
||||
/* Extract the context of a string as a structured Nix value.
|
||||
|
||||
The context is represented as an attribute set whose keys are the
|
||||
paths in the context set and whose values are attribute sets with
|
||||
the following keys:
|
||||
path: True if the relevant path is in the context as a plain store
|
||||
path (i.e. the kind of context you get when interpolating
|
||||
a Nix path (e.g. ./.) into a string). False if missing.
|
||||
allOutputs: True if the relevant path is a derivation and it is
|
||||
in the context as a drv file with all of its outputs
|
||||
(i.e. the kind of context you get when referencing
|
||||
.drvPath of some derivation). False if missing.
|
||||
outputs: If a non-empty list, the relevant path is a derivation
|
||||
and the provided outputs are referenced in the context
|
||||
(i.e. the kind of context you get when referencing
|
||||
.outPath of some derivation). Empty list if missing.
|
||||
Note that for a given path any combination of the above attributes
|
||||
may be present.
|
||||
*/
|
||||
static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
struct ContextInfo {
|
||||
bool path = false;
|
||||
bool allOutputs = false;
|
||||
Strings outputs;
|
||||
};
|
||||
NixStringContext context;
|
||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
||||
auto contextInfos = std::map<StorePath, ContextInfo>();
|
||||
for (auto && i : context) {
|
||||
std::visit(overloaded {
|
||||
[&](NixStringContextElem::DrvDeep && d) {
|
||||
contextInfos[std::move(d.drvPath)].allOutputs = true;
|
||||
},
|
||||
[&](NixStringContextElem::Built && b) {
|
||||
// FIXME should eventually show string context as is, no
|
||||
// resolving here.
|
||||
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
|
||||
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
|
||||
},
|
||||
[&](NixStringContextElem::Opaque && o) {
|
||||
contextInfos[std::move(o.path)].path = true;
|
||||
},
|
||||
}, ((NixStringContextElem &&) i).raw);
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(contextInfos.size());
|
||||
|
||||
auto sPath = state.symbols.create("path");
|
||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||
for (const auto & info : contextInfos) {
|
||||
auto infoAttrs = state.buildBindings(3);
|
||||
if (info.second.path)
|
||||
infoAttrs.alloc(sPath).mkBool(true);
|
||||
if (info.second.allOutputs)
|
||||
infoAttrs.alloc(sAllOutputs).mkBool(true);
|
||||
if (!info.second.outputs.empty()) {
|
||||
auto list = state.buildList(info.second.outputs.size());
|
||||
for (const auto & [i, output] : enumerate(info.second.outputs))
|
||||
(list[i] = state.allocValue())->mkString(output);
|
||||
infoAttrs.alloc(state.sOutputs).mkList(list);
|
||||
}
|
||||
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_getContext({
|
||||
.name = "__getContext",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Return the string context of *s*.
|
||||
|
||||
The string context tracks references to derivations within a string.
|
||||
It is represented as an attribute set of [store derivation](@docroot@/glossary.md#gloss-store-derivation) paths mapping to output names.
|
||||
|
||||
Using [string interpolation](@docroot@/language/string-interpolation.md) on a derivation will add that derivation to the string context.
|
||||
For example,
|
||||
|
||||
```nix
|
||||
builtins.getContext "${derivation { name = "a"; builder = "b"; system = "c"; }}"
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```
|
||||
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_getContext
|
||||
});
|
||||
|
||||
|
||||
/* Append the given context to a given string.
|
||||
|
||||
See the commentary above getContext for details of the
|
||||
context representation.
|
||||
*/
|
||||
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
||||
|
||||
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
|
||||
|
||||
auto sPath = state.symbols.create("path");
|
||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||
for (auto & i : *args[1]->attrs()) {
|
||||
const auto & name = state.symbols[i.name];
|
||||
if (!state.store->isStorePath(name))
|
||||
state.error<EvalError>(
|
||||
"context key '%s' is not a store path",
|
||||
name
|
||||
).atPos(i.pos).debugThrow();
|
||||
auto namePath = state.store->parseStorePath(name);
|
||||
if (!settings.readOnlyMode)
|
||||
state.store->ensurePath(namePath);
|
||||
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
||||
|
||||
if (auto attr = i.value->attrs()->get(sPath)) {
|
||||
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `path` attribute of a string context"))
|
||||
context.emplace(NixStringContextElem::Opaque {
|
||||
.path = namePath,
|
||||
});
|
||||
}
|
||||
|
||||
if (auto attr = i.value->attrs()->get(sAllOutputs)) {
|
||||
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `allOutputs` attribute of a string context")) {
|
||||
if (!isDerivation(name)) {
|
||||
state.error<EvalError>(
|
||||
"tried to add all-outputs context of %s, which is not a derivation, to a string",
|
||||
name
|
||||
).atPos(i.pos).debugThrow();
|
||||
}
|
||||
context.emplace(NixStringContextElem::DrvDeep {
|
||||
.drvPath = namePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (auto attr = i.value->attrs()->get(state.sOutputs)) {
|
||||
state.forceList(*attr->value, attr->pos, "while evaluating the `outputs` attribute of a string context");
|
||||
if (attr->value->listSize() && !isDerivation(name)) {
|
||||
state.error<EvalError>(
|
||||
"tried to add derivation output context of %s, which is not a derivation, to a string",
|
||||
name
|
||||
).atPos(i.pos).debugThrow();
|
||||
}
|
||||
for (auto elem : attr->value->listItems()) {
|
||||
auto outputName = state.forceStringNoCtx(*elem, attr->pos, "while evaluating an output name within a string context");
|
||||
context.emplace(NixStringContextElem::Built {
|
||||
.drvPath = makeConstantStorePathRef(namePath),
|
||||
.output = std::string { outputName },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.mkString(orig, context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_appendContext({
|
||||
.name = "__appendContext",
|
||||
.arity = 2,
|
||||
.fun = prim_appendContext
|
||||
});
|
||||
|
||||
}
|
||||
52
subprojects/libexpr/primops/derivation.nix
Normal file
52
subprojects/libexpr/primops/derivation.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# This is the implementation of the ‘derivation’ builtin function.
|
||||
# It's actually a wrapper around the ‘derivationStrict’ primop.
|
||||
# Note that the following comment will be shown in :doc in the repl, but not in the manual.
|
||||
|
||||
/**
|
||||
Create a derivation.
|
||||
|
||||
# Inputs
|
||||
|
||||
The single argument is an attribute set that describes what to build and how to build it.
|
||||
See https://nix.dev/manual/nix/2.23/language/derivations
|
||||
|
||||
# Output
|
||||
|
||||
The result is an attribute set that describes the derivation.
|
||||
Notably it contains the outputs, which in the context of the Nix language are special strings that refer to the output paths, which may not yet exist.
|
||||
The realisation of these outputs only occurs when needed; for example
|
||||
|
||||
* When `nix-build` or a similar command is run, it realises the outputs that were requested on its command line.
|
||||
See https://nix.dev/manual/nix/2.23/command-ref/nix-build
|
||||
|
||||
* When `import`, `readFile`, `readDir` or some other functions are called, they have to realise the outputs they depend on.
|
||||
This is referred to as "import from derivation".
|
||||
See https://nix.dev/manual/nix/2.23/language/import-from-derivation
|
||||
|
||||
Note that `derivation` is very bare-bones, and provides almost no commands during the build.
|
||||
Most likely, you'll want to use functions like `stdenv.mkDerivation` in Nixpkgs to set up a basic environment.
|
||||
*/
|
||||
drvAttrs @ { outputs ? [ "out" ], ... }:
|
||||
|
||||
let
|
||||
|
||||
strict = derivationStrict drvAttrs;
|
||||
|
||||
commonAttrs = drvAttrs // (builtins.listToAttrs outputsList) //
|
||||
{ all = map (x: x.value) outputsList;
|
||||
inherit drvAttrs;
|
||||
};
|
||||
|
||||
outputToAttrListElement = outputName:
|
||||
{ name = outputName;
|
||||
value = commonAttrs // {
|
||||
outPath = builtins.getAttr outputName strict;
|
||||
drvPath = strict.drvPath;
|
||||
type = "derivation";
|
||||
inherit outputName;
|
||||
};
|
||||
};
|
||||
|
||||
outputsList = map outputToAttrListElement outputs;
|
||||
|
||||
in (builtins.head outputsList).value
|
||||
287
subprojects/libexpr/primops/fetchClosure.cc
Normal file
287
subprojects/libexpr/primops/fetchClosure.cc
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#include "primops.hh"
|
||||
#include "store-api.hh"
|
||||
#include "realisation.hh"
|
||||
#include "make-content-addressed.hh"
|
||||
#include "url.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Handler for the content addressed case.
|
||||
*
|
||||
* @param state Evaluator state and store to write to.
|
||||
* @param fromStore Store containing the path to rewrite.
|
||||
* @param fromPath Source path to be rewritten.
|
||||
* @param toPathMaybe Path to write the rewritten path to. If empty, the error shows the actual path.
|
||||
* @param v Return `Value`
|
||||
*/
|
||||
static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, const std::optional<StorePath> & toPathMaybe, Value &v) {
|
||||
|
||||
// establish toPath or throw
|
||||
|
||||
if (!toPathMaybe || !state.store->isValidPath(*toPathMaybe)) {
|
||||
auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath);
|
||||
if (toPathMaybe && *toPathMaybe != rewrittenPath)
|
||||
throw Error({
|
||||
.msg = HintFmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
|
||||
state.store->printStorePath(fromPath),
|
||||
state.store->printStorePath(rewrittenPath),
|
||||
state.store->printStorePath(*toPathMaybe)),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
if (!toPathMaybe)
|
||||
throw Error({
|
||||
.msg = HintFmt(
|
||||
"rewriting '%s' to content-addressed form yielded '%s'\n"
|
||||
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
|
||||
state.store->printStorePath(fromPath),
|
||||
state.store->printStorePath(rewrittenPath)),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
auto toPath = *toPathMaybe;
|
||||
|
||||
// check and return
|
||||
|
||||
auto resultInfo = state.store->queryPathInfo(toPath);
|
||||
|
||||
if (!resultInfo->isContentAddressed(*state.store)) {
|
||||
// We don't perform the rewriting when outPath already exists, as an optimisation.
|
||||
// However, we can quickly detect a mistake if the toPath is input addressed.
|
||||
throw Error({
|
||||
.msg = HintFmt(
|
||||
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
|
||||
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
|
||||
state.store->printStorePath(toPath)),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
state.mkStorePathString(toPath, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the closure and make sure it's content addressed.
|
||||
*/
|
||||
static void runFetchClosureWithContentAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
|
||||
|
||||
if (!state.store->isValidPath(fromPath))
|
||||
copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
|
||||
|
||||
auto info = state.store->queryPathInfo(fromPath);
|
||||
|
||||
if (!info->isContentAddressed(*state.store)) {
|
||||
throw Error({
|
||||
.msg = HintFmt(
|
||||
"The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
|
||||
"If you do intend to fetch an input-addressed store path, add\n\n"
|
||||
" inputAddressed = true;\n\n"
|
||||
"to the 'fetchClosure' arguments.\n\n"
|
||||
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
|
||||
state.store->printStorePath(fromPath)),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
state.mkStorePathString(fromPath, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the closure and make sure it's input addressed.
|
||||
*/
|
||||
static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
|
||||
|
||||
if (!state.store->isValidPath(fromPath))
|
||||
copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
|
||||
|
||||
auto info = state.store->queryPathInfo(fromPath);
|
||||
|
||||
if (info->isContentAddressed(*state.store)) {
|
||||
throw Error({
|
||||
.msg = HintFmt(
|
||||
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
|
||||
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
|
||||
state.store->printStorePath(fromPath)),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
state.mkStorePathString(fromPath, v);
|
||||
}
|
||||
|
||||
typedef std::optional<StorePath> StorePathOrGap;
|
||||
|
||||
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
|
||||
|
||||
std::optional<std::string> fromStoreUrl;
|
||||
std::optional<StorePath> fromPath;
|
||||
std::optional<StorePathOrGap> toPath;
|
||||
std::optional<bool> inputAddressedMaybe;
|
||||
|
||||
for (auto & attr : *args[0]->attrs()) {
|
||||
const auto & attrName = state.symbols[attr.name];
|
||||
auto attrHint = [&]() -> std::string {
|
||||
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
|
||||
};
|
||||
|
||||
if (attrName == "fromPath") {
|
||||
NixStringContext context;
|
||||
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
|
||||
}
|
||||
|
||||
else if (attrName == "toPath") {
|
||||
state.forceValue(*attr.value, attr.pos);
|
||||
bool isEmptyString = attr.value->type() == nString && attr.value->string_view() == "";
|
||||
if (isEmptyString) {
|
||||
toPath = StorePathOrGap {};
|
||||
}
|
||||
else {
|
||||
NixStringContext context;
|
||||
toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
|
||||
}
|
||||
}
|
||||
|
||||
else if (attrName == "fromStore")
|
||||
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
|
||||
attrHint());
|
||||
|
||||
else if (attrName == "inputAddressed")
|
||||
inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint());
|
||||
|
||||
else
|
||||
throw Error({
|
||||
.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
if (!fromPath)
|
||||
throw Error({
|
||||
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
|
||||
bool inputAddressed = inputAddressedMaybe.value_or(false);
|
||||
|
||||
if (inputAddressed) {
|
||||
if (toPath)
|
||||
throw Error({
|
||||
.msg = HintFmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
|
||||
"inputAddressed",
|
||||
"toPath"),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
|
||||
if (!fromStoreUrl)
|
||||
throw Error({
|
||||
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
|
||||
auto parsedURL = parseURL(*fromStoreUrl);
|
||||
|
||||
if (parsedURL.scheme != "http" &&
|
||||
parsedURL.scheme != "https" &&
|
||||
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
|
||||
throw Error({
|
||||
.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
|
||||
if (!parsedURL.query.empty())
|
||||
throw Error({
|
||||
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
|
||||
.pos = state.positions[pos]
|
||||
});
|
||||
|
||||
auto fromStore = openStore(parsedURL.to_string());
|
||||
|
||||
if (toPath)
|
||||
runFetchClosureWithRewrite(state, pos, *fromStore, *fromPath, *toPath, v);
|
||||
else if (inputAddressed)
|
||||
runFetchClosureWithInputAddressedPath(state, pos, *fromStore, *fromPath, v);
|
||||
else
|
||||
runFetchClosureWithContentAddressedPath(state, pos, *fromStore, *fromPath, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchClosure({
|
||||
.name = "__fetchClosure",
|
||||
.args = {"args"},
|
||||
.doc = R"(
|
||||
Fetch a store path [closure](@docroot@/glossary.md#gloss-closure) from a binary cache, and return the store path as a string with context.
|
||||
|
||||
This function can be invoked in three ways, that we will discuss in order of preference.
|
||||
|
||||
**Fetch a content-addressed store path**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
|
||||
}
|
||||
```
|
||||
|
||||
This is the simplest invocation, and it does not require the user of the expression to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
|
||||
|
||||
If your store path is [input addressed](@docroot@/glossary.md#gloss-input-addressed-store-object) instead of content addressed, consider the other two invocations.
|
||||
|
||||
**Fetch any store path and rewrite it to a fully content-addressed store path**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
|
||||
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
|
||||
}
|
||||
```
|
||||
|
||||
This example fetches `/nix/store/r2jd...` from the specified binary cache,
|
||||
and rewrites it into the content-addressed store path
|
||||
`/nix/store/ldbh...`.
|
||||
|
||||
Like the previous example, no extra configuration or privileges are required.
|
||||
|
||||
To find out the correct value for `toPath` given a `fromPath`,
|
||||
use [`nix store make-content-addressed`](@docroot@/command-ref/new-cli/nix3-store-make-content-addressed.md):
|
||||
|
||||
```console
|
||||
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
|
||||
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
|
||||
```
|
||||
|
||||
Alternatively, set `toPath = ""` and find the correct `toPath` in the error message.
|
||||
|
||||
**Fetch an input-addressed store path as is**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
|
||||
inputAddressed = true;
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to fetch an [input-addressed store path](@docroot@/glossary.md#gloss-input-addressed-store-object) and return it as is.
|
||||
However, this is the least preferred way of invoking `fetchClosure`, because it requires that the input-addressed paths are trusted by the Nix configuration.
|
||||
|
||||
**`builtins.storePath`**
|
||||
|
||||
`fetchClosure` is similar to [`builtins.storePath`](#builtins-storePath) in that it allows you to use a previously built store path in a Nix expression.
|
||||
However, `fetchClosure` is more reproducible because it specifies a binary cache from which the path can be fetched.
|
||||
Also, using content-addressed store paths does not require users to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
|
||||
)",
|
||||
.fun = prim_fetchClosure,
|
||||
.experimentalFeature = Xp::FetchClosure,
|
||||
});
|
||||
|
||||
}
|
||||
91
subprojects/libexpr/primops/fetchMercurial.cc
Normal file
91
subprojects/libexpr/primops/fetchMercurial.cc
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "eval-settings.hh"
|
||||
#include "store-api.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "url.hh"
|
||||
#include "url-parts.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
std::string url;
|
||||
std::optional<Hash> rev;
|
||||
std::optional<std::string> ref;
|
||||
std::string_view name = "source";
|
||||
NixStringContext context;
|
||||
|
||||
state.forceValue(*args[0], pos);
|
||||
|
||||
if (args[0]->type() == nAttrs) {
|
||||
|
||||
for (auto & attr : *args[0]->attrs()) {
|
||||
std::string_view n(state.symbols[attr.name]);
|
||||
if (n == "url")
|
||||
url = state.coerceToString(attr.pos, *attr.value, context,
|
||||
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
else if (n == "rev") {
|
||||
// Ugly: unlike fetchGit, here the "rev" attribute can
|
||||
// be both a revision or a branch/tag name.
|
||||
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
|
||||
if (std::regex_match(value.begin(), value.end(), revRegex))
|
||||
rev = Hash::parseAny(value, HashAlgorithm::SHA1);
|
||||
else
|
||||
ref = value;
|
||||
}
|
||||
else if (n == "name")
|
||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
|
||||
else
|
||||
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
|
||||
}
|
||||
|
||||
if (url.empty())
|
||||
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
|
||||
|
||||
} else
|
||||
url = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
|
||||
// FIXME: git externals probably can be used to bypass the URI
|
||||
// whitelist. Ah well.
|
||||
state.checkURI(url);
|
||||
|
||||
if (state.settings.pureEval && !rev)
|
||||
throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
attrs.insert_or_assign("type", "hg");
|
||||
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
|
||||
attrs.insert_or_assign("name", std::string(name));
|
||||
if (ref) attrs.insert_or_assign("ref", *ref);
|
||||
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
||||
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||
|
||||
auto attrs2 = state.buildBindings(8);
|
||||
state.mkStorePathString(storePath, attrs2.alloc(state.sOutPath));
|
||||
if (input2.getRef())
|
||||
attrs2.alloc("branch").mkString(*input2.getRef());
|
||||
// Backward compatibility: set 'rev' to
|
||||
// 0000000000000000000000000000000000000000 for a dirty tree.
|
||||
auto rev2 = input2.getRev().value_or(Hash(HashAlgorithm::SHA1));
|
||||
attrs2.alloc("rev").mkString(rev2.gitRev());
|
||||
attrs2.alloc("shortRev").mkString(rev2.gitRev().substr(0, 12));
|
||||
if (auto revCount = input2.getRevCount())
|
||||
attrs2.alloc("revCount").mkInt(*revCount);
|
||||
v.mkAttrs(attrs2);
|
||||
|
||||
state.allowPath(storePath);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r_fetchMercurial({
|
||||
.name = "fetchMercurial",
|
||||
.arity = 1,
|
||||
.fun = prim_fetchMercurial
|
||||
});
|
||||
|
||||
}
|
||||
826
subprojects/libexpr/primops/fetchTree.cc
Normal file
826
subprojects/libexpr/primops/fetchTree.cc
Normal file
|
|
@ -0,0 +1,826 @@
|
|||
#include "attrs.hh"
|
||||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "eval-settings.hh"
|
||||
#include "store-api.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "registry.hh"
|
||||
#include "tarball.hh"
|
||||
#include "url.hh"
|
||||
#include "value-to-json.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void emitTreeAttrs(
|
||||
EvalState & state,
|
||||
const StorePath & storePath,
|
||||
const fetchers::Input & input,
|
||||
Value & v,
|
||||
bool emptyRevFallback,
|
||||
bool forceDirty)
|
||||
{
|
||||
auto attrs = state.buildBindings(100);
|
||||
|
||||
state.mkStorePathString(storePath, attrs.alloc(state.sOutPath));
|
||||
|
||||
// FIXME: support arbitrary input attributes.
|
||||
|
||||
auto narHash = input.getNarHash();
|
||||
assert(narHash);
|
||||
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
|
||||
|
||||
if (input.getType() == "git")
|
||||
attrs.alloc("submodules").mkBool(
|
||||
fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
|
||||
|
||||
if (!forceDirty) {
|
||||
|
||||
if (auto rev = input.getRev()) {
|
||||
attrs.alloc("rev").mkString(rev->gitRev());
|
||||
attrs.alloc("shortRev").mkString(rev->gitShortRev());
|
||||
} else if (emptyRevFallback) {
|
||||
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
|
||||
auto emptyHash = Hash(HashAlgorithm::SHA1);
|
||||
attrs.alloc("rev").mkString(emptyHash.gitRev());
|
||||
attrs.alloc("shortRev").mkString(emptyHash.gitShortRev());
|
||||
}
|
||||
|
||||
if (auto revCount = input.getRevCount())
|
||||
attrs.alloc("revCount").mkInt(*revCount);
|
||||
else if (emptyRevFallback)
|
||||
attrs.alloc("revCount").mkInt(0);
|
||||
|
||||
}
|
||||
|
||||
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
|
||||
attrs.alloc("dirtyRev").mkString(*dirtyRev);
|
||||
attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev"));
|
||||
}
|
||||
|
||||
if (auto lastModified = input.getLastModified()) {
|
||||
attrs.alloc("lastModified").mkInt(*lastModified);
|
||||
attrs.alloc("lastModifiedDate").mkString(
|
||||
fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S")));
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
struct FetchTreeParams {
|
||||
bool emptyRevFallback = false;
|
||||
bool allowNameArgument = false;
|
||||
bool isFetchGit = false;
|
||||
};
|
||||
|
||||
static void fetchTree(
|
||||
EvalState & state,
|
||||
const PosIdx pos,
|
||||
Value * * args,
|
||||
Value & v,
|
||||
const FetchTreeParams & params = FetchTreeParams{}
|
||||
) {
|
||||
fetchers::Input input { state.fetchSettings };
|
||||
NixStringContext context;
|
||||
std::optional<std::string> type;
|
||||
if (params.isFetchGit) type = "git";
|
||||
|
||||
state.forceValue(*args[0], pos);
|
||||
|
||||
if (args[0]->type() == nAttrs) {
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
|
||||
if (auto aType = args[0]->attrs()->get(state.sType)) {
|
||||
if (type)
|
||||
state.error<EvalError>(
|
||||
"unexpected attribute 'type'"
|
||||
).atPos(pos).debugThrow();
|
||||
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
||||
} else if (!type)
|
||||
state.error<EvalError>(
|
||||
"attribute 'type' is missing in call to 'fetchTree'"
|
||||
).atPos(pos).debugThrow();
|
||||
|
||||
attrs.emplace("type", type.value());
|
||||
|
||||
for (auto & attr : *args[0]->attrs()) {
|
||||
if (attr.name == state.sType) continue;
|
||||
state.forceValue(*attr.value, attr.pos);
|
||||
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
||||
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
|
||||
attrs.emplace(state.symbols[attr.name],
|
||||
params.isFetchGit && state.symbols[attr.name] == "url"
|
||||
? fixGitURL(s)
|
||||
: s);
|
||||
}
|
||||
else if (attr.value->type() == nBool)
|
||||
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean()});
|
||||
else if (attr.value->type() == nInt) {
|
||||
auto intValue = attr.value->integer().value;
|
||||
|
||||
if (intValue < 0) {
|
||||
state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
} else if (state.symbols[attr.name] == "publicKeys") {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
|
||||
}
|
||||
else
|
||||
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||
}
|
||||
|
||||
if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
|
||||
attrs.emplace("exportIgnore", Explicit<bool>{true});
|
||||
}
|
||||
|
||||
// fetchTree should fetch git repos with shallow = true by default
|
||||
if (type == "git" && !params.isFetchGit && !attrs.contains("shallow")) {
|
||||
attrs.emplace("shallow", Explicit<bool>{true});
|
||||
}
|
||||
|
||||
if (!params.allowNameArgument)
|
||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||
state.error<EvalError>(
|
||||
"attribute 'name' isn’t supported in call to 'fetchTree'"
|
||||
).atPos(pos).debugThrow();
|
||||
|
||||
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
} else {
|
||||
auto url = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to the fetcher",
|
||||
false, false).toOwned();
|
||||
|
||||
if (params.isFetchGit) {
|
||||
fetchers::Attrs attrs;
|
||||
attrs.emplace("type", "git");
|
||||
attrs.emplace("url", fixGitURL(url));
|
||||
if (!attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
|
||||
attrs.emplace("exportIgnore", Explicit<bool>{true});
|
||||
}
|
||||
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
} else {
|
||||
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
state.error<EvalError>(
|
||||
"passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"
|
||||
).atPos(pos).debugThrow();
|
||||
input = fetchers::Input::fromURL(state.fetchSettings, url);
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.store, input).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked()) {
|
||||
auto fetcher = "fetchTree";
|
||||
if (params.isFetchGit)
|
||||
fetcher = "fetchGit";
|
||||
|
||||
state.error<EvalError>(
|
||||
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'",
|
||||
fetcher, input.to_string()
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
state.checkURI(input.toURLString());
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||
|
||||
state.allowPath(storePath);
|
||||
|
||||
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
|
||||
}
|
||||
|
||||
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetchTree(state, pos, args, v, { });
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchTree({
|
||||
.name = "fetchTree",
|
||||
.args = {"input"},
|
||||
.doc = R"(
|
||||
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
|
||||
|
||||
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
|
||||
- the corresponding [NAR](@docroot@/store/file-system-object/content-address.md#serial-nix-archive) hash
|
||||
- backend-specific metadata (currently not documented). <!-- TODO: document output attributes -->
|
||||
|
||||
*input* must be an attribute set with the following attributes:
|
||||
|
||||
- `type` (String, required)
|
||||
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
|
||||
- `narHash` (String, optional)
|
||||
|
||||
The `narHash` parameter can be used to substitute the source of the tree.
|
||||
It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism.
|
||||
If `narHash` is set, the source is first looked up is the Nix store and [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
|
||||
|
||||
A subset of the output attributes of `fetchTree` can be re-used for subsequent calls to `fetchTree` to produce the same result again.
|
||||
That is, `fetchTree` is idempotent.
|
||||
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source will be fetched from the network if both are true:
|
||||
- A NAR hash is supplied and the corresponding store path is not [valid](@docroot@/glossary.md#gloss-validity), that is, not available in the store
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
|
||||
## Source types
|
||||
|
||||
The following source types and associated input attributes are supported.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
|
||||
- `"file"`
|
||||
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
Supported protocols:
|
||||
|
||||
- `https`
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- `http`
|
||||
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
|
||||
- `file`
|
||||
|
||||
A file on the local file system.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- `"tarball"`
|
||||
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underyling implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- `"git"`
|
||||
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
The URL formats supported are the same as for Git itself.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix will only consider files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory.
|
||||
|
||||
- `ref` (String, optional)
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
A [Git reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), such as a branch or tag name.
|
||||
|
||||
Default: `"HEAD"`
|
||||
|
||||
- `rev` (String, optional)
|
||||
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
|
||||
- `shallow` (Bool, optional)
|
||||
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
Default: `true`
|
||||
|
||||
- `submodules` (Bool, optional)
|
||||
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `allRefs` (Bool, optional)
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(Without setting this option, only `rev`s from the specified `ref` are supported).
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `lastModified` (Integer, optional)
|
||||
|
||||
Unix timestamp of the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
- `revCount` (Integer, optional)
|
||||
|
||||
Number of revisions in the history of the Git repository before the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
The following input types are still subject to change:
|
||||
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
|
||||
*input* can also be a [URL-like reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references).
|
||||
The additional input types and the URL-like syntax requires the [`flakes` experimental feature](@docroot@/development/experimental-features.md#xp-feature-flakes) to be enabled.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch a GitHub repository using the attribute set representation:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree {
|
||||
> type = "github";
|
||||
> owner = "NixOS";
|
||||
> repo = "nixpkgs";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> This evaluates to the following attribute set:
|
||||
>
|
||||
> ```nix
|
||||
> {
|
||||
> lastModified = 1686503798;
|
||||
> lastModifiedDate = "20230611171638";
|
||||
> narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
|
||||
> outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> shortRev = "ae2e6b3";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)",
|
||||
.fun = prim_fetchTree,
|
||||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
|
||||
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
|
||||
const std::string & who, bool unpack, std::string name)
|
||||
{
|
||||
std::optional<std::string> url;
|
||||
std::optional<Hash> expectedHash;
|
||||
|
||||
state.forceValue(*args[0], pos);
|
||||
|
||||
bool isArgAttrs = args[0]->type() == nAttrs;
|
||||
bool nameAttrPassed = false;
|
||||
|
||||
if (isArgAttrs) {
|
||||
|
||||
for (auto & attr : *args[0]->attrs()) {
|
||||
std::string_view n(state.symbols[attr.name]);
|
||||
if (n == "url")
|
||||
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
|
||||
else if (n == "sha256")
|
||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), HashAlgorithm::SHA256);
|
||||
else if (n == "name") {
|
||||
nameAttrPassed = true;
|
||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
|
||||
}
|
||||
else
|
||||
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
|
||||
.atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
if (!url)
|
||||
state.error<EvalError>(
|
||||
"'url' argument required").atPos(pos).debugThrow();
|
||||
} else
|
||||
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||
|
||||
if (who == "fetchTarball")
|
||||
url = state.settings.resolvePseudoUrl(*url);
|
||||
|
||||
state.checkURI(*url);
|
||||
|
||||
if (name == "")
|
||||
name = baseNameOf(*url);
|
||||
|
||||
try {
|
||||
checkName(name);
|
||||
} catch (BadStorePathName & e) {
|
||||
auto resolution =
|
||||
nameAttrPassed ? HintFmt("Please change the value for the 'name' attribute passed to '%s', so that it can create a valid store path.", who) :
|
||||
isArgAttrs ? HintFmt("Please add a valid 'name' attribute to the argument for '%s', so that it can create a valid store path.", who) :
|
||||
HintFmt("Please pass an attribute set with 'url' and 'name' attributes to '%s', so that it can create a valid store path.", who);
|
||||
|
||||
state.error<EvalError>(
|
||||
std::string("invalid store path name when fetching URL '%s': %s. %s"), *url, Uncolored(e.message()), Uncolored(resolution.str()))
|
||||
.atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
if (state.settings.pureEval && !expectedHash)
|
||||
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
|
||||
|
||||
// early exit if pinned and already in the store
|
||||
if (expectedHash && expectedHash->algo == HashAlgorithm::SHA256) {
|
||||
auto expectedPath = state.store->makeFixedOutputPath(
|
||||
name,
|
||||
FixedOutputInfo {
|
||||
.method = unpack ? FileIngestionMethod::NixArchive : FileIngestionMethod::Flat,
|
||||
.hash = *expectedHash,
|
||||
.references = {}
|
||||
});
|
||||
|
||||
if (state.store->isValidPath(expectedPath)) {
|
||||
state.allowAndSetStorePathString(expectedPath, v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fetching may fail, yet the path may be substitutable.
|
||||
// https://github.com/NixOS/nix/issues/4313
|
||||
auto storePath =
|
||||
unpack
|
||||
? fetchToStore(
|
||||
*state.store,
|
||||
fetchers::downloadTarball(state.store, state.fetchSettings, *url),
|
||||
FetchMode::Copy,
|
||||
name)
|
||||
: fetchers::downloadFile(state.store, *url, name).storePath;
|
||||
|
||||
if (expectedHash) {
|
||||
auto hash = unpack
|
||||
? state.store->queryPathInfo(storePath)->narHash
|
||||
: hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath));
|
||||
if (hash != *expectedHash) {
|
||||
state.error<EvalError>(
|
||||
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
|
||||
*url,
|
||||
expectedHash->to_string(HashFormat::Nix32, true),
|
||||
hash.to_string(HashFormat::Nix32, true)
|
||||
).withExitStatus(102)
|
||||
.debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
state.allowAndSetStorePathString(storePath, v);
|
||||
}
|
||||
|
||||
static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetch(state, pos, args, v, "fetchurl", false, "");
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchurl({
|
||||
.name = "__fetchurl",
|
||||
.args = {"arg"},
|
||||
.doc = R"(
|
||||
Download the specified URL and return the path of the downloaded file.
|
||||
`arg` can be either a string denoting the URL, or an attribute set with the following attributes:
|
||||
|
||||
- `url`
|
||||
|
||||
The URL of the file to download.
|
||||
|
||||
- `name` (default: the last path component of the URL)
|
||||
|
||||
A name for the file in the store. This can be useful if the URL has any
|
||||
characters that are invalid for the store.
|
||||
|
||||
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
|
||||
)",
|
||||
.fun = prim_fetchurl,
|
||||
});
|
||||
|
||||
static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchTarball({
|
||||
.name = "fetchTarball",
|
||||
.args = {"args"},
|
||||
.doc = R"(
|
||||
Download the specified URL, unpack it and return the path of the
|
||||
unpacked tree. The file must be a tape archive (`.tar`) compressed
|
||||
with `gzip`, `bzip2` or `xz`. If the tarball consists of a
|
||||
single directory, then the top-level path component of the files
|
||||
in the tarball is removed. The typical use of the function is to
|
||||
obtain external Nix expression dependencies, such as a
|
||||
particular version of Nixpkgs, e.g.
|
||||
|
||||
```nix
|
||||
with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz) {};
|
||||
|
||||
stdenv.mkDerivation { … }
|
||||
```
|
||||
|
||||
The fetched tarball is cached for a certain amount of time (1
|
||||
hour by default) in `~/.cache/nix/tarballs/`. You can change the
|
||||
cache timeout either on the command line with `--tarball-ttl`
|
||||
*number-of-seconds* or in the Nix configuration file by adding
|
||||
the line `tarball-ttl = ` *number-of-seconds*.
|
||||
|
||||
Note that when obtaining the hash with `nix-prefetch-url` the
|
||||
option `--unpack` is required.
|
||||
|
||||
This function can also verify the contents against a hash. In that
|
||||
case, the function takes a set instead of a URL. The set requires
|
||||
the attribute `url` and the attribute `sha256`, e.g.
|
||||
|
||||
```nix
|
||||
with import (fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz";
|
||||
sha256 = "1jppksrfvbk5ypiqdz4cddxdl8z6zyzdb2srq8fcffr327ld5jj2";
|
||||
}) {};
|
||||
|
||||
stdenv.mkDerivation { … }
|
||||
```
|
||||
|
||||
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
|
||||
)",
|
||||
.fun = prim_fetchTarball,
|
||||
});
|
||||
|
||||
static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetchTree(state, pos, args, v,
|
||||
FetchTreeParams {
|
||||
.emptyRevFallback = true,
|
||||
.allowNameArgument = true,
|
||||
.isFetchGit = true
|
||||
});
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchGit({
|
||||
.name = "fetchGit",
|
||||
.args = {"args"},
|
||||
.doc = R"(
|
||||
Fetch a path from git. *args* can be a URL, in which case the HEAD
|
||||
of the repo at that URL is fetched. Otherwise, it can be an
|
||||
attribute with the following attributes (all except `url` optional):
|
||||
|
||||
- `url`
|
||||
|
||||
The URL of the repo.
|
||||
|
||||
- `name` (default: `source`)
|
||||
|
||||
The name of the directory the repo should be exported to in the store.
|
||||
|
||||
- `rev` (default: *the tip of `ref`*)
|
||||
|
||||
The [Git revision] to fetch.
|
||||
This is typically a commit hash.
|
||||
|
||||
[Git revision]: https://git-scm.com/docs/git-rev-parse#_specifying_revisions
|
||||
|
||||
- `ref` (default: `HEAD`)
|
||||
|
||||
The [Git reference] under which to look for the requested revision.
|
||||
This is often a branch or tag name.
|
||||
|
||||
[Git reference]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
|
||||
|
||||
This option has no effect once `shallow` cloning is enabled.
|
||||
|
||||
By default, the `ref` value is prefixed with `refs/heads/`.
|
||||
As of 2.3.0, Nix will not prefix `refs/heads/` if `ref` starts with `refs/`.
|
||||
|
||||
- `submodules` (default: `false`)
|
||||
|
||||
A Boolean parameter that specifies whether submodules should be checked out.
|
||||
|
||||
- `exportIgnore` (default: `true`)
|
||||
|
||||
A Boolean parameter that specifies whether `export-ignore` from `.gitattributes` should be applied.
|
||||
This approximates part of the `git archive` behavior.
|
||||
|
||||
Enabling this option is not recommended because it is unknown whether the Git developers commit to the reproducibility of `export-ignore` in newer Git versions.
|
||||
|
||||
- `shallow` (default: `false`)
|
||||
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
- `allRefs`
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(by default only `rev`s from the specified `ref` are supported).
|
||||
|
||||
This option has no effect once `shallow` cloning is enabled.
|
||||
|
||||
- `verifyCommit` (default: `true` if `publicKey` or `publicKeys` are provided, otherwise `false`)
|
||||
|
||||
Whether to check `rev` for a signature matching `publicKey` or `publicKeys`.
|
||||
If `verifyCommit` is enabled, then `fetchGit` cannot use a local repository with uncommitted changes.
|
||||
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
|
||||
|
||||
- `publicKey`
|
||||
|
||||
The public key against which `rev` is verified if `verifyCommit` is enabled.
|
||||
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
|
||||
|
||||
- `keytype` (default: `"ssh-ed25519"`)
|
||||
|
||||
The key type of `publicKey`.
|
||||
Possible values:
|
||||
- `"ssh-dsa"`
|
||||
- `"ssh-ecdsa"`
|
||||
- `"ssh-ecdsa-sk"`
|
||||
- `"ssh-ed25519"`
|
||||
- `"ssh-ed25519-sk"`
|
||||
- `"ssh-rsa"`
|
||||
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
|
||||
|
||||
- `publicKeys`
|
||||
|
||||
The public keys against which `rev` is verified if `verifyCommit` is enabled.
|
||||
Must be given as a list of attribute sets with the following form:
|
||||
|
||||
```nix
|
||||
{
|
||||
key = "<public key>";
|
||||
type = "<key type>"; # optional, default: "ssh-ed25519"
|
||||
}
|
||||
```
|
||||
|
||||
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
|
||||
|
||||
|
||||
Here are some examples of how to use `fetchGit`.
|
||||
|
||||
- To fetch a private repository over SSH:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "git@github.com:my-secret/repository.git";
|
||||
ref = "master";
|
||||
rev = "adab8b916a45068c044658c4158d81878f9ed1c3";
|
||||
}
|
||||
```
|
||||
|
||||
- To fetch an arbitrary reference:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "https://github.com/NixOS/nix.git";
|
||||
ref = "refs/heads/0.5-release";
|
||||
}
|
||||
```
|
||||
|
||||
- If the revision you're looking for is in the default branch of
|
||||
the git repository you don't strictly need to specify the branch
|
||||
name in the `ref` attribute.
|
||||
|
||||
However, if the revision you're looking for is in a future
|
||||
branch for the non-default branch you will need to specify the
|
||||
the `ref` attribute as well.
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "https://github.com/nixos/nix.git";
|
||||
rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452";
|
||||
ref = "1.11-maintenance";
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> It is nice to always specify the branch which a revision
|
||||
> belongs to. Without the branch being specified, the fetcher
|
||||
> might fail if the default branch changes. Additionally, it can
|
||||
> be confusing to try a commit from a non-default branch and see
|
||||
> the fetch fail. If the branch is specified the fault is much
|
||||
> more obvious.
|
||||
|
||||
- If the revision you're looking for is in the default branch of
|
||||
the git repository you may omit the `ref` attribute.
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "https://github.com/nixos/nix.git";
|
||||
rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452";
|
||||
}
|
||||
```
|
||||
|
||||
- To fetch a specific tag:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "https://github.com/nixos/nix.git";
|
||||
ref = "refs/tags/1.9";
|
||||
}
|
||||
```
|
||||
|
||||
- To fetch the latest version of a remote branch:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "ssh://git@github.com/nixos/nix.git";
|
||||
ref = "master";
|
||||
}
|
||||
```
|
||||
|
||||
- To verify the commit signature:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit {
|
||||
url = "ssh://git@github.com/nixos/nix.git";
|
||||
verifyCommit = true;
|
||||
publicKeys = [
|
||||
{
|
||||
type = "ssh-ed25519";
|
||||
key = "AAAAC3NzaC1lZDI1NTE5AAAAIArPKULJOid8eS6XETwUjO48/HKBWl7FTCK0Z//fplDi";
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Nix will refetch the branch according to the [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl) setting.
|
||||
|
||||
This behavior is disabled in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
- To fetch the content of a checked-out work directory:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit ./work-dir
|
||||
```
|
||||
|
||||
If the URL points to a local directory, and no `ref` or `rev` is
|
||||
given, `fetchGit` will use the current content of the checked-out
|
||||
files, even if they are not committed or added to Git's index. It will
|
||||
only consider files added to the Git repository, as listed by `git ls-files`.
|
||||
)",
|
||||
.fun = prim_fetchGit,
|
||||
});
|
||||
|
||||
}
|
||||
110
subprojects/libexpr/primops/fromTOML.cc
Normal file
110
subprojects/libexpr/primops/fromTOML.cc
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <toml.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
|
||||
{
|
||||
auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
|
||||
|
||||
std::istringstream tomlStream(std::string{toml});
|
||||
|
||||
std::function<void(Value &, toml::value)> visit;
|
||||
|
||||
visit = [&](Value & v, toml::value t) {
|
||||
|
||||
switch(t.type())
|
||||
{
|
||||
case toml::value_t::table:
|
||||
{
|
||||
auto table = toml::get<toml::table>(t);
|
||||
|
||||
size_t size = 0;
|
||||
for (auto & i : table) { (void) i; size++; }
|
||||
|
||||
auto attrs = state.buildBindings(size);
|
||||
|
||||
for(auto & elem : table)
|
||||
visit(attrs.alloc(elem.first), elem.second);
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
break;;
|
||||
case toml::value_t::array:
|
||||
{
|
||||
auto array = toml::get<std::vector<toml::value>>(t);
|
||||
|
||||
auto list = state.buildList(array.size());
|
||||
for (const auto & [n, v] : enumerate(list))
|
||||
visit(*(v = state.allocValue()), array[n]);
|
||||
v.mkList(list);
|
||||
}
|
||||
break;;
|
||||
case toml::value_t::boolean:
|
||||
v.mkBool(toml::get<bool>(t));
|
||||
break;;
|
||||
case toml::value_t::integer:
|
||||
v.mkInt(toml::get<int64_t>(t));
|
||||
break;;
|
||||
case toml::value_t::floating:
|
||||
v.mkFloat(toml::get<NixFloat>(t));
|
||||
break;;
|
||||
case toml::value_t::string:
|
||||
v.mkString(toml::get<std::string>(t));
|
||||
break;;
|
||||
case toml::value_t::local_datetime:
|
||||
case toml::value_t::offset_datetime:
|
||||
case toml::value_t::local_date:
|
||||
case toml::value_t::local_time:
|
||||
{
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
|
||||
auto attrs = state.buildBindings(2);
|
||||
attrs.alloc("_type").mkString("timestamp");
|
||||
std::ostringstream s;
|
||||
s << t;
|
||||
attrs.alloc("value").mkString(s.str());
|
||||
v.mkAttrs(attrs);
|
||||
} else {
|
||||
throw std::runtime_error("Dates and times are not supported");
|
||||
}
|
||||
}
|
||||
break;;
|
||||
case toml::value_t::empty:
|
||||
v.mkNull();
|
||||
break;;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
|
||||
} catch (std::exception & e) { // TODO: toml::syntax_error
|
||||
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fromTOML({
|
||||
.name = "fromTOML",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Convert a TOML string to a Nix value. For example,
|
||||
|
||||
```nix
|
||||
builtins.fromTOML ''
|
||||
x=1
|
||||
s="a"
|
||||
[table]
|
||||
y=2
|
||||
''
|
||||
```
|
||||
|
||||
returns the value `{ s = "a"; table = { y = 2; }; x = 1; }`.
|
||||
)",
|
||||
.fun = prim_fromTOML
|
||||
});
|
||||
|
||||
}
|
||||
12
subprojects/libexpr/primops/meson.build
Normal file
12
subprojects/libexpr/primops/meson.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
generated_headers += gen_header.process(
|
||||
'derivation.nix',
|
||||
preserve_path_from: meson.project_source_root(),
|
||||
)
|
||||
|
||||
sources += files(
|
||||
'context.cc',
|
||||
'fetchClosure.cc',
|
||||
'fetchMercurial.cc',
|
||||
'fetchTree.cc',
|
||||
'fromTOML.cc',
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue