Compare commits
77 Commits
cancelled-
...
dead-code-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e8ce8e21f | ||
|
|
5caebab63a | ||
|
|
19d83d2605 | ||
|
|
70b9fbd76c | ||
|
|
40b25153b8 | ||
|
|
09755e696a | ||
|
|
fa380e0991 | ||
|
|
f7de5b326a | ||
|
|
533cced249 | ||
|
|
8b167ea89b | ||
|
|
76bd600302 | ||
|
|
b975f719b1 | ||
|
|
063cdb5508 | ||
|
|
72dbd43882 | ||
|
|
fb989bd93f | ||
|
|
b309826a48 | ||
|
|
bed0570629 | ||
|
|
ef6dbe76dc | ||
|
|
dfac44cdfb | ||
|
|
36f4e290d0 | ||
|
|
bd0b338e15 | ||
|
|
b3dfe37aea | ||
|
|
87d3c3ba1a | ||
|
|
1e42e55fb4 | ||
|
|
e704b8eeed | ||
|
|
6d65f8eea2 | ||
|
|
f4989b118b | ||
|
|
2de742155a | ||
|
|
09d6847490 | ||
|
|
53af1119fb | ||
|
|
68d2292f3a | ||
|
|
16f0279d4f | ||
|
|
8165419a0c | ||
|
|
7721fa6df4 | ||
|
|
cb5d97a607 | ||
|
|
436bc1f39e | ||
|
|
ae4ed24257 | ||
|
|
fcf3bdcac8 | ||
|
|
4b97f1130a | ||
|
|
614e143a20 | ||
|
|
77982c55b2 | ||
|
|
acacdf87b4 | ||
|
|
d5b6e1a0fc | ||
|
|
3511a919b4 | ||
|
|
f6aa8c0486 | ||
|
|
cd5cac0c40 | ||
|
|
958866b9a6 | ||
|
|
d07c24f4c8 | ||
|
|
95da93c05b | ||
|
|
bae1ca257a | ||
|
|
f8141a2c26 | ||
|
|
bdeaf976bd | ||
|
|
2cc0b1b404 | ||
|
|
cdba2534cf | ||
|
|
5446d6345f | ||
|
|
b115c90043 | ||
|
|
13b896a188 | ||
|
|
5462c5eedd | ||
|
|
aec59a973a | ||
|
|
8642c0a9a2 | ||
|
|
653d701300 | ||
|
|
8d881ee3a3 | ||
|
|
2872c8ede0 | ||
|
|
57f526ecda | ||
|
|
1f2a994fb9 | ||
|
|
70e56a41ce | ||
|
|
0e81a35881 | ||
|
|
a235b454cc | ||
|
|
94c3bb3e4c | ||
|
|
30dbc7ee0c | ||
|
|
19ab65c9d7 | ||
|
|
805496657d | ||
|
|
e95503cf9a | ||
|
|
1bcbe652fb | ||
|
|
292bd390af | ||
|
|
2150d7a754 | ||
|
|
ac9d2a5b06 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -174,7 +174,7 @@ jobs:
|
||||
echo "installer-url=file://$GITHUB_WORKSPACE/out" >> "$GITHUB_OUTPUT"
|
||||
TARBALL_PATH="$(find "$GITHUB_WORKSPACE/out" -name 'nix*.tar.xz' -print | head -n 1)"
|
||||
echo "tarball-path=file://$TARBALL_PATH" >> "$GITHUB_OUTPUT"
|
||||
- uses: cachix/install-nix-action@456688f15bc354bef6d396e4a35f4f89d40bf2b7 # v31.8.2
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
if: ${{ !matrix.experimental-installer }}
|
||||
with:
|
||||
install_url: ${{ format('{0}/install', steps.installer-tarball-url.outputs.installer-url) }}
|
||||
|
||||
@@ -36,7 +36,7 @@ to a temporary location. The tarball must include a single top-level
|
||||
directory containing at least a file named `default.nix`.
|
||||
|
||||
`nix-build` is essentially a wrapper around
|
||||
[`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix
|
||||
[`nix-instantiate`](./nix-instantiate.md) (to translate a high-level Nix
|
||||
expression to a low-level [store derivation]) and [`nix-store
|
||||
--realise`](@docroot@/command-ref/nix-store/realise.md) (to build the store
|
||||
derivation).
|
||||
@@ -52,8 +52,8 @@ derivation).
|
||||
# Options
|
||||
|
||||
All options not listed here are passed to
|
||||
[`nix-store --realise`](nix-store/realise.md),
|
||||
except for `--arg` and `--attr` / `-A` which are passed to [`nix-instantiate`](nix-instantiate.md).
|
||||
[`nix-store --realise`](./nix-store/realise.md),
|
||||
except for `--arg` and `--attr` / `-A` which are passed to [`nix-instantiate`](./nix-instantiate.md).
|
||||
|
||||
- <span id="opt-no-out-link">[`--no-out-link`](#opt-no-out-link)<span>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ standard input.
|
||||
|
||||
- `--add-root` *path*
|
||||
|
||||
See the [corresponding option](nix-store.md) in `nix-store`.
|
||||
See the [corresponding option](./nix-store.md) in `nix-store`.
|
||||
|
||||
- `--parse`
|
||||
|
||||
|
||||
@@ -130,15 +130,19 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
|
||||
havePerl = stdenv.buildPlatform == stdenv.hostPlatform && stdenv.hostPlatform.isUnix;
|
||||
ignoreCrossFile = flags: builtins.filter (flag: !(lib.strings.hasInfix "cross-file" flag)) flags;
|
||||
|
||||
availableComponents = lib.filterAttrs (
|
||||
k: v: lib.meta.availableOn pkgs.hostPlatform v
|
||||
) allComponents;
|
||||
|
||||
activeComponents = buildInputsClosureCond isInternal (
|
||||
lib.attrValues (finalAttrs.passthru.config.getComponents allComponents)
|
||||
lib.attrValues (finalAttrs.passthru.config.getComponents availableComponents)
|
||||
);
|
||||
|
||||
allComponents = lib.filterAttrs (k: v: lib.isDerivation v) pkgs.nixComponents2;
|
||||
internalDrvs = byDrvPath (
|
||||
# Drop the attr names (not present in buildInputs anyway)
|
||||
lib.attrValues allComponents
|
||||
++ lib.concatMap (c: lib.attrValues c.tests or { }) (lib.attrValues allComponents)
|
||||
lib.attrValues availableComponents
|
||||
++ lib.concatMap (c: lib.attrValues c.tests or { }) (lib.attrValues availableComponents)
|
||||
);
|
||||
|
||||
isInternal =
|
||||
@@ -187,19 +191,19 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
|
||||
);
|
||||
|
||||
small =
|
||||
(finalAttrs.finalPackage.withActiveComponents (c: {
|
||||
inherit (c)
|
||||
nix-cli
|
||||
nix-util-tests
|
||||
nix-store-tests
|
||||
nix-expr-tests
|
||||
nix-fetchers-tests
|
||||
nix-flake-tests
|
||||
nix-functional-tests
|
||||
# Currently required
|
||||
nix-perl-bindings
|
||||
;
|
||||
})).overrideAttrs
|
||||
(finalAttrs.finalPackage.withActiveComponents (
|
||||
c:
|
||||
lib.intersectAttrs (lib.genAttrs [
|
||||
"nix-cli"
|
||||
"nix-util-tests"
|
||||
"nix-store-tests"
|
||||
"nix-expr-tests"
|
||||
"nix-fetchers-tests"
|
||||
"nix-flake-tests"
|
||||
"nix-functional-tests"
|
||||
"nix-perl-bindings"
|
||||
] (_: null)) c
|
||||
)).overrideAttrs
|
||||
(o: {
|
||||
mesonFlags = o.mesonFlags ++ [
|
||||
# TODO: infer from activeComponents or vice versa
|
||||
@@ -269,6 +273,8 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
|
||||
CXX_LD = "mold";
|
||||
};
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
|
||||
mesonFlags =
|
||||
map (transformFlag "libutil") (ignoreCrossFile pkgs.nixComponents2.nix-util.mesonFlags)
|
||||
++ map (transformFlag "libstore") (ignoreCrossFile pkgs.nixComponents2.nix-store.mesonFlags)
|
||||
|
||||
@@ -65,6 +65,6 @@ mkMesonDerivation (finalAttrs: {
|
||||
'';
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.all;
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
})
|
||||
|
||||
@@ -33,7 +33,8 @@ EvalSettings evalSettings{
|
||||
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string{rest}, {}, true, false);
|
||||
debug("fetching flake search path element '%s''", rest);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto [accessor, lockedRef] =
|
||||
flakeRef.resolve(fetchSettings, *state.store).lazyFetch(fetchSettings, *state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings,
|
||||
*state.store,
|
||||
@@ -179,7 +180,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
|
||||
{
|
||||
if (EvalSettings::isPseudoUrl(s)) {
|
||||
auto accessor = fetchers::downloadTarball(state.store, state.fetchSettings, EvalSettings::resolvePseudoUrl(s));
|
||||
auto accessor = fetchers::downloadTarball(*state.store, state.fetchSettings, EvalSettings::resolvePseudoUrl(s));
|
||||
auto storePath = fetchToStore(state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy);
|
||||
return state.storePath(storePath);
|
||||
}
|
||||
@@ -187,7 +188,8 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
||||
else if (hasPrefix(s, "flake:")) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto [accessor, lockedRef] =
|
||||
flakeRef.resolve(fetchSettings, *state.store).lazyFetch(fetchSettings, *state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
|
||||
state.allowPath(storePath);
|
||||
|
||||
@@ -409,7 +409,7 @@ void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::strin
|
||||
Args::completeDir(completions, 0, prefix);
|
||||
|
||||
/* Look for registry entries that match the prefix. */
|
||||
for (auto & registry : fetchers::getRegistries(fetchSettings, store)) {
|
||||
for (auto & registry : fetchers::getRegistries(fetchSettings, *store)) {
|
||||
for (auto & entry : registry->entries) {
|
||||
auto from = entry.from.to_string();
|
||||
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
|
||||
|
||||
@@ -738,8 +738,8 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||
}
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked(fetchSettings))
|
||||
throw Error("cannot use ':load-flake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
|
||||
Value v;
|
||||
|
||||
|
||||
@@ -137,6 +137,8 @@ nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Sto
|
||||
|
||||
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
|
||||
{
|
||||
if (builder)
|
||||
builder->~nix_eval_state_builder();
|
||||
operator delete(builder, static_cast<std::align_val_t>(alignof(nix_eval_state_builder)));
|
||||
}
|
||||
|
||||
@@ -203,6 +205,8 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
|
||||
|
||||
void nix_state_free(EvalState * state)
|
||||
{
|
||||
if (state)
|
||||
state->~EvalState();
|
||||
operator delete(state, static_cast<std::align_val_t>(alignof(EvalState)));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace nixC {
|
||||
|
||||
TEST_F(nix_api_store_test, nix_eval_state_lookup_path)
|
||||
TEST_F(nix_api_expr_test, nix_eval_state_lookup_path)
|
||||
{
|
||||
auto tmpDir = nix::createTempDir();
|
||||
auto delTmpDir = std::make_unique<nix::AutoDelete>(tmpDir, true);
|
||||
@@ -42,12 +42,16 @@ TEST_F(nix_api_store_test, nix_eval_state_lookup_path)
|
||||
nix_expr_eval_from_string(ctx, state, "builtins.seq <nixos-config> <nixpkgs>", ".", value);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_state_free(state);
|
||||
|
||||
ASSERT_EQ(nix_get_type(ctx, value), NIX_TYPE_PATH);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto pathStr = nix_get_path_string(ctx, value);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(0, strcmp(pathStr, nixpkgs.c_str()));
|
||||
|
||||
nix_gc_decref(nullptr, value);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
|
||||
|
||||
@@ -661,8 +661,14 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
CASE(R"(null)", ""),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
|
||||
CASE(R"({ outPath = "foo"; })", "foo"),
|
||||
CASE(R"(./test)", "/test")));
|
||||
CASE(R"({ outPath = "foo"; })", "foo")
|
||||
// this is broken on cygwin because canonPath("//./test", false) returns //./test
|
||||
// FIXME: don't use canonPath
|
||||
#ifndef __CYGWIN__
|
||||
,
|
||||
CASE(R"(./test)", "/test")
|
||||
#endif
|
||||
));
|
||||
#undef CASE
|
||||
|
||||
TEST_F(PrimOpTest, substring)
|
||||
|
||||
@@ -205,7 +205,7 @@ bool Value::isTrivial() const
|
||||
{
|
||||
return !isa<tApp, tPrimOpApp>()
|
||||
&& (!isa<tThunk>()
|
||||
|| (dynamic_cast<ExprAttrs *>(thunk().expr) && ((ExprAttrs *) thunk().expr)->dynamicAttrs.empty())
|
||||
|| (dynamic_cast<ExprAttrs *>(thunk().expr) && ((ExprAttrs *) thunk().expr)->dynamicAttrs->empty())
|
||||
|| dynamic_cast<ExprLambda *>(thunk().expr) || dynamic_cast<ExprList *>(thunk().expr));
|
||||
}
|
||||
|
||||
@@ -517,15 +517,16 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
||||
if (primOp.arity == 0) {
|
||||
primOp.arity = 1;
|
||||
auto vPrimOp = allocValue();
|
||||
vPrimOp->mkPrimOp(new PrimOp(primOp));
|
||||
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
|
||||
Value v;
|
||||
v.mkApp(vPrimOp, vPrimOp);
|
||||
auto & primOp1 = *vPrimOp->primOp();
|
||||
return addConstant(
|
||||
primOp.name,
|
||||
primOp1.name,
|
||||
v,
|
||||
{
|
||||
.type = nThunk, // FIXME
|
||||
.doc = primOp.doc,
|
||||
.doc = primOp1.doc ? primOp1.doc->c_str() : nullptr,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -565,13 +566,14 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||
{
|
||||
if (v.isPrimOp()) {
|
||||
auto v2 = &v;
|
||||
if (auto * doc = v2->primOp()->doc)
|
||||
auto & primOp = *v2->primOp();
|
||||
if (primOp.doc)
|
||||
return Doc{
|
||||
.pos = {},
|
||||
.name = v2->primOp()->name,
|
||||
.arity = v2->primOp()->arity,
|
||||
.args = v2->primOp()->args,
|
||||
.doc = doc,
|
||||
.name = primOp.name,
|
||||
.arity = primOp.arity,
|
||||
.args = primOp.args,
|
||||
.doc = primOp.doc->c_str(),
|
||||
};
|
||||
}
|
||||
if (v.isLambda()) {
|
||||
@@ -1224,26 +1226,26 @@ Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
||||
|
||||
void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
auto bindings = state.buildBindings(attrs.size() + dynamicAttrs.size());
|
||||
auto bindings = state.buildBindings(attrs->size() + dynamicAttrs->size());
|
||||
auto dynamicEnv = &env;
|
||||
bool sort = false;
|
||||
|
||||
if (recursive) {
|
||||
/* Create a new environment that contains the attributes in
|
||||
this `rec'. */
|
||||
Env & env2(state.mem.allocEnv(attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs->size()));
|
||||
env2.up = &env;
|
||||
dynamicEnv = &env2;
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
||||
AttrDefs::iterator overrides = attrs.find(state.s.overrides);
|
||||
bool hasOverrides = overrides != attrs.end();
|
||||
AttrDefs::iterator overrides = attrs->find(state.s.overrides);
|
||||
bool hasOverrides = overrides != attrs->end();
|
||||
|
||||
/* The recursive attributes are evaluated in the new
|
||||
environment, while the inherited attributes are evaluated
|
||||
in the original environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs) {
|
||||
for (auto & i : *attrs) {
|
||||
Value * vAttr;
|
||||
if (hasOverrides && i.second.kind != AttrDef::Kind::Inherited) {
|
||||
vAttr = state.allocValue();
|
||||
@@ -1270,8 +1272,8 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
"while evaluating the `__overrides` attribute");
|
||||
bindings.grow(state.buildBindings(bindings.capacity() + vOverrides->attrs()->size()));
|
||||
for (auto & i : *vOverrides->attrs()) {
|
||||
AttrDefs::iterator j = attrs.find(i.name);
|
||||
if (j != attrs.end()) {
|
||||
AttrDefs::iterator j = attrs->find(i.name);
|
||||
if (j != attrs->end()) {
|
||||
(*bindings.bindings)[j->second.displ] = i;
|
||||
env2.values[j->second.displ] = i.value;
|
||||
} else
|
||||
@@ -1283,13 +1285,13 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
|
||||
else {
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env) : nullptr;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
bindings.insert(
|
||||
i.first, i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)), i.second.pos);
|
||||
}
|
||||
|
||||
/* Dynamic attrs apply *after* rec and __overrides. */
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
Value nameVal;
|
||||
i.nameExpr->eval(state, *dynamicEnv, nameVal);
|
||||
state.forceValue(nameVal, i.pos);
|
||||
@@ -1323,7 +1325,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
/* Create a new environment that contains the attributes in this
|
||||
`let'. */
|
||||
Env & env2(state.mem.allocEnv(attrs->attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs->attrs->size()));
|
||||
env2.up = &env;
|
||||
|
||||
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
|
||||
@@ -1332,7 +1334,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
||||
while the inherited attributes are evaluated in the original
|
||||
environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs->attrs) {
|
||||
for (auto & i : *attrs->attrs) {
|
||||
env2.values[displ++] = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
||||
}
|
||||
|
||||
@@ -3188,7 +3190,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||
|
||||
if (EvalSettings::isPseudoUrl(value)) {
|
||||
try {
|
||||
auto accessor = fetchers::downloadTarball(store, fetchSettings, EvalSettings::resolvePseudoUrl(value));
|
||||
auto accessor = fetchers::downloadTarball(*store, fetchSettings, EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(fetchSettings, *store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(this->storePath(storePath));
|
||||
} catch (Error & e) {
|
||||
|
||||
@@ -108,7 +108,7 @@ struct PrimOp
|
||||
/**
|
||||
* Optional free-form documentation about the primop.
|
||||
*/
|
||||
const char * doc = nullptr;
|
||||
std::optional<std::string> doc;
|
||||
|
||||
/**
|
||||
* Add a trace item, while calling the `<name>` builtin.
|
||||
|
||||
@@ -395,9 +395,13 @@ struct ExprAttrs : Expr
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<Symbol, AttrDef> AttrDefs;
|
||||
AttrDefs attrs;
|
||||
std::unique_ptr<std::vector<Expr *>> inheritFromExprs;
|
||||
typedef std::pmr::map<Symbol, AttrDef> AttrDefs;
|
||||
/**
|
||||
* attrs will never be null. we use std::optional so that we can call emplace() to re-initialize the value with a
|
||||
* new pmr::map using a different allocator (move assignment will copy into the old allocator)
|
||||
*/
|
||||
std::optional<AttrDefs> attrs;
|
||||
std::unique_ptr<std::pmr::vector<Expr *>> inheritFromExprs;
|
||||
|
||||
struct DynamicAttrDef
|
||||
{
|
||||
@@ -409,13 +413,20 @@ struct ExprAttrs : Expr
|
||||
, pos(pos) {};
|
||||
};
|
||||
|
||||
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
|
||||
DynamicAttrDefs dynamicAttrs;
|
||||
typedef std::pmr::vector<DynamicAttrDef> DynamicAttrDefs;
|
||||
/**
|
||||
* dynamicAttrs will never be null. See comment on AttrDefs above.
|
||||
*/
|
||||
std::optional<DynamicAttrDefs> dynamicAttrs;
|
||||
ExprAttrs(const PosIdx & pos)
|
||||
: recursive(false)
|
||||
, pos(pos) {};
|
||||
, pos(pos)
|
||||
, attrs(AttrDefs{})
|
||||
, dynamicAttrs(DynamicAttrDefs{}) {};
|
||||
ExprAttrs()
|
||||
: recursive(false) {};
|
||||
: recursive(false)
|
||||
, attrs(AttrDefs{})
|
||||
, dynamicAttrs(DynamicAttrDefs{}) {};
|
||||
|
||||
PosIdx getPos() const override
|
||||
{
|
||||
|
||||
@@ -126,8 +126,8 @@ inline void ParserState::addAttr(
|
||||
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
|
||||
ExprAttrs * nested;
|
||||
if (i->symbol) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
||||
if (j != attrs->attrs.end()) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs->find(i->symbol);
|
||||
if (j != attrs->attrs->end()) {
|
||||
nested = dynamic_cast<ExprAttrs *>(j->second.e);
|
||||
if (!nested) {
|
||||
attrPath.erase(i + 1, attrPath.end());
|
||||
@@ -135,11 +135,11 @@ inline void ParserState::addAttr(
|
||||
}
|
||||
} else {
|
||||
nested = exprs.add<ExprAttrs>();
|
||||
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
||||
(*attrs->attrs)[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
||||
}
|
||||
} else {
|
||||
nested = exprs.add<ExprAttrs>();
|
||||
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
|
||||
attrs->dynamicAttrs->push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
|
||||
}
|
||||
attrs = nested;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ inline void ParserState::addAttr(
|
||||
if (i->symbol) {
|
||||
addAttr(attrs, attrPath, i->symbol, ExprAttrs::AttrDef(e, pos));
|
||||
} else {
|
||||
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
|
||||
attrs->dynamicAttrs->push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
|
||||
}
|
||||
|
||||
auto it = lexerState.positionToDocComment.find(pos);
|
||||
@@ -165,8 +165,8 @@ inline void ParserState::addAttr(
|
||||
inline void
|
||||
ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def)
|
||||
{
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(symbol);
|
||||
if (j != attrs->attrs.end()) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs->find(symbol);
|
||||
if (j != attrs->attrs->end()) {
|
||||
// This attr path is already defined. However, if both
|
||||
// e and the expr pointed by the attr path are two attribute sets,
|
||||
// we want to merge them.
|
||||
@@ -181,8 +181,8 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
// See https://github.com/NixOS/nix/issues/9020.
|
||||
if (jAttrs && ae) {
|
||||
if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
|
||||
jAttrs->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
|
||||
for (auto & ad : ae->attrs) {
|
||||
jAttrs->inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>();
|
||||
for (auto & ad : *ae->attrs) {
|
||||
if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
|
||||
auto & sel = dynamic_cast<ExprSelect &>(*ad.second.e);
|
||||
auto & from = dynamic_cast<ExprInheritFrom &>(*sel.e);
|
||||
@@ -192,12 +192,12 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
addAttr(jAttrs, attrPath, ad.first, std::move(ad.second));
|
||||
attrPath.pop_back();
|
||||
}
|
||||
ae->attrs.clear();
|
||||
jAttrs->dynamicAttrs.insert(
|
||||
jAttrs->dynamicAttrs.end(),
|
||||
std::make_move_iterator(ae->dynamicAttrs.begin()),
|
||||
std::make_move_iterator(ae->dynamicAttrs.end()));
|
||||
ae->dynamicAttrs.clear();
|
||||
ae->attrs->clear();
|
||||
jAttrs->dynamicAttrs->insert(
|
||||
jAttrs->dynamicAttrs->end(),
|
||||
std::make_move_iterator(ae->dynamicAttrs->begin()),
|
||||
std::make_move_iterator(ae->dynamicAttrs->end()));
|
||||
ae->dynamicAttrs->clear();
|
||||
if (ae->inheritFromExprs) {
|
||||
jAttrs->inheritFromExprs->insert(
|
||||
jAttrs->inheritFromExprs->end(),
|
||||
@@ -210,7 +210,7 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
}
|
||||
} else {
|
||||
// This attr path is not defined. Let's create it.
|
||||
attrs->attrs.emplace(symbol, def);
|
||||
attrs->attrs->emplace(symbol, def);
|
||||
def.e->setName(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||
|
||||
void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) const
|
||||
{
|
||||
typedef const decltype(attrs)::value_type * Attr;
|
||||
typedef const AttrDefs::value_type * Attr;
|
||||
std::vector<Attr> sorted;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
sorted.push_back(&i);
|
||||
std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
|
||||
std::string_view sa = symbols[a->first], sb = symbols[b->first];
|
||||
@@ -122,7 +122,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
|
||||
str << "; ";
|
||||
}
|
||||
}
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
str << "\"${";
|
||||
i.nameExpr->show(symbols, str);
|
||||
str << "}\" = ";
|
||||
@@ -401,15 +401,26 @@ ExprAttrs::bindInheritSources(EvalState & es, const std::shared_ptr<const Static
|
||||
|
||||
void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
// Move storage into the Exprs arena
|
||||
{
|
||||
auto arena = es.mem.exprs.alloc;
|
||||
AttrDefs newAttrs{std::move(*attrs), arena};
|
||||
attrs.emplace(std::move(newAttrs), arena);
|
||||
DynamicAttrDefs newDynamicAttrs{std::move(*dynamicAttrs), arena};
|
||||
dynamicAttrs.emplace(std::move(newDynamicAttrs), arena);
|
||||
if (inheritFromExprs)
|
||||
inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>(std::move(*inheritFromExprs), arena);
|
||||
}
|
||||
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
if (recursive) {
|
||||
auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs.size());
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->size());
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
return newEnv;
|
||||
}();
|
||||
@@ -417,20 +428,20 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
|
||||
// No need to sort newEnv since attrs is in sorted order.
|
||||
|
||||
auto inheritFromEnv = bindInheritSources(es, newEnv);
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
|
||||
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, newEnv);
|
||||
i.valueExpr->bindVars(es, newEnv);
|
||||
}
|
||||
} else {
|
||||
auto inheritFromEnv = bindInheritSources(es, env);
|
||||
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(env, env, inheritFromEnv));
|
||||
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, env);
|
||||
i.valueExpr->bindVars(es, env);
|
||||
}
|
||||
@@ -486,10 +497,10 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs.size());
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs->size());
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs->attrs)
|
||||
for (auto & i : *attrs->attrs)
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
return newEnv;
|
||||
}();
|
||||
@@ -497,7 +508,7 @@ void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||
// No need to sort newEnv since attrs->attrs is in sorted order.
|
||||
|
||||
auto inheritFromEnv = attrs->bindInheritSources(es, newEnv);
|
||||
for (auto & i : attrs->attrs)
|
||||
for (auto & i : *attrs->attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
|
||||
|
||||
if (es.debugRepl)
|
||||
|
||||
@@ -211,7 +211,7 @@ expr_function
|
||||
| WITH expr ';' expr_function
|
||||
{ $$ = state->exprs.add<ExprWith>(CUR_POS, $2, $4); }
|
||||
| LET binds IN_KW expr_function
|
||||
{ if (!$2->dynamicAttrs.empty())
|
||||
{ if (!$2->dynamicAttrs->empty())
|
||||
throw ParseError({
|
||||
.msg = HintFmt("dynamic attributes not allowed in let"),
|
||||
.pos = state->positions[CUR_POS]
|
||||
@@ -413,9 +413,9 @@ binds1
|
||||
| binds[accum] INHERIT attrs ';'
|
||||
{ $$ = $accum;
|
||||
for (auto & [i, iPos] : $attrs) {
|
||||
if ($accum->attrs.find(i.symbol) != $accum->attrs.end())
|
||||
state->dupAttr(i.symbol, iPos, $accum->attrs[i.symbol].pos);
|
||||
$accum->attrs.emplace(
|
||||
if ($accum->attrs->find(i.symbol) != $accum->attrs->end())
|
||||
state->dupAttr(i.symbol, iPos, (*$accum->attrs)[i.symbol].pos);
|
||||
$accum->attrs->emplace(
|
||||
i.symbol,
|
||||
ExprAttrs::AttrDef(state->exprs.add<ExprVar>(iPos, i.symbol), iPos, ExprAttrs::AttrDef::Kind::Inherited));
|
||||
}
|
||||
@@ -423,13 +423,13 @@ binds1
|
||||
| binds[accum] INHERIT '(' expr ')' attrs ';'
|
||||
{ $$ = $accum;
|
||||
if (!$accum->inheritFromExprs)
|
||||
$accum->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
|
||||
$accum->inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>();
|
||||
$accum->inheritFromExprs->push_back($expr);
|
||||
auto from = state->exprs.add<ExprInheritFrom>(state->at(@expr), $accum->inheritFromExprs->size() - 1);
|
||||
for (auto & [i, iPos] : $attrs) {
|
||||
if ($accum->attrs.find(i.symbol) != $accum->attrs.end())
|
||||
state->dupAttr(i.symbol, iPos, $accum->attrs[i.symbol].pos);
|
||||
$accum->attrs.emplace(
|
||||
if ($accum->attrs->find(i.symbol) != $accum->attrs->end())
|
||||
state->dupAttr(i.symbol, iPos, (*$accum->attrs)[i.symbol].pos);
|
||||
$accum->attrs->emplace(
|
||||
i.symbol,
|
||||
ExprAttrs::AttrDef(
|
||||
state->exprs.add<ExprSelect>(state->exprs.alloc, iPos, from, i.symbol),
|
||||
|
||||
@@ -81,7 +81,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** ar
|
||||
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 [storePath, input2] = input.fetchToStore(state.fetchSettings, *state.store);
|
||||
|
||||
auto attrs2 = state.buildBindings(8);
|
||||
state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath));
|
||||
|
||||
@@ -82,7 +82,7 @@ struct FetchTreeParams
|
||||
static void fetchTree(
|
||||
EvalState & state, const PosIdx pos, Value ** args, Value & v, const FetchTreeParams & params = FetchTreeParams{})
|
||||
{
|
||||
fetchers::Input input{state.fetchSettings};
|
||||
fetchers::Input input{};
|
||||
NixStringContext context;
|
||||
std::optional<std::string> type;
|
||||
auto fetcher = params.isFetchGit ? "fetchGit" : "fetchTree";
|
||||
@@ -194,9 +194,9 @@ static void fetchTree(
|
||||
}
|
||||
|
||||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
input = lookupInRegistries(state.fetchSettings, *state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked()) {
|
||||
if (state.settings.pureEval && !input.isLocked(state.fetchSettings)) {
|
||||
if (input.getNarHash())
|
||||
warn(
|
||||
"Input '%s' is unlocked (e.g. lacks a Git revision) but is checked by NAR hash. "
|
||||
@@ -219,7 +219,8 @@ static void fetchTree(
|
||||
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
|
||||
}
|
||||
|
||||
auto cachedInput = state.inputCache->getAccessor(state.store, input, fetchers::UseRegistries::No);
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, *state.store, input, fetchers::UseRegistries::No);
|
||||
|
||||
auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);
|
||||
|
||||
@@ -234,229 +235,127 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** 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:
|
||||
.doc = []() -> std::string {
|
||||
std::string doc = stripIndentation(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 -->
|
||||
- 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:
|
||||
*input* must be an attribute set with the following attributes:
|
||||
|
||||
- `type` (String, required)
|
||||
- `type` (String, required)
|
||||
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
|
||||
- `narHash` (String, optional)
|
||||
- `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.
|
||||
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.
|
||||
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 is 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
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source is 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**
|
||||
> **Note**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
|
||||
- `http`
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
## Source types
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
The following source types and associated input attributes are supported.
|
||||
|
||||
- `file`
|
||||
<!-- 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`! -->
|
||||
)");
|
||||
|
||||
A file on the local file system.
|
||||
auto indentString = [](std::string const & str, std::string const & indent) {
|
||||
std::string result;
|
||||
std::istringstream stream(str);
|
||||
std::string line;
|
||||
bool first = true;
|
||||
while (std::getline(stream, line)) {
|
||||
if (!first)
|
||||
result += "\n";
|
||||
result += indent + line;
|
||||
first = false;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
doc += "\n- `" + quoteString(schemeName, '"') + "`\n\n";
|
||||
doc += indentString(scheme->schemeDescription(), " ");
|
||||
if (!doc.empty() && doc.back() != '\n')
|
||||
doc += "\n";
|
||||
|
||||
- `"git"`
|
||||
for (const auto & [attrName, attribute] : scheme->allowedAttrs()) {
|
||||
doc += "\n - `" + attrName + "` (" + attribute.type + ", "
|
||||
+ (attribute.required ? "required" : "optional") + ")\n\n";
|
||||
doc += indentString(stripIndentation(attribute.doc), " ");
|
||||
if (!doc.empty() && doc.back() != '\n')
|
||||
doc += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
doc += "\n" + stripIndentation(R"(
|
||||
The following input types are still subject to change:
|
||||
|
||||
- `allRefs` (Bool, optional)
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
|
||||
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.
|
||||
|
||||
- `lfs` (Bool, optional)
|
||||
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `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`
|
||||
|
||||
- `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.
|
||||
|
||||
- `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`
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
The URL formats supported are the same as for Git itself.
|
||||
*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
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> 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";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Note**
|
||||
> **Example**
|
||||
>
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix only considers files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory.
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)");
|
||||
|
||||
- `"tarball"`
|
||||
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying 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";
|
||||
> }
|
||||
> ```
|
||||
|
||||
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"
|
||||
> ```
|
||||
)",
|
||||
return doc;
|
||||
}(),
|
||||
.fun = prim_fetchTree,
|
||||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
@@ -581,10 +480,10 @@ static void fetch(
|
||||
auto storePath = unpack ? fetchToStore(
|
||||
state.fetchSettings,
|
||||
*state.store,
|
||||
fetchers::downloadTarball(state.store, state.fetchSettings, *url),
|
||||
fetchers::downloadTarball(*state.store, state.fetchSettings, *url),
|
||||
FetchMode::Copy,
|
||||
name)
|
||||
: fetchers::downloadFile(state.store, state.fetchSettings, *url, name).storePath;
|
||||
: fetchers::downloadFile(*state.store, state.fetchSettings, *url, name).storePath;
|
||||
|
||||
if (expectedHash) {
|
||||
auto hash = unpack ? state.store->queryPathInfo(storePath)->narHash
|
||||
|
||||
@@ -196,7 +196,7 @@ TEST_F(GitTest, submodulePeriodSupport)
|
||||
{"ref", "main"},
|
||||
});
|
||||
|
||||
auto [accessor, i] = input.getAccessor(store);
|
||||
auto [accessor, i] = input.getAccessor(settings, *store);
|
||||
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("deps/sub/lib.txt")), "hello from submodule\n");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
#include "nix/util/url.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -26,18 +27,9 @@ void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
|
||||
throw Error("Input scheme with name %s already registered", schemeName);
|
||||
}
|
||||
|
||||
nlohmann::json dumpRegisterInputSchemeInfo()
|
||||
const InputSchemeMap & getAllInputSchemes()
|
||||
{
|
||||
using nlohmann::json;
|
||||
|
||||
auto res = json::object();
|
||||
|
||||
for (auto & [name, scheme] : inputSchemes()) {
|
||||
auto & r = res[name] = json::object();
|
||||
r["allowedAttrs"] = scheme->allowedAttrs();
|
||||
}
|
||||
|
||||
return res;
|
||||
return inputSchemes();
|
||||
}
|
||||
|
||||
Input Input::fromURL(const Settings & settings, const std::string & url, bool requireTree)
|
||||
@@ -89,7 +81,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
||||
// but not all of them. Doing this is to support those other
|
||||
// operations which are supposed to be robust on
|
||||
// unknown/uninterpretable inputs.
|
||||
Input input{settings};
|
||||
Input input;
|
||||
input.attrs = attrs;
|
||||
fixupInput(input);
|
||||
return input;
|
||||
@@ -119,7 +111,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
||||
return std::move(*res);
|
||||
}
|
||||
|
||||
std::optional<std::string> Input::getFingerprint(ref<Store> store) const
|
||||
std::optional<std::string> Input::getFingerprint(Store & store) const
|
||||
{
|
||||
if (!scheme)
|
||||
return std::nullopt;
|
||||
@@ -159,9 +151,9 @@ bool Input::isDirect() const
|
||||
return !scheme || scheme->isDirect(*this);
|
||||
}
|
||||
|
||||
bool Input::isLocked() const
|
||||
bool Input::isLocked(const Settings & settings) const
|
||||
{
|
||||
return scheme && scheme->isLocked(*this);
|
||||
return scheme && scheme->isLocked(settings, *this);
|
||||
}
|
||||
|
||||
bool Input::isFinal() const
|
||||
@@ -198,19 +190,19 @@ bool Input::contains(const Input & other) const
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
||||
std::pair<StorePath, Input> Input::fetchToStore(const Settings & settings, Store & store) const
|
||||
{
|
||||
if (!scheme)
|
||||
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
|
||||
|
||||
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
|
||||
auto storePath =
|
||||
nix::fetchToStore(*settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
nix::fetchToStore(settings, store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(storePath)->narHash;
|
||||
auto narHash = store.queryPathInfo(storePath)->narHash;
|
||||
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
@@ -297,10 +289,10 @@ void Input::checkLocks(Input specified, Input & result)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, Store & store) const
|
||||
{
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
|
||||
@@ -313,7 +305,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings & settings, Store & store) const
|
||||
{
|
||||
// FIXME: cache the accessor
|
||||
|
||||
@@ -333,13 +325,13 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
||||
*/
|
||||
if (isFinal() && getNarHash()) {
|
||||
try {
|
||||
auto storePath = computeStorePath(*store);
|
||||
auto storePath = computeStorePath(store);
|
||||
|
||||
store->ensurePath(storePath);
|
||||
store.ensurePath(storePath);
|
||||
|
||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
|
||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store.printStorePath(storePath));
|
||||
|
||||
auto accessor = store->requireStoreObjectAccessor(storePath);
|
||||
auto accessor = store.requireStoreObjectAccessor(storePath);
|
||||
|
||||
accessor->fingerprint = getFingerprint(store);
|
||||
|
||||
@@ -349,7 +341,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
||||
if (accessor->fingerprint) {
|
||||
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
|
||||
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
|
||||
settings->getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||
settings.getCache()->upsert(cacheKey, store, {}, storePath);
|
||||
}
|
||||
|
||||
accessor->setPathDisplay("«" + to_string() + "»");
|
||||
@@ -360,7 +352,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
||||
}
|
||||
}
|
||||
|
||||
auto [accessor, result] = scheme->getAccessor(store, *this);
|
||||
auto [accessor, result] = scheme->getAccessor(settings, store, *this);
|
||||
|
||||
if (!accessor->fingerprint)
|
||||
accessor->fingerprint = result.getFingerprint(store);
|
||||
@@ -377,10 +369,10 @@ Input Input::applyOverrides(std::optional<std::string> ref, std::optional<Hash>
|
||||
return scheme->applyOverrides(*this, ref, rev);
|
||||
}
|
||||
|
||||
void Input::clone(const Path & destDir) const
|
||||
void Input::clone(const Settings & settings, Store & store, const std::filesystem::path & destDir) const
|
||||
{
|
||||
assert(scheme);
|
||||
scheme->clone(*this, destDir);
|
||||
scheme->clone(settings, store, *this, destDir);
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> Input::getSourcePath() const
|
||||
@@ -493,9 +485,19 @@ void InputScheme::putFile(
|
||||
throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
|
||||
}
|
||||
|
||||
void InputScheme::clone(const Input & input, const Path & destDir) const
|
||||
void InputScheme::clone(
|
||||
const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir) const
|
||||
{
|
||||
throw Error("do not know how to clone input '%s'", input.to_string());
|
||||
if (std::filesystem::exists(destDir))
|
||||
throw Error("cannot clone into existing path %s", destDir);
|
||||
|
||||
auto [accessor, input2] = getAccessor(settings, store, input);
|
||||
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir));
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) { accessor->dumpPath(CanonPath::root, sink); });
|
||||
|
||||
restorePath(destDir, *source);
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
|
||||
|
||||
@@ -209,7 +209,7 @@ std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Pointer> & pointe
|
||||
auto url = api.endpoint + "/objects/batch";
|
||||
const auto & authHeader = api.authHeader;
|
||||
FileTransferRequest request(parseURL(url));
|
||||
request.method = HttpMethod::POST;
|
||||
request.method = HttpMethod::Post;
|
||||
Headers headers;
|
||||
if (authHeader.has_value())
|
||||
headers.push_back({"Authorization", *authHeader});
|
||||
|
||||
@@ -194,28 +194,183 @@ struct GitInputScheme : InputScheme
|
||||
return "git";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"shallow",
|
||||
"submodules",
|
||||
"lfs",
|
||||
"exportIgnore",
|
||||
"lastModified",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"allRefs",
|
||||
"name",
|
||||
"dirtyRev",
|
||||
"dirtyShortRev",
|
||||
"verifyCommit",
|
||||
"keytype",
|
||||
"publicKey",
|
||||
"publicKeys",
|
||||
return stripIndentation(R"(
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
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 only considers files added to the Git index, as listed by `git ls-files` but uses the *current file contents* of the Git working directory.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
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",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"shallow",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
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",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"lfs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"exportIgnore",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
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",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
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.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"allRefs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
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`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyShortRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"verifyCommit",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"keytype",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKey",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKeys",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -229,7 +384,7 @@ struct GitInputScheme : InputScheme
|
||||
if (auto ref = maybeGetStrAttr(attrs, "ref"); ref && !isLegalRefName(*ref))
|
||||
throw BadURL("invalid Git branch/tag name '%s'", *ref);
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
input.attrs["url"] = fixGitURL(getStrAttr(attrs, "url")).to_string();
|
||||
getShallowAttr(input);
|
||||
@@ -278,7 +433,8 @@ struct GitInputScheme : InputScheme
|
||||
return res;
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto repoInfo = getRepoInfo(input);
|
||||
|
||||
@@ -623,7 +779,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromCommit(ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromCommit(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
assert(!repoInfo.workdirInfo.isDirty);
|
||||
|
||||
@@ -733,10 +889,10 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
auto rev = *input.getRev();
|
||||
|
||||
input.attrs.insert_or_assign("lastModified", getLastModified(*input.settings, repoInfo, repoDir, rev));
|
||||
input.attrs.insert_or_assign("lastModified", getLastModified(settings, repoInfo, repoDir, rev));
|
||||
|
||||
if (!getShallowAttr(input))
|
||||
input.attrs.insert_or_assign("revCount", getRevCount(*input.settings, repoInfo, repoDir, rev));
|
||||
input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev));
|
||||
|
||||
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
|
||||
|
||||
@@ -779,8 +935,8 @@ struct GitInputScheme : InputScheme
|
||||
attrs.insert_or_assign("submodules", Explicit<bool>{true});
|
||||
attrs.insert_or_assign("lfs", Explicit<bool>{smudgeLfs});
|
||||
attrs.insert_or_assign("allRefs", Explicit<bool>{true});
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(settings, store);
|
||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||
mounts.insert_or_assign(submodule.path, submoduleAccessor);
|
||||
}
|
||||
@@ -797,7 +953,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromWorkdir(ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromWorkdir(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
auto repoPath = repoInfo.getPath().value();
|
||||
|
||||
@@ -829,8 +985,8 @@ struct GitInputScheme : InputScheme
|
||||
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
|
||||
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(settings, store);
|
||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||
|
||||
/* If the submodule is dirty, mark this repo dirty as
|
||||
@@ -857,12 +1013,12 @@ struct GitInputScheme : InputScheme
|
||||
input.attrs.insert_or_assign("rev", rev.gitRev());
|
||||
if (!getShallowAttr(input)) {
|
||||
input.attrs.insert_or_assign(
|
||||
"revCount", rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev));
|
||||
"revCount", rev == nullRev ? 0 : getRevCount(settings, repoInfo, repoPath, rev));
|
||||
}
|
||||
|
||||
verifyCommit(input, repo);
|
||||
} else {
|
||||
repoInfo.warnDirty(*input.settings);
|
||||
repoInfo.warnDirty(settings);
|
||||
|
||||
if (repoInfo.workdirInfo.headRev) {
|
||||
input.attrs.insert_or_assign("dirtyRev", repoInfo.workdirInfo.headRev->gitRev() + "-dirty");
|
||||
@@ -874,14 +1030,14 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
input.attrs.insert_or_assign(
|
||||
"lastModified",
|
||||
repoInfo.workdirInfo.headRev
|
||||
? getLastModified(*input.settings, repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
|
||||
: 0);
|
||||
repoInfo.workdirInfo.headRev ? getLastModified(settings, repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
|
||||
: 0);
|
||||
|
||||
return {accessor, std::move(input)};
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
@@ -897,13 +1053,13 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
auto [accessor, final] = input.getRef() || input.getRev() || !repoInfo.getPath()
|
||||
? getAccessorFromCommit(store, repoInfo, std::move(input))
|
||||
: getAccessorFromWorkdir(store, repoInfo, std::move(input));
|
||||
? getAccessorFromCommit(settings, store, repoInfo, std::move(input))
|
||||
: getAccessorFromWorkdir(settings, store, repoInfo, std::move(input));
|
||||
|
||||
return {accessor, std::move(final)};
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
auto makeFingerprint = [&](const Hash & rev) {
|
||||
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "")
|
||||
@@ -934,7 +1090,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
}
|
||||
|
||||
bool isLocked(const Input & input) const override
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
auto rev = input.getRev();
|
||||
return rev && rev != nullRev;
|
||||
|
||||
@@ -92,7 +92,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (ref && rev)
|
||||
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url, *ref, rev->gitRev());
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs.insert_or_assign("type", std::string{schemeName()});
|
||||
input.attrs.insert_or_assign("owner", path[0]);
|
||||
input.attrs.insert_or_assign("repo", path[1]);
|
||||
@@ -110,18 +110,43 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
return {
|
||||
"owner",
|
||||
"repo",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
"lastModified",
|
||||
"host",
|
||||
"treeHash",
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"owner",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"repo",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"host",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"treeHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const fetchers::Settings & settings, const Attrs & attrs) const override
|
||||
@@ -129,7 +154,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
getStrAttr(attrs, "owner");
|
||||
getStrAttr(attrs, "repo");
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -233,9 +258,9 @@ struct GitArchiveInputScheme : InputScheme
|
||||
std::optional<Hash> treeHash;
|
||||
};
|
||||
|
||||
virtual RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const = 0;
|
||||
virtual RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const = 0;
|
||||
|
||||
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
|
||||
virtual DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const = 0;
|
||||
|
||||
struct TarballInfo
|
||||
{
|
||||
@@ -243,7 +268,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
time_t lastModified;
|
||||
};
|
||||
|
||||
std::pair<Input, TarballInfo> downloadArchive(ref<Store> store, Input input) const
|
||||
std::pair<Input, TarballInfo> downloadArchive(const Settings & settings, Store & store, Input input) const
|
||||
{
|
||||
if (!maybeGetStrAttr(input.attrs, "ref"))
|
||||
input.attrs.insert_or_assign("ref", "HEAD");
|
||||
@@ -252,7 +277,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
|
||||
auto rev = input.getRev();
|
||||
if (!rev) {
|
||||
auto refInfo = getRevFromRef(store, input);
|
||||
auto refInfo = getRevFromRef(settings, store, input);
|
||||
rev = refInfo.rev;
|
||||
upstreamTreeHash = refInfo.treeHash;
|
||||
debug("HEAD revision for '%s' is %s", input.to_string(), refInfo.rev.gitRev());
|
||||
@@ -261,7 +286,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
input.attrs.erase("ref");
|
||||
input.attrs.insert_or_assign("rev", rev->gitRev());
|
||||
|
||||
auto cache = input.settings->getCache();
|
||||
auto cache = settings.getCache();
|
||||
|
||||
Cache::Key treeHashKey{"gitRevToTreeHash", {{"rev", rev->gitRev()}}};
|
||||
Cache::Key lastModifiedKey{"gitRevToLastModified", {{"rev", rev->gitRev()}}};
|
||||
@@ -270,7 +295,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (auto lastModifiedAttrs = cache->lookup(lastModifiedKey)) {
|
||||
auto treeHash = getRevAttr(*treeHashAttrs, "treeHash");
|
||||
auto lastModified = getIntAttr(*lastModifiedAttrs, "lastModified");
|
||||
if (input.settings->getTarballCache()->hasObject(treeHash))
|
||||
if (settings.getTarballCache()->hasObject(treeHash))
|
||||
return {std::move(input), TarballInfo{.treeHash = treeHash, .lastModified = (time_t) lastModified}};
|
||||
else
|
||||
debug("Git tree with hash '%s' has disappeared from the cache, refetching...", treeHash.gitRev());
|
||||
@@ -278,7 +303,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
}
|
||||
|
||||
/* Stream the tarball into the tarball cache. */
|
||||
auto url = getDownloadUrl(input);
|
||||
auto url = getDownloadUrl(settings, input);
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
FileTransferRequest req(url.url);
|
||||
@@ -290,7 +315,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
*logger, lvlInfo, actUnknown, fmt("unpacking '%s' into the Git cache", input.to_string()));
|
||||
|
||||
TarArchive archive{*source};
|
||||
auto tarballCache = input.settings->getTarballCache();
|
||||
auto tarballCache = settings.getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
@@ -315,9 +340,10 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return {std::move(input), tarballInfo};
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto [input, tarballInfo] = downloadArchive(store, _input);
|
||||
auto [input, tarballInfo] = downloadArchive(settings, store, _input);
|
||||
|
||||
#if 0
|
||||
input.attrs.insert_or_assign("treeHash", tarballInfo.treeHash.gitRev());
|
||||
@@ -325,19 +351,18 @@ struct GitArchiveInputScheme : InputScheme
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
|
||||
|
||||
auto accessor =
|
||||
input.settings->getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
settings.getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
|
||||
return {accessor, input};
|
||||
}
|
||||
|
||||
bool isLocked(const Input & input) const override
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
/* Since we can't verify the integrity of the tarball from the
|
||||
Git revision alone, we also require a NAR hash for
|
||||
locking. FIXME: in the future, we may want to require a Git
|
||||
tree hash instead of a NAR hash. */
|
||||
return input.getRev().has_value()
|
||||
&& (input.settings->trustTarballsFromGitForges || input.getNarHash().has_value());
|
||||
return input.getRev().has_value() && (settings.trustTarballsFromGitForges || input.getNarHash().has_value());
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> experimentalFeature() const override
|
||||
@@ -345,7 +370,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return Xp::Flakes;
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto rev = input.getRev())
|
||||
return rev->gitRev();
|
||||
@@ -361,6 +386,12 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return "github";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Github supports PAT/OAuth2 tokens and HTTP Basic
|
||||
@@ -387,7 +418,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return getStrAttr(input.attrs, "repo");
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
auto url = fmt(
|
||||
@@ -397,22 +428,22 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
getRepo(input),
|
||||
*input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
store.requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
return RefInfo{
|
||||
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
||||
.treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
|
||||
// If we have no auth headers then we default to the public archive
|
||||
// urls so we do not run into rate limits.
|
||||
@@ -426,12 +457,13 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
|
||||
Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -442,6 +474,12 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return "gitlab";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Gitlab supports 4 kinds of authorization, two of which are
|
||||
@@ -461,7 +499,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return std::make_pair(token.substr(0, fldsplit), token.substr(fldsplit + 1));
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// See rate limiting note below
|
||||
@@ -472,11 +510,11 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
*input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
store.requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
||||
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
||||
@@ -488,7 +526,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
}
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
// This endpoint has a rate limit threshold that may be
|
||||
// server-specific and vary based whether the user is
|
||||
@@ -503,19 +541,20 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(HashFormat::Base16, false));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// FIXME: get username somewhere
|
||||
Input::fromURL(
|
||||
*input.settings,
|
||||
settings,
|
||||
fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -526,6 +565,12 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return "sourcehut";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// SourceHut supports both PAT and OAuth2. See
|
||||
@@ -536,7 +581,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
// Once it is implemented, however, should work as expected.
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
// TODO: In the future, when the sourcehut graphql API is implemented for mercurial
|
||||
// and with anonymous access, this method should use it instead.
|
||||
@@ -547,12 +592,12 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
auto base_url =
|
||||
fmt("https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
|
||||
std::string refUri;
|
||||
if (ref == "HEAD") {
|
||||
auto downloadFileResult = downloadFile(store, *input.settings, fmt("%s/HEAD", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/HEAD", base_url), "source", headers);
|
||||
auto contents = store.requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
|
||||
auto remoteLine = git::parseLsRemoteLine(getLine(contents).first);
|
||||
if (!remoteLine) {
|
||||
@@ -564,9 +609,8 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
}
|
||||
std::regex refRegex(refUri);
|
||||
|
||||
auto downloadFileResult =
|
||||
downloadFile(store, *input.settings, fmt("%s/info/refs", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/info/refs", base_url), "source", headers);
|
||||
auto contents = store.requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
std::istringstream is(contents);
|
||||
|
||||
std::string line;
|
||||
@@ -583,7 +627,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return RefInfo{.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||
auto url =
|
||||
@@ -593,18 +637,19 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(HashFormat::Base16, false));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||
Input::fromURL(
|
||||
*input.settings,
|
||||
settings,
|
||||
fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,13 +36,6 @@ struct Input
|
||||
{
|
||||
friend struct InputScheme;
|
||||
|
||||
const Settings * settings;
|
||||
|
||||
Input(const Settings & settings)
|
||||
: settings{&settings}
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<InputScheme> scheme; // note: can be null
|
||||
Attrs attrs;
|
||||
|
||||
@@ -87,7 +80,7 @@ public:
|
||||
* attributes like a Git revision or NAR hash that uniquely
|
||||
* identify its contents.
|
||||
*/
|
||||
bool isLocked() const;
|
||||
bool isLocked(const Settings & settings) const;
|
||||
|
||||
/**
|
||||
* Only for relative path flakes, i.e. 'path:./foo', returns the
|
||||
@@ -120,7 +113,7 @@ public:
|
||||
* Fetch the entire input into the Nix store, returning the
|
||||
* location in the Nix store and the locked input.
|
||||
*/
|
||||
std::pair<StorePath, Input> fetchToStore(ref<Store> store) const;
|
||||
std::pair<StorePath, Input> fetchToStore(const Settings & settings, Store & store) const;
|
||||
|
||||
/**
|
||||
* Check the locking attributes in `result` against
|
||||
@@ -140,17 +133,17 @@ public:
|
||||
* input without copying it to the store. Also return a possibly
|
||||
* unlocked input.
|
||||
*/
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(const Settings & settings, Store & store) const;
|
||||
|
||||
private:
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(const Settings & settings, Store & store) const;
|
||||
|
||||
public:
|
||||
|
||||
Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
void clone(const Path & destDir) const;
|
||||
void clone(const Settings & settings, Store & store, const std::filesystem::path & destDir) const;
|
||||
|
||||
std::optional<std::filesystem::path> getSourcePath() const;
|
||||
|
||||
@@ -180,7 +173,7 @@ public:
|
||||
*
|
||||
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
|
||||
*/
|
||||
std::optional<std::string> getFingerprint(ref<Store> store) const;
|
||||
std::optional<std::string> getFingerprint(Store & store) const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -210,20 +203,34 @@ struct InputScheme
|
||||
*/
|
||||
virtual std::string_view schemeName() const = 0;
|
||||
|
||||
/**
|
||||
* Longform description of this scheme, for documentation purposes.
|
||||
*/
|
||||
virtual std::string schemeDescription() const = 0;
|
||||
|
||||
// TODO remove these defaults
|
||||
struct AttributeInfo
|
||||
{
|
||||
const char * type = "String";
|
||||
bool required = true;
|
||||
const char * doc = "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Allowed attributes in an attribute set that is converted to an
|
||||
* input.
|
||||
* input, and documentation for each attribute.
|
||||
*
|
||||
* `type` is not included from this set, because the `type` field is
|
||||
* `type` is not included from this map, because the `type` field is
|
||||
parsed first to choose which scheme; `type` is always required.
|
||||
*/
|
||||
virtual StringSet allowedAttrs() const = 0;
|
||||
virtual const std::map<std::string, AttributeInfo> & allowedAttrs() const = 0;
|
||||
|
||||
virtual ParsedURL toURL(const Input & input) const;
|
||||
|
||||
virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
virtual void clone(const Input & input, const Path & destDir) const;
|
||||
virtual void
|
||||
clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir) const;
|
||||
|
||||
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;
|
||||
|
||||
@@ -233,7 +240,8 @@ struct InputScheme
|
||||
std::string_view contents,
|
||||
std::optional<std::string> commitMsg) const;
|
||||
|
||||
virtual std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & input) const = 0;
|
||||
virtual std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & input) const = 0;
|
||||
|
||||
/**
|
||||
* Is this `InputScheme` part of an experimental feature?
|
||||
@@ -245,12 +253,12 @@ struct InputScheme
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
|
||||
virtual std::optional<std::string> getFingerprint(Store & store, const Input & input) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual bool isLocked(const Input & input) const
|
||||
virtual bool isLocked(const Settings & settings, const Input & input) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -269,7 +277,12 @@ struct InputScheme
|
||||
|
||||
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
|
||||
|
||||
nlohmann::json dumpRegisterInputSchemeInfo();
|
||||
using InputSchemeMap = std::map<std::string_view, std::shared_ptr<InputScheme>>;
|
||||
|
||||
/**
|
||||
* Use this for docs, not for finding a specific scheme
|
||||
*/
|
||||
const InputSchemeMap & getAllInputSchemes();
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace nix::fetchers {
|
||||
|
||||
enum class UseRegistries : int;
|
||||
struct Settings;
|
||||
|
||||
struct InputCache
|
||||
{
|
||||
@@ -14,7 +15,8 @@ struct InputCache
|
||||
Attrs extraAttrs;
|
||||
};
|
||||
|
||||
CachedResult getAccessor(ref<Store> store, const Input & originalInput, UseRegistries useRegistries);
|
||||
CachedResult
|
||||
getAccessor(const Settings & settings, Store & store, const Input & originalInput, UseRegistries useRegistries);
|
||||
|
||||
struct CachedInput
|
||||
{
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace nix::fetchers {
|
||||
|
||||
struct Registry
|
||||
{
|
||||
const Settings & settings;
|
||||
|
||||
enum RegistryType {
|
||||
Flag = 0,
|
||||
User = 1,
|
||||
@@ -34,9 +32,8 @@ struct Registry
|
||||
|
||||
std::vector<Entry> entries;
|
||||
|
||||
Registry(const Settings & settings, RegistryType type)
|
||||
: settings{settings}
|
||||
, type{type}
|
||||
Registry(RegistryType type)
|
||||
: type{type}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,7 +54,7 @@ std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Pat
|
||||
|
||||
Path getUserRegistryPath();
|
||||
|
||||
Registries getRegistries(const Settings & settings, ref<Store> store);
|
||||
Registries getRegistries(const Settings & settings, Store & store);
|
||||
|
||||
void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs);
|
||||
|
||||
@@ -71,6 +68,7 @@ enum class UseRegistries : int {
|
||||
* Rewrite a flakeref using the registries. If `filter` is set, only
|
||||
* use the registries for which the filter function returns true.
|
||||
*/
|
||||
std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & input, UseRegistries useRegistries);
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, Store & store, const Input & input, UseRegistries useRegistries);
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -25,7 +25,7 @@ struct DownloadFileResult
|
||||
};
|
||||
|
||||
DownloadFileResult downloadFile(
|
||||
ref<Store> store,
|
||||
Store & store,
|
||||
const Settings & settings,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
@@ -43,6 +43,6 @@ struct DownloadTarballResult
|
||||
* Download and import a tarball into the Git cache. The result is the
|
||||
* Git tree hash of the root directory.
|
||||
*/
|
||||
ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url);
|
||||
ref<SourceAccessor> downloadTarball(Store & store, const Settings & settings, const std::string & url);
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -44,7 +44,7 @@ struct IndirectInputScheme : InputScheme
|
||||
|
||||
// FIXME: forbid query params?
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs.insert_or_assign("type", "indirect");
|
||||
input.attrs.insert_or_assign("id", id);
|
||||
if (rev)
|
||||
@@ -60,14 +60,33 @@ struct IndirectInputScheme : InputScheme
|
||||
return "indirect";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"id",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -76,7 +95,7 @@ struct IndirectInputScheme : InputScheme
|
||||
if (!std::regex_match(id, flakeRegex))
|
||||
throw BadURL("'%s' is not a valid flake ID", id);
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -106,7 +125,8 @@ struct IndirectInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & input) const override
|
||||
{
|
||||
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
|
||||
}
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
InputCache::CachedResult
|
||||
InputCache::getAccessor(ref<Store> store, const Input & originalInput, UseRegistries useRegistries)
|
||||
InputCache::CachedResult InputCache::getAccessor(
|
||||
const Settings & settings, Store & store, const Input & originalInput, UseRegistries useRegistries)
|
||||
{
|
||||
auto fetched = lookup(originalInput);
|
||||
Input resolvedInput = originalInput;
|
||||
|
||||
if (!fetched) {
|
||||
if (originalInput.isDirect()) {
|
||||
auto [accessor, lockedInput] = originalInput.getAccessor(store);
|
||||
auto [accessor, lockedInput] = originalInput.getAccessor(settings, store);
|
||||
fetched.emplace(CachedInput{.lockedInput = lockedInput, .accessor = accessor});
|
||||
} else {
|
||||
if (useRegistries != UseRegistries::No) {
|
||||
auto [res, extraAttrs] = lookupInRegistries(store, originalInput, useRegistries);
|
||||
auto [res, extraAttrs] = lookupInRegistries(settings, store, originalInput, useRegistries);
|
||||
resolvedInput = std::move(res);
|
||||
fetched = lookup(resolvedInput);
|
||||
if (!fetched) {
|
||||
auto [accessor, lockedInput] = resolvedInput.getAccessor(store);
|
||||
auto [accessor, lockedInput] = resolvedInput.getAccessor(settings, store);
|
||||
fetched.emplace(
|
||||
CachedInput{.lockedInput = lockedInput, .accessor = accessor, .extraAttrs = extraAttrs});
|
||||
}
|
||||
|
||||
@@ -68,16 +68,41 @@ struct MercurialInputScheme : InputScheme
|
||||
return "hg";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"name",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -89,7 +114,7 @@ struct MercurialInputScheme : InputScheme
|
||||
throw BadURL("invalid Mercurial branch/tag name '%s'", *ref);
|
||||
}
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -154,7 +179,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return {isLocal, isLocal ? renderUrlPathEnsureLegal(url.path) : url.to_string()};
|
||||
}
|
||||
|
||||
StorePath fetchToStore(ref<Store> store, Input & input) const
|
||||
StorePath fetchToStore(const Settings & settings, Store & store, Input & input) const
|
||||
{
|
||||
auto origRev = input.getRev();
|
||||
|
||||
@@ -176,10 +201,10 @@ struct MercurialInputScheme : InputScheme
|
||||
/* This is an unclean working tree. So copy all tracked
|
||||
files. */
|
||||
|
||||
if (!input.settings->allowDirty)
|
||||
if (!settings.allowDirty)
|
||||
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
||||
|
||||
if (input.settings->warnDirty)
|
||||
if (settings.warnDirty)
|
||||
warn("Mercurial tree '%s' is unclean", actualUrl);
|
||||
|
||||
input.attrs.insert_or_assign("ref", chomp(runHg({"branch", "-R", actualUrl})));
|
||||
@@ -205,7 +230,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore(
|
||||
auto storePath = store.addToStore(
|
||||
input.getName(),
|
||||
{getFSSourceAccessor(), CanonPath(actualPath)},
|
||||
ContentAddressMethod::Raw::NixArchive,
|
||||
@@ -226,7 +251,7 @@ struct MercurialInputScheme : InputScheme
|
||||
"Hash '%s' is not supported by Mercurial. Only sha1 is supported.",
|
||||
rev.to_string(HashFormat::Base16, true));
|
||||
|
||||
return Cache::Key{"hgRev", {{"store", store->storeDir}, {"name", name}, {"rev", input.getRev()->gitRev()}}};
|
||||
return Cache::Key{"hgRev", {{"store", store.storeDir}, {"name", name}, {"rev", input.getRev()->gitRev()}}};
|
||||
};
|
||||
|
||||
auto makeResult = [&](const Attrs & infoAttrs, const StorePath & storePath) -> StorePath {
|
||||
@@ -240,13 +265,13 @@ struct MercurialInputScheme : InputScheme
|
||||
Cache::Key refToRevKey{"hgRefToRev", {{"url", actualUrl}, {"ref", *input.getRef()}}};
|
||||
|
||||
if (!input.getRev()) {
|
||||
if (auto res = input.settings->getCache()->lookupWithTTL(refToRevKey))
|
||||
if (auto res = settings.getCache()->lookupWithTTL(refToRevKey))
|
||||
input.attrs.insert_or_assign("rev", getRevAttr(*res, "rev").gitRev());
|
||||
}
|
||||
|
||||
/* If we have a rev, check if we have a cached store path. */
|
||||
if (auto rev = input.getRev()) {
|
||||
if (auto res = input.settings->getCache()->lookupStorePath(revInfoKey(*rev), *store))
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(*rev), store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
}
|
||||
|
||||
@@ -300,7 +325,7 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
/* Now that we have the rev, check the cache again for a
|
||||
cached store path. */
|
||||
if (auto res = input.settings->getCache()->lookupStorePath(revInfoKey(rev), *store))
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(rev), store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
|
||||
Path tmpDir = createTempDir();
|
||||
@@ -310,38 +335,39 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
deletePath(tmpDir + "/.hg_archival.txt");
|
||||
|
||||
auto storePath = store->addToStore(name, {getFSSourceAccessor(), CanonPath(tmpDir)});
|
||||
auto storePath = store.addToStore(name, {getFSSourceAccessor(), CanonPath(tmpDir)});
|
||||
|
||||
Attrs infoAttrs({
|
||||
{"revCount", (uint64_t) revCount},
|
||||
});
|
||||
|
||||
if (!origRev)
|
||||
input.settings->getCache()->upsert(refToRevKey, {{"rev", rev.gitRev()}});
|
||||
settings.getCache()->upsert(refToRevKey, {{"rev", rev.gitRev()}});
|
||||
|
||||
input.settings->getCache()->upsert(revInfoKey(rev), *store, infoAttrs, storePath);
|
||||
settings.getCache()->upsert(revInfoKey(rev), store, infoAttrs, storePath);
|
||||
|
||||
return makeResult(infoAttrs, std::move(storePath));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
auto storePath = fetchToStore(store, input);
|
||||
auto accessor = store->requireStoreObjectAccessor(storePath);
|
||||
auto storePath = fetchToStore(settings, store, input);
|
||||
auto accessor = store.requireStoreObjectAccessor(storePath);
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
return {accessor, input};
|
||||
}
|
||||
|
||||
bool isLocked(const Input & input) const override
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
return (bool) input.getRev();
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto rev = input.getRev())
|
||||
return rev->gitRev();
|
||||
|
||||
@@ -17,7 +17,7 @@ struct PathInputScheme : InputScheme
|
||||
if (url.authority && url.authority->host.size())
|
||||
throw Error("path URL '%s' should not have an authority ('%s')", url, *url.authority);
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs.insert_or_assign("type", "path");
|
||||
input.attrs.insert_or_assign("path", renderUrlPathEnsureLegal(url.path));
|
||||
|
||||
@@ -40,27 +40,49 @@ struct PathInputScheme : InputScheme
|
||||
return "path";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"path",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"path",
|
||||
{},
|
||||
},
|
||||
/* Allow the user to pass in "fake" tree info
|
||||
attributes. This is useful for making a pinned tree work
|
||||
the same as the repository from which is exported (e.g.
|
||||
path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...).
|
||||
*/
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
"narHash",
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
{
|
||||
getStrAttr(attrs, "path");
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -101,7 +123,7 @@ struct PathInputScheme : InputScheme
|
||||
return path;
|
||||
}
|
||||
|
||||
bool isLocked(const Input & input) const override
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
return (bool) input.getNarHash();
|
||||
}
|
||||
@@ -116,7 +138,8 @@ struct PathInputScheme : InputScheme
|
||||
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
@@ -124,31 +147,31 @@ struct PathInputScheme : InputScheme
|
||||
auto absPath = getAbsPath(input);
|
||||
|
||||
// FIXME: check whether access to 'path' is allowed.
|
||||
auto storePath = store->maybeParseStorePath(absPath.string());
|
||||
auto storePath = store.maybeParseStorePath(absPath.string());
|
||||
|
||||
if (storePath)
|
||||
store->addTempRoot(*storePath);
|
||||
store.addTempRoot(*storePath);
|
||||
|
||||
time_t mtime = 0;
|
||||
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
|
||||
if (!storePath || storePath->name() != "source" || !store.isValidPath(*storePath)) {
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying %s to the store", absPath));
|
||||
// FIXME: try to substitute storePath.
|
||||
auto src = sinkToSource(
|
||||
[&](Sink & sink) { mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter); });
|
||||
storePath = store->addToStoreFromDump(*src, "source");
|
||||
storePath = store.addToStoreFromDump(*src, "source");
|
||||
}
|
||||
|
||||
auto accessor = store->requireStoreObjectAccessor(*storePath);
|
||||
auto accessor = store.requireStoreObjectAccessor(*storePath);
|
||||
|
||||
// To prevent `fetchToStore()` copying the path again to Nix
|
||||
// store, pre-create an entry in the fetcher cache.
|
||||
auto info = store->queryPathInfo(*storePath);
|
||||
auto info = store.queryPathInfo(*storePath);
|
||||
accessor->fingerprint =
|
||||
fmt("path:%s", store->queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
|
||||
input.settings->getCache()->upsert(
|
||||
fmt("path:%s", store.queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
|
||||
settings.getCache()->upsert(
|
||||
makeFetchToStoreCacheKey(
|
||||
input.getName(), *accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
|
||||
*store,
|
||||
store,
|
||||
{},
|
||||
*storePath);
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ std::shared_ptr<Registry> Registry::read(const Settings & settings, const Source
|
||||
{
|
||||
debug("reading registry '%s'", path);
|
||||
|
||||
auto registry = std::make_shared<Registry>(settings, type);
|
||||
auto registry = std::make_shared<Registry>(type);
|
||||
|
||||
if (!path.pathExists())
|
||||
return std::make_shared<Registry>(settings, type);
|
||||
return std::make_shared<Registry>(type);
|
||||
|
||||
try {
|
||||
|
||||
@@ -125,23 +125,23 @@ std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Pat
|
||||
return customRegistry;
|
||||
}
|
||||
|
||||
std::shared_ptr<Registry> getFlagRegistry(const Settings & settings)
|
||||
std::shared_ptr<Registry> getFlagRegistry()
|
||||
{
|
||||
static auto flagRegistry = std::make_shared<Registry>(settings, Registry::Flag);
|
||||
static auto flagRegistry = std::make_shared<Registry>(Registry::Flag);
|
||||
return flagRegistry;
|
||||
}
|
||||
|
||||
void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs)
|
||||
{
|
||||
getFlagRegistry(*from.settings)->add(from, to, extraAttrs);
|
||||
getFlagRegistry()->add(from, to, extraAttrs);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, ref<Store> store)
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, Store & store)
|
||||
{
|
||||
static auto reg = [&]() {
|
||||
auto path = settings.flakeRegistry.get();
|
||||
if (path == "") {
|
||||
return std::make_shared<Registry>(settings, Registry::Global); // empty registry
|
||||
return std::make_shared<Registry>(Registry::Global); // empty registry
|
||||
}
|
||||
|
||||
return Registry::read(
|
||||
@@ -149,9 +149,9 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
|
||||
[&] -> SourcePath {
|
||||
if (!isAbsolute(path)) {
|
||||
auto storePath = downloadFile(store, settings, path, "flake-registry.json").storePath;
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
||||
if (auto store2 = dynamic_cast<LocalFSStore *>(&store))
|
||||
store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json");
|
||||
return {store->requireStoreObjectAccessor(storePath)};
|
||||
return {store.requireStoreObjectAccessor(storePath)};
|
||||
} else {
|
||||
return SourcePath{getFSSourceAccessor(), CanonPath{path}}.resolveSymlinks();
|
||||
}
|
||||
@@ -162,17 +162,18 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
|
||||
return reg;
|
||||
}
|
||||
|
||||
Registries getRegistries(const Settings & settings, ref<Store> store)
|
||||
Registries getRegistries(const Settings & settings, Store & store)
|
||||
{
|
||||
Registries registries;
|
||||
registries.push_back(getFlagRegistry(settings));
|
||||
registries.push_back(getFlagRegistry());
|
||||
registries.push_back(getUserRegistry(settings));
|
||||
registries.push_back(getSystemRegistry(settings));
|
||||
registries.push_back(getGlobalRegistry(settings, store));
|
||||
return registries;
|
||||
}
|
||||
|
||||
std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & _input, UseRegistries useRegistries)
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, Store & store, const Input & _input, UseRegistries useRegistries)
|
||||
{
|
||||
Attrs extraAttrs;
|
||||
int n = 0;
|
||||
@@ -187,7 +188,7 @@ restart:
|
||||
if (n > 100)
|
||||
throw Error("cycle detected in flake registry for '%s'", input.to_string());
|
||||
|
||||
for (auto & registry : getRegistries(*input.settings, store)) {
|
||||
for (auto & registry : getRegistries(settings, store)) {
|
||||
if (useRegistries == UseRegistries::Limited
|
||||
&& !(registry->type == fetchers::Registry::Flag || registry->type == fetchers::Registry::Global))
|
||||
continue;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace nix::fetchers {
|
||||
|
||||
DownloadFileResult downloadFile(
|
||||
ref<Store> store,
|
||||
Store & store,
|
||||
const Settings & settings,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
@@ -28,7 +28,7 @@ DownloadFileResult downloadFile(
|
||||
{"name", name},
|
||||
}}};
|
||||
|
||||
auto cached = settings.getCache()->lookupStorePath(key, *store);
|
||||
auto cached = settings.getCache()->lookupStorePath(key, store);
|
||||
|
||||
auto useCached = [&]() -> DownloadFileResult {
|
||||
return {
|
||||
@@ -74,7 +74,7 @@ DownloadFileResult downloadFile(
|
||||
dumpString(res.data, sink);
|
||||
auto hash = hashString(HashAlgorithm::SHA256, res.data);
|
||||
auto info = ValidPathInfo::makeFromCA(
|
||||
*store,
|
||||
store,
|
||||
name,
|
||||
FixedOutputInfo{
|
||||
.method = FileIngestionMethod::Flat,
|
||||
@@ -84,7 +84,7 @@ DownloadFileResult downloadFile(
|
||||
hashString(HashAlgorithm::SHA256, sink.s));
|
||||
info.narSize = sink.s.size();
|
||||
auto source = StringSource{sink.s};
|
||||
store->addToStore(info, source, NoRepair, NoCheckSigs);
|
||||
store.addToStore(info, source, NoRepair, NoCheckSigs);
|
||||
storePath = std::move(info.path);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ DownloadFileResult downloadFile(
|
||||
key.second.insert_or_assign("url", url);
|
||||
assert(!res.urls.empty());
|
||||
infoAttrs.insert_or_assign("url", *res.urls.rbegin());
|
||||
settings.getCache()->upsert(key, *store, infoAttrs, *storePath);
|
||||
settings.getCache()->upsert(key, store, infoAttrs, *storePath);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -214,7 +214,7 @@ static DownloadTarballResult downloadTarball_(
|
||||
return attrsToResult(infoAttrs);
|
||||
}
|
||||
|
||||
ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url)
|
||||
ref<SourceAccessor> downloadTarball(Store & store, const Settings & settings, const std::string & url)
|
||||
{
|
||||
/* Go through Input::getAccessor() to ensure that the resulting
|
||||
accessor has a fingerprint. */
|
||||
@@ -224,7 +224,7 @@ ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings,
|
||||
|
||||
auto input = Input::fromAttrs(settings, std::move(attrs));
|
||||
|
||||
return input.getAccessor(store).first;
|
||||
return input.getAccessor(settings, store).first;
|
||||
}
|
||||
|
||||
// An input scheme corresponding to a curl-downloadable resource.
|
||||
@@ -252,7 +252,7 @@ struct CurlInputScheme : InputScheme
|
||||
if (!isValidURL(_url, requireTree))
|
||||
return std::nullopt;
|
||||
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
|
||||
auto url = _url;
|
||||
|
||||
@@ -278,7 +278,7 @@ struct CurlInputScheme : InputScheme
|
||||
HTTP request. Now that we've processed the Nix-specific
|
||||
attributes above, remove them so we don't also send them as
|
||||
part of the HTTP request. */
|
||||
for (auto & param : allowedAttrs())
|
||||
for (auto & [param, _] : allowedAttrs())
|
||||
url.query.erase(param);
|
||||
|
||||
input.attrs.insert_or_assign("type", std::string{schemeName()});
|
||||
@@ -286,23 +286,88 @@ struct CurlInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
static const std::map<std::string, AttributeInfo> & allowedAttrsImpl()
|
||||
{
|
||||
return {
|
||||
"type",
|
||||
"url",
|
||||
"narHash",
|
||||
"name",
|
||||
"unpack",
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
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";
|
||||
> }
|
||||
> ```
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"unpack",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
return allowedAttrsImpl();
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
{
|
||||
Input input{settings};
|
||||
Input input{};
|
||||
input.attrs = attrs;
|
||||
|
||||
// input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
||||
@@ -319,7 +384,7 @@ struct CurlInputScheme : InputScheme
|
||||
return url;
|
||||
}
|
||||
|
||||
bool isLocked(const Input & input) const override
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
{
|
||||
return (bool) input.getNarHash();
|
||||
}
|
||||
@@ -332,6 +397,14 @@ struct FileInputScheme : CurlInputScheme
|
||||
return "file";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
)");
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -340,7 +413,8 @@ struct FileInputScheme : CurlInputScheme
|
||||
: (!requireTree && !hasTarballExtension(url)));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
@@ -348,12 +422,12 @@ struct FileInputScheme : CurlInputScheme
|
||||
the Nix store directly, since there is little deduplication
|
||||
benefit in using the Git cache for single big files like
|
||||
tarballs. */
|
||||
auto file = downloadFile(store, *input.settings, getStrAttr(input.attrs, "url"), input.getName());
|
||||
auto file = downloadFile(store, settings, getStrAttr(input.attrs, "url"), input.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
||||
auto narHash = store.queryPathInfo(file.storePath)->narHash;
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
auto accessor = ref{store->getFSAccessor(file.storePath)};
|
||||
auto accessor = ref{store.getFSAccessor(file.storePath)};
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
@@ -368,6 +442,34 @@ struct TarballInputScheme : CurlInputScheme
|
||||
return "tarball";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = [] {
|
||||
auto attrs = CurlInputScheme::allowedAttrsImpl();
|
||||
// Override the "url" attribute to add tarball-specific example
|
||||
attrs["url"].doc = R"(
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
)";
|
||||
return attrs;
|
||||
}();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -377,15 +479,15 @@ struct TarballInputScheme : CurlInputScheme
|
||||
: (requireTree || hasTarballExtension(url)));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
auto result =
|
||||
downloadTarball_(*input.settings, getStrAttr(input.attrs, "url"), {}, "«" + input.to_string() + "»");
|
||||
auto result = downloadTarball_(settings, getStrAttr(input.attrs, "url"), {}, "«" + input.to_string() + "»");
|
||||
|
||||
if (result.immutableUrl) {
|
||||
auto immutableInput = Input::fromURL(*input.settings, *result.immutableUrl);
|
||||
auto immutableInput = Input::fromURL(settings, *result.immutableUrl);
|
||||
// FIXME: would be nice to support arbitrary flakerefs
|
||||
// here, e.g. git flakes.
|
||||
if (immutableInput.getType() != "tarball")
|
||||
@@ -398,14 +500,12 @@ struct TarballInputScheme : CurlInputScheme
|
||||
|
||||
input.attrs.insert_or_assign(
|
||||
"narHash",
|
||||
input.settings->getTarballCache()
|
||||
->treeHashToNarHash(*input.settings, result.treeHash)
|
||||
.to_string(HashFormat::SRI, true));
|
||||
settings.getTarballCache()->treeHashToNarHash(settings, result.treeHash).to_string(HashFormat::SRI, true));
|
||||
|
||||
return {result.accessor, input};
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto narHash = input.getNarHash())
|
||||
return narHash->to_string(HashFormat::SRI, true);
|
||||
|
||||
@@ -38,7 +38,7 @@ PrimOp getFlake(const Settings & settings)
|
||||
std::string flakeRefS(
|
||||
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked())
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
|
||||
throw Error(
|
||||
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
|
||||
flakeRefS,
|
||||
|
||||
@@ -372,7 +372,8 @@ static Flake getFlake(
|
||||
const InputAttrPath & lockRootAttrPath)
|
||||
{
|
||||
// Fetch a lazy tree first.
|
||||
auto cachedInput = state.inputCache->getAccessor(state.store, originalRef.input, useRegistries);
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, *state.store, originalRef.input, useRegistries);
|
||||
|
||||
auto subdir = fetchers::maybeGetStrAttr(cachedInput.extraAttrs, "dir").value_or(originalRef.subdir);
|
||||
auto resolvedRef = FlakeRef(std::move(cachedInput.resolvedInput), subdir);
|
||||
@@ -388,7 +389,8 @@ static Flake getFlake(
|
||||
debug("refetching input '%s' due to self attribute", newLockedRef);
|
||||
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
|
||||
newLockedRef.input.attrs.erase("narHash");
|
||||
auto cachedInput2 = state.inputCache->getAccessor(state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
auto cachedInput2 = state.inputCache->getAccessor(
|
||||
state.fetchSettings, *state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
cachedInput.accessor = cachedInput2.accessor;
|
||||
lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir);
|
||||
}
|
||||
@@ -704,7 +706,8 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
||||
this input. */
|
||||
debug("creating new input '%s'", inputAttrPathS);
|
||||
|
||||
if (!lockFlags.allowUnlocked && !input.ref->input.isLocked() && !input.ref->input.isRelative())
|
||||
if (!lockFlags.allowUnlocked && !input.ref->input.isLocked(state.fetchSettings)
|
||||
&& !input.ref->input.isRelative())
|
||||
throw Error("cannot update unlocked flake input '%s' in pure mode", inputAttrPathS);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
@@ -753,7 +756,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
||||
return {*resolvedPath, *input.ref};
|
||||
} else {
|
||||
auto cachedInput = state.inputCache->getAccessor(
|
||||
state.store, input.ref->input, useRegistriesInputs);
|
||||
state.fetchSettings, *state.store, input.ref->input, useRegistriesInputs);
|
||||
|
||||
auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir);
|
||||
|
||||
@@ -972,7 +975,7 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
|
||||
state.callFunction(*vCallFlake, args, vRes, noPos);
|
||||
}
|
||||
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store, const fetchers::Settings & fetchSettings) const
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(Store & store, const fetchers::Settings & fetchSettings) const
|
||||
{
|
||||
if (lockFile.isUnlocked(fetchSettings))
|
||||
return std::nullopt;
|
||||
@@ -1002,7 +1005,7 @@ Flake::~Flake() {}
|
||||
ref<eval_cache::EvalCache> openEvalCache(EvalState & state, ref<const LockedFlake> lockedFlake)
|
||||
{
|
||||
auto fingerprint = state.settings.useEvalCache && state.settings.pureEval
|
||||
? lockedFlake->getFingerprint(state.store, state.fetchSettings)
|
||||
? lockedFlake->getFingerprint(*state.store, state.fetchSettings)
|
||||
: std::nullopt;
|
||||
auto rootLoader = [&state, lockedFlake]() {
|
||||
/* For testing whether the evaluation cache is
|
||||
|
||||
@@ -64,9 +64,10 @@ std::ostream & operator<<(std::ostream & str, const FlakeRef & flakeRef)
|
||||
return str;
|
||||
}
|
||||
|
||||
FlakeRef FlakeRef::resolve(ref<Store> store, fetchers::UseRegistries useRegistries) const
|
||||
FlakeRef
|
||||
FlakeRef::resolve(const fetchers::Settings & fetchSettings, Store & store, fetchers::UseRegistries useRegistries) const
|
||||
{
|
||||
auto [input2, extraAttrs] = lookupInRegistries(store, input, useRegistries);
|
||||
auto [input2, extraAttrs] = lookupInRegistries(fetchSettings, store, input, useRegistries);
|
||||
return FlakeRef(std::move(input2), fetchers::maybeGetStrAttr(extraAttrs, "dir").value_or(subdir));
|
||||
}
|
||||
|
||||
@@ -287,9 +288,10 @@ FlakeRef FlakeRef::fromAttrs(const fetchers::Settings & fetchSettings, const fet
|
||||
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, FlakeRef>
|
||||
FlakeRef::lazyFetch(const fetchers::Settings & fetchSettings, Store & store) const
|
||||
{
|
||||
auto [accessor, lockedInput] = input.getAccessor(store);
|
||||
auto [accessor, lockedInput] = input.getAccessor(fetchSettings, store);
|
||||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ struct LockedFlake
|
||||
*/
|
||||
std::map<ref<Node>, SourcePath> nodePaths;
|
||||
|
||||
std::optional<Fingerprint> getFingerprint(ref<Store> store, const fetchers::Settings & fetchSettings) const;
|
||||
std::optional<Fingerprint> getFingerprint(Store & store, const fetchers::Settings & fetchSettings) const;
|
||||
};
|
||||
|
||||
struct LockFlags
|
||||
|
||||
@@ -71,11 +71,14 @@ struct FlakeRef
|
||||
|
||||
fetchers::Attrs toAttrs() const;
|
||||
|
||||
FlakeRef resolve(ref<Store> store, fetchers::UseRegistries useRegistries = fetchers::UseRegistries::All) const;
|
||||
FlakeRef resolve(
|
||||
const fetchers::Settings & fetchSettings,
|
||||
Store & store,
|
||||
fetchers::UseRegistries useRegistries = fetchers::UseRegistries::All) const;
|
||||
|
||||
static FlakeRef fromAttrs(const fetchers::Settings & fetchSettings, const fetchers::Attrs & attrs);
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(const fetchers::Settings & fetchSettings, Store & store) const;
|
||||
|
||||
/**
|
||||
* Canonicalize a flakeref for the purpose of comparing "old" and
|
||||
|
||||
@@ -74,7 +74,7 @@ LockedNode::LockedNode(const fetchers::Settings & fetchSettings, const nlohmann:
|
||||
, parentInputAttrPath(
|
||||
json.find("parent") != json.end() ? (std::optional<InputAttrPath>) json["parent"] : std::nullopt)
|
||||
{
|
||||
if (!lockedRef.input.isLocked() && !lockedRef.input.isRelative()) {
|
||||
if (!lockedRef.input.isLocked(fetchSettings) && !lockedRef.input.isRelative()) {
|
||||
if (lockedRef.input.getNarHash())
|
||||
warn(
|
||||
"Lock file entry '%s' is unlocked (e.g. lacks a Git revision) but is checked by NAR hash. "
|
||||
@@ -282,7 +282,7 @@ std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSet
|
||||
latter case, we can verify the input but we may not be able to
|
||||
fetch it from anywhere. */
|
||||
auto isConsideredLocked = [&](const fetchers::Input & input) {
|
||||
return input.isLocked() || (fetchSettings.allowDirtyLocks && input.getNarHash());
|
||||
return input.isLocked(fetchSettings) || (fetchSettings.allowDirtyLocks && input.getNarHash());
|
||||
};
|
||||
|
||||
for (auto & i : nodes) {
|
||||
|
||||
@@ -35,6 +35,8 @@ include_dirs = [ include_directories('.') ]
|
||||
|
||||
headers = files(
|
||||
'nix_api_store.h',
|
||||
'nix_api_store/derivation.h',
|
||||
'nix_api_store/store_path.h',
|
||||
)
|
||||
|
||||
# TODO don't install this once tests don't use it and/or move the header into `libstore`, non-`c`
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_store/store_path.h"
|
||||
#include "nix_api_store/derivation.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -21,10 +23,6 @@ extern "C" {
|
||||
|
||||
/** @brief Reference to a Nix store */
|
||||
typedef struct Store Store;
|
||||
/** @brief Nix store path */
|
||||
typedef struct StorePath StorePath;
|
||||
/** @brief Nix Derivation */
|
||||
typedef struct nix_derivation nix_derivation;
|
||||
|
||||
/**
|
||||
* @brief Initializes the Nix store library
|
||||
@@ -118,30 +116,6 @@ nix_store_get_storedir(nix_c_context * context, Store * store, nix_get_string_ca
|
||||
*/
|
||||
StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path);
|
||||
|
||||
/**
|
||||
* @brief Get the path name (e.g. "name" in /nix/store/...-name)
|
||||
*
|
||||
* @param[in] store_path the path to get the name from
|
||||
* @param[in] callback called with the name
|
||||
* @param[in] user_data arbitrary data, passed to the callback when it's called.
|
||||
*/
|
||||
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
/**
|
||||
* @brief Copy a StorePath
|
||||
*
|
||||
* @param[in] p the path to copy
|
||||
* @return a new StorePath
|
||||
*/
|
||||
StorePath * nix_store_path_clone(const StorePath * p);
|
||||
|
||||
/** @brief Deallocate a StorePath
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] p the path to free
|
||||
*/
|
||||
void nix_store_path_free(StorePath * p);
|
||||
|
||||
/**
|
||||
* @brief Check if a StorePath is valid (i.e. that corresponding store object and its closure of references exists in
|
||||
* the store)
|
||||
@@ -229,14 +203,6 @@ nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store
|
||||
*/
|
||||
StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation);
|
||||
|
||||
/**
|
||||
* @brief Deallocate a `nix_derivation`
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] drv the derivation to free
|
||||
*/
|
||||
void nix_derivation_free(nix_derivation * drv);
|
||||
|
||||
/**
|
||||
* @brief Copy the closure of `path` from `srcStore` to `dstStore`.
|
||||
*
|
||||
|
||||
38
src/libstore-c/nix_api_store/derivation.h
Normal file
38
src/libstore-c/nix_api_store/derivation.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef NIX_API_STORE_DERIVATION_H
|
||||
#define NIX_API_STORE_DERIVATION_H
|
||||
/**
|
||||
* @defgroup libstore_derivation Derivation
|
||||
* @ingroup libstore
|
||||
* @brief Derivation operations that don't require a Store
|
||||
* @{
|
||||
*/
|
||||
/** @file
|
||||
* @brief Derivation operations
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// cffi start
|
||||
|
||||
/** @brief Nix Derivation */
|
||||
typedef struct nix_derivation nix_derivation;
|
||||
|
||||
/**
|
||||
* @brief Deallocate a `nix_derivation`
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] drv the derivation to free
|
||||
*/
|
||||
void nix_derivation_free(nix_derivation * drv);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
#endif // NIX_API_STORE_DERIVATION_H
|
||||
54
src/libstore-c/nix_api_store/store_path.h
Normal file
54
src/libstore-c/nix_api_store/store_path.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef NIX_API_STORE_STORE_PATH_H
|
||||
#define NIX_API_STORE_STORE_PATH_H
|
||||
/**
|
||||
* @defgroup libstore_storepath StorePath
|
||||
* @ingroup libstore
|
||||
* @brief Store path operations that don't require a Store
|
||||
* @{
|
||||
*/
|
||||
/** @file
|
||||
* @brief Store path operations
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// cffi start
|
||||
|
||||
/** @brief Nix store path */
|
||||
typedef struct StorePath StorePath;
|
||||
|
||||
/**
|
||||
* @brief Copy a StorePath
|
||||
*
|
||||
* @param[in] p the path to copy
|
||||
* @return a new StorePath
|
||||
*/
|
||||
StorePath * nix_store_path_clone(const StorePath * p);
|
||||
|
||||
/** @brief Deallocate a StorePath
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] p the path to free
|
||||
*/
|
||||
void nix_store_path_free(StorePath * p);
|
||||
|
||||
/**
|
||||
* @brief Get the path name (e.g. "<name>" in /nix/store/<hash>-<name>)
|
||||
*
|
||||
* @param[in] store_path the path to get the name from
|
||||
* @param[in] callback called with the name
|
||||
* @param[in] user_data arbitrary data, passed to the callback when it's called.
|
||||
*/
|
||||
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
#endif // NIX_API_STORE_STORE_PATH_H
|
||||
@@ -4,10 +4,13 @@
|
||||
"allowSubstitutes": false,
|
||||
"exportReferencesGraph": {
|
||||
"refs1": [
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"refs2": [
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
]
|
||||
},
|
||||
"impureEnvVars": [
|
||||
@@ -20,18 +23,36 @@
|
||||
"outputChecks": {
|
||||
"forAllOutputs": {
|
||||
"allowedReferences": [
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"bin"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "bin"
|
||||
},
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "dev"
|
||||
}
|
||||
],
|
||||
"disallowedReferences": [
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"dev"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "dev"
|
||||
},
|
||||
{
|
||||
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
|
||||
{
|
||||
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
"output": "dev"
|
||||
}
|
||||
],
|
||||
"ignoreSelfRefs": true,
|
||||
"maxClosureSize": null,
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
"allowSubstitutes": false,
|
||||
"exportReferencesGraph": {
|
||||
"refs1": [
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"refs2": [
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
]
|
||||
},
|
||||
"impureEnvVars": [
|
||||
@@ -23,11 +26,20 @@
|
||||
"allowedReferences": null,
|
||||
"allowedRequisites": null,
|
||||
"disallowedReferences": [
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"dev"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "dev"
|
||||
},
|
||||
{
|
||||
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
|
||||
{
|
||||
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
"output": "dev"
|
||||
}
|
||||
],
|
||||
"ignoreSelfRefs": false,
|
||||
"maxClosureSize": null,
|
||||
@@ -44,11 +56,20 @@
|
||||
},
|
||||
"out": {
|
||||
"allowedReferences": [
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "out"
|
||||
}
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"bin"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "bin"
|
||||
},
|
||||
{
|
||||
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
|
||||
"output": "dev"
|
||||
}
|
||||
],
|
||||
"disallowedReferences": [],
|
||||
"disallowedRequisites": [],
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"allowSubstitutes": false,
|
||||
"exportReferencesGraph": {
|
||||
"refs1": [
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"refs2": [
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
]
|
||||
},
|
||||
"impureEnvVars": [
|
||||
@@ -20,18 +20,24 @@
|
||||
"outputChecks": {
|
||||
"forAllOutputs": {
|
||||
"allowedReferences": [
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"bin"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "bin"
|
||||
},
|
||||
"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
|
||||
],
|
||||
"disallowedReferences": [
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"dev"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "dev"
|
||||
},
|
||||
"r5cff30838majxk5mp3ip2diffi8vpaj-bar"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
"9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
],
|
||||
"ignoreSelfRefs": true,
|
||||
"maxClosureSize": null,
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"allowSubstitutes": false,
|
||||
"exportReferencesGraph": {
|
||||
"refs1": [
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"refs2": [
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
]
|
||||
},
|
||||
"impureEnvVars": [
|
||||
@@ -23,11 +23,14 @@
|
||||
"allowedReferences": null,
|
||||
"allowedRequisites": null,
|
||||
"disallowedReferences": [
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"dev"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "dev"
|
||||
},
|
||||
"r5cff30838majxk5mp3ip2diffi8vpaj-bar"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
"9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
],
|
||||
"ignoreSelfRefs": false,
|
||||
"maxClosureSize": null,
|
||||
@@ -44,11 +47,14 @@
|
||||
},
|
||||
"out": {
|
||||
"allowedReferences": [
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"bin"
|
||||
{
|
||||
"drvPath": "self",
|
||||
"output": "bin"
|
||||
},
|
||||
"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
|
||||
],
|
||||
"disallowedReferences": [],
|
||||
"disallowedRequisites": [],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "nix/util/experimental-features.hh"
|
||||
#include "nix/store/derivations.hh"
|
||||
#include "nix/store/derivations.hh"
|
||||
#include "nix/store/derived-path.hh"
|
||||
#include "nix/store/derivation-options.hh"
|
||||
#include "nix/store/parsed-derivations.hh"
|
||||
#include "nix/util/types.hh"
|
||||
@@ -17,7 +17,7 @@ namespace nix {
|
||||
using namespace nlohmann;
|
||||
|
||||
class DerivationAdvancedAttrsTest : public JsonCharacterizationTest<Derivation>,
|
||||
public JsonCharacterizationTest<DerivationOptions>,
|
||||
public JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>,
|
||||
public LibStoreTest
|
||||
{
|
||||
protected:
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
{
|
||||
this->readTest(fileName, [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedFeatures);
|
||||
});
|
||||
}
|
||||
@@ -51,11 +52,14 @@ public:
|
||||
* Helper function to test DerivationOptions parsing and comparison
|
||||
*/
|
||||
void testDerivationOptions(
|
||||
const std::string & fileName, const DerivationOptions & expected, const StringSet & expectedSystemFeatures)
|
||||
const std::string & fileName,
|
||||
const DerivationOptions<SingleDerivedPath> & expected,
|
||||
const StringSet & expectedSystemFeatures)
|
||||
{
|
||||
this->readTest(fileName, [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
|
||||
EXPECT_EQ(options, expected);
|
||||
EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedSystemFeatures);
|
||||
@@ -131,22 +135,38 @@ TEST_ATERM_JSON(advancedAttributes_structuredAttrs_defaults, "advanced-attribute
|
||||
* Since these are both repeated and sensative opaque values, it makes
|
||||
* sense to give them names in this file.
|
||||
*/
|
||||
static std::string pathFoo = "/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo",
|
||||
pathFooDev = "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
pathBar = "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
pathBarDev = "/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev",
|
||||
pathBarDrvIA = "/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",
|
||||
pathBarDrvCA = "/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
placeholderFoo = "/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9",
|
||||
placeholderFooDev = "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
placeholderBar = "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
placeholderBarDev = "/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8";
|
||||
static SingleDerivedPath
|
||||
pathFoo = SingleDerivedPath::Opaque{StorePath{"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
|
||||
pathFooDev = SingleDerivedPath::Opaque{StorePath{"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"}},
|
||||
pathBar = SingleDerivedPath::Opaque{StorePath{"r5cff30838majxk5mp3ip2diffi8vpaj-bar"}},
|
||||
pathBarDev = SingleDerivedPath::Opaque{StorePath{"9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"}},
|
||||
pathBarDrvIA = SingleDerivedPath::Opaque{StorePath{"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
|
||||
pathBarDrvCA = SingleDerivedPath::Opaque{StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
|
||||
placeholderFoo =
|
||||
SingleDerivedPath::Built{
|
||||
.drvPath = makeConstantStorePathRef(StorePath{"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv"}),
|
||||
.output = "out",
|
||||
},
|
||||
placeholderFooDev =
|
||||
SingleDerivedPath::Built{
|
||||
.drvPath = makeConstantStorePathRef(StorePath{"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv"}),
|
||||
.output = "dev",
|
||||
},
|
||||
placeholderBar =
|
||||
SingleDerivedPath::Built{
|
||||
.drvPath = makeConstantStorePathRef(StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}),
|
||||
.output = "out",
|
||||
},
|
||||
placeholderBarDev = SingleDerivedPath::Built{
|
||||
.drvPath = makeConstantStorePathRef(StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}),
|
||||
.output = "dev",
|
||||
};
|
||||
|
||||
using ExportReferencesMap = decltype(DerivationOptions::exportReferencesGraph);
|
||||
using ExportReferencesMap = decltype(DerivationOptions<SingleDerivedPath>::exportReferencesGraph);
|
||||
|
||||
static const DerivationOptions advancedAttributes_defaults = {
|
||||
static const DerivationOptions<SingleDerivedPath> advancedAttributes_defaults = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
@@ -167,7 +187,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_defaults)
|
||||
this->readTest("advanced-attributes-defaults.drv", [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
|
||||
EXPECT_TRUE(!got.structuredAttrs);
|
||||
|
||||
@@ -192,9 +213,9 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_defaults)
|
||||
|
||||
TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
|
||||
{
|
||||
DerivationOptions expected = {
|
||||
DerivationOptions<SingleDerivedPath> expected = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
@@ -212,12 +233,13 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
|
||||
this->readTest("advanced-attributes.drv", [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
|
||||
EXPECT_TRUE(!got.structuredAttrs);
|
||||
|
||||
// Reset fields that vary between test cases to enable whole-object comparison
|
||||
options.outputChecks = DerivationOptions::OutputChecks{.ignoreSelfRefs = true};
|
||||
options.outputChecks = DerivationOptions<SingleDerivedPath>::OutputChecks{.ignoreSelfRefs = true};
|
||||
options.exportReferencesGraph = {};
|
||||
|
||||
EXPECT_EQ(options, expected);
|
||||
@@ -227,14 +249,14 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
|
||||
});
|
||||
};
|
||||
|
||||
DerivationOptions advancedAttributes_ia = {
|
||||
DerivationOptions<SingleDerivedPath> advancedAttributes_ia = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
.allowedReferences = StringSet{pathFoo},
|
||||
.disallowedReferences = StringSet{pathBar, "dev"},
|
||||
.allowedRequisites = StringSet{pathFooDev, "bin"},
|
||||
.disallowedRequisites = StringSet{pathBarDev},
|
||||
.allowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathFoo},
|
||||
.disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathBar, OutputName{"dev"}},
|
||||
.allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathFooDev, OutputName{"bin"}},
|
||||
.disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathBarDev},
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
.passAsFile = {},
|
||||
@@ -257,14 +279,14 @@ TEST_F(DerivationAdvancedAttrsTest, advancedAttributes_ia)
|
||||
testDerivationOptions("advanced-attributes.drv", advancedAttributes_ia, {"rainbow", "uid-range"});
|
||||
};
|
||||
|
||||
DerivationOptions advancedAttributes_ca = {
|
||||
DerivationOptions<SingleDerivedPath> advancedAttributes_ca = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
.allowedReferences = StringSet{placeholderFoo},
|
||||
.disallowedReferences = StringSet{placeholderBar, "dev"},
|
||||
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
|
||||
.disallowedRequisites = StringSet{placeholderBarDev},
|
||||
.allowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderFoo},
|
||||
.disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderBar, OutputName{"dev"}},
|
||||
.allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderFooDev, OutputName{"bin"}},
|
||||
.disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderBarDev},
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
.passAsFile = {},
|
||||
@@ -287,8 +309,8 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes)
|
||||
testDerivationOptions("advanced-attributes.drv", advancedAttributes_ca, {"rainbow", "uid-range", "ca-derivations"});
|
||||
};
|
||||
|
||||
DerivationOptions advancedAttributes_structuredAttrs_defaults = {
|
||||
.outputChecks = std::map<std::string, DerivationOptions::OutputChecks>{},
|
||||
DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_defaults = {
|
||||
.outputChecks = std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{},
|
||||
.unsafeDiscardReferences = {},
|
||||
.passAsFile = {},
|
||||
.exportReferencesGraph = {},
|
||||
@@ -307,7 +329,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs_d
|
||||
this->readTest("advanced-attributes-structured-attrs-defaults.drv", [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
|
||||
EXPECT_TRUE(got.structuredAttrs);
|
||||
|
||||
@@ -332,11 +355,11 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs_default
|
||||
|
||||
TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
|
||||
{
|
||||
DerivationOptions expected = {
|
||||
DerivationOptions<SingleDerivedPath> expected = {
|
||||
.outputChecks =
|
||||
std::map<std::string, DerivationOptions::OutputChecks>{
|
||||
std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
|
||||
{"dev",
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.maxSize = 789,
|
||||
.maxClosureSize = 5909,
|
||||
}},
|
||||
@@ -357,7 +380,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
|
||||
this->readTest("advanced-attributes-structured-attrs.drv", [&](auto encoded) {
|
||||
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
|
||||
|
||||
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs);
|
||||
auto options = derivationOptionsFromStructuredAttrs(
|
||||
*this->store, got.inputDrvs, got.env, get(got.structuredAttrs), true, this->mockXpSettings);
|
||||
|
||||
EXPECT_TRUE(got.structuredAttrs);
|
||||
|
||||
@@ -365,7 +389,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
|
||||
{
|
||||
// Delete all keys but "dev" in options.outputChecks
|
||||
auto * outputChecksMapP =
|
||||
std::get_if<std::map<std::string, DerivationOptions::OutputChecks>>(&options.outputChecks);
|
||||
std::get_if<std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>>(
|
||||
&options.outputChecks);
|
||||
ASSERT_TRUE(outputChecksMapP);
|
||||
auto & outputChecksMap = *outputChecksMapP;
|
||||
auto devEntry = outputChecksMap.find("dev");
|
||||
@@ -385,21 +410,21 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
|
||||
});
|
||||
};
|
||||
|
||||
DerivationOptions advancedAttributes_structuredAttrs_ia = {
|
||||
DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_ia = {
|
||||
.outputChecks =
|
||||
std::map<std::string, DerivationOptions::OutputChecks>{
|
||||
std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
|
||||
{"out",
|
||||
DerivationOptions::OutputChecks{
|
||||
.allowedReferences = StringSet{pathFoo},
|
||||
.allowedRequisites = StringSet{pathFooDev, "bin"},
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.allowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathFoo},
|
||||
.allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathFooDev, OutputName{"bin"}},
|
||||
}},
|
||||
{"bin",
|
||||
DerivationOptions::OutputChecks{
|
||||
.disallowedReferences = StringSet{pathBar, "dev"},
|
||||
.disallowedRequisites = StringSet{pathBarDev},
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathBar, OutputName{"dev"}},
|
||||
.disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathBarDev},
|
||||
}},
|
||||
{"dev",
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.maxSize = 789,
|
||||
.maxClosureSize = 5909,
|
||||
}},
|
||||
@@ -427,21 +452,21 @@ TEST_F(DerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs)
|
||||
"advanced-attributes-structured-attrs.drv", advancedAttributes_structuredAttrs_ia, {"rainbow", "uid-range"});
|
||||
};
|
||||
|
||||
DerivationOptions advancedAttributes_structuredAttrs_ca = {
|
||||
DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_ca = {
|
||||
.outputChecks =
|
||||
std::map<std::string, DerivationOptions::OutputChecks>{
|
||||
std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
|
||||
{"out",
|
||||
DerivationOptions::OutputChecks{
|
||||
.allowedReferences = StringSet{placeholderFoo},
|
||||
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.allowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderFoo},
|
||||
.allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderFooDev, OutputName{"bin"}},
|
||||
}},
|
||||
{"bin",
|
||||
DerivationOptions::OutputChecks{
|
||||
.disallowedReferences = StringSet{placeholderBar, "dev"},
|
||||
.disallowedRequisites = StringSet{placeholderBarDev},
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderBar, OutputName{"dev"}},
|
||||
.disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderBarDev},
|
||||
}},
|
||||
{"dev",
|
||||
DerivationOptions::OutputChecks{
|
||||
DerivationOptions<SingleDerivedPath>::OutputChecks{
|
||||
.maxSize = 789,
|
||||
.maxClosureSize = 5909,
|
||||
}},
|
||||
@@ -471,14 +496,16 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs)
|
||||
{"rainbow", "uid-range", "ca-derivations"});
|
||||
};
|
||||
|
||||
#define TEST_JSON_OPTIONS(FIXUTURE, VAR, VAR2) \
|
||||
TEST_F(FIXUTURE, DerivationOptions_##VAR##_from_json) \
|
||||
{ \
|
||||
this->JsonCharacterizationTest<DerivationOptions>::readJsonTest(#VAR, advancedAttributes_##VAR2); \
|
||||
} \
|
||||
TEST_F(FIXUTURE, DerivationOptions_##VAR##_to_json) \
|
||||
{ \
|
||||
this->JsonCharacterizationTest<DerivationOptions>::writeJsonTest(#VAR, advancedAttributes_##VAR2); \
|
||||
#define TEST_JSON_OPTIONS(FIXUTURE, VAR, VAR2) \
|
||||
TEST_F(FIXUTURE, DerivationOptions_##VAR##_from_json) \
|
||||
{ \
|
||||
this->JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>::readJsonTest( \
|
||||
#VAR, advancedAttributes_##VAR2); \
|
||||
} \
|
||||
TEST_F(FIXUTURE, DerivationOptions_##VAR##_to_json) \
|
||||
{ \
|
||||
this->JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>::writeJsonTest( \
|
||||
#VAR, advancedAttributes_##VAR2); \
|
||||
}
|
||||
|
||||
TEST_JSON_OPTIONS(DerivationAdvancedAttrsTest, defaults, defaults)
|
||||
|
||||
@@ -40,7 +40,7 @@ static BuildResult::Success::Status successStatusFromString(std::string_view str
|
||||
throw Error("unknown built result success status '%s'", str);
|
||||
}
|
||||
|
||||
static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_view>, 13> failureStatusStrings{{
|
||||
static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_view>, 12> failureStatusStrings{{
|
||||
#define ENUM_ENTRY(e) {BuildResult::Failure::e, #e}
|
||||
ENUM_ENTRY(PermanentFailure),
|
||||
ENUM_ENTRY(InputRejected),
|
||||
@@ -54,7 +54,6 @@ static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_
|
||||
ENUM_ENTRY(NotDeterministic),
|
||||
ENUM_ENTRY(NoSubstituters),
|
||||
ENUM_ENTRY(HashMismatch),
|
||||
ENUM_ENTRY(Cancelled),
|
||||
#undef ENUM_ENTRY
|
||||
}};
|
||||
|
||||
|
||||
@@ -32,14 +32,6 @@ DerivationBuildingGoal::DerivationBuildingGoal(
|
||||
, drv{std::make_unique<Derivation>(drv)}
|
||||
, buildMode(buildMode)
|
||||
{
|
||||
try {
|
||||
drvOptions =
|
||||
std::make_unique<DerivationOptions>(DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
|
||||
throw;
|
||||
}
|
||||
|
||||
name = fmt("building derivation '%s'", worker.store.printStorePath(drvPath));
|
||||
trace("created");
|
||||
|
||||
@@ -206,6 +198,38 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation)
|
||||
|
||||
Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
{
|
||||
auto drvOptions = [&] {
|
||||
DerivationOptions<SingleDerivedPath> temp;
|
||||
try {
|
||||
temp =
|
||||
derivationOptionsFromStructuredAttrs(worker.store, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
|
||||
throw;
|
||||
}
|
||||
|
||||
auto res = tryResolve(
|
||||
temp,
|
||||
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
|
||||
try {
|
||||
return resolveDerivedPath(
|
||||
worker.store, SingleDerivedPath::Built{drvPath, outputName}, &worker.evalStore);
|
||||
} catch (Error &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
});
|
||||
|
||||
/* The derivation must have all of its inputs gotten this point,
|
||||
so the resolution will surely succeed.
|
||||
|
||||
(Actually, we shouldn't even enter this goal until we have a
|
||||
resolved derivation, or derivation with only input addressed
|
||||
transitive inputs, so this should be a no-opt anyways.)
|
||||
*/
|
||||
assert(res);
|
||||
return *res;
|
||||
}();
|
||||
|
||||
std::map<std::string, InitialOutput> initialOutputs;
|
||||
|
||||
/* Recheck at this point. In particular, whereas before we were
|
||||
@@ -344,13 +368,13 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
/* Don't do a remote build if the derivation has the attribute
|
||||
`preferLocalBuild' set. Also, check and repair modes are only
|
||||
supported for local builds. */
|
||||
bool buildLocally = (buildMode != bmNormal || drvOptions->willBuildLocally(worker.store, *drv))
|
||||
bool buildLocally = (buildMode != bmNormal || drvOptions.willBuildLocally(worker.store, *drv))
|
||||
&& settings.maxBuildJobs.get() != 0;
|
||||
|
||||
if (buildLocally) {
|
||||
useHook = false;
|
||||
} else {
|
||||
switch (tryBuildHook(initialOutputs)) {
|
||||
switch (tryBuildHook(initialOutputs, drvOptions)) {
|
||||
case rpAccept:
|
||||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
@@ -379,7 +403,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
|
||||
externalBuilder = settings.findExternalDerivationBuilderIfSupported(*drv);
|
||||
|
||||
if (!externalBuilder && !drvOptions->canBuildLocally(worker.store, *drv)) {
|
||||
if (!externalBuilder && !drvOptions.canBuildLocally(worker.store, *drv)) {
|
||||
auto msg =
|
||||
fmt("Cannot build '%s'.\n"
|
||||
"Reason: " ANSI_RED "required system or feature not available" ANSI_NORMAL
|
||||
@@ -388,7 +412,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
"Current system: '%s' with features {%s}",
|
||||
Magenta(worker.store.printStorePath(drvPath)),
|
||||
Magenta(drv->platform),
|
||||
concatStringsSep(", ", drvOptions->getRequiredSystemFeatures(*drv)),
|
||||
concatStringsSep(", ", drvOptions.getRequiredSystemFeatures(*drv)),
|
||||
Magenta(settings.thisSystem),
|
||||
concatStringsSep<StringSet>(", ", worker.store.Store::config.systemFeatures));
|
||||
|
||||
@@ -586,7 +610,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
}
|
||||
|
||||
try {
|
||||
desugaredEnv = DesugaredEnv::create(worker.store, *drv, *drvOptions, inputPaths);
|
||||
desugaredEnv = DesugaredEnv::create(worker.store, *drv, drvOptions, inputPaths);
|
||||
} catch (BuildError & e) {
|
||||
outputLocks.unlock();
|
||||
worker.permanentFailure = true;
|
||||
@@ -597,7 +621,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
|
||||
.drvPath = drvPath,
|
||||
.buildResult = buildResult,
|
||||
.drv = *drv,
|
||||
.drvOptions = *drvOptions,
|
||||
.drvOptions = drvOptions,
|
||||
.inputPaths = inputPaths,
|
||||
.initialOutputs = initialOutputs,
|
||||
.buildMode = buildMode,
|
||||
@@ -803,7 +827,8 @@ BuildError DerivationBuildingGoal::fixupBuilderFailureErrorMessage(BuilderFailur
|
||||
return BuildError{e.status, msg};
|
||||
}
|
||||
|
||||
HookReply DerivationBuildingGoal::tryBuildHook(const std::map<std::string, InitialOutput> & initialOutputs)
|
||||
HookReply DerivationBuildingGoal::tryBuildHook(
|
||||
const std::map<std::string, InitialOutput> & initialOutputs, const DerivationOptions<StorePath> & drvOptions)
|
||||
{
|
||||
#ifdef _WIN32 // TODO enable build hook on Windows
|
||||
return rpDecline;
|
||||
@@ -820,7 +845,7 @@ HookReply DerivationBuildingGoal::tryBuildHook(const std::map<std::string, Initi
|
||||
|
||||
/* Send the request to the hook. */
|
||||
worker.hook->sink << "try" << (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0) << drv->platform
|
||||
<< worker.store.printStorePath(drvPath) << drvOptions->getRequiredSystemFeatures(*drv);
|
||||
<< worker.store.printStorePath(drvPath) << drvOptions.getRequiredSystemFeatures(*drv);
|
||||
worker.hook->sink.flush();
|
||||
|
||||
/* Read the first line of input, which should be a word indicating
|
||||
|
||||
@@ -11,7 +11,7 @@ void checkOutputs(
|
||||
Store & store,
|
||||
const StorePath & drvPath,
|
||||
const decltype(Derivation::outputs) & drvOutputs,
|
||||
const decltype(DerivationOptions::outputChecks) & outputChecks,
|
||||
const decltype(DerivationOptions<StorePath>::outputChecks) & outputChecks,
|
||||
const std::map<std::string, ValidPathInfo> & outputs)
|
||||
{
|
||||
std::map<Path, const ValidPathInfo &> outputsByPath;
|
||||
@@ -85,7 +85,7 @@ void checkOutputs(
|
||||
return std::make_pair(std::move(pathsDone), closureSize);
|
||||
};
|
||||
|
||||
auto applyChecks = [&](const DerivationOptions::OutputChecks & checks) {
|
||||
auto applyChecks = [&](const DerivationOptions<StorePath>::OutputChecks & checks) {
|
||||
if (checks.maxSize && info.narSize > *checks.maxSize)
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
@@ -105,28 +105,33 @@ void checkOutputs(
|
||||
*checks.maxClosureSize);
|
||||
}
|
||||
|
||||
auto checkRefs = [&](const StringSet & value, bool allowed, bool recursive) {
|
||||
auto checkRefs = [&](const std::set<DrvRef<StorePath>> & value, bool allowed, bool recursive) {
|
||||
/* Parse a list of reference specifiers. Each element must
|
||||
either be a store path, or the symbolic name of the output
|
||||
of the derivation (such as `out'). */
|
||||
StorePathSet spec;
|
||||
for (auto & i : value) {
|
||||
if (store.isStorePath(i))
|
||||
spec.insert(store.parseStorePath(i));
|
||||
else if (auto output = get(outputs, i))
|
||||
spec.insert(output->path);
|
||||
else {
|
||||
std::string outputsListing =
|
||||
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"derivation '%s' output check for '%s' contains an illegal reference specifier '%s',"
|
||||
" expected store path or output name (one of [%s])",
|
||||
store.printStorePath(drvPath),
|
||||
outputName,
|
||||
i,
|
||||
outputsListing);
|
||||
}
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const StorePath & path) { spec.insert(path); },
|
||||
[&](const OutputName & refOutputName) {
|
||||
if (auto output = get(outputs, refOutputName))
|
||||
spec.insert(output->path);
|
||||
else {
|
||||
std::string outputsListing =
|
||||
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"derivation '%s' output check for '%s' contains output name '%s',"
|
||||
" but this is not a valid output of this derivation."
|
||||
" (Valid outputs are [%s].)",
|
||||
store.printStorePath(drvPath),
|
||||
outputName,
|
||||
refOutputName,
|
||||
outputsListing);
|
||||
}
|
||||
}},
|
||||
i);
|
||||
}
|
||||
|
||||
auto used = recursive ? getClosure(info.path).first : info.references;
|
||||
@@ -180,8 +185,8 @@ void checkOutputs(
|
||||
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const DerivationOptions::OutputChecks & checks) { applyChecks(checks); },
|
||||
[&](const std::map<std::string, DerivationOptions::OutputChecks> & checksPerOutput) {
|
||||
[&](const DerivationOptions<StorePath>::OutputChecks & checks) { applyChecks(checks); },
|
||||
[&](const std::map<std::string, DerivationOptions<StorePath>::OutputChecks> & checksPerOutput) {
|
||||
if (auto outputChecks = get(checksPerOutput, outputName))
|
||||
|
||||
applyChecks(*outputChecks);
|
||||
|
||||
@@ -21,7 +21,7 @@ void checkOutputs(
|
||||
Store & store,
|
||||
const StorePath & drvPath,
|
||||
const decltype(Derivation::outputs) & drvOutputs,
|
||||
const decltype(DerivationOptions::outputChecks) & drvOptions,
|
||||
const decltype(DerivationOptions<StorePath>::outputChecks) & drvOptions,
|
||||
const std::map<std::string, ValidPathInfo> & outputs);
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -18,7 +18,10 @@ std::string & DesugaredEnv::atFileEnvPair(std::string_view name, std::string fil
|
||||
}
|
||||
|
||||
DesugaredEnv DesugaredEnv::create(
|
||||
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths)
|
||||
Store & store,
|
||||
const Derivation & drv,
|
||||
const DerivationOptions<StorePath> & drvOptions,
|
||||
const StorePathSet & inputPaths)
|
||||
{
|
||||
DesugaredEnv res;
|
||||
|
||||
@@ -46,7 +49,7 @@ DesugaredEnv DesugaredEnv::create(
|
||||
}
|
||||
|
||||
/* Handle exportReferencesGraph(), if set. */
|
||||
for (auto & [fileName, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) {
|
||||
for (auto & [fileName, storePaths] : drvOptions.exportReferencesGraph) {
|
||||
/* Write closure info to <fileName>. */
|
||||
res.extraFiles.insert_or_assign(
|
||||
fileName, store.makeValidityRegistration(store.exportReferences(storePaths, inputPaths), false, false));
|
||||
|
||||
@@ -64,9 +64,10 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
|
||||
{
|
||||
trace("have derivation");
|
||||
|
||||
auto drvOptions = [&]() -> DerivationOptions {
|
||||
auto drvOptions = [&]() -> DerivationOptions<SingleDerivedPath> {
|
||||
try {
|
||||
return DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs);
|
||||
return derivationOptionsFromStructuredAttrs(
|
||||
worker.store, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
|
||||
throw;
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
#include "nix/util/json-utils.hh"
|
||||
#include "nix/store/parsed-derivations.hh"
|
||||
#include "nix/store/derivations.hh"
|
||||
#include "nix/store/derived-path.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/types.hh"
|
||||
#include "nix/util/util.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/util/variant-wrapper.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <regex>
|
||||
#include <ranges>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -90,14 +93,38 @@ getStringSetAttr(const StringMap & env, const StructuredAttrs * parsed, const st
|
||||
return ss ? (std::optional{StringSet{ss->begin(), ss->end()}}) : (std::optional<StringSet>{});
|
||||
}
|
||||
|
||||
using OutputChecks = DerivationOptions::OutputChecks;
|
||||
template<typename Inputs>
|
||||
using OutputChecks = DerivationOptions<Inputs>::OutputChecks;
|
||||
|
||||
using OutputChecksVariant = std::variant<OutputChecks, std::map<std::string, OutputChecks>>;
|
||||
template<typename Inputs>
|
||||
using OutputChecksVariant = std::variant<OutputChecks<Inputs>, std::map<std::string, OutputChecks<Inputs>>>;
|
||||
|
||||
DerivationOptions DerivationOptions::fromStructuredAttrs(
|
||||
const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn)
|
||||
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
|
||||
const StoreDirConfig & store,
|
||||
const StringMap & env,
|
||||
const StructuredAttrs * parsed,
|
||||
bool shouldWarn,
|
||||
const ExperimentalFeatureSettings & mockXpSettings)
|
||||
{
|
||||
return fromStructuredAttrs(env, parsed ? &*parsed : nullptr);
|
||||
/* Use the SingleDerivedPath version with empty inputDrvs, then
|
||||
resolve. */
|
||||
DerivedPathMap<StringSet> emptyInputDrvs{};
|
||||
auto singleDerivedPathOptions =
|
||||
derivationOptionsFromStructuredAttrs(store, emptyInputDrvs, env, parsed, shouldWarn, mockXpSettings);
|
||||
|
||||
/* "Resolve" all SingleDerivedPath inputs to StorePath. */
|
||||
auto resolved = tryResolve(
|
||||
singleDerivedPathOptions,
|
||||
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
|
||||
// there should be nothing to resolve
|
||||
assert(false);
|
||||
});
|
||||
|
||||
/* Since we should never need to call the call back, there should be
|
||||
no way it fails. */
|
||||
assert(resolved);
|
||||
|
||||
return *resolved;
|
||||
}
|
||||
|
||||
static void flatten(const nlohmann::json & value, StringSet & res)
|
||||
@@ -111,10 +138,63 @@ static void flatten(const nlohmann::json & value, StringSet & res)
|
||||
throw Error("'exportReferencesGraph' value is not an array or a string");
|
||||
}
|
||||
|
||||
DerivationOptions
|
||||
DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn)
|
||||
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
|
||||
const StoreDirConfig & store,
|
||||
const DerivedPathMap<StringSet> & inputDrvs,
|
||||
const StringMap & env,
|
||||
const StructuredAttrs * parsed,
|
||||
bool shouldWarn,
|
||||
const ExperimentalFeatureSettings & mockXpSettings)
|
||||
{
|
||||
DerivationOptions defaults = {};
|
||||
DerivationOptions<SingleDerivedPath> defaults = {};
|
||||
|
||||
std::map<std::string, SingleDerivedPath::Built> placeholders;
|
||||
if (mockXpSettings.isEnabled(Xp::CaDerivations)) {
|
||||
/* Initialize placeholder map from inputDrvs */
|
||||
auto initPlaceholders = [&](this const auto & initPlaceholders,
|
||||
ref<const SingleDerivedPath> basePath,
|
||||
const DerivedPathMap<StringSet>::ChildNode & node) -> void {
|
||||
for (const auto & outputName : node.value) {
|
||||
auto built = SingleDerivedPath::Built{
|
||||
.drvPath = basePath,
|
||||
.output = outputName,
|
||||
};
|
||||
placeholders.insert_or_assign(
|
||||
DownstreamPlaceholder::fromSingleDerivedPathBuilt(built, mockXpSettings).render(),
|
||||
std::move(built));
|
||||
}
|
||||
|
||||
for (const auto & [outputName, childNode] : node.childMap) {
|
||||
initPlaceholders(
|
||||
make_ref<const SingleDerivedPath>(SingleDerivedPath::Built{
|
||||
.drvPath = basePath,
|
||||
.output = outputName,
|
||||
}),
|
||||
childNode);
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto & [drvPath, outputs] : inputDrvs.map) {
|
||||
auto basePath = make_ref<const SingleDerivedPath>(SingleDerivedPath::Opaque{drvPath});
|
||||
initPlaceholders(basePath, outputs);
|
||||
}
|
||||
}
|
||||
|
||||
auto parseSingleDerivedPath = [&](const std::string & pathS) -> SingleDerivedPath {
|
||||
if (auto it = placeholders.find(pathS); it != placeholders.end())
|
||||
return it->second;
|
||||
else
|
||||
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
|
||||
};
|
||||
|
||||
auto parseRef = [&](const std::string & pathS) -> DrvRef<SingleDerivedPath> {
|
||||
if (auto it = placeholders.find(pathS); it != placeholders.end())
|
||||
return it->second;
|
||||
if (store.isStorePath(pathS))
|
||||
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
|
||||
else
|
||||
return pathS;
|
||||
};
|
||||
|
||||
if (shouldWarn && parsed) {
|
||||
auto & structuredAttrs = parsed->structuredAttrs;
|
||||
@@ -146,14 +226,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
}
|
||||
|
||||
return {
|
||||
.outputChecks = [&]() -> OutputChecksVariant {
|
||||
.outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
|
||||
if (parsed) {
|
||||
auto & structuredAttrs = parsed->structuredAttrs;
|
||||
|
||||
std::map<std::string, OutputChecks> res;
|
||||
std::map<std::string, OutputChecks<SingleDerivedPath>> res;
|
||||
if (auto * outputChecks = get(structuredAttrs, "outputChecks")) {
|
||||
for (auto & [outputName, output_] : getObject(*outputChecks)) {
|
||||
OutputChecks checks;
|
||||
OutputChecks<SingleDerivedPath> checks;
|
||||
|
||||
auto & output = getObject(output_);
|
||||
|
||||
@@ -163,13 +243,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
if (auto maxClosureSize = get(output, "maxClosureSize"))
|
||||
checks.maxClosureSize = maxClosureSize->get<uint64_t>();
|
||||
|
||||
auto get_ = [&output = output](const std::string & name) -> std::optional<StringSet> {
|
||||
auto get_ =
|
||||
[&](const std::string & name) -> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
|
||||
if (auto i = get(output, name)) {
|
||||
StringSet res;
|
||||
std::set<DrvRef<SingleDerivedPath>> res;
|
||||
for (auto j = i->begin(); j != i->end(); ++j) {
|
||||
if (!j->is_string())
|
||||
throw Error("attribute '%s' must be a list of strings", name);
|
||||
res.insert(j->get<std::string>());
|
||||
res.insert(parseRef(j->get<std::string>()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -178,7 +259,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
|
||||
res.insert_or_assign(
|
||||
outputName,
|
||||
OutputChecks{
|
||||
OutputChecks<SingleDerivedPath>{
|
||||
.maxSize = [&]() -> std::optional<uint64_t> {
|
||||
if (auto maxSize = get(output, "maxSize"))
|
||||
return maxSize->get<uint64_t>();
|
||||
@@ -192,21 +273,32 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
return std::nullopt;
|
||||
}(),
|
||||
.allowedReferences = get_("allowedReferences"),
|
||||
.disallowedReferences = get_("disallowedReferences").value_or(StringSet{}),
|
||||
.disallowedReferences =
|
||||
get_("disallowedReferences").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
|
||||
.allowedRequisites = get_("allowedRequisites"),
|
||||
.disallowedRequisites = get_("disallowedRequisites").value_or(StringSet{}),
|
||||
.disallowedRequisites =
|
||||
get_("disallowedRequisites").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return OutputChecks{
|
||||
auto parseRefSet = [&](const std::optional<StringSet> optionalStringSet)
|
||||
-> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
|
||||
if (!optionalStringSet)
|
||||
return std::nullopt;
|
||||
auto range = *optionalStringSet | std::views::transform(parseRef);
|
||||
return std::set<DrvRef<SingleDerivedPath>>(range.begin(), range.end());
|
||||
};
|
||||
return OutputChecks<SingleDerivedPath>{
|
||||
// legacy non-structured-attributes case
|
||||
.ignoreSelfRefs = true,
|
||||
.allowedReferences = getStringSetAttr(env, parsed, "allowedReferences"),
|
||||
.disallowedReferences = getStringSetAttr(env, parsed, "disallowedReferences").value_or(StringSet{}),
|
||||
.allowedRequisites = getStringSetAttr(env, parsed, "allowedRequisites"),
|
||||
.disallowedRequisites = getStringSetAttr(env, parsed, "disallowedRequisites").value_or(StringSet{}),
|
||||
.allowedReferences = parseRefSet(getStringSetAttr(env, parsed, "allowedReferences")),
|
||||
.disallowedReferences = parseRefSet(getStringSetAttr(env, parsed, "disallowedReferences"))
|
||||
.value_or(std::set<DrvRef<SingleDerivedPath>>{}),
|
||||
.allowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "allowedRequisites")),
|
||||
.disallowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "disallowedRequisites"))
|
||||
.value_or(std::set<DrvRef<SingleDerivedPath>>{}),
|
||||
};
|
||||
}
|
||||
}(),
|
||||
@@ -245,16 +337,19 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
}(),
|
||||
.exportReferencesGraph =
|
||||
[&] {
|
||||
std::map<std::string, StringSet> ret;
|
||||
std::map<std::string, std::set<SingleDerivedPath>> ret;
|
||||
|
||||
if (parsed) {
|
||||
auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph");
|
||||
if (!e || !e->is_object())
|
||||
return ret;
|
||||
for (auto & [key, value] : getObject(*e)) {
|
||||
for (auto & [key, storePathsJson] : getObject(*e)) {
|
||||
StringSet ss;
|
||||
flatten(value, ss);
|
||||
ret.insert_or_assign(key, std::move(ss));
|
||||
flatten(storePathsJson, ss);
|
||||
std::set<SingleDerivedPath> storePaths;
|
||||
for (auto & s : ss)
|
||||
storePaths.insert(parseSingleDerivedPath(s));
|
||||
ret.insert_or_assign(key, std::move(storePaths));
|
||||
}
|
||||
} else {
|
||||
auto s = getOr(env, "exportReferencesGraph", "");
|
||||
@@ -268,7 +363,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName);
|
||||
|
||||
auto & storePathS = *i++;
|
||||
ret.insert_or_assign(std::move(fileName), StringSet{storePathS});
|
||||
ret.insert_or_assign(std::move(fileName), std::set{parseSingleDerivedPath(storePathS)});
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -286,28 +381,8 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
|
||||
};
|
||||
}
|
||||
|
||||
std::map<std::string, StorePathSet>
|
||||
DerivationOptions::getParsedExportReferencesGraph(const StoreDirConfig & store) const
|
||||
{
|
||||
std::map<std::string, StorePathSet> res;
|
||||
|
||||
for (auto & [fileName, ss] : exportReferencesGraph) {
|
||||
StorePathSet storePaths;
|
||||
for (auto & storePathS : ss) {
|
||||
if (!store.isInStore(storePathS))
|
||||
throw BuildError(
|
||||
BuildResult::Failure::InputRejected,
|
||||
"'exportReferencesGraph' contains a non-store path '%1%'",
|
||||
storePathS);
|
||||
storePaths.insert(store.toStorePath(storePathS).first);
|
||||
}
|
||||
res.insert_or_assign(fileName, storePaths);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & drv) const
|
||||
template<typename Input>
|
||||
StringSet DerivationOptions<Input>::getRequiredSystemFeatures(const BasicDerivation & drv) const
|
||||
{
|
||||
// FIXME: cache this?
|
||||
StringSet res;
|
||||
@@ -318,7 +393,8 @@ StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & d
|
||||
return res;
|
||||
}
|
||||
|
||||
bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivation & drv) const
|
||||
template<typename Input>
|
||||
bool DerivationOptions<Input>::canBuildLocally(Store & localStore, const BasicDerivation & drv) const
|
||||
{
|
||||
if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform)
|
||||
&& !drv.isBuiltin())
|
||||
@@ -334,42 +410,194 @@ bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivatio
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DerivationOptions::willBuildLocally(Store & localStore, const BasicDerivation & drv) const
|
||||
template<typename Input>
|
||||
bool DerivationOptions<Input>::willBuildLocally(Store & localStore, const BasicDerivation & drv) const
|
||||
{
|
||||
return preferLocalBuild && canBuildLocally(localStore, drv);
|
||||
}
|
||||
|
||||
bool DerivationOptions::substitutesAllowed() const
|
||||
template<typename Input>
|
||||
bool DerivationOptions<Input>::substitutesAllowed() const
|
||||
{
|
||||
return settings.alwaysAllowSubstitutes ? true : allowSubstitutes;
|
||||
}
|
||||
|
||||
bool DerivationOptions::useUidRange(const BasicDerivation & drv) const
|
||||
template<typename Input>
|
||||
bool DerivationOptions<Input>::useUidRange(const BasicDerivation & drv) const
|
||||
{
|
||||
return getRequiredSystemFeatures(drv).count("uid-range");
|
||||
}
|
||||
|
||||
std::optional<DerivationOptions<StorePath>> tryResolve(
|
||||
const DerivationOptions<SingleDerivedPath> & drvOptions,
|
||||
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
|
||||
queryResolutionChain)
|
||||
{
|
||||
auto tryResolvePath = [&](const SingleDerivedPath & input) -> std::optional<StorePath> {
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const SingleDerivedPath::Opaque & p) -> std::optional<StorePath> { return p.path; },
|
||||
[&](const SingleDerivedPath::Built & p) -> std::optional<StorePath> {
|
||||
return queryResolutionChain(p.drvPath, p.output);
|
||||
}},
|
||||
input.raw());
|
||||
};
|
||||
|
||||
auto tryResolveRef = [&](const DrvRef<SingleDerivedPath> & ref) -> std::optional<DrvRef<StorePath>> {
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const OutputName & outputName) -> std::optional<DrvRef<StorePath>> { return outputName; },
|
||||
[&](const SingleDerivedPath & input) -> std::optional<DrvRef<StorePath>> {
|
||||
return tryResolvePath(input);
|
||||
}},
|
||||
ref);
|
||||
};
|
||||
|
||||
auto tryResolveRefSet =
|
||||
[&](const std::set<DrvRef<SingleDerivedPath>> & refSet) -> std::optional<std::set<DrvRef<StorePath>>> {
|
||||
std::set<DrvRef<StorePath>> resolvedSet;
|
||||
for (const auto & ref : refSet) {
|
||||
auto resolvedRef = tryResolveRef(ref);
|
||||
if (!resolvedRef)
|
||||
return std::nullopt;
|
||||
resolvedSet.insert(*resolvedRef);
|
||||
}
|
||||
return resolvedSet;
|
||||
};
|
||||
|
||||
// Helper function to try resolving OutputChecks using functional style
|
||||
auto tryResolveOutputChecks = [&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
|
||||
-> std::optional<DerivationOptions<StorePath>::OutputChecks> {
|
||||
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedReferences;
|
||||
if (checks.allowedReferences) {
|
||||
resolvedAllowedReferences = tryResolveRefSet(*checks.allowedReferences);
|
||||
if (!resolvedAllowedReferences)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedRequisites;
|
||||
if (checks.allowedRequisites) {
|
||||
resolvedAllowedRequisites = tryResolveRefSet(*checks.allowedRequisites);
|
||||
if (!resolvedAllowedRequisites)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto resolvedDisallowedReferences = tryResolveRefSet(checks.disallowedReferences);
|
||||
if (!resolvedDisallowedReferences)
|
||||
return std::nullopt;
|
||||
|
||||
auto resolvedDisallowedRequisites = tryResolveRefSet(checks.disallowedRequisites);
|
||||
if (!resolvedDisallowedRequisites)
|
||||
return std::nullopt;
|
||||
|
||||
return DerivationOptions<StorePath>::OutputChecks{
|
||||
.ignoreSelfRefs = checks.ignoreSelfRefs,
|
||||
.maxSize = checks.maxSize,
|
||||
.maxClosureSize = checks.maxClosureSize,
|
||||
.allowedReferences = resolvedAllowedReferences,
|
||||
.disallowedReferences = *resolvedDisallowedReferences,
|
||||
.allowedRequisites = resolvedAllowedRequisites,
|
||||
.disallowedRequisites = *resolvedDisallowedRequisites,
|
||||
};
|
||||
};
|
||||
|
||||
// Helper function to resolve exportReferencesGraph using functional style
|
||||
auto tryResolveExportReferencesGraph = [&](const std::map<std::string, std::set<SingleDerivedPath>> & exportGraph)
|
||||
-> std::optional<std::map<std::string, std::set<StorePath>>> {
|
||||
std::map<std::string, std::set<StorePath>> resolved;
|
||||
for (const auto & [name, inputPaths] : exportGraph) {
|
||||
std::set<StorePath> resolvedPaths;
|
||||
for (const auto & inputPath : inputPaths) {
|
||||
auto resolvedPath = tryResolvePath(inputPath);
|
||||
if (!resolvedPath)
|
||||
return std::nullopt;
|
||||
resolvedPaths.insert(*resolvedPath);
|
||||
}
|
||||
resolved.emplace(name, std::move(resolvedPaths));
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
// Resolve outputChecks using functional style with std::visit
|
||||
auto resolvedOutputChecks = std::visit(
|
||||
overloaded{
|
||||
[&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
|
||||
-> std::optional<std::variant<
|
||||
DerivationOptions<StorePath>::OutputChecks,
|
||||
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
|
||||
auto resolved = tryResolveOutputChecks(checks);
|
||||
if (!resolved)
|
||||
return std::nullopt;
|
||||
return std::variant<
|
||||
DerivationOptions<StorePath>::OutputChecks,
|
||||
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(*resolved);
|
||||
},
|
||||
[&](const std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks> & checksMap)
|
||||
-> std::optional<std::variant<
|
||||
DerivationOptions<StorePath>::OutputChecks,
|
||||
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
|
||||
std::map<std::string, DerivationOptions<StorePath>::OutputChecks> resolvedMap;
|
||||
for (const auto & [outputName, checks] : checksMap) {
|
||||
auto resolved = tryResolveOutputChecks(checks);
|
||||
if (!resolved)
|
||||
return std::nullopt;
|
||||
resolvedMap.emplace(outputName, *resolved);
|
||||
}
|
||||
return std::variant<
|
||||
DerivationOptions<StorePath>::OutputChecks,
|
||||
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(resolvedMap);
|
||||
}},
|
||||
drvOptions.outputChecks);
|
||||
|
||||
if (!resolvedOutputChecks)
|
||||
return std::nullopt;
|
||||
|
||||
// Resolve exportReferencesGraph
|
||||
auto resolvedExportGraph = tryResolveExportReferencesGraph(drvOptions.exportReferencesGraph);
|
||||
if (!resolvedExportGraph)
|
||||
return std::nullopt;
|
||||
|
||||
// Return resolved DerivationOptions using designated initializers
|
||||
return DerivationOptions<StorePath>{
|
||||
.outputChecks = *resolvedOutputChecks,
|
||||
.unsafeDiscardReferences = drvOptions.unsafeDiscardReferences,
|
||||
.passAsFile = drvOptions.passAsFile,
|
||||
.exportReferencesGraph = *resolvedExportGraph,
|
||||
.additionalSandboxProfile = drvOptions.additionalSandboxProfile,
|
||||
.noChroot = drvOptions.noChroot,
|
||||
.impureHostDeps = drvOptions.impureHostDeps,
|
||||
.impureEnvVars = drvOptions.impureEnvVars,
|
||||
.allowLocalNetworking = drvOptions.allowLocalNetworking,
|
||||
.requiredSystemFeatures = drvOptions.requiredSystemFeatures,
|
||||
.preferLocalBuild = drvOptions.preferLocalBuild,
|
||||
.allowSubstitutes = drvOptions.allowSubstitutes,
|
||||
};
|
||||
}
|
||||
|
||||
template struct DerivationOptions<StorePath>;
|
||||
template struct DerivationOptions<SingleDerivedPath>;
|
||||
|
||||
} // namespace nix
|
||||
|
||||
namespace nlohmann {
|
||||
|
||||
using namespace nix;
|
||||
|
||||
DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json_)
|
||||
DerivationOptions<SingleDerivedPath> adl_serializer<DerivationOptions<SingleDerivedPath>>::from_json(const json & json_)
|
||||
{
|
||||
auto & json = getObject(json_);
|
||||
|
||||
return {
|
||||
.outputChecks = [&]() -> OutputChecksVariant {
|
||||
.outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
|
||||
auto outputChecks = getObject(valueAt(json, "outputChecks"));
|
||||
|
||||
auto forAllOutputsOpt = optionalValueAt(outputChecks, "forAllOutputs");
|
||||
auto perOutputOpt = optionalValueAt(outputChecks, "perOutput");
|
||||
|
||||
if (forAllOutputsOpt && !perOutputOpt) {
|
||||
return static_cast<OutputChecks>(*forAllOutputsOpt);
|
||||
return static_cast<OutputChecks<SingleDerivedPath>>(*forAllOutputsOpt);
|
||||
} else if (perOutputOpt && !forAllOutputsOpt) {
|
||||
return static_cast<std::map<std::string, OutputChecks>>(*perOutputOpt);
|
||||
return static_cast<std::map<std::string, OutputChecks<SingleDerivedPath>>>(*perOutputOpt);
|
||||
} else {
|
||||
throw Error("Exactly one of 'perOutput' or 'forAllOutputs' is required");
|
||||
}
|
||||
@@ -377,7 +605,7 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
|
||||
|
||||
.unsafeDiscardReferences = valueAt(json, "unsafeDiscardReferences"),
|
||||
.passAsFile = getStringSet(valueAt(json, "passAsFile")),
|
||||
.exportReferencesGraph = getMap<StringSet>(getObject(valueAt(json, "exportReferencesGraph")), getStringSet),
|
||||
.exportReferencesGraph = valueAt(json, "exportReferencesGraph"),
|
||||
|
||||
.additionalSandboxProfile = getString(valueAt(json, "additionalSandboxProfile")),
|
||||
.noChroot = getBoolean(valueAt(json, "noChroot")),
|
||||
@@ -391,16 +619,17 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
|
||||
};
|
||||
}
|
||||
|
||||
void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOptions & o)
|
||||
void adl_serializer<DerivationOptions<SingleDerivedPath>>::to_json(
|
||||
json & json, const DerivationOptions<SingleDerivedPath> & o)
|
||||
{
|
||||
json["outputChecks"] = std::visit(
|
||||
overloaded{
|
||||
[&](const OutputChecks & checks) {
|
||||
[&](const OutputChecks<SingleDerivedPath> & checks) {
|
||||
nlohmann::json outputChecks;
|
||||
outputChecks["forAllOutputs"] = checks;
|
||||
return outputChecks;
|
||||
},
|
||||
[&](const std::map<std::string, OutputChecks> & checksPerOutput) {
|
||||
[&](const std::map<std::string, OutputChecks<SingleDerivedPath>> & checksPerOutput) {
|
||||
nlohmann::json outputChecks;
|
||||
outputChecks["perOutput"] = checksPerOutput;
|
||||
return outputChecks;
|
||||
@@ -423,7 +652,7 @@ void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOpt
|
||||
json["allowSubstitutes"] = o.allowSubstitutes;
|
||||
}
|
||||
|
||||
DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>::from_json(const json & json_)
|
||||
OutputChecks<SingleDerivedPath> adl_serializer<OutputChecks<SingleDerivedPath>>::from_json(const json & json_)
|
||||
{
|
||||
auto & json = getObject(json_);
|
||||
|
||||
@@ -431,14 +660,16 @@ DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>:
|
||||
.ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")),
|
||||
.maxSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxSize"))),
|
||||
.maxClosureSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxClosureSize"))),
|
||||
.allowedReferences = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedReferences"))),
|
||||
.disallowedReferences = getStringSet(valueAt(json, "disallowedReferences")),
|
||||
.allowedRequisites = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedRequisites"))),
|
||||
.disallowedRequisites = getStringSet(valueAt(json, "disallowedRequisites")),
|
||||
.allowedReferences =
|
||||
ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedReferences"))),
|
||||
.disallowedReferences = valueAt(json, "disallowedReferences"),
|
||||
.allowedRequisites =
|
||||
ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedRequisites"))),
|
||||
.disallowedRequisites = valueAt(json, "disallowedRequisites"),
|
||||
};
|
||||
}
|
||||
|
||||
void adl_serializer<DerivationOptions::OutputChecks>::to_json(json & json, const DerivationOptions::OutputChecks & c)
|
||||
void adl_serializer<OutputChecks<SingleDerivedPath>>::to_json(json & json, const OutputChecks<SingleDerivedPath> & c)
|
||||
{
|
||||
json["ignoreSelfRefs"] = c.ignoreSelfRefs;
|
||||
json["maxSize"] = c.maxSize;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -791,71 +792,63 @@ std::string outputPathName(std::string_view drvName, OutputNameView outputName)
|
||||
|
||||
DerivationType BasicDerivation::type() const
|
||||
{
|
||||
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs,
|
||||
impureOutputs;
|
||||
std::optional<HashAlgorithm> floatingHashAlgo;
|
||||
std::optional<DerivationType> ty;
|
||||
|
||||
auto decide = [&](DerivationType newTy) {
|
||||
if (!ty)
|
||||
ty = newTy;
|
||||
else if (ty.value() != newTy)
|
||||
throw Error("can't mix derivation output types");
|
||||
else if (ty.value() == DerivationType::ContentAddressed{.sandboxed = false, .fixed = true})
|
||||
// FIXME: Experimental feature?
|
||||
throw Error("only one fixed output is allowed for now");
|
||||
};
|
||||
|
||||
for (auto & i : outputs) {
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const DerivationOutput::InputAddressed &) { inputAddressedOutputs.insert(i.first); },
|
||||
[&](const DerivationOutput::CAFixed &) { fixedCAOutputs.insert(i.first); },
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
floatingCAOutputs.insert(i.first);
|
||||
if (!floatingHashAlgo) {
|
||||
floatingHashAlgo = dof.hashAlgo;
|
||||
} else {
|
||||
if (*floatingHashAlgo != dof.hashAlgo)
|
||||
throw Error("all floating outputs must use the same hash algorithm");
|
||||
}
|
||||
[&](const DerivationOutput::InputAddressed &) {
|
||||
decide(
|
||||
DerivationType::InputAddressed{
|
||||
.deferred = false,
|
||||
});
|
||||
},
|
||||
[&](const DerivationOutput::Deferred &) { deferredIAOutputs.insert(i.first); },
|
||||
[&](const DerivationOutput::Impure &) { impureOutputs.insert(i.first); },
|
||||
[&](const DerivationOutput::CAFixed &) {
|
||||
decide(
|
||||
DerivationType::ContentAddressed{
|
||||
.sandboxed = false,
|
||||
.fixed = true,
|
||||
});
|
||||
if (i.first != "out"sv)
|
||||
throw Error("single fixed output must be named \"out\"");
|
||||
},
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
decide(
|
||||
DerivationType::ContentAddressed{
|
||||
.sandboxed = true,
|
||||
.fixed = false,
|
||||
});
|
||||
if (!floatingHashAlgo)
|
||||
floatingHashAlgo = dof.hashAlgo;
|
||||
else if (*floatingHashAlgo != dof.hashAlgo)
|
||||
throw Error("all floating outputs must use the same hash algorithm");
|
||||
},
|
||||
[&](const DerivationOutput::Deferred &) {
|
||||
decide(
|
||||
DerivationType::InputAddressed{
|
||||
.deferred = true,
|
||||
});
|
||||
},
|
||||
[&](const DerivationOutput::Impure &) { decide(DerivationType::Impure{}); },
|
||||
},
|
||||
i.second.raw);
|
||||
}
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()
|
||||
&& deferredIAOutputs.empty() && impureOutputs.empty())
|
||||
if (!ty)
|
||||
throw Error("must have at least one output");
|
||||
|
||||
if (!inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()
|
||||
&& deferredIAOutputs.empty() && impureOutputs.empty())
|
||||
return DerivationType::InputAddressed{
|
||||
.deferred = false,
|
||||
};
|
||||
|
||||
if (inputAddressedOutputs.empty() && !fixedCAOutputs.empty() && floatingCAOutputs.empty()
|
||||
&& deferredIAOutputs.empty() && impureOutputs.empty()) {
|
||||
if (fixedCAOutputs.size() > 1)
|
||||
// FIXME: Experimental feature?
|
||||
throw Error("only one fixed output is allowed for now");
|
||||
if (*fixedCAOutputs.begin() != "out"sv)
|
||||
throw Error("single fixed output must be named \"out\"");
|
||||
return DerivationType::ContentAddressed{
|
||||
.sandboxed = false,
|
||||
.fixed = true,
|
||||
};
|
||||
}
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && !floatingCAOutputs.empty()
|
||||
&& deferredIAOutputs.empty() && impureOutputs.empty())
|
||||
return DerivationType::ContentAddressed{
|
||||
.sandboxed = true,
|
||||
.fixed = false,
|
||||
};
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()
|
||||
&& !deferredIAOutputs.empty() && impureOutputs.empty())
|
||||
return DerivationType::InputAddressed{
|
||||
.deferred = true,
|
||||
};
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()
|
||||
&& deferredIAOutputs.empty() && !impureOutputs.empty())
|
||||
return DerivationType::Impure{};
|
||||
|
||||
throw Error("can't mix derivation output types");
|
||||
return ty.value();
|
||||
}
|
||||
|
||||
DrvHashes drvHashes;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "nix/store/downstream-placeholder.hh"
|
||||
#include "nix/store/derivations.hh"
|
||||
#include "nix/util/json-utils.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -49,3 +50,45 @@ DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
namespace nlohmann {
|
||||
|
||||
using namespace nix;
|
||||
|
||||
template<typename Item>
|
||||
DrvRef<Item> adl_serializer<DrvRef<Item>>::from_json(const json & json)
|
||||
{
|
||||
// OutputName case: { "drvPath": "self", "output": <output> }
|
||||
if (json.type() == nlohmann::json::value_t::object) {
|
||||
auto & obj = getObject(json);
|
||||
if (auto * drvPath_ = get(obj, "drvPath")) {
|
||||
auto & drvPath = *drvPath_;
|
||||
if (drvPath.type() == nlohmann::json::value_t::string && getString(drvPath) == "self") {
|
||||
return getString(valueAt(obj, "output"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input case
|
||||
return adl_serializer<Item>::from_json(json);
|
||||
}
|
||||
|
||||
template<typename Item>
|
||||
void adl_serializer<DrvRef<Item>>::to_json(json & json, const DrvRef<Item> & ref)
|
||||
{
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const OutputName & outputName) {
|
||||
json = nlohmann::json::object();
|
||||
json["drvPath"] = "self";
|
||||
json["output"] = outputName;
|
||||
},
|
||||
[&](const Item & item) { json = item; },
|
||||
},
|
||||
ref);
|
||||
}
|
||||
|
||||
template struct adl_serializer<nix::DrvRef<StorePath>>;
|
||||
template struct adl_serializer<nix::DrvRef<SingleDerivedPath>>;
|
||||
|
||||
} // namespace nlohmann
|
||||
|
||||
@@ -151,15 +151,23 @@ struct curlFileTransfer : public FileTransfer
|
||||
}
|
||||
}
|
||||
|
||||
void failEx(std::exception_ptr ex)
|
||||
void failEx(std::exception_ptr ex) noexcept
|
||||
{
|
||||
assert(!done);
|
||||
done = true;
|
||||
try {
|
||||
std::rethrow_exception(ex);
|
||||
} catch (nix::Error & e) {
|
||||
/* Add more context to the error message. */
|
||||
e.addTrace({}, "during %s of '%s'", Uncolored(request.verb()), request.uri.to_string());
|
||||
} catch (...) {
|
||||
/* Can't add more context to the error. */
|
||||
}
|
||||
callback.rethrow(ex);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void fail(T && e)
|
||||
void fail(T && e) noexcept
|
||||
{
|
||||
failEx(std::make_exception_ptr(std::forward<T>(e)));
|
||||
}
|
||||
@@ -168,32 +176,30 @@ struct curlFileTransfer : public FileTransfer
|
||||
std::shared_ptr<FinishSink> decompressionSink;
|
||||
std::optional<StringSink> errorSink;
|
||||
|
||||
std::exception_ptr writeException;
|
||||
std::exception_ptr callbackException;
|
||||
|
||||
size_t writeCallback(void * contents, size_t size, size_t nmemb)
|
||||
{
|
||||
try {
|
||||
size_t realSize = size * nmemb;
|
||||
result.bodySize += realSize;
|
||||
size_t writeCallback(void * contents, size_t size, size_t nmemb) noexcept
|
||||
try {
|
||||
size_t realSize = size * nmemb;
|
||||
result.bodySize += realSize;
|
||||
|
||||
if (!decompressionSink) {
|
||||
decompressionSink = makeDecompressionSink(encoding, finalSink);
|
||||
if (!successfulStatuses.count(getHTTPStatus())) {
|
||||
// In this case we want to construct a TeeSink, to keep
|
||||
// the response around (which we figure won't be big
|
||||
// like an actual download should be) to improve error
|
||||
// messages.
|
||||
errorSink = StringSink{};
|
||||
}
|
||||
if (!decompressionSink) {
|
||||
decompressionSink = makeDecompressionSink(encoding, finalSink);
|
||||
if (!successfulStatuses.count(getHTTPStatus())) {
|
||||
// In this case we want to construct a TeeSink, to keep
|
||||
// the response around (which we figure won't be big
|
||||
// like an actual download should be) to improve error
|
||||
// messages.
|
||||
errorSink = StringSink{};
|
||||
}
|
||||
|
||||
(*decompressionSink)({(char *) contents, realSize});
|
||||
|
||||
return realSize;
|
||||
} catch (...) {
|
||||
writeException = std::current_exception();
|
||||
return 0;
|
||||
}
|
||||
|
||||
(*decompressionSink)({(char *) contents, realSize});
|
||||
|
||||
return realSize;
|
||||
} catch (...) {
|
||||
callbackException = std::current_exception();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t writeCallbackWrapper(void * contents, size_t size, size_t nmemb, void * userp)
|
||||
@@ -209,8 +215,8 @@ struct curlFileTransfer : public FileTransfer
|
||||
result.urls.push_back(effectiveUriCStr);
|
||||
}
|
||||
|
||||
size_t headerCallback(void * contents, size_t size, size_t nmemb)
|
||||
{
|
||||
size_t headerCallback(void * contents, size_t size, size_t nmemb) noexcept
|
||||
try {
|
||||
size_t realSize = size * nmemb;
|
||||
std::string line((char *) contents, realSize);
|
||||
printMsg(lvlVomit, "got header for '%s': %s", request.uri, trim(line));
|
||||
@@ -263,6 +269,15 @@ struct curlFileTransfer : public FileTransfer
|
||||
}
|
||||
}
|
||||
return realSize;
|
||||
} catch (...) {
|
||||
#if LIBCURL_VERSION_NUM >= 0x075700
|
||||
/* https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html:
|
||||
You can also abort the transfer by returning CURL_WRITEFUNC_ERROR. */
|
||||
callbackException = std::current_exception();
|
||||
return CURL_WRITEFUNC_ERROR;
|
||||
#else
|
||||
return realSize;
|
||||
#endif
|
||||
}
|
||||
|
||||
static size_t headerCallbackWrapper(void * contents, size_t size, size_t nmemb, void * userp)
|
||||
@@ -270,14 +285,17 @@ struct curlFileTransfer : public FileTransfer
|
||||
return ((TransferItem *) userp)->headerCallback(contents, size, nmemb);
|
||||
}
|
||||
|
||||
int progressCallback(curl_off_t dltotal, curl_off_t dlnow)
|
||||
{
|
||||
try {
|
||||
act.progress(dlnow, dltotal);
|
||||
} catch (nix::Interrupted &) {
|
||||
assert(getInterrupted());
|
||||
}
|
||||
int progressCallback(curl_off_t dltotal, curl_off_t dlnow) noexcept
|
||||
try {
|
||||
act.progress(dlnow, dltotal);
|
||||
return getInterrupted();
|
||||
} catch (nix::Interrupted &) {
|
||||
assert(getInterrupted());
|
||||
return 1;
|
||||
} catch (...) {
|
||||
/* Something unexpected has happened like logger throwing an exception. */
|
||||
callbackException = std::current_exception();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int progressCallbackWrapper(
|
||||
@@ -288,11 +306,14 @@ struct curlFileTransfer : public FileTransfer
|
||||
return item.progressCallback(isUpload ? ultotal : dltotal, isUpload ? ulnow : dlnow);
|
||||
}
|
||||
|
||||
static int debugCallback(CURL * handle, curl_infotype type, char * data, size_t size, void * userptr)
|
||||
{
|
||||
static int debugCallback(CURL * handle, curl_infotype type, char * data, size_t size, void * userptr) noexcept
|
||||
try {
|
||||
if (type == CURLINFO_TEXT)
|
||||
vomit("curl: %s", chomp(std::string(data, size)));
|
||||
return 0;
|
||||
} catch (...) {
|
||||
/* Swallow the exception. Nothing left to do. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t readCallback(char * buffer, size_t size, size_t nitems) noexcept
|
||||
@@ -302,6 +323,7 @@ struct curlFileTransfer : public FileTransfer
|
||||
} catch (EndOfFile &) {
|
||||
return 0;
|
||||
} catch (...) {
|
||||
callbackException = std::current_exception();
|
||||
return CURL_READFUNC_ABORT;
|
||||
}
|
||||
|
||||
@@ -333,6 +355,7 @@ struct curlFileTransfer : public FileTransfer
|
||||
}
|
||||
return CURL_SEEKFUNC_OK;
|
||||
} catch (...) {
|
||||
callbackException = std::current_exception();
|
||||
return CURL_SEEKFUNC_FAIL;
|
||||
}
|
||||
|
||||
@@ -386,17 +409,17 @@ struct curlFileTransfer : public FileTransfer
|
||||
if (settings.downloadSpeed.get() > 0)
|
||||
curl_easy_setopt(req, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) (settings.downloadSpeed.get() * 1024));
|
||||
|
||||
if (request.method == HttpMethod::HEAD)
|
||||
if (request.method == HttpMethod::Head)
|
||||
curl_easy_setopt(req, CURLOPT_NOBODY, 1);
|
||||
|
||||
if (request.method == HttpMethod::DELETE)
|
||||
if (request.method == HttpMethod::Delete)
|
||||
curl_easy_setopt(req, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
|
||||
if (request.data) {
|
||||
if (request.method == HttpMethod::POST) {
|
||||
if (request.method == HttpMethod::Post) {
|
||||
curl_easy_setopt(req, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(req, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) request.data->sizeHint);
|
||||
} else if (request.method == HttpMethod::PUT) {
|
||||
} else if (request.method == HttpMethod::Put) {
|
||||
curl_easy_setopt(req, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(req, CURLOPT_INFILESIZE_LARGE, (curl_off_t) request.data->sizeHint);
|
||||
} else {
|
||||
@@ -476,7 +499,7 @@ struct curlFileTransfer : public FileTransfer
|
||||
try {
|
||||
decompressionSink->finish();
|
||||
} catch (...) {
|
||||
writeException = std::current_exception();
|
||||
callbackException = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,8 +508,8 @@ struct curlFileTransfer : public FileTransfer
|
||||
httpStatus = 304;
|
||||
}
|
||||
|
||||
if (writeException)
|
||||
failEx(writeException);
|
||||
if (callbackException)
|
||||
failEx(callbackException);
|
||||
|
||||
else if (code == CURLE_OK && successfulStatuses.count(httpStatus)) {
|
||||
result.cached = httpStatus == 304;
|
||||
|
||||
@@ -120,7 +120,7 @@ bool HttpBinaryCacheStore::fileExists(const std::string & path)
|
||||
|
||||
try {
|
||||
FileTransferRequest request(makeRequest(path));
|
||||
request.method = HttpMethod::HEAD;
|
||||
request.method = HttpMethod::Head;
|
||||
getFileTransfer()->download(request);
|
||||
return true;
|
||||
} catch (FileTransferError & e) {
|
||||
@@ -141,7 +141,7 @@ void HttpBinaryCacheStore::upload(
|
||||
std::optional<Headers> headers)
|
||||
{
|
||||
auto req = makeRequest(path);
|
||||
req.method = HttpMethod::PUT;
|
||||
req.method = HttpMethod::Put;
|
||||
|
||||
if (headers) {
|
||||
req.headers.reserve(req.headers.size() + headers->size());
|
||||
|
||||
@@ -74,7 +74,6 @@ struct BuildResult
|
||||
/// know about this one, so change it back to `OutputRejected`
|
||||
/// before serialization.
|
||||
HashMismatch = 15,
|
||||
Cancelled = 16,
|
||||
} status = MiscFailure;
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ struct DerivationBuilderParams
|
||||
*
|
||||
* @todo this should be part of `Derivation`.
|
||||
*/
|
||||
const DerivationOptions & drvOptions;
|
||||
const DerivationOptions<StorePath> & drvOptions;
|
||||
|
||||
// The remainder is state held during the build.
|
||||
|
||||
|
||||
@@ -52,8 +52,6 @@ private:
|
||||
*/
|
||||
std::unique_ptr<Derivation> drv;
|
||||
|
||||
std::unique_ptr<DerivationOptions> drvOptions;
|
||||
|
||||
/**
|
||||
* The remainder is state held during the build.
|
||||
*/
|
||||
@@ -115,7 +113,8 @@ private:
|
||||
/**
|
||||
* Is the build hook willing to perform the build?
|
||||
*/
|
||||
HookReply tryBuildHook(const std::map<std::string, InitialOutput> & initialOutputs);
|
||||
HookReply tryBuildHook(
|
||||
const std::map<std::string, InitialOutput> & initialOutputs, const DerivationOptions<StorePath> & drvOptions);
|
||||
|
||||
/**
|
||||
* Open a log file and a pipe to it.
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace nix {
|
||||
|
||||
class Store;
|
||||
struct Derivation;
|
||||
template<typename Input>
|
||||
struct DerivationOptions;
|
||||
|
||||
/**
|
||||
@@ -77,7 +78,10 @@ struct DesugaredEnv
|
||||
* just part of `Derivation`.
|
||||
*/
|
||||
static DesugaredEnv create(
|
||||
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths);
|
||||
Store & store,
|
||||
const Derivation & drv,
|
||||
const DerivationOptions<StorePath> & drvOptions,
|
||||
const StorePathSet & inputPaths);
|
||||
};
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
/**
|
||||
* Build result.
|
||||
*/
|
||||
BuildResult buildResult = {.inner = BuildResult::Failure{.status = BuildResult::Failure::Cancelled}};
|
||||
BuildResult buildResult;
|
||||
|
||||
/**
|
||||
* Suspend our goal and wait until we get `work`-ed again.
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
#include "nix/util/types.hh"
|
||||
#include "nix/util/json-impls.hh"
|
||||
#include "nix/store/path.hh"
|
||||
#include "nix/store/store-dir-config.hh"
|
||||
#include "nix/store/downstream-placeholder.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -17,6 +18,9 @@ struct StoreDirConfig;
|
||||
struct BasicDerivation;
|
||||
struct StructuredAttrs;
|
||||
|
||||
template<typename V>
|
||||
struct DerivedPathMap;
|
||||
|
||||
/**
|
||||
* This represents all the special options on a `Derivation`.
|
||||
*
|
||||
@@ -34,6 +38,7 @@ struct StructuredAttrs;
|
||||
* separately. That would be nice to separate concerns, and not make any
|
||||
* environment variable names magical.
|
||||
*/
|
||||
template<typename Input>
|
||||
struct DerivationOptions
|
||||
{
|
||||
struct OutputChecks
|
||||
@@ -41,13 +46,15 @@ struct DerivationOptions
|
||||
bool ignoreSelfRefs = false;
|
||||
std::optional<uint64_t> maxSize, maxClosureSize;
|
||||
|
||||
using DrvRef = nix::DrvRef<Input>;
|
||||
|
||||
/**
|
||||
* env: allowedReferences
|
||||
*
|
||||
* A value of `nullopt` indicates that the check is skipped.
|
||||
* This means that all references are allowed.
|
||||
*/
|
||||
std::optional<StringSet> allowedReferences;
|
||||
std::optional<std::set<DrvRef>> allowedReferences;
|
||||
|
||||
/**
|
||||
* env: disallowedReferences
|
||||
@@ -55,21 +62,21 @@ struct DerivationOptions
|
||||
* No needed for `std::optional`, because skipping the check is
|
||||
* the same as disallowing the references.
|
||||
*/
|
||||
StringSet disallowedReferences;
|
||||
std::set<DrvRef> disallowedReferences;
|
||||
|
||||
/**
|
||||
* env: allowedRequisites
|
||||
*
|
||||
* See `allowedReferences`
|
||||
*/
|
||||
std::optional<StringSet> allowedRequisites;
|
||||
std::optional<std::set<DrvRef>> allowedRequisites;
|
||||
|
||||
/**
|
||||
* env: disallowedRequisites
|
||||
*
|
||||
* See `disallowedReferences`
|
||||
*/
|
||||
StringSet disallowedRequisites;
|
||||
std::set<DrvRef> disallowedRequisites;
|
||||
|
||||
bool operator==(const OutputChecks &) const = default;
|
||||
};
|
||||
@@ -116,23 +123,7 @@ struct DerivationOptions
|
||||
* attributes give to the builder. The set of paths in the original JSON
|
||||
* is replaced with a list of `PathInfo` in JSON format.
|
||||
*/
|
||||
std::map<std::string, StringSet> exportReferencesGraph;
|
||||
|
||||
/**
|
||||
* Once a derivations is resolved, the strings in in
|
||||
* `exportReferencesGraph` should all be store paths (with possible
|
||||
* suffix paths, but those are discarded).
|
||||
*
|
||||
* @return The parsed path set for for each key in the map.
|
||||
*
|
||||
* @todo Ideally, `exportReferencesGraph` would just store
|
||||
* `StorePath`s for this, but we can't just do that, because for CA
|
||||
* derivations they is actually in general `DerivedPath`s (via
|
||||
* placeholder strings) until the derivation is resolved and exact
|
||||
* inputs store paths are known. We can use better types for that
|
||||
* too, but that is a longer project.
|
||||
*/
|
||||
std::map<std::string, StorePathSet> getParsedExportReferencesGraph(const StoreDirConfig & store) const;
|
||||
std::map<std::string, std::set<Input>> exportReferencesGraph;
|
||||
|
||||
/**
|
||||
* env: __sandboxProfile
|
||||
@@ -185,18 +176,6 @@ struct DerivationOptions
|
||||
|
||||
bool operator==(const DerivationOptions &) const = default;
|
||||
|
||||
/**
|
||||
* Parse this information from its legacy encoding as part of the
|
||||
* environment. This should not be used with nice greenfield formats
|
||||
* (e.g. JSON) but is necessary for supporting old formats (e.g.
|
||||
* ATerm).
|
||||
*/
|
||||
static DerivationOptions
|
||||
fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn = true);
|
||||
|
||||
static DerivationOptions
|
||||
fromStructuredAttrs(const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn = true);
|
||||
|
||||
/**
|
||||
* @param drv Must be the same derivation we parsed this from. In
|
||||
* the future we'll flip things around so a `BasicDerivation` has
|
||||
@@ -222,7 +201,49 @@ struct DerivationOptions
|
||||
bool useUidRange(const BasicDerivation & drv) const;
|
||||
};
|
||||
|
||||
extern template struct DerivationOptions<StorePath>;
|
||||
extern template struct DerivationOptions<SingleDerivedPath>;
|
||||
|
||||
struct DerivationOutput;
|
||||
|
||||
/**
|
||||
* Parse this information from its legacy encoding as part of the
|
||||
* environment. This should not be used with nice greenfield formats
|
||||
* (e.g. JSON) but is necessary for supporting old formats (e.g.
|
||||
* ATerm).
|
||||
*/
|
||||
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
|
||||
const StoreDirConfig & store,
|
||||
const DerivedPathMap<StringSet> & inputDrvs,
|
||||
const StringMap & env,
|
||||
const StructuredAttrs * parsed,
|
||||
bool shouldWarn = true,
|
||||
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
|
||||
|
||||
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
|
||||
const StoreDirConfig & store,
|
||||
const StringMap & env,
|
||||
const StructuredAttrs * parsed,
|
||||
bool shouldWarn = true,
|
||||
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
|
||||
|
||||
/**
|
||||
* This is the counterpart of `Derivation::tryResolve`. In particular,
|
||||
* it takes the same sort of callback, which is used to reolve
|
||||
* non-constant deriving paths.
|
||||
*
|
||||
* We need this function when resolving a derivation, and we will use
|
||||
* this as part of that if/when `Derivation` includes
|
||||
* `DerivationOptions`
|
||||
*/
|
||||
std::optional<DerivationOptions<StorePath>> tryResolve(
|
||||
const DerivationOptions<SingleDerivedPath> & drvOptions,
|
||||
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
|
||||
queryResolutionChain);
|
||||
|
||||
}; // namespace nix
|
||||
|
||||
JSON_IMPL(DerivationOptions);
|
||||
JSON_IMPL(DerivationOptions::OutputChecks)
|
||||
JSON_IMPL(nix::DerivationOptions<nix::StorePath>);
|
||||
JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>);
|
||||
JSON_IMPL(nix::DerivationOptions<nix::StorePath>::OutputChecks)
|
||||
JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>::OutputChecks)
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
///@file
|
||||
|
||||
#include "nix/util/hash.hh"
|
||||
#include "nix/util/json-impls.hh"
|
||||
#include "nix/store/path.hh"
|
||||
#include "nix/store/derived-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* A reference is either to a to-be-registered output (by name),
|
||||
* or to an already-registered store object (by `Input`).
|
||||
*
|
||||
* `Ref<SingleDerivedPath` is a representation of something that can be
|
||||
* turned into a placeholder. (Regular own-output placeholder in the
|
||||
* first case, `DownstreamPlaceholder` in the second case.)
|
||||
*/
|
||||
template<typename Input>
|
||||
using DrvRef = std::variant<OutputName, Input>;
|
||||
|
||||
/**
|
||||
* Downstream Placeholders are opaque and almost certainly unique values
|
||||
* used to allow derivations to refer to store objects which are yet to
|
||||
@@ -92,3 +104,17 @@ public:
|
||||
};
|
||||
|
||||
} // namespace nix
|
||||
|
||||
namespace nlohmann {
|
||||
|
||||
template<typename Item>
|
||||
struct adl_serializer<nix::DrvRef<Item>>
|
||||
{
|
||||
static nix::DrvRef<Item> from_json(const json & json);
|
||||
static void to_json(json & json, const nix::DrvRef<Item> & t);
|
||||
};
|
||||
|
||||
extern template struct adl_serializer<nix::DrvRef<nix::StorePath>>;
|
||||
extern template struct adl_serializer<nix::DrvRef<nix::SingleDerivedPath>>;
|
||||
|
||||
} // namespace nlohmann
|
||||
|
||||
@@ -87,11 +87,11 @@ extern const unsigned int RETRY_TIME_MS_DEFAULT;
|
||||
* HTTP methods supported by FileTransfer.
|
||||
*/
|
||||
enum struct HttpMethod {
|
||||
GET,
|
||||
PUT,
|
||||
HEAD,
|
||||
POST,
|
||||
DELETE,
|
||||
Get,
|
||||
Put,
|
||||
Head,
|
||||
Post,
|
||||
Delete,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,7 +110,7 @@ struct FileTransferRequest
|
||||
VerbatimURL uri;
|
||||
Headers headers;
|
||||
std::string expectedETag;
|
||||
HttpMethod method = HttpMethod::GET;
|
||||
HttpMethod method = HttpMethod::Get;
|
||||
size_t tries = fileTransferSettings.tries;
|
||||
unsigned int baseRetryTimeMs = RETRY_TIME_MS_DEFAULT;
|
||||
ActivityId parentAct;
|
||||
@@ -164,14 +164,14 @@ struct FileTransferRequest
|
||||
std::string verb() const
|
||||
{
|
||||
switch (method) {
|
||||
case HttpMethod::HEAD:
|
||||
case HttpMethod::GET:
|
||||
case HttpMethod::Head:
|
||||
case HttpMethod::Get:
|
||||
return "download";
|
||||
case HttpMethod::PUT:
|
||||
case HttpMethod::POST:
|
||||
case HttpMethod::Put:
|
||||
case HttpMethod::Post:
|
||||
assert(data);
|
||||
return "upload";
|
||||
case HttpMethod::DELETE:
|
||||
case HttpMethod::Delete:
|
||||
return "delet";
|
||||
}
|
||||
unreachable();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
template<typename Input>
|
||||
struct DerivationOptions;
|
||||
struct DerivationOutput;
|
||||
|
||||
@@ -47,7 +48,7 @@ struct StructuredAttrs
|
||||
|
||||
nlohmann::json::object_t prepareStructuredAttrs(
|
||||
Store & store,
|
||||
const DerivationOptions & drvOptions,
|
||||
const DerivationOptions<StorePath> & drvOptions,
|
||||
const StorePathSet & inputPaths,
|
||||
const DerivationOutputs & outputs) const;
|
||||
|
||||
|
||||
@@ -1246,10 +1246,8 @@ StorePath LocalStore::addToStoreFromDump(
|
||||
|
||||
auto desc = ContentAddressWithReferences::fromParts(
|
||||
hashMethod,
|
||||
methodsMatch
|
||||
? dumpHash
|
||||
: hashPath(PosixSourceAccessor::createAtRoot(tempPath), hashMethod.getFileIngestionMethod(), hashAlgo)
|
||||
.first,
|
||||
methodsMatch ? dumpHash
|
||||
: hashPath(makeFSSourceAccessor(tempPath), hashMethod.getFileIngestionMethod(), hashAlgo).first,
|
||||
{
|
||||
.others = references,
|
||||
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||
@@ -1385,12 +1383,9 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||
checkInterrupt();
|
||||
auto name = link.path().filename();
|
||||
printMsg(lvlTalkative, "checking contents of %s", name);
|
||||
PosixSourceAccessor accessor;
|
||||
std::string hash = hashPath(
|
||||
PosixSourceAccessor::createAtRoot(link.path()),
|
||||
FileIngestionMethod::NixArchive,
|
||||
HashAlgorithm::SHA256)
|
||||
.first.to_string(HashFormat::Nix32, false);
|
||||
std::string hash =
|
||||
hashPath(makeFSSourceAccessor(link.path()), FileIngestionMethod::NixArchive, HashAlgorithm::SHA256)
|
||||
.first.to_string(HashFormat::Nix32, false);
|
||||
if (hash != name.string()) {
|
||||
printError("link %s was modified! expected hash %s, got '%s'", link.path(), name, hash);
|
||||
if (repair) {
|
||||
|
||||
@@ -225,11 +225,12 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
|
||||
return;
|
||||
|
||||
auto drv = make_ref<Derivation>(derivationFromPath(drvPath));
|
||||
DerivationOptions drvOptions;
|
||||
DerivationOptions<SingleDerivedPath> drvOptions;
|
||||
try {
|
||||
// FIXME: this is a lot of work just to get the value
|
||||
// of `allowSubstitutes`.
|
||||
drvOptions = DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs);
|
||||
drvOptions = derivationOptionsFromStructuredAttrs(
|
||||
*this, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while parsing derivation '%s'", printStorePath(drvPath));
|
||||
throw;
|
||||
|
||||
@@ -172,7 +172,7 @@ void LocalStore::optimisePath_(
|
||||
auto stLink = lstat(linkPath.string());
|
||||
if (st.st_size != stLink.st_size || (repair && hash != ({
|
||||
hashPath(
|
||||
PosixSourceAccessor::createAtRoot(linkPath),
|
||||
makeFSSourceAccessor(linkPath),
|
||||
FileSerialisationMethod::NixArchive,
|
||||
HashAlgorithm::SHA256)
|
||||
.hash;
|
||||
|
||||
@@ -100,7 +100,7 @@ static nlohmann::json pathInfoToJSON(Store & store, const StorePathSet & storePa
|
||||
|
||||
nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
|
||||
Store & store,
|
||||
const DerivationOptions & drvOptions,
|
||||
const DerivationOptions<StorePath> & drvOptions,
|
||||
const StorePathSet & inputPaths,
|
||||
const DerivationOutputs & outputs) const
|
||||
{
|
||||
@@ -114,8 +114,8 @@ nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
|
||||
json["outputs"] = std::move(outputsJson);
|
||||
|
||||
/* Handle exportReferencesGraph. */
|
||||
for (auto & [key, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) {
|
||||
json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, storePaths));
|
||||
for (auto & [key, storePaths] : drvOptions.exportReferencesGraph) {
|
||||
json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, inputPaths));
|
||||
}
|
||||
|
||||
return json;
|
||||
|
||||
@@ -295,7 +295,7 @@ std::string S3BinaryCacheStore::createMultipartUpload(
|
||||
url.query["uploads"] = "";
|
||||
req.uri = VerbatimURL(url);
|
||||
|
||||
req.method = HttpMethod::POST;
|
||||
req.method = HttpMethod::Post;
|
||||
StringSource payload{std::string_view("")};
|
||||
req.data = {payload};
|
||||
req.mimeType = mimeType;
|
||||
@@ -325,7 +325,7 @@ S3BinaryCacheStore::uploadPart(std::string_view key, std::string_view uploadId,
|
||||
}
|
||||
|
||||
auto req = makeRequest(key);
|
||||
req.method = HttpMethod::PUT;
|
||||
req.method = HttpMethod::Put;
|
||||
req.setupForS3();
|
||||
|
||||
auto url = req.uri.parsed();
|
||||
@@ -355,7 +355,7 @@ void S3BinaryCacheStore::abortMultipartUpload(std::string_view key, std::string_
|
||||
auto url = req.uri.parsed();
|
||||
url.query["uploadId"] = uploadId;
|
||||
req.uri = VerbatimURL(url);
|
||||
req.method = HttpMethod::DELETE;
|
||||
req.method = HttpMethod::Delete;
|
||||
|
||||
getFileTransfer()->enqueueFileTransfer(req).get();
|
||||
} catch (...) {
|
||||
@@ -372,7 +372,7 @@ void S3BinaryCacheStore::completeMultipartUpload(
|
||||
auto url = req.uri.parsed();
|
||||
url.query["uploadId"] = uploadId;
|
||||
req.uri = VerbatimURL(url);
|
||||
req.method = HttpMethod::POST;
|
||||
req.method = HttpMethod::Post;
|
||||
|
||||
std::string xml = "<CompleteMultipartUpload>";
|
||||
for (const auto & [idx, etag] : enumerate(partEtags)) {
|
||||
|
||||
@@ -36,7 +36,7 @@ nix_err nix_context_error(nix_c_context * context)
|
||||
const char * demangled = abi::__cxa_demangle(typeid(e).name(), 0, 0, &status);
|
||||
if (demangled) {
|
||||
context->name = demangled;
|
||||
// todo: free(demangled);
|
||||
free((void *) demangled);
|
||||
} else {
|
||||
context->name = typeid(e).name();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#ifndef _WIN32
|
||||
// TODO: investigate why this is hanging on cygwin
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
|
||||
# include "nix/util/util.hh"
|
||||
# include "nix/util/monitor-fd.hh"
|
||||
|
||||
@@ -103,9 +103,9 @@ void SourceAccessor::dumpPath(const CanonPath & path, Sink & sink, PathFilter &
|
||||
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
auto path2 = PosixSourceAccessor::createAtRoot(path);
|
||||
auto path2 = PosixSourceAccessor::createAtRoot(path, /*trackLastModified=*/true);
|
||||
path2.dumpPath(sink, filter);
|
||||
return path2.accessor.dynamic_pointer_cast<PosixSourceAccessor>()->mtime;
|
||||
return path2.accessor->getLastModified().value();
|
||||
}
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
@@ -200,54 +200,54 @@ static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath
|
||||
}
|
||||
|
||||
else if (type == "directory") {
|
||||
sink.createDirectory(path);
|
||||
sink.createDirectory(path, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) {
|
||||
std::map<Path, int, CaseInsensitiveCompare> names;
|
||||
|
||||
std::map<Path, int, CaseInsensitiveCompare> names;
|
||||
std::string prevName;
|
||||
|
||||
std::string prevName;
|
||||
while (1) {
|
||||
auto tag = getString();
|
||||
|
||||
while (1) {
|
||||
auto tag = getString();
|
||||
if (tag == ")")
|
||||
break;
|
||||
|
||||
if (tag == ")")
|
||||
break;
|
||||
if (tag != "entry")
|
||||
throw badArchive("expected tag 'entry' or ')', got '%s'", tag);
|
||||
|
||||
if (tag != "entry")
|
||||
throw badArchive("expected tag 'entry' or ')', got '%s'", tag);
|
||||
expectTag("(");
|
||||
|
||||
expectTag("(");
|
||||
expectTag("name");
|
||||
|
||||
expectTag("name");
|
||||
auto name = getString();
|
||||
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos
|
||||
|| name.find((char) 0) != std::string::npos)
|
||||
throw badArchive("NAR contains invalid file name '%1%'", name);
|
||||
if (name <= prevName)
|
||||
throw badArchive("NAR directory is not sorted");
|
||||
prevName = name;
|
||||
if (archiveSettings.useCaseHack) {
|
||||
auto i = names.find(name);
|
||||
if (i != names.end()) {
|
||||
debug("case collision between '%1%' and '%2%'", i->first, name);
|
||||
name += caseHackSuffix;
|
||||
name += std::to_string(++i->second);
|
||||
auto j = names.find(name);
|
||||
if (j != names.end())
|
||||
throw badArchive(
|
||||
"NAR contains file name '%s' that collides with case-hacked file name '%s'",
|
||||
prevName,
|
||||
j->first);
|
||||
} else
|
||||
names[name] = 0;
|
||||
}
|
||||
|
||||
auto name = getString();
|
||||
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos
|
||||
|| name.find((char) 0) != std::string::npos)
|
||||
throw badArchive("NAR contains invalid file name '%1%'", name);
|
||||
if (name <= prevName)
|
||||
throw badArchive("NAR directory is not sorted");
|
||||
prevName = name;
|
||||
if (archiveSettings.useCaseHack) {
|
||||
auto i = names.find(name);
|
||||
if (i != names.end()) {
|
||||
debug("case collision between '%1%' and '%2%'", i->first, name);
|
||||
name += caseHackSuffix;
|
||||
name += std::to_string(++i->second);
|
||||
auto j = names.find(name);
|
||||
if (j != names.end())
|
||||
throw badArchive(
|
||||
"NAR contains file name '%s' that collides with case-hacked file name '%s'",
|
||||
prevName,
|
||||
j->first);
|
||||
} else
|
||||
names[name] = 0;
|
||||
expectTag("node");
|
||||
|
||||
parse(dirSink, source, relDirPath / name);
|
||||
|
||||
expectTag(")");
|
||||
}
|
||||
|
||||
expectTag("node");
|
||||
|
||||
parse(sink, source, path / name);
|
||||
|
||||
expectTag(")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else if (type == "symlink") {
|
||||
|
||||
@@ -14,44 +14,6 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
void copyRecursive(SourceAccessor & accessor, const CanonPath & from, FileSystemObjectSink & sink, const CanonPath & to)
|
||||
{
|
||||
auto stat = accessor.lstat(from);
|
||||
|
||||
switch (stat.type) {
|
||||
case SourceAccessor::tSymlink: {
|
||||
sink.createSymlink(to, accessor.readLink(from));
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tRegular: {
|
||||
sink.createRegularFile(to, [&](CreateRegularFileSink & crf) {
|
||||
if (stat.isExecutable)
|
||||
crf.isExecutable();
|
||||
accessor.readFile(from, crf, [&](uint64_t size) { crf.preallocateContents(size); });
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tDirectory: {
|
||||
sink.createDirectory(to);
|
||||
for (auto & [name, _] : accessor.readDirectory(from)) {
|
||||
copyRecursive(accessor, from / name, sink, to / name);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tChar:
|
||||
case SourceAccessor::tBlock:
|
||||
case SourceAccessor::tSocket:
|
||||
case SourceAccessor::tFifo:
|
||||
case SourceAccessor::tUnknown:
|
||||
default:
|
||||
throw Error("file '%1%' has an unsupported type of %2%", from, stat.typeString());
|
||||
}
|
||||
}
|
||||
|
||||
struct RestoreSinkSettings : Config
|
||||
{
|
||||
Setting<bool> preallocateContents{
|
||||
@@ -70,11 +32,60 @@ static std::filesystem::path append(const std::filesystem::path & src, const Can
|
||||
return dst;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
void RestoreSink::createDirectory(const CanonPath & path, DirectoryCreatedCallback callback)
|
||||
{
|
||||
if (path.isRoot()) {
|
||||
createDirectory(path);
|
||||
callback(*this, path);
|
||||
return;
|
||||
}
|
||||
|
||||
createDirectory(path);
|
||||
assert(dirFd); // If that's not true the above call must have thrown an exception.
|
||||
|
||||
RestoreSink dirSink{startFsync};
|
||||
dirSink.dstPath = append(dstPath, path);
|
||||
dirSink.dirFd = ::openat(dirFd.get(), path.rel_c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
|
||||
if (!dirSink.dirFd)
|
||||
throw SysError("opening directory '%s'", dirSink.dstPath.string());
|
||||
|
||||
callback(dirSink, CanonPath::root);
|
||||
}
|
||||
#endif
|
||||
|
||||
void RestoreSink::createDirectory(const CanonPath & path)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
|
||||
#ifndef _WIN32
|
||||
if (dirFd) {
|
||||
if (path.isRoot())
|
||||
/* Trying to create a directory that we already have a file descriptor for. */
|
||||
throw Error("path '%s' already exists", p.string());
|
||||
|
||||
if (::mkdirat(dirFd.get(), path.rel_c_str(), 0777) == -1)
|
||||
throw SysError("creating directory '%s'", p.string());
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!std::filesystem::create_directory(p))
|
||||
throw Error("path '%s' already exists", p.string());
|
||||
|
||||
#ifndef _WIN32
|
||||
if (path.isRoot()) {
|
||||
assert(!dirFd); // Handled above
|
||||
|
||||
/* Open directory for further *at operations relative to the sink root
|
||||
directory. */
|
||||
dirFd = open(p.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (!dirFd)
|
||||
throw SysError("creating directory '%1%'", p.string());
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct RestoreRegularFile : CreateRegularFileSink
|
||||
@@ -114,7 +125,14 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL)
|
||||
#else
|
||||
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)
|
||||
[&]() {
|
||||
/* O_EXCL together with O_CREAT ensures symbolic links in the last
|
||||
component are not followed. */
|
||||
constexpr int flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
|
||||
if (!dirFd)
|
||||
return ::open(p.c_str(), flags, 0666);
|
||||
return ::openat(dirFd.get(), path.rel_c_str(), flags, 0666);
|
||||
}();
|
||||
#endif
|
||||
;
|
||||
if (!crf.fd)
|
||||
@@ -161,6 +179,13 @@ void RestoreRegularFile::operator()(std::string_view data)
|
||||
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
#ifndef _WIN32
|
||||
if (dirFd) {
|
||||
if (::symlinkat(requireCString(target), dirFd.get(), path.rel_c_str()) == -1)
|
||||
throw SysError("creating symlink from '%1%' -> '%2%'", p.string(), target);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
nix::createSymlink(target, p.string());
|
||||
}
|
||||
|
||||
|
||||
@@ -217,29 +217,6 @@ std::optional<Mode> convertMode(SourceAccessor::Type type)
|
||||
}
|
||||
}
|
||||
|
||||
void restore(FileSystemObjectSink & sink, Source & source, HashAlgorithm hashAlgo, std::function<RestoreHook> hook)
|
||||
{
|
||||
parse(sink, CanonPath::root, source, BlobMode::Regular, hashAlgo, [&](CanonPath name, TreeEntry entry) {
|
||||
auto [accessor, from] = hook(entry.hash);
|
||||
auto stat = accessor->lstat(from);
|
||||
auto gotOpt = convertMode(stat.type);
|
||||
if (!gotOpt)
|
||||
throw Error(
|
||||
"file '%s' (git hash %s) has an unsupported type",
|
||||
from,
|
||||
entry.hash.to_string(HashFormat::Base16, false));
|
||||
auto & got = *gotOpt;
|
||||
if (got != entry.mode)
|
||||
throw Error(
|
||||
"git mode of file '%s' (git hash %s) is %o but expected %o",
|
||||
from,
|
||||
entry.hash.to_string(HashFormat::Base16, false),
|
||||
(RawMode) got,
|
||||
(RawMode) entry.mode);
|
||||
copyRecursive(*accessor, from, sink, name);
|
||||
});
|
||||
}
|
||||
|
||||
void dumpBlobPrefix(uint64_t size, Sink & sink, const ExperimentalFeatureSettings & xpSettings)
|
||||
{
|
||||
xpSettings.require(Xp::GitHashing);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <ranges>
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
|
||||
@@ -122,33 +123,70 @@ public:
|
||||
return &cs[1];
|
||||
}
|
||||
|
||||
struct Iterator
|
||||
class Iterator
|
||||
{
|
||||
/**
|
||||
* Helper class with overloaded operator-> for "drill-down" behavior.
|
||||
* This was a "temporary" string_view doesn't have to be stored anywhere.
|
||||
*/
|
||||
class PointerProxy
|
||||
{
|
||||
std::string_view segment;
|
||||
|
||||
public:
|
||||
PointerProxy(std::string_view segment_)
|
||||
: segment(segment_)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string_view * operator->() const
|
||||
{
|
||||
return &segment;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using value_type = std::string_view;
|
||||
using reference_type = const std::string_view;
|
||||
using pointer_type = PointerProxy;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
std::string_view remaining;
|
||||
size_t slash;
|
||||
|
||||
/**
|
||||
* Dummy default constructor required for forward iterators. Doesn't return
|
||||
* a usable iterator.
|
||||
*/
|
||||
Iterator()
|
||||
: remaining()
|
||||
, slash(0)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(std::string_view remaining)
|
||||
: remaining(remaining)
|
||||
, slash(remaining.find('/'))
|
||||
{
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator & x) const
|
||||
{
|
||||
return remaining.data() != x.remaining.data();
|
||||
}
|
||||
|
||||
bool operator==(const Iterator & x) const
|
||||
{
|
||||
return !(*this != x);
|
||||
return remaining.data() == x.remaining.data();
|
||||
}
|
||||
|
||||
const std::string_view operator*() const
|
||||
reference_type operator*() const
|
||||
{
|
||||
return remaining.substr(0, slash);
|
||||
}
|
||||
|
||||
void operator++()
|
||||
pointer_type operator->() const
|
||||
{
|
||||
return PointerProxy(**this);
|
||||
}
|
||||
|
||||
Iterator & operator++()
|
||||
{
|
||||
if (slash == remaining.npos)
|
||||
remaining = remaining.substr(remaining.size());
|
||||
@@ -156,9 +194,19 @@ public:
|
||||
remaining = remaining.substr(slash + 1);
|
||||
slash = remaining.find('/');
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(std::forward_iterator<Iterator>);
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(rel());
|
||||
@@ -265,6 +313,8 @@ public:
|
||||
friend std::size_t hash_value(const CanonPath &);
|
||||
};
|
||||
|
||||
static_assert(std::ranges::forward_range<CanonPath>);
|
||||
|
||||
std::ostream & operator<<(std::ostream & stream, const CanonPath & path);
|
||||
|
||||
inline std::size_t hash_value(const CanonPath & path)
|
||||
|
||||
@@ -36,6 +36,23 @@ struct FileSystemObjectSink
|
||||
|
||||
virtual void createDirectory(const CanonPath & path) = 0;
|
||||
|
||||
using DirectoryCreatedCallback = std::function<void(FileSystemObjectSink & dirSink, const CanonPath & dirRelPath)>;
|
||||
|
||||
/**
|
||||
* Create a directory and invoke a callback with a pair of sink + CanonPath
|
||||
* of the created subdirectory relative to dirSink.
|
||||
*
|
||||
* @note This allows for UNIX RestoreSink implementations to implement
|
||||
* *at-style accessors that always keep an open file descriptor for the
|
||||
* freshly created directory. Use this when it's important to disallow any
|
||||
* intermediate path components from being symlinks.
|
||||
*/
|
||||
virtual void createDirectory(const CanonPath & path, DirectoryCreatedCallback callback)
|
||||
{
|
||||
createDirectory(path);
|
||||
callback(*this, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function in general is no re-entrant. Only one file can be
|
||||
* written at a time.
|
||||
@@ -82,6 +99,18 @@ struct NullFileSystemObjectSink : FileSystemObjectSink
|
||||
struct RestoreSink : FileSystemObjectSink
|
||||
{
|
||||
std::filesystem::path dstPath;
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* File descriptor for the directory located at dstPath. Used for *at
|
||||
* operations relative to this file descriptor. This sink must *never*
|
||||
* follow intermediate symlinks (starting from dstPath) in case a file
|
||||
* collision is encountered for various reasons like case-insensitivity or
|
||||
* other types on normalization. using appropriate *at system calls and traversing
|
||||
* only one path component at a time ensures that writing is race-free and is
|
||||
* is not susceptible to symlink replacement.
|
||||
*/
|
||||
AutoCloseFD dirFd;
|
||||
#endif
|
||||
bool startFsync = false;
|
||||
|
||||
explicit RestoreSink(bool startFsync)
|
||||
@@ -91,6 +120,10 @@ struct RestoreSink : FileSystemObjectSink
|
||||
|
||||
void createDirectory(const CanonPath & path) override;
|
||||
|
||||
#ifndef _WIN32
|
||||
void createDirectory(const CanonPath & path, DirectoryCreatedCallback callback) override;
|
||||
#endif
|
||||
|
||||
void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)>) override;
|
||||
|
||||
void createSymlink(const CanonPath & path, const std::string & target) override;
|
||||
|
||||
@@ -136,13 +136,6 @@ std::optional<Mode> convertMode(SourceAccessor::Type type);
|
||||
*/
|
||||
using RestoreHook = SourcePath(Hash);
|
||||
|
||||
/**
|
||||
* Wrapper around `parse` and `RestoreSink`
|
||||
*
|
||||
* @param hashAlgo must be `HashAlgo::SHA1` or `HashAlgo::SHA256` for now.
|
||||
*/
|
||||
void restore(FileSystemObjectSink & sink, Source & source, HashAlgorithm hashAlgo, std::function<RestoreHook> hook);
|
||||
|
||||
/**
|
||||
* Dumps a single file to a sink
|
||||
*
|
||||
|
||||
@@ -9,7 +9,7 @@ struct SourcePath;
|
||||
/**
|
||||
* A source accessor that uses the Unix filesystem.
|
||||
*/
|
||||
struct PosixSourceAccessor : virtual SourceAccessor
|
||||
class PosixSourceAccessor : virtual public SourceAccessor
|
||||
{
|
||||
/**
|
||||
* Optional root path to prefix all operations into the native file
|
||||
@@ -18,8 +18,12 @@ struct PosixSourceAccessor : virtual SourceAccessor
|
||||
*/
|
||||
const std::filesystem::path root;
|
||||
|
||||
const bool trackLastModified = false;
|
||||
|
||||
public:
|
||||
|
||||
PosixSourceAccessor();
|
||||
PosixSourceAccessor(std::filesystem::path && root);
|
||||
PosixSourceAccessor(std::filesystem::path && root, bool trackLastModified = false);
|
||||
|
||||
/**
|
||||
* The most recent mtime seen by lstat(). This is a hack to
|
||||
@@ -43,6 +47,9 @@ struct PosixSourceAccessor : virtual SourceAccessor
|
||||
* Create a `PosixSourceAccessor` and `SourcePath` corresponding to
|
||||
* some native path.
|
||||
*
|
||||
* @param Whether the accessor should return a non-null getLastModified.
|
||||
* When true the accessor must be used only by a single thread.
|
||||
*
|
||||
* The `PosixSourceAccessor` is rooted as far up the tree as
|
||||
* possible, (e.g. on Windows it could scoped to a drive like
|
||||
* `C:\`). This allows more `..` parent accessing to work.
|
||||
@@ -64,7 +71,12 @@ struct PosixSourceAccessor : virtual SourceAccessor
|
||||
* and
|
||||
* [`std::filesystem::path::relative_path`](https://en.cppreference.com/w/cpp/filesystem/path/relative_path).
|
||||
*/
|
||||
static SourcePath createAtRoot(const std::filesystem::path & path);
|
||||
static SourcePath createAtRoot(const std::filesystem::path & path, bool trackLastModified = false);
|
||||
|
||||
std::optional<std::time_t> getLastModified() override
|
||||
{
|
||||
return trackLastModified ? std::optional{mtime} : std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -166,4 +166,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the string does not contain any NUL bytes and return c_str().
|
||||
* @throws Error if str contains '\0' bytes.
|
||||
*/
|
||||
const char * requireCString(const std::string & str);
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Determine whether \param fd is a terminal.
|
||||
*/
|
||||
bool isTTY(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Determine whether ANSI escape sequences are appropriate for the
|
||||
* present output.
|
||||
|
||||
@@ -33,15 +33,28 @@ auto concatStrings(Parts &&... parts)
|
||||
return concatStringsSep({}, views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add quotes around a string.
|
||||
*/
|
||||
inline std::string quoteString(std::string_view s, char quote = '\'')
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(s.size() + 2);
|
||||
result += quote;
|
||||
result += s;
|
||||
result += quote;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add quotes around a collection of strings.
|
||||
*/
|
||||
template<class C>
|
||||
Strings quoteStrings(const C & c)
|
||||
Strings quoteStrings(const C & c, char quote = '\'')
|
||||
{
|
||||
Strings res;
|
||||
for (auto & s : c)
|
||||
res.push_back("'" + s + "'");
|
||||
res.push_back(quoteString(s, quote));
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -229,6 +242,28 @@ std::string stripIndentation(std::string_view s);
|
||||
*/
|
||||
std::pair<std::string_view, std::string_view> getLine(std::string_view s);
|
||||
|
||||
/**
|
||||
* Get a pointer to the contents of a `std::optional` if it is set, or a
|
||||
* null pointer otherise.
|
||||
*
|
||||
* Const version.
|
||||
*/
|
||||
template<class T>
|
||||
const T * get(const std::optional<T> & opt)
|
||||
{
|
||||
return opt ? &*opt : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-const counterpart of `const T * get(const std::optional<T>)`.
|
||||
* Takes a mutable reference, but returns a mutable pointer.
|
||||
*/
|
||||
template<class T>
|
||||
T * get(std::optional<T> & opt)
|
||||
{
|
||||
return opt ? &*opt : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for the specified key from an associate container.
|
||||
*/
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot)
|
||||
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot, bool trackLastModified)
|
||||
: root(std::move(argRoot))
|
||||
, trackLastModified(trackLastModified)
|
||||
{
|
||||
assert(root.empty() || root.is_absolute());
|
||||
displayPrefix = root.string();
|
||||
@@ -19,11 +20,11 @@ PosixSourceAccessor::PosixSourceAccessor()
|
||||
{
|
||||
}
|
||||
|
||||
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
|
||||
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path, bool trackLastModified)
|
||||
{
|
||||
std::filesystem::path path2 = absPath(path);
|
||||
return {
|
||||
make_ref<PosixSourceAccessor>(path2.root_path()),
|
||||
make_ref<PosixSourceAccessor>(path2.root_path(), trackLastModified),
|
||||
CanonPath{path2.relative_path().string()},
|
||||
};
|
||||
}
|
||||
@@ -114,9 +115,12 @@ std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonP
|
||||
auto st = cachedLstat(path);
|
||||
if (!st)
|
||||
return std::nullopt;
|
||||
// This makes the accessor thread-unsafe, but we only seem to use the actual value in a single threaded context in
|
||||
// `src/libfetchers/path.cc`.
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
|
||||
/* The contract is that trackLastModified implies that the caller uses the accessor
|
||||
from a single thread. Thus this is not a CAS loop. */
|
||||
if (trackLastModified)
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
|
||||
return Stat{
|
||||
.type = S_ISREG(st->st_mode) ? tRegular
|
||||
: S_ISDIR(st->st_mode) ? tDirectory
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "nix/util/strings-inline.hh"
|
||||
#include "nix/util/os-string.hh"
|
||||
#include "nix/util/error.hh"
|
||||
#include "nix/util/util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -152,4 +153,14 @@ std::string optionalBracket(std::string_view prefix, std::string_view content, s
|
||||
return result;
|
||||
}
|
||||
|
||||
const char * requireCString(const std::string & s)
|
||||
{
|
||||
if (std::memchr(s.data(), '\0', s.size())) [[unlikely]] {
|
||||
using namespace std::string_view_literals;
|
||||
auto str = replaceStrings(s, "\0"sv, "␀"sv);
|
||||
throw Error("string '%s' with null (\\0) bytes used where it's not allowed", str);
|
||||
}
|
||||
return s.c_str();
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -64,6 +64,16 @@ inline std::pair<int, size_t> charWidthUTF8Helper(std::string_view s)
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool isTTY(Descriptor fd)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return isatty(fd);
|
||||
#else
|
||||
DWORD mode;
|
||||
return GetConsoleMode(fd, &mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isTTY()
|
||||
{
|
||||
static const bool tty = isatty(STDERR_FILENO) && getEnv("TERM").value_or("dumb") != "dumb"
|
||||
|
||||
@@ -327,8 +327,11 @@ Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath)
|
||||
/* This is only really valid for UNIX. Windows has more restrictions. */
|
||||
if (comp.contains('/'))
|
||||
throw BadURL("URL path component '%s' contains '/', which is not allowed in file names", comp);
|
||||
if (comp.contains(char(0)))
|
||||
throw BadURL("URL path component '%s' contains NUL byte which is not allowed", comp);
|
||||
if (comp.contains(char(0))) {
|
||||
using namespace std::string_view_literals;
|
||||
auto str = replaceStrings(comp, "\0"sv, "␀"sv);
|
||||
throw BadURL("URL path component '%s' contains NUL byte which is not allowed", str);
|
||||
}
|
||||
}
|
||||
|
||||
return concatStringsSep("/", urlPath);
|
||||
|
||||
@@ -75,7 +75,7 @@ struct CmdCatNar : StoreCommand, MixCat
|
||||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
AutoCloseFD fd = open(narPath.c_str(), O_RDONLY);
|
||||
AutoCloseFD fd = toDescriptor(open(narPath.c_str(), O_RDONLY));
|
||||
if (!fd)
|
||||
throw SysError("opening NAR file '%s'", narPath);
|
||||
auto source = FdSource{fd.get()};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "nix/cmd/command.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/terminal.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
||||
static FdSink getNarSink()
|
||||
{
|
||||
auto fd = getStandardOutput();
|
||||
if (isatty(fd))
|
||||
if (isTTY(fd))
|
||||
throw UsageError("refusing to write NAR to a terminal");
|
||||
return FdSink(std::move(fd));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ struct CmdFlakePrefetchInputs : FlakeCommand
|
||||
if (auto lockedNode = dynamic_cast<const LockedNode *>(&node)) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("fetching '%s'", lockedNode->lockedRef));
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(store).first;
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(fetchSettings, *store).first;
|
||||
fetchToStore(
|
||||
fetchSettings, *store, accessor, FetchMode::Copy, lockedNode->lockedRef.input.getName());
|
||||
} catch (Error & e) {
|
||||
|
||||
@@ -240,12 +240,12 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
j["lastModified"] = *lastModified;
|
||||
j["path"] = storePath;
|
||||
j["locks"] = lockedFlake.lockFile.toJSON().first;
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(*store, fetchSettings))
|
||||
j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false);
|
||||
printJSON(j);
|
||||
} else {
|
||||
logger->cout(ANSI_BOLD "Resolved URL:" ANSI_NORMAL " %s", flake.resolvedRef.to_string());
|
||||
if (flake.lockedRef.input.isLocked())
|
||||
if (flake.lockedRef.input.isLocked(fetchSettings))
|
||||
logger->cout(ANSI_BOLD "Locked URL:" ANSI_NORMAL " %s", flake.lockedRef.to_string());
|
||||
if (flake.description)
|
||||
logger->cout(ANSI_BOLD "Description:" ANSI_NORMAL " %s", *flake.description);
|
||||
@@ -260,7 +260,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
logger->cout(
|
||||
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
|
||||
std::put_time(std::localtime(&*lastModified), "%F %T"));
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(*store, fetchSettings))
|
||||
logger->cout(
|
||||
ANSI_BOLD "Fingerprint:" ANSI_NORMAL " %s", fingerprint->to_string(HashFormat::Base16, false));
|
||||
|
||||
@@ -1019,7 +1019,7 @@ struct CmdFlakeNew : CmdFlakeInitCommon
|
||||
|
||||
struct CmdFlakeClone : FlakeCommand
|
||||
{
|
||||
Path destDir;
|
||||
std::filesystem::path destDir;
|
||||
|
||||
std::string description() override
|
||||
{
|
||||
@@ -1049,7 +1049,7 @@ struct CmdFlakeClone : FlakeCommand
|
||||
if (destDir.empty())
|
||||
throw Error("missing flag '--dest'");
|
||||
|
||||
getFlakeRef().resolve(store).input.clone(destDir);
|
||||
getFlakeRef().resolve(fetchSettings, *store).input.clone(fetchSettings, *store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1100,7 +1100,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun, MixNoCheckSigs
|
||||
std::optional<StorePath> storePath;
|
||||
if (!(*inputNode)->lockedRef.input.isRelative()) {
|
||||
storePath = dryRun ? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(store).first;
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(fetchSettings, *store).first;
|
||||
sources.insert(*storePath);
|
||||
}
|
||||
if (json) {
|
||||
@@ -1324,8 +1324,10 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||
try {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
throw Error("expected a derivation");
|
||||
else {
|
||||
auto name = visitor.getAttrPathStr(state->s.name);
|
||||
logger->warn(fmt("%s is not a derivation", name));
|
||||
}
|
||||
} catch (IFDError & e) {
|
||||
if (!json) {
|
||||
logger->cout(
|
||||
@@ -1496,8 +1498,8 @@ struct CmdFlakePrefetch : FlakeCommand, MixJSON
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
auto originalRef = getFlakeRef();
|
||||
auto resolvedRef = originalRef.resolve(store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(store);
|
||||
auto resolvedRef = originalRef.resolve(fetchSettings, *store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(getEvalState()->fetchSettings, *store);
|
||||
auto storePath =
|
||||
fetchToStore(getEvalState()->fetchSettings, *store, accessor, FetchMode::Copy, lockedRef.input.getName());
|
||||
auto hash = store->queryPathInfo(storePath)->narHash;
|
||||
|
||||
@@ -145,7 +145,7 @@ struct CmdLsNar : Command, MixLs
|
||||
|
||||
void run() override
|
||||
{
|
||||
AutoCloseFD fd = open(narPath.c_str(), O_RDONLY);
|
||||
AutoCloseFD fd = toDescriptor(open(narPath.c_str(), O_RDONLY));
|
||||
if (!fd)
|
||||
throw SysError("opening NAR file '%s'", narPath);
|
||||
auto source = FdSource{fd.get()};
|
||||
|
||||
@@ -193,20 +193,38 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
|
||||
|
||||
std::string dumpCli()
|
||||
{
|
||||
auto res = nlohmann::json::object();
|
||||
using nlohmann::json;
|
||||
|
||||
auto res = json::object();
|
||||
|
||||
res["args"] = toJSON();
|
||||
|
||||
auto stores = nlohmann::json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
{
|
||||
auto & stores = res["stores"] = json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
}
|
||||
}
|
||||
res["stores"] = std::move(stores);
|
||||
res["fetchers"] = fetchers::dumpRegisterInputSchemeInfo();
|
||||
|
||||
{
|
||||
auto & fetchers = res["fetchers"] = json::object();
|
||||
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
auto & s = fetchers[schemeName] = json::object();
|
||||
s["description"] = scheme->schemeDescription();
|
||||
auto & attrs = s["allowedAttrs"] = json::object();
|
||||
for (auto & [fieldName, field] : scheme->allowedAttrs()) {
|
||||
auto & f = attrs[fieldName] = json::object();
|
||||
f["type"] = field.type;
|
||||
f["required"] = field.required;
|
||||
f["doc"] = stripIndentation(field.doc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return res.dump();
|
||||
}
|
||||
@@ -440,7 +458,7 @@ void mainWrapped(int argc, char ** argv)
|
||||
if (!primOp->doc)
|
||||
continue;
|
||||
b["args"] = primOp->args;
|
||||
b["doc"] = trim(stripIndentation(primOp->doc));
|
||||
b["doc"] = trim(stripIndentation(*primOp->doc));
|
||||
if (primOp->experimentalFeature)
|
||||
b["experimental-feature"] = primOp->experimentalFeature;
|
||||
builtinsJson.emplace(state.symbols[builtin.name], std::move(b));
|
||||
|
||||
@@ -554,9 +554,9 @@ static void main_nix_build(int argc, char ** argv)
|
||||
env["NIX_STORE"] = store->storeDir;
|
||||
env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores ? settings.buildCores : settings.getDefaultCores());
|
||||
|
||||
DerivationOptions drvOptions;
|
||||
DerivationOptions<StorePath> drvOptions;
|
||||
try {
|
||||
drvOptions = DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs);
|
||||
drvOptions = derivationOptionsFromStructuredAttrs(*store, drv.env, get(drv.structuredAttrs));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while parsing derivation '%s'", store->printStorePath(packageInfo.requireDrvPath()));
|
||||
throw;
|
||||
|
||||
@@ -121,7 +121,7 @@ static void update(const StringSet & channelNames)
|
||||
// We want to download the url to a file to see if it's a tarball while also checking if we
|
||||
// got redirected in the process, so that we can grab the various parts of a nix channel
|
||||
// definition from a consistent location if the redirect changes mid-download.
|
||||
auto result = fetchers::downloadFile(store, fetchSettings, url, std::string(baseNameOf(url)));
|
||||
auto result = fetchers::downloadFile(*store, fetchSettings, url, std::string(baseNameOf(url)));
|
||||
url = result.effectiveUrl;
|
||||
|
||||
bool unpacked = false;
|
||||
@@ -139,10 +139,10 @@ static void update(const StringSet & channelNames)
|
||||
if (!unpacked) {
|
||||
// Download the channel tarball.
|
||||
try {
|
||||
result = fetchers::downloadFile(store, fetchSettings, url + "/nixexprs.tar.xz", "nixexprs.tar.xz");
|
||||
result = fetchers::downloadFile(*store, fetchSettings, url + "/nixexprs.tar.xz", "nixexprs.tar.xz");
|
||||
} catch (FileTransferError & e) {
|
||||
result =
|
||||
fetchers::downloadFile(store, fetchSettings, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2");
|
||||
fetchers::downloadFile(*store, fetchSettings, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2");
|
||||
}
|
||||
}
|
||||
// Regardless of where it came from, add the expression representing this channel to accumulated expression
|
||||
|
||||
@@ -138,8 +138,7 @@ std::tuple<StorePath, Hash> prefetchFile(
|
||||
|
||||
Activity act(*logger, lvlChatty, actUnknown, fmt("adding '%s' to the store", url.to_string()));
|
||||
|
||||
auto info = store->addToStoreSlow(
|
||||
*name, PosixSourceAccessor::createAtRoot(tmpFile), method, hashAlgo, {}, expectedHash);
|
||||
auto info = store->addToStoreSlow(*name, makeFSSourceAccessor(tmpFile), method, hashAlgo, {}, expectedHash);
|
||||
storePath = info.path;
|
||||
assert(info.ca);
|
||||
hash = info.ca->hash;
|
||||
|
||||
@@ -23,6 +23,6 @@ R""(
|
||||
|
||||
This command shows the difference between the closures of subsequent
|
||||
versions of a profile. See [`nix store
|
||||
diff-closures`](nix3-store-diff-closures.md) for details.
|
||||
diff-closures`](./nix3-store-diff-closures.md) for details.
|
||||
|
||||
)""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user