aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2019-05-31 09:57:04 +0200
committerGitHub <noreply@github.com>2019-05-31 09:57:04 +0200
commit65e88694c27a2b1de583b9e6584287fcba0b75b4 (patch)
treedcac4ef693b8eeee60b130bceac8452946c3ef86
parent6636808e9000241face21141a43de1ab6c9aa494 (diff)
parent49436bdbb77f32ffec2035e836add04f98be49e3 (diff)
Merge pull request #2903 from NixOS/check-flake
Add "nix flake check"
-rw-r--r--flake.nix13
-rw-r--r--shell.nix2
-rw-r--r--src/libexpr/eval.hh9
-rw-r--r--src/libexpr/primops/flake.cc102
-rw-r--r--src/libexpr/primops/flake.hh12
-rw-r--r--src/nix/command.hh5
-rw-r--r--src/nix/flake.cc146
-rw-r--r--src/nix/installables.cc26
8 files changed, 248 insertions, 67 deletions
diff --git a/flake.nix b/flake.nix
index ab316c7c6..20ad2bc7e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,13 +14,22 @@
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;
devShell = import ./shell.nix {
nixpkgs = deps.nixpkgs;
};
-
};
}
diff --git a/shell.nix b/shell.nix
index 228a6685b..d7e63bad3 100644
--- a/shell.nix
+++ b/shell.nix
@@ -2,7 +2,7 @@
, 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; };
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 1e45bc1a8..46c6ea271 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -17,7 +17,10 @@ namespace nix {
class Store;
class EvalState;
enum RepairFlag : bool;
+
+namespace flake {
struct FlakeRegistry;
+}
typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
@@ -323,12 +326,12 @@ private:
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:
- std::shared_ptr<FlakeRegistry> _globalFlakeRegistry;
+ std::shared_ptr<flake::FlakeRegistry> _globalFlakeRegistry;
std::once_flag _globalFlakeRegistryInit;
};
diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc
index 257b81887..0d762f2a1 100644
--- a/src/libexpr/primops/flake.cc
+++ b/src/libexpr/primops/flake.cc
@@ -14,6 +14,10 @@
namespace nix {
+using namespace flake;
+
+namespace flake {
+
/* Read a registry. */
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
}
-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()
{
return getHome() + "/.config/nix/registry.json";
@@ -170,17 +156,6 @@ std::shared_ptr<FlakeRegistry> getFlagRegistry(RegistryOverrides registryOverrid
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,
std::vector<FlakeRef> pastSearches = {});
@@ -327,7 +302,9 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool impureIsAllowe
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);
if (flake.epoch > 2019)
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))
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);
for (unsigned int n = 0; n < (**requires).value->listSize(); ++n)
flake.requires.push_back(FlakeRef(state.forceStringNoCtx(
*(**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);
for (Attr attr : *(*(**nonFlakeRequires).value).attrs) {
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);
flake.vProvides = (**provides).value;
} else
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;
}
@@ -572,18 +566,11 @@ void callFlake(EvalState & state, const ResolvedFlake & resFlake, Value & v)
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.
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos),
- evalSettings.pureEval ? AllPure : UseUpdatedLockFile, v);
+ callFlake(state, resolveFlake(state, state.forceStringNoCtx(*args[0], pos),
+ evalSettings.pureEval ? AllPure : UseUpdatedLockFile), v);
}
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;
+}
+
+}
diff --git a/src/libexpr/primops/flake.hh b/src/libexpr/primops/flake.hh
index 0e2706e32..340b97c65 100644
--- a/src/libexpr/primops/flake.hh
+++ b/src/libexpr/primops/flake.hh
@@ -5,13 +5,15 @@
namespace nix {
+struct Value;
+class EvalState;
+
+namespace flake {
+
static const size_t FLAG_REGISTRY = 0;
static const size_t USER_REGISTRY = 1;
static const size_t GLOBAL_REGISTRY = 2;
-struct Value;
-class EvalState;
-
struct FlakeRegistry
{
std::map<FlakeRef, FlakeRef> entries;
@@ -73,8 +75,6 @@ enum HandleLockFile : unsigned int
, UseNewLockFile // `RecreateLockFile` without writing to file
};
-void makeFlakeValue(EvalState &, const FlakeRef &, HandleLockFile, Value &);
-
std::shared_ptr<FlakeRegistry> readRegistry(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);
}
+
+}
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'.