Merge pull request #2903 from NixOS/check-flake

Add "nix flake check"
This commit is contained in:
Eelco Dolstra 2019-05-31 09:57:04 +02:00 committed by GitHub
commit 65e88694c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 248 additions and 67 deletions

View file

@ -14,13 +14,22 @@
nixpkgs = deps.nixpkgs; nixpkgs = deps.nixpkgs;
}; };
packages.nix = hydraJobs.build.x86_64-linux; checks = {
binaryTarball = hydraJobs.binaryTarball.x86_64-linux;
perlBindings = hydraJobs.perlBindings.x86_64-linux;
inherit (hydraJobs.tests) remoteBuilds nix-copy-closure;
setuid = hydraJobs.tests.setuid.x86_64-linux;
};
packages = {
nix = hydraJobs.build.x86_64-linux;
nix-perl-bindings = hydraJobs.perlBindings.x86_64-linux;
};
defaultPackage = packages.nix; defaultPackage = packages.nix;
devShell = import ./shell.nix { devShell = import ./shell.nix {
nixpkgs = deps.nixpkgs; nixpkgs = deps.nixpkgs;
}; };
}; };
} }

View file

@ -2,7 +2,7 @@
, nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz , nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz
}: }:
with import (builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz) { system = builtins.currentSystem or "x86_64-linux"; }; with import nixpkgs { system = builtins.currentSystem or "x86_64-linux"; };
with import ./release-common.nix { inherit pkgs; }; with import ./release-common.nix { inherit pkgs; };

View file

@ -17,7 +17,10 @@ namespace nix {
class Store; class Store;
class EvalState; class EvalState;
enum RepairFlag : bool; enum RepairFlag : bool;
namespace flake {
struct FlakeRegistry; struct FlakeRegistry;
}
typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v); typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
@ -323,12 +326,12 @@ private:
public: public:
const std::vector<std::shared_ptr<FlakeRegistry>> getFlakeRegistries(); const std::vector<std::shared_ptr<flake::FlakeRegistry>> getFlakeRegistries();
std::shared_ptr<FlakeRegistry> getGlobalFlakeRegistry(); std::shared_ptr<flake::FlakeRegistry> getGlobalFlakeRegistry();
private: private:
std::shared_ptr<FlakeRegistry> _globalFlakeRegistry; std::shared_ptr<flake::FlakeRegistry> _globalFlakeRegistry;
std::once_flag _globalFlakeRegistryInit; std::once_flag _globalFlakeRegistryInit;
}; };

View file

@ -14,6 +14,10 @@
namespace nix { namespace nix {
using namespace flake;
namespace flake {
/* Read a registry. */ /* Read a registry. */
std::shared_ptr<FlakeRegistry> readRegistry(const Path & path) std::shared_ptr<FlakeRegistry> readRegistry(const Path & path)
{ {
@ -133,24 +137,6 @@ void writeLockFile(const LockFile & lockFile, const Path & path)
writeFile(path, json.dump(4) + "\n"); // '4' = indentation in json file writeFile(path, json.dump(4) + "\n"); // '4' = indentation in json file
} }
std::shared_ptr<FlakeRegistry> EvalState::getGlobalFlakeRegistry()
{
std::call_once(_globalFlakeRegistryInit, [&]() {
auto path = evalSettings.flakeRegistry;
if (!hasPrefix(path, "/")) {
CachedDownloadRequest request(evalSettings.flakeRegistry);
request.name = "flake-registry.json";
request.gcRoot = true;
path = getDownloader()->downloadCached(store, request).path;
}
_globalFlakeRegistry = readRegistry(path);
});
return _globalFlakeRegistry;
}
Path getUserRegistryPath() Path getUserRegistryPath()
{ {
return getHome() + "/.config/nix/registry.json"; return getHome() + "/.config/nix/registry.json";
@ -170,17 +156,6 @@ std::shared_ptr<FlakeRegistry> getFlagRegistry(RegistryOverrides registryOverrid
return flagRegistry; return flagRegistry;
} }
// This always returns a vector with flakeReg, userReg, globalReg.
// If one of them doesn't exist, the registry is left empty but does exist.
const Registries EvalState::getFlakeRegistries()
{
Registries registries;
registries.push_back(getFlagRegistry(registryOverrides));
registries.push_back(getUserRegistry());
registries.push_back(getGlobalFlakeRegistry());
return registries;
}
static FlakeRef lookupFlake(EvalState & state, const FlakeRef & flakeRef, const Registries & registries, static FlakeRef lookupFlake(EvalState & state, const FlakeRef & flakeRef, const Registries & registries,
std::vector<FlakeRef> pastSearches = {}); std::vector<FlakeRef> pastSearches = {});
@ -327,7 +302,9 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool impureIsAllowe
state.forceAttrs(vInfo); state.forceAttrs(vInfo);
if (auto epoch = vInfo.attrs->get(state.symbols.create("epoch"))) { auto sEpoch = state.symbols.create("epoch");
if (auto epoch = vInfo.attrs->get(sEpoch)) {
flake.epoch = state.forceInt(*(**epoch).value, *(**epoch).pos); flake.epoch = state.forceInt(*(**epoch).value, *(**epoch).pos);
if (flake.epoch > 2019) if (flake.epoch > 2019)
throw Error("flake '%s' requires unsupported epoch %d; please upgrade Nix", flakeRef, flake.epoch); throw Error("flake '%s' requires unsupported epoch %d; please upgrade Nix", flakeRef, flake.epoch);
@ -342,14 +319,18 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool impureIsAllowe
if (auto description = vInfo.attrs->get(state.sDescription)) if (auto description = vInfo.attrs->get(state.sDescription))
flake.description = state.forceStringNoCtx(*(**description).value, *(**description).pos); flake.description = state.forceStringNoCtx(*(**description).value, *(**description).pos);
if (auto requires = vInfo.attrs->get(state.symbols.create("requires"))) { auto sRequires = state.symbols.create("requires");
if (auto requires = vInfo.attrs->get(sRequires)) {
state.forceList(*(**requires).value, *(**requires).pos); state.forceList(*(**requires).value, *(**requires).pos);
for (unsigned int n = 0; n < (**requires).value->listSize(); ++n) for (unsigned int n = 0; n < (**requires).value->listSize(); ++n)
flake.requires.push_back(FlakeRef(state.forceStringNoCtx( flake.requires.push_back(FlakeRef(state.forceStringNoCtx(
*(**requires).value->listElems()[n], *(**requires).pos))); *(**requires).value->listElems()[n], *(**requires).pos)));
} }
if (std::optional<Attr *> nonFlakeRequires = vInfo.attrs->get(state.symbols.create("nonFlakeRequires"))) { auto sNonFlakeRequires = state.symbols.create("nonFlakeRequires");
if (std::optional<Attr *> nonFlakeRequires = vInfo.attrs->get(sNonFlakeRequires)) {
state.forceAttrs(*(**nonFlakeRequires).value, *(**nonFlakeRequires).pos); state.forceAttrs(*(**nonFlakeRequires).value, *(**nonFlakeRequires).pos);
for (Attr attr : *(*(**nonFlakeRequires).value).attrs) { for (Attr attr : *(*(**nonFlakeRequires).value).attrs) {
std::string myNonFlakeUri = state.forceStringNoCtx(*attr.value, *attr.pos); std::string myNonFlakeUri = state.forceStringNoCtx(*attr.value, *attr.pos);
@ -358,12 +339,25 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool impureIsAllowe
} }
} }
if (auto provides = vInfo.attrs->get(state.symbols.create("provides"))) { auto sProvides = state.symbols.create("provides");
if (auto provides = vInfo.attrs->get(sProvides)) {
state.forceFunction(*(**provides).value, *(**provides).pos); state.forceFunction(*(**provides).value, *(**provides).pos);
flake.vProvides = (**provides).value; flake.vProvides = (**provides).value;
} else } else
throw Error("flake '%s' lacks attribute 'provides'", flakeRef); throw Error("flake '%s' lacks attribute 'provides'", flakeRef);
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEpoch &&
attr.name != state.sName &&
attr.name != state.sDescription &&
attr.name != sRequires &&
attr.name != sNonFlakeRequires &&
attr.name != sProvides)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
flakeRef, attr.name, *attr.pos);
}
return flake; return flake;
} }
@ -572,18 +566,11 @@ void callFlake(EvalState & state, const ResolvedFlake & resFlake, Value & v)
v.attrs->sort(); v.attrs->sort();
} }
// Return the `provides` of the top flake, while assigning to `v` the provides
// of the dependencies as well.
void makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, HandleLockFile handle, Value & v)
{
callFlake(state, resolveFlake(state, flakeRef, handle), v);
}
// This function is exposed to be used in nix files. // This function is exposed to be used in nix files.
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos), callFlake(state, resolveFlake(state, state.forceStringNoCtx(*args[0], pos),
evalSettings.pureEval ? AllPure : UseUpdatedLockFile, v); evalSettings.pureEval ? AllPure : UseUpdatedLockFile), v);
} }
static RegisterPrimOp r2("getFlake", 1, prim_getFlake); static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
@ -618,3 +605,34 @@ void gitCloneFlake(FlakeRef flakeRef, EvalState & state, Registries registries,
} }
} }
std::shared_ptr<flake::FlakeRegistry> EvalState::getGlobalFlakeRegistry()
{
std::call_once(_globalFlakeRegistryInit, [&]() {
auto path = evalSettings.flakeRegistry;
if (!hasPrefix(path, "/")) {
CachedDownloadRequest request(evalSettings.flakeRegistry);
request.name = "flake-registry.json";
request.gcRoot = true;
path = getDownloader()->downloadCached(store, request).path;
}
_globalFlakeRegistry = readRegistry(path);
});
return _globalFlakeRegistry;
}
// This always returns a vector with flakeReg, userReg, globalReg.
// If one of them doesn't exist, the registry is left empty but does exist.
const Registries EvalState::getFlakeRegistries()
{
Registries registries;
registries.push_back(getFlagRegistry(registryOverrides));
registries.push_back(getUserRegistry());
registries.push_back(getGlobalFlakeRegistry());
return registries;
}
}

