Initial flake support
This commit is contained in:
parent
f216c76c56
commit
7a5cf31060
9 changed files with 282 additions and 47 deletions
3
corepkgs/default-installation-source.nix
Normal file
3
corepkgs/default-installation-source.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
builtins.mapAttrs (flakeName: flakeInfo:
|
||||
(getFlake flakeInfo.uri).${flakeName}.provides.packages or {})
|
||||
builtins.flakeRegistry
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
23
src/libexpr/primops/fetchGit.hh
Normal file
23
src/libexpr/primops/fetchGit.hh
Normal 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;
|
||||
|
||||
}
|
161
src/libexpr/primops/flake.cc
Normal file
161
src/libexpr/primops/flake.cc
Normal 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
65
src/nix/flake.cc
Normal 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>());
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue