diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2019-05-31 09:57:04 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-31 09:57:04 +0200 |
commit | 65e88694c27a2b1de583b9e6584287fcba0b75b4 (patch) | |
tree | dcac4ef693b8eeee60b130bceac8452946c3ef86 /src/nix | |
parent | 6636808e9000241face21141a43de1ab6c9aa494 (diff) | |
parent | 49436bdbb77f32ffec2035e836add04f98be49e3 (diff) |
Merge pull request #2903 from NixOS/check-flake
Add "nix flake check"
Diffstat (limited to 'src/nix')
-rw-r--r-- | src/nix/command.hh | 5 | ||||
-rw-r--r-- | src/nix/flake.cc | 146 | ||||
-rw-r--r-- | src/nix/installables.cc | 26 |
3 files changed, 163 insertions, 14 deletions
diff --git a/src/nix/command.hh b/src/nix/command.hh index a841b879a..26c308331 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -12,7 +12,10 @@ struct Value; class Bindings; class EvalState; class Store; + +namespace flake { enum HandleLockFile : unsigned int; +} /* A command that require a Nix store. */ struct StoreCommand : virtual Command @@ -71,7 +74,7 @@ struct MixFlakeOptions : virtual Args MixFlakeOptions(); - HandleLockFile getLockFileMode(); + flake::HandleLockFile getLockFileMode(); }; struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 7836f0cfe..872ec2849 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -3,13 +3,17 @@ #include "shared.hh" #include "progress-bar.hh" #include "eval.hh" +#include "eval-inline.hh" #include "primops/flake.hh" +#include "get-drvs.hh" +#include "store-api.hh" #include <nlohmann/json.hpp> #include <queue> #include <iomanip> using namespace nix; +using namespace nix::flake; class FlakeCommand : virtual Args, public EvalCommand, public MixFlakeOptions { @@ -33,12 +37,12 @@ public: Flake getFlake() { auto evalState = getEvalState(); - return nix::getFlake(*evalState, getFlakeRef(), useRegistries); + return flake::getFlake(*evalState, getFlakeRef(), useRegistries); } ResolvedFlake resolveFlake() { - return nix::resolveFlake(*getEvalState(), getFlakeRef(), getLockFileMode()); + return flake::resolveFlake(*getEvalState(), getFlakeRef(), getLockFileMode()); } }; @@ -195,6 +199,19 @@ struct CmdFlakeUpdate : FlakeCommand } }; +static void enumerateProvides(EvalState & state, Value & vFlake, + std::function<void(const std::string & name, Value & vProvide)> callback) +{ + state.forceAttrs(vFlake); + + auto vProvides = (*vFlake.attrs->get(state.symbols.create("provides")))->value; + + state.forceAttrs(*vProvides); + + for (auto & attr : *vProvides->attrs) + callback(attr.name, *attr.value); +} + struct CmdFlakeInfo : FlakeCommand, MixJSON { std::string name() override @@ -207,19 +224,133 @@ struct CmdFlakeInfo : FlakeCommand, MixJSON return "list info about a given flake"; } - CmdFlakeInfo () { } - void run(nix::ref<nix::Store> store) override { auto flake = getFlake(); stopProgressBar(); - if (json) - std::cout << flakeToJson(flake).dump() << std::endl; - else + + if (json) { + auto json = flakeToJson(flake); + + auto state = getEvalState(); + + auto vFlake = state->allocValue(); + flake::callFlake(*state, flake, *vFlake); + + auto provides = nlohmann::json::object(); + + enumerateProvides(*state, + *vFlake, + [&](const std::string & name, Value & vProvide) { + auto provide = nlohmann::json::object(); + + if (name == "checks" || name == "packages") { + state->forceAttrs(vProvide); + for (auto & aCheck : *vProvide.attrs) + provide[aCheck.name] = nlohmann::json::object(); + } + + provides[name] = provide; + }); + + json["provides"] = std::move(provides); + + std::cout << json.dump() << std::endl; + } else printFlakeInfo(flake); } }; +struct CmdFlakeCheck : FlakeCommand, MixJSON +{ + bool build = true; + + CmdFlakeCheck() + { + mkFlag() + .longName("no-build") + .description("do not build checks") + .set(&build, false); + } + + std::string name() override + { + return "check"; + } + + std::string description() override + { + return "check whether the flake evaluates and run its tests"; + } + + void run(nix::ref<nix::Store> store) override + { + settings.readOnlyMode = !build; + + auto state = getEvalState(); + auto flake = resolveFlake(); + + auto checkDerivation = [&](const std::string & attrPath, Value & v) { + try { + auto drvInfo = getDerivation(*state, v, false); + if (!drvInfo) + throw Error("flake attribute '%s' is not a derivation", attrPath); + // FIXME: check meta attributes + return drvInfo->queryDrvPath(); + } catch (Error & e) { + e.addPrefix(fmt("while checking flake attribute '" ANSI_BOLD "%s" ANSI_NORMAL "':\n", attrPath)); + throw; + } + }; + + PathSet drvPaths; + + { + Activity act(*logger, lvlInfo, actUnknown, "evaluating flake"); + + auto vFlake = state->allocValue(); + flake::callFlake(*state, flake, *vFlake); + + enumerateProvides(*state, + *vFlake, + [&](const std::string & name, Value & vProvide) { + Activity act(*logger, lvlChatty, actUnknown, + fmt("checking flake output '%s'", name)); + + try { + state->forceValue(vProvide); + + if (name == "checks") { + state->forceAttrs(vProvide); + for (auto & aCheck : *vProvide.attrs) + drvPaths.insert(checkDerivation( + name + "." + (std::string) aCheck.name, *aCheck.value)); + } + + else if (name == "packages") { + state->forceAttrs(vProvide); + for (auto & aCheck : *vProvide.attrs) + checkDerivation( + name + "." + (std::string) aCheck.name, *aCheck.value); + } + + else if (name == "defaultPackage" || name == "devShell") + checkDerivation(name, vProvide); + + } catch (Error & e) { + e.addPrefix(fmt("while checking flake output '" ANSI_BOLD "%s" ANSI_NORMAL "':\n", name)); + throw; + } + }); + } + + if (build) { + Activity act(*logger, lvlInfo, actUnknown, "running flake checks"); + store->buildPaths(drvPaths); + } + } +}; + struct CmdFlakeAdd : MixEvalArgs, Command { FlakeUri alias; @@ -386,6 +517,7 @@ struct CmdFlake : virtual MultiCommand, virtual Command : MultiCommand({make_ref<CmdFlakeList>() , make_ref<CmdFlakeUpdate>() , make_ref<CmdFlakeInfo>() + , make_ref<CmdFlakeCheck>() , make_ref<CmdFlakeDeps>() , make_ref<CmdFlakeAdd>() , make_ref<CmdFlakeRemove>() diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 4f9161666..ea12cd79c 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -32,8 +32,9 @@ MixFlakeOptions::MixFlakeOptions() .set(&useRegistries, false); } -HandleLockFile MixFlakeOptions::getLockFileMode() +flake::HandleLockFile MixFlakeOptions::getLockFileMode() { + using namespace flake; return useRegistries ? recreateLockFile @@ -163,18 +164,20 @@ struct InstallableAttrPath : InstallableValue } }; -void makeFlakeClosureGCRoot(Store & store, const FlakeRef & origFlakeRef, const ResolvedFlake & resFlake) +void makeFlakeClosureGCRoot(Store & store, + const FlakeRef & origFlakeRef, + const flake::ResolvedFlake & resFlake) { if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return; /* Get the store paths of all non-local flakes. */ PathSet closure; - std::queue<std::reference_wrapper<const ResolvedFlake>> queue; + std::queue<std::reference_wrapper<const flake::ResolvedFlake>> queue; queue.push(resFlake); while (!queue.empty()) { - const ResolvedFlake & flake = queue.front(); + const flake::ResolvedFlake & flake = queue.front(); queue.pop(); if (!std::get_if<FlakeRef::IsPath>(&flake.flake.sourceInfo.resolvedRef.data)) closure.insert(flake.flake.sourceInfo.storePath); @@ -233,9 +236,9 @@ struct InstallableFlake : InstallableValue auto emptyArgs = state.allocBindings(0); - // As a convenience, look for the attribute in - // 'provides.packages'. if (searchPackages) { + // As a convenience, look for the attribute in + // 'provides.packages'. if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) { try { auto * v = findAlongAttrPath(state, *attrPaths.begin(), *emptyArgs, *aPackages->value); @@ -244,6 +247,17 @@ struct InstallableFlake : InstallableValue } catch (AttrPathNotFound & e) { } } + + // As a temporary hack until Nixpkgs is properly converted + // to provide a clean 'packages' set, look in 'legacyPackages'. + if (auto aPackages = *vProvides->attrs->get(state.symbols.create("legacyPackages"))) { + try { + auto * v = findAlongAttrPath(state, *attrPaths.begin(), *emptyArgs, *aPackages->value); + state.forceValue(*v); + return v; + } catch (AttrPathNotFound & e) { + } + } } // Otherwise, look for it in 'provides'. |