View file

@ -5,13 +5,15 @@
namespace nix { namespace nix {
struct Value;
class EvalState;
namespace flake {
static const size_t FLAG_REGISTRY = 0; static const size_t FLAG_REGISTRY = 0;
static const size_t USER_REGISTRY = 1; static const size_t USER_REGISTRY = 1;
static const size_t GLOBAL_REGISTRY = 2; static const size_t GLOBAL_REGISTRY = 2;
struct Value;
class EvalState;
struct FlakeRegistry struct FlakeRegistry
{ {
std::map<FlakeRef, FlakeRef> entries; std::map<FlakeRef, FlakeRef> entries;
@ -73,8 +75,6 @@ enum HandleLockFile : unsigned int
, UseNewLockFile // `RecreateLockFile` without writing to file , UseNewLockFile // `RecreateLockFile` without writing to file
}; };
void makeFlakeValue(EvalState &, const FlakeRef &, HandleLockFile, Value &);
std::shared_ptr<FlakeRegistry> readRegistry(const Path &); std::shared_ptr<FlakeRegistry> readRegistry(const Path &);
void writeRegistry(const FlakeRegistry &, const Path &); void writeRegistry(const FlakeRegistry &, const Path &);
@ -143,3 +143,5 @@ void updateLockFile(EvalState &, const FlakeRef & flakeRef, bool recreateLockFil
void gitCloneFlake(FlakeRef flakeRef, EvalState &, Registries, const Path & destDir); void gitCloneFlake(FlakeRef flakeRef, EvalState &, Registries, const Path & destDir);
} }
}

View file

@ -12,7 +12,10 @@ struct Value;
class Bindings; class Bindings;
class EvalState; class EvalState;
class Store; class Store;
namespace flake {
enum HandleLockFile : unsigned int; enum HandleLockFile : unsigned int;
}
/* A command that require a Nix store. */ /* A command that require a Nix store. */
struct StoreCommand : virtual Command struct StoreCommand : virtual Command
@ -71,7 +74,7 @@ struct MixFlakeOptions : virtual Args
MixFlakeOptions(); MixFlakeOptions();
HandleLockFile getLockFileMode(); flake::HandleLockFile getLockFileMode();
}; };
struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions

View file

