forked from lix-project/lix
d4dcffd643
In this mode, the following restrictions apply: * The builtins currentTime, currentSystem and storePath throw an error. * $NIX_PATH and -I are ignored. * fetchGit and fetchMercurial require a revision hash. * fetchurl and fetchTarball require a sha256 attribute. * No file system access is allowed outside of the paths returned by fetch{Git,Mercurial,url,Tarball}. Thus 'nix build -f ./foo.nix' is not allowed. Thus, the evaluation result is completely reproducible from the command line arguments. E.g. nix build --pure-eval '( let nix = fetchGit { url = https://github.com/NixOS/nixpkgs.git; rev = "9c927de4b179a6dd210dd88d34bda8af4b575680"; }; nixpkgs = fetchGit { url = https://github.com/NixOS/nixpkgs.git; ref = "release-17.09"; rev = "66b4de79e3841530e6d9c6baf98702aa1f7124e4"; }; in (import (nix + "/release.nix") { inherit nix nixpkgs; }).build.x86_64-linux )' The goal is to enable completely reproducible and traceable evaluation. For example, a NixOS configuration could be fully described by a single Git commit hash. 'nixos-rebuild' would do something like nix build --pure-eval '( (import (fetchGit { url = file:///my-nixos-config; rev = "..."; })).system ') where the Git repository /my-nixos-config would use further fetchGit calls or Git externals to fetch Nixpkgs and whatever other dependencies it has. Either way, the commit hash would uniquely identify the NixOS configuration and allow it to reproduced.
210 lines
6.6 KiB
C++
210 lines
6.6 KiB
C++
#include "primops.hh"
|
||
#include "eval-inline.hh"
|
||
#include "download.hh"
|
||
#include "store-api.hh"
|
||
#include "pathlocks.hh"
|
||
|
||
#include <sys/time.h>
|
||
|
||
#include <regex>
|
||
|
||
#include <nlohmann/json.hpp>
|
||
|
||
using namespace std::string_literals;
|
||
|
||
namespace nix {
|
||
|
||
struct HgInfo
|
||
{
|
||
Path storePath;
|
||
std::string branch;
|
||
std::string rev;
|
||
uint64_t revCount = 0;
|
||
};
|
||
|
||
std::regex commitHashRegex("^[0-9a-fA-F]{40}$");
|
||
|
||
HgInfo exportMercurial(ref<Store> store, const std::string & uri,
|
||
std::string rev, const std::string & name)
|
||
{
|
||
if (settings.pureEval && rev == "")
|
||
throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
|
||
|
||
if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) {
|
||
|
||
bool clean = runProgram("hg", true, { "status", "-R", uri, "--modified", "--added", "--removed" }) == "";
|
||
|
||
if (!clean) {
|
||
|
||
/* This is an unclean working tree. So copy all tracked
|
||
files. */
|
||
|
||
printTalkative("copying unclean Mercurial working tree '%s'", uri);
|
||
|
||
HgInfo hgInfo;
|
||
hgInfo.rev = "0000000000000000000000000000000000000000";
|
||
hgInfo.branch = chomp(runProgram("hg", true, { "branch", "-R", uri }));
|
||
|
||
auto files = tokenizeString<std::set<std::string>>(
|
||
runProgram("hg", true, { "status", "-R", uri, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
|
||
|
||
PathFilter filter = [&](const Path & p) -> bool {
|
||
assert(hasPrefix(p, uri));
|
||
std::string file(p, uri.size() + 1);
|
||
|
||
auto st = lstat(p);
|
||
|
||
if (S_ISDIR(st.st_mode)) {
|
||
auto prefix = file + "/";
|
||
auto i = files.lower_bound(prefix);
|
||
return i != files.end() && hasPrefix(*i, prefix);
|
||
}
|
||
|
||
return files.count(file);
|
||
};
|
||
|
||
hgInfo.storePath = store->addToStore("source", uri, true, htSHA256, filter);
|
||
|
||
return hgInfo;
|
||
}
|
||
}
|
||
|
||
if (rev == "") rev = "default";
|
||
|
||
Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, uri).to_string(Base32, false));
|
||
|
||
Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(htSHA512, rev).to_string(Base32, false));
|
||
|
||
/* If we haven't pulled this repo less than ‘tarball-ttl’ seconds,
|
||
do so now. */
|
||
time_t now = time(0);
|
||
struct stat st;
|
||
if (stat(stampFile.c_str(), &st) != 0 ||
|
||
st.st_mtime <= now - settings.tarballTtl)
|
||
{
|
||
/* Except that if this is a commit hash that we already have,
|
||
we don't have to pull again. */
|
||
if (!(std::regex_match(rev, commitHashRegex)
|
||
&& pathExists(cacheDir)
|
||
&& runProgram(
|
||
RunOptions("hg", { "log", "-R", cacheDir, "-r", rev, "--template", "1" })
|
||
.killStderr(true)).second == "1"))
|
||
{
|
||
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", uri));
|
||
|
||
if (pathExists(cacheDir)) {
|
||
runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri });
|
||
} else {
|
||
createDirs(dirOf(cacheDir));
|
||
runProgram("hg", true, { "clone", "--noupdate", "--", uri, cacheDir });
|
||
}
|
||
}
|
||
|
||
writeFile(stampFile, "");
|
||
}
|
||
|
||
auto tokens = tokenizeString<std::vector<std::string>>(
|
||
runProgram("hg", true, { "log", "-R", cacheDir, "-r", rev, "--template", "{node} {rev} {branch}" }));
|
||
assert(tokens.size() == 3);
|
||
|
||
HgInfo hgInfo;
|
||
hgInfo.rev = tokens[0];
|
||
hgInfo.revCount = std::stoull(tokens[1]);
|
||
hgInfo.branch = tokens[2];
|
||
|
||
std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base32, false);
|
||
Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName);
|
||
|
||
try {
|
||
auto json = nlohmann::json::parse(readFile(storeLink));
|
||
|
||
assert(json["name"] == name && json["rev"] == hgInfo.rev);
|
||
|
||
hgInfo.storePath = json["storePath"];
|
||
|
||
if (store->isValidPath(hgInfo.storePath)) {
|
||
printTalkative("using cached Mercurial store path '%s'", hgInfo.storePath);
|
||
return hgInfo;
|
||
}
|
||
|
||
} catch (SysError & e) {
|
||
if (e.errNo != ENOENT) throw;
|
||
}
|
||
|
||
Path tmpDir = createTempDir();
|
||
AutoDelete delTmpDir(tmpDir, true);
|
||
|
||
runProgram("hg", true, { "archive", "-R", cacheDir, "-r", rev, tmpDir });
|
||
|
||
deletePath(tmpDir + "/.hg_archival.txt");
|
||
|
||
hgInfo.storePath = store->addToStore(name, tmpDir);
|
||
|
||
nlohmann::json json;
|
||
json["storePath"] = hgInfo.storePath;
|
||
json["uri"] = uri;
|
||
json["name"] = name;
|
||
json["branch"] = hgInfo.branch;
|
||
json["rev"] = hgInfo.rev;
|
||
json["revCount"] = hgInfo.revCount;
|
||
|
||
writeFile(storeLink, json.dump());
|
||
|
||
return hgInfo;
|
||
}
|
||
|
||
static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||
{
|
||
std::string url;
|
||
std::string rev;
|
||
std::string name = "source";
|
||
PathSet context;
|
||
|
||
state.forceValue(*args[0]);
|
||
|
||
if (args[0]->type == tAttrs) {
|
||
|
||
state.forceAttrs(*args[0], pos);
|
||
|
||
for (auto & attr : *args[0]->attrs) {
|
||
string n(attr.name);
|
||
if (n == "url")
|
||
url = state.coerceToString(*attr.pos, *attr.value, context, false, false);
|
||
else if (n == "rev")
|
||
rev = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||
else if (n == "name")
|
||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||
else
|
||
throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos);
|
||
}
|
||
|
||
if (url.empty())
|
||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
||
|
||
} else
|
||
url = state.coerceToString(pos, *args[0], context, false, false);
|
||
|
||
if (!isUri(url)) url = absPath(url);
|
||
|
||
// FIXME: git externals probably can be used to bypass the URI
|
||
// whitelist. Ah well.
|
||
state.checkURI(url);
|
||
|
||
auto hgInfo = exportMercurial(state.store, url, rev, name);
|
||
|
||
state.mkAttrs(v, 8);
|
||
mkString(*state.allocAttr(v, state.sOutPath), hgInfo.storePath, PathSet({hgInfo.storePath}));
|
||
mkString(*state.allocAttr(v, state.symbols.create("branch")), hgInfo.branch);
|
||
mkString(*state.allocAttr(v, state.symbols.create("rev")), hgInfo.rev);
|
||
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), std::string(hgInfo.rev, 0, 12));
|
||
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), hgInfo.revCount);
|
||
v.attrs->sort();
|
||
|
||
if (state.allowedPaths)
|
||
state.allowedPaths->insert(hgInfo.storePath);
|
||
}
|
||
|
||
static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial);
|
||
|
||
}
|