Initial flake support

This commit is contained in:
Eelco Dolstra 2018-11-29 19:18:36 +01:00
parent f216c76c56
commit 7a5cf31060
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
9 changed files with 282 additions and 47 deletions

View file

@ -0,0 +1,3 @@
builtins.mapAttrs (flakeName: flakeInfo:
(getFlake flakeInfo.uri).${flakeName}.provides.packages or {})
builtins.flakeRegistry

View file

@ -1,4 +1,10 @@
corepkgs_FILES = buildenv.nix unpack-channel.nix derivation.nix fetchurl.nix imported-drv-to-derivation.nix
corepkgs_FILES = \
buildenv.nix \
unpack-channel.nix \
derivation.nix \
fetchurl.nix \
imported-drv-to-derivation.nix \
default-installation-source.nix
$(foreach file,config.nix $(corepkgs_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/corepkgs)))

View file

@ -290,6 +290,7 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
, sOutputHash(symbols.create("outputHash"))
, sOutputHashAlgo(symbols.create("outputHashAlgo"))
, sOutputHashMode(symbols.create("outputHashMode"))
, sDescription(symbols.create("description"))
, repair(NoRepair)
, store(store)
, baseEnv(allocEnv(128))

View file

@ -72,7 +72,8 @@ public:
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
sFile, sLine, sColumn, sFunctor, sToString,
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
sOutputHash, sOutputHashAlgo, sOutputHashMode;
sOutputHash, sOutputHashAlgo, sOutputHashMode,
sDescription;
Symbol sDerivationNix;
/* If set, force copying files to the Nix store even if they
@ -311,6 +312,23 @@ private:
friend struct ExprOpConcatLists;
friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
public:
struct FlakeRegistry
{
struct Entry
{
std::string uri;
};
std::map<std::string, Entry> entries;
};
const FlakeRegistry & getFlakeRegistry();
private:
std::unique_ptr<FlakeRegistry> _flakeRegistry;
std::once_flag _flakeRegistryInit;
};

View file

@ -1,3 +1,4 @@
#include "fetchGit.hh"
#include "primops.hh"
#include "eval-inline.hh"
#include "download.hh"
@ -15,14 +16,6 @@ using namespace std::string_literals;
namespace nix {
struct GitInfo
{
Path storePath;
std::string rev;
std::string shortRev;
uint64_t revCount = 0;
};
std::regex revRegex("^[0-9a-fA-F]{40}$");
GitInfo exportGit(ref<Store> store, const std::string & uri,

View file

@ -0,0 +1,23 @@
#pragma once
#include "store-api.hh"
#include <regex>
namespace nix {
struct GitInfo
{
Path storePath;
std::string rev;
std::string shortRev;
uint64_t revCount = 0;
};
GitInfo exportGit(ref<Store> store, const std::string & uri,
std::experimental::optional<std::string> ref, std::string rev,
const std::string & name);
extern std::regex revRegex;
}

View file

@ -0,0 +1,161 @@
#include "primops.hh"
#include "eval-inline.hh"
#include "fetchGit.hh"
#include "download.hh"
#include <queue>
#include <nlohmann/json.hpp>
namespace nix {
const EvalState::FlakeRegistry & EvalState::getFlakeRegistry()
{
std::call_once(_flakeRegistryInit, [&]()
{
_flakeRegistry = std::make_unique<FlakeRegistry>();
if (!evalSettings.pureEval) {
auto registryUri = "file:///home/eelco/Dev/gists/nix-flakes/registry.json";
auto registryFile = getDownloader()->download(DownloadRequest(registryUri));
auto json = nlohmann::json::parse(*registryFile.data);
auto version = json.value("version", 0);
if (version != 1)
throw Error("flake registry '%s' has unsupported version %d", registryUri, version);
auto flakes = json["flakes"];
for (auto i = flakes.begin(); i != flakes.end(); ++i) {
FlakeRegistry::Entry entry;
entry.uri = i->value("uri", "");
if (entry.uri.empty())
throw Error("invalid flake registry entry");
_flakeRegistry->entries.emplace(i.key(), entry);
}
}
});
return *_flakeRegistry;
}
static void prim_flakeRegistry(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
auto registry = state.getFlakeRegistry();
state.mkAttrs(v, registry.entries.size());
for (auto & entry : registry.entries) {
auto vEntry = state.allocAttr(v, entry.first);
state.mkAttrs(*vEntry, 2);
mkString(*state.allocAttr(*vEntry, state.symbols.create("uri")), entry.second.uri);
vEntry->attrs->sort();
}
v.attrs->sort();
}
static RegisterPrimOp r1("__flakeRegistry", 0, prim_flakeRegistry);
struct Flake
{
std::string name;
std::string description;
Path path;
std::set<std::string> requires;
Value * vProvides; // FIXME: gc
};
static Flake fetchFlake(EvalState & state, const std::string & flakeUri)
{
Flake flake;
auto gitInfo = exportGit(state.store, flakeUri, {}, "", "source");
state.store->assertStorePath(gitInfo.storePath);
Value vInfo;
state.evalFile(gitInfo.storePath + "/flake.nix", vInfo);
state.forceAttrs(vInfo);
if (auto name = vInfo.attrs->get(state.sName))
flake.name = state.forceStringNoCtx(*(**name).value, *(**name).pos);
else
throw Error("flake lacks attribute 'name'");
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"))) {
state.forceList(*(**requires).value, *(**requires).pos);
for (unsigned int n = 0; n < (**requires).value->listSize(); ++n)
flake.requires.insert(state.forceStringNoCtx(
*(**requires).value->listElems()[n], *(**requires).pos));
}
if (auto provides = vInfo.attrs->get(state.symbols.create("provides"))) {
state.forceFunction(*(**provides).value, *(**provides).pos);
flake.vProvides = (**provides).value;
} else
throw Error("flake lacks attribute 'provides'");
return flake;
}
static std::map<std::string, Flake> resolveFlakes(EvalState & state, const StringSet & flakeUris)
{
auto registry = state.getFlakeRegistry();
std::map<std::string, Flake> done;
std::queue<std::string> todo;
for (auto & i : flakeUris) todo.push(i);
while (!todo.empty()) {
auto flakeUri = todo.front();
todo.pop();
if (done.count(flakeUri)) continue;
auto flake = fetchFlake(state, flakeUri);
for (auto & require : flake.requires) {
auto i = registry.entries.find(require);
if (i == registry.entries.end())
throw Error("unknown flake '%s'", require);
todo.push(i->second.uri);
}
done.emplace(flake.name, flake);
}
return done;
}
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
std::string flakeUri = state.forceStringNoCtx(*args[0], pos);
auto flakes = resolveFlakes(state, {flakeUri});
auto vResult = state.allocValue();
state.mkAttrs(*vResult, flakes.size());
for (auto & flake : flakes) {
auto vFlake = state.allocAttr(*vResult, flake.second.name);
state.mkAttrs(*vFlake, 2);
mkString(*state.allocAttr(*vFlake, state.sDescription), flake.second.description);
auto vProvides = state.allocAttr(*vFlake, state.symbols.create("provides"));
mkApp(*vProvides, *flake.second.vProvides, *vResult);
vFlake->attrs->sort();
}
vResult->attrs->sort();
v = *vResult;
}
static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
}

65
src/nix/flake.cc Normal file
View file

@ -0,0 +1,65 @@
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
#include "progress-bar.hh"
#include "eval.hh"
using namespace nix;
struct CmdFlakeList : StoreCommand, MixEvalArgs
{
std::string name() override
{
return "list";
}
std::string description() override
{
return "list available Nix flakes";
}
void run(nix::ref<nix::Store> store) override
{
auto evalState = std::make_shared<EvalState>(searchPath, store);
auto registry = evalState->getFlakeRegistry();
stopProgressBar();
for (auto & entry : registry.entries) {
std::cout << entry.first << " " << entry.second.uri << "\n";
}
}
};
struct CmdFlake : virtual MultiCommand, virtual Command
{
CmdFlake()
: MultiCommand({make_ref<CmdFlakeList>()})
{
}
std::string name() override
{
return "flake";
}
std::string description() override
{
return "manage Nix flakes";
}
void run() override
{
if (!command)
throw UsageError("'nix flake' requires a sub-command.");
command->run();
}
void printHelp(const string & programName, std::ostream & out) override
{
MultiCommand::printHelp(programName, out);
}
};
static RegisterCommand r1(make_ref<CmdFlake>());

View file

@ -26,47 +26,12 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
{
if (vSourceExpr) return vSourceExpr;
auto sToplevel = state.symbols.create("_toplevel");
vSourceExpr = state.allocValue();
if (file != "")
state.evalFile(lookupFileArg(state, file), *vSourceExpr);
else {
/* Construct the installation source from $NIX_PATH. */
auto searchPath = state.getSearchPath();
state.mkAttrs(*vSourceExpr, searchPath.size() + 1);
mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
std::unordered_set<std::string> seen;
for (auto & i : searchPath) {
if (i.first == "") continue;
if (seen.count(i.first)) continue;
seen.insert(i.first);
#if 0
auto res = state.resolveSearchPathElem(i);
if (!res.first) continue;
if (!pathExists(res.second)) continue;
mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)),
state.getBuiltin("import"),
mkString(*state.allocValue(), res.second));
#endif
Value * v1 = state.allocValue();
mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
Value * v2 = state.allocValue();
mkApp(*v2, *v1, mkString(*state.allocValue(), i.first));
mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)),
state.getBuiltin("import"), *v2);
}
vSourceExpr->attrs->sort();
}
else
state.evalFile(lookupFileArg(state, "<nix/default-installation-source.nix>"), *vSourceExpr);
return vSourceExpr;
}