From 343239fc8a1993f707a990c2cd54a41f1fa3de99 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 Oct 2020 20:45:39 +0100 Subject: [PATCH] Allow nix.conf options to be set in flake.nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it possible to have per-project configuration in flake.nix, e.g. binary caches and other stuff: nixConfig.bash-prompt-suffix = "ngi# "; nixConfig.substituters = [ "https://cache.ngi0.nixos.org/" ]; --- src/libexpr/flake/flake.cc | 70 +++++++++++++++++++++++++++++++++++--- src/libexpr/flake/flake.hh | 11 +++++- src/nix/installables.cc | 5 ++- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index ca3b185f9..bdcf63c21 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -71,11 +71,17 @@ static std::tuple fetchOrSubstituteTree( return {std::move(tree), resolvedRef, lockedRef}; } -static void expectType(EvalState & state, ValueType type, - Value & value, const Pos & pos) +static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) { if (value.type == tThunk && value.isTrivial()) state.forceValue(value, pos); +} + + +static void expectType(EvalState & state, ValueType type, + Value & value, const Pos & pos) +{ + forceTrivialValue(state, value, pos); if (value.type != type) throw Error("expected %s but got %s at %s", showType(type), showType(value.type), pos); @@ -114,7 +120,6 @@ static FlakeInput parseFlakeInput(EvalState & state, expectType(state, tString, *attr.value, *attr.pos); input.follows = parseInputPath(attr.value->string.s); } else { - state.forceValue(*attr.value); if (attr.value->type == tString) attrs.emplace(attr.name, attr.value->string.s); else @@ -223,10 +228,41 @@ static Flake getFlake( } else throw Error("flake '%s' lacks attribute 'outputs'", lockedRef); + auto sNixConfig = state.symbols.create("nixConfig"); + + if (auto nixConfig = vInfo.attrs->get(sNixConfig)) { + expectType(state, tAttrs, *nixConfig->value, *nixConfig->pos); + + for (auto & option : *nixConfig->value->attrs) { + forceTrivialValue(state, *option.value, *option.pos); + if (option.value->type == tString) + flake.config.options.insert({option.name, state.forceStringNoCtx(*option.value, *option.pos)}); + else if (option.value->type == tInt) + flake.config.options.insert({option.name, state.forceInt(*option.value, *option.pos)}); + else if (option.value->type == tBool) + flake.config.options.insert({option.name, state.forceBool(*option.value, *option.pos)}); + else if (option.value->isList()) { + std::vector ss; + for (unsigned int n = 0; n < option.value->listSize(); ++n) { + auto elem = option.value->listElems()[n]; + if (elem->type != tString) + throw TypeError("list element in flake configuration option '%s' is %s while a string is expected", + option.name, showType(*option.value)); + ss.push_back(state.forceStringNoCtx(*elem, *option.pos)); + } + flake.config.options.insert({option.name, ss}); + } + else + throw TypeError("flake configuration option '%s' is %s", + option.name, showType(*option.value)); + } + } + for (auto & attr : *vInfo.attrs) { if (attr.name != state.sDescription && attr.name != sInputs && - attr.name != sOutputs) + attr.name != sOutputs && + attr.name != sNixConfig) throw Error("flake '%s' has an unsupported attribute '%s', at %s", lockedRef, attr.name, *attr.pos); } @@ -599,4 +635,30 @@ Fingerprint LockedFlake::getFingerprint() const Flake::~Flake() { } +void ConfigFile::apply() +{ + for (auto & [name, value] : options) { + // FIXME: support 'trusted-public-keys' (and other options), but make it TOFU. + if (name != "bash-prompt-suffix" && + name != "bash-prompt" && + name != "substituters" && + name != "extra-substituters") + { + warn("ignoring untrusted flake configuration option '%s'", name); + continue; + } + // FIXME: Move into libutil/config.cc. + if (auto s = std::get_if(&value)) + globalConfig.set(name, *s); + else if (auto n = std::get_if(&value)) + globalConfig.set(name, fmt("%d", n)); + else if (auto b = std::get_if>(&value)) + globalConfig.set(name, b->t ? "true" : "false"); + else if (auto ss = std::get_if>(&value)) + globalConfig.set(name, concatStringsSep(" ", *ss)); // FIXME: evil + else + assert(false); + } +} + } diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index cf62c7741..7eebd9044 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -47,8 +47,16 @@ struct FlakeInput FlakeInputs overrides; }; -// The Flake structure is the main internal representation of a flake.nix file. +struct ConfigFile +{ + using ConfigValue = std::variant, std::vector>; + std::map options; + + void apply(); +}; + +/* The contents of a flake.nix file. */ struct Flake { FlakeRef originalRef; // the original flake specification (by the user) @@ -57,6 +65,7 @@ struct Flake std::optional description; std::shared_ptr sourceInfo; FlakeInputs inputs; + ConfigFile config; // 'nixConfig' attribute ~Flake(); }; diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 7473c9758..fb264491a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -533,8 +533,11 @@ InstallableFlake::getCursors(EvalState & state) std::shared_ptr InstallableFlake::getLockedFlake() const { - if (!_lockedFlake) + if (!_lockedFlake) { _lockedFlake = std::make_shared(lockFlake(*state, flakeRef, lockFlags)); + _lockedFlake->flake.config.apply(); + // FIXME: send new config to the daemon. + } return _lockedFlake; }