@ -3,13 +3,17 @@
#include "shared.hh" #include "shared.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-inline.hh"
#include "primops/flake.hh" #include "primops/flake.hh"
#include "get-drvs.hh"
#include "store-api.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <queue> #include <queue>
#include <iomanip> #include <iomanip>
using namespace nix; using namespace nix;
using namespace nix::flake;
class FlakeCommand : virtual Args, public EvalCommand, public MixFlakeOptions class FlakeCommand : virtual Args, public EvalCommand, public MixFlakeOptions
{ {
@ -33,12 +37,12 @@ public:
Flake getFlake() Flake getFlake()
{ {
auto evalState = getEvalState(); auto evalState = getEvalState();
return nix::getFlake(*evalState, getFlakeRef(), useRegistries); return flake::getFlake(*evalState, getFlakeRef(), useRegistries);
} }
ResolvedFlake resolveFlake() 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 struct CmdFlakeInfo : FlakeCommand, MixJSON
{ {
std::string name() override std::string name() override
@ -207,19 +224,133 @@ struct CmdFlakeInfo : FlakeCommand, MixJSON
return "list info about a given flake"; return "list info about a given flake";
} }
CmdFlakeInfo () { }
void run(nix::ref<nix::Store> store) override void run(nix::ref<nix::Store> store) override
{ {
auto flake = getFlake(); auto flake = getFlake();
stopProgressBar(); stopProgressBar();
if (json)
std::cout << flakeToJson(flake).dump() << std::endl; if (json) {
else 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); 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 struct CmdFlakeAdd : MixEvalArgs, Command
{ {
FlakeUri alias; FlakeUri alias;
@ -386,6 +517,7 @@ struct CmdFlake : virtual MultiCommand, virtual Command
: MultiCommand({make_ref<CmdFlakeList>() : MultiCommand({make_ref<CmdFlakeList>()
, make_ref<CmdFlakeUpdate>() , make_ref<CmdFlakeUpdate>()
, make_ref<CmdFlakeInfo>() , make_ref<CmdFlakeInfo>()
, make_ref<CmdFlakeCheck>()
, make_ref<CmdFlakeDeps>() , make_ref<CmdFlakeDeps>()
, make_ref<CmdFlakeAdd>() , make_ref<CmdFlakeAdd>()
, make_ref<CmdFlakeRemove>() , make_ref<CmdFlakeRemove>()

View file

@ -32,8 +32,9 @@ MixFlakeOptions::MixFlakeOptions()
.set(&useRegistries, false); .set(&useRegistries, false);
} }
HandleLockFile MixFlakeOptions::getLockFileMode() flake::HandleLockFile MixFlakeOptions::getLockFileMode()
{ {
using namespace flake;
return return
useRegistries useRegistries
? recreateLockFile ? 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; if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return;
/* Get the store paths of all non-local flakes. */ /* Get the store paths of all non-local flakes. */
PathSet closure; PathSet closure;
std::queue<std::reference_wrapper<const ResolvedFlake>> queue; std::queue<std::reference_wrapper<const flake::ResolvedFlake>> queue;
queue.push(resFlake); queue.push(resFlake);
while (!queue.empty()) { while (!queue.empty()) {
const ResolvedFlake & flake = queue.front(); const flake::ResolvedFlake & flake = queue.front();
queue.pop(); queue.pop();
if (!std::get_if<FlakeRef::IsPath>(&flake.flake.sourceInfo.resolvedRef.data)) if (!std::get_if<FlakeRef::IsPath>(&flake.flake.sourceInfo.resolvedRef.data))
closure.insert(flake.flake.sourceInfo.storePath); closure.insert(flake.flake.sourceInfo.storePath);
@ -233,9 +236,9 @@ struct InstallableFlake : InstallableValue
auto emptyArgs = state.allocBindings(0); auto emptyArgs = state.allocBindings(0);
// As a convenience, look for the attribute in
// 'provides.packages'.
if (searchPackages) { if (searchPackages) {
// As a convenience, look for the attribute in
// 'provides.packages'.
if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) { if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) {
try { try {
auto * v = findAlongAttrPath(state, *attrPaths.begin(), *emptyArgs, *aPackages->value); auto * v = findAlongAttrPath(state, *attrPaths.begin(), *emptyArgs, *aPackages->value);
@ -244,6 +247,17 @@ struct InstallableFlake : InstallableValue
} catch (AttrPathNotFound & e) { } 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'. // Otherwise, look for it in 'provides'.