* Store user environment manifests as a Nix expression in

$out/manifest.nix rather than as an ATerm.

  (Hm, I thought I committed this two days ago...)
This commit is contained in:
Eelco Dolstra 2010-04-21 15:08:58 +00:00
parent f3b8833a48
commit fe2d869e04
11 changed files with 205 additions and 183 deletions

View file

@ -160,4 +160,4 @@ while (scalar(keys %postponed) > 0) {
print STDERR "created $symlinks symlinks in user environment\n"; print STDERR "created $symlinks symlinks in user environment\n";
symlink($ENV{"manifest"}, "$out/manifest") or die "cannot create manifest"; symlink($ENV{"manifest"}, "$out/manifest.nix") or die "cannot create manifest";

View file

@ -98,6 +98,7 @@ EvalState::EvalState()
, sType(symbols.create("type")) , sType(symbols.create("type"))
, sMeta(symbols.create("meta")) , sMeta(symbols.create("meta"))
, sName(symbols.create("name")) , sName(symbols.create("name"))
, sSystem(symbols.create("system"))
, baseEnv(allocEnv(128)) , baseEnv(allocEnv(128))
, baseEnvDispl(0) , baseEnvDispl(0)
, staticBaseEnv(false, 0) , staticBaseEnv(false, 0)
@ -131,12 +132,13 @@ void EvalState::addPrimOp(const string & name,
unsigned int arity, PrimOp primOp) unsigned int arity, PrimOp primOp)
{ {
Value v; Value v;
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
v.type = tPrimOp; v.type = tPrimOp;
v.primOp.arity = arity; v.primOp.arity = arity;
v.primOp.fun = primOp; v.primOp.fun = primOp;
v.primOp.name = strdup(name2.c_str());
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl; staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
(*baseEnv.values[0].attrs)[symbols.create(name2)] = v; (*baseEnv.values[0].attrs)[symbols.create(name2)] = v;
} }
@ -550,7 +552,12 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v)
vArgs[n--] = arg->primOpApp.right; vArgs[n--] = arg->primOpApp.right;
/* And call the primop. */ /* And call the primop. */
try {
primOp->primOp.fun(*this, vArgs, v); primOp->primOp.fun(*this, vArgs, v);
} catch (Error & e) {
addErrorPrefix(e, "while evaluating the builtin function `%1%':\n", primOp->primOp.name);
throw;
}
} else { } else {
Value * v2 = allocValues(2); Value * v2 = allocValues(2);
v2[0] = fun; v2[0] = fun;

View file

@ -92,6 +92,7 @@ struct Value
Value * val; Value * val;
struct { struct {
PrimOp fun; PrimOp fun;
char * name;
unsigned int arity; unsigned int arity;
} primOp; } primOp;
struct { struct {
@ -138,6 +139,14 @@ static inline void mkCopy(Value & v, Value & src)
} }
static inline void mkApp(Value & v, Value & left, Value & right)
{
v.type = tApp;
v.app.left = &left;
v.app.right = &right;
}
void mkString(Value & v, const char * s); void mkString(Value & v, const char * s);
void mkString(Value & v, const string & s, const PathSet & context = PathSet()); void mkString(Value & v, const string & s, const PathSet & context = PathSet());
void mkPath(Value & v, const char * s); void mkPath(Value & v, const char * s);
@ -162,7 +171,7 @@ public:
SymbolTable symbols; SymbolTable symbols;
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName; const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sSystem;
private: private:
SrcToStore srcToStore; SrcToStore srcToStore;

View file

@ -70,27 +70,6 @@ void DrvInfo::setMetaInfo(const MetaInfo & meta)
{ {
metaInfoRead = true; metaInfoRead = true;
this->meta = meta; this->meta = meta;
#if 0
Value * metaAttrs = state.allocValues(1);
foreach (MetaInfo::const_iterator, i, meta) {
Expr e;
switch (i->second.type) {
case MetaValue::tpInt: e = makeInt(i->second.intValue); break;
case MetaValue::tpString: e = makeStr(i->second.stringValue); break;
case MetaValue::tpStrings: {
ATermList es = ATempty;
foreach (Strings::const_iterator, j, i->second.stringValues)
es = ATinsert(es, makeStr(*j));
e = makeList(ATreverse(es));
break;
}
default: abort();
}
metaAttrs.set(toATerm(i->first), makeAttrRHS(e, makeNoPos()));
}
attrs->set(toATerm("meta"), makeAttrs(metaAttrs));
#endif
} }
@ -122,7 +101,7 @@ static bool getDerivation(EvalState & state, Value & v,
if (i == v.attrs->end()) throw TypeError("derivation name missing"); if (i == v.attrs->end()) throw TypeError("derivation name missing");
drv.name = state.forceStringNoCtx(i->second); drv.name = state.forceStringNoCtx(i->second);
i = v.attrs->find(state.symbols.create("system")); i = v.attrs->find(state.sSystem);
if (i == v.attrs->end()) if (i == v.attrs->end())
drv.system = "unknown"; drv.system = "unknown";
else else

View file

@ -41,7 +41,7 @@ public:
/* !!! make this private */ /* !!! make this private */
Bindings * attrs; Bindings * attrs;
DrvInfo() : metaInfoRead(false) { }; DrvInfo() : metaInfoRead(false), attrs(0) { };
string queryDrvPath(EvalState & state) const; string queryDrvPath(EvalState & state) const;
string queryOutPath(EvalState & state) const; string queryOutPath(EvalState & state) const;

View file

@ -89,24 +89,29 @@ static void prim_genericClosure(EvalState & state, Value * * args, Value & v)
{ {
startNest(nest, lvlDebug, "finding dependencies"); startNest(nest, lvlDebug, "finding dependencies");
Expr attrs = evalExpr(state, args[0]); state.forceAttrs(*args[0]);
/* Get the start set. */ /* Get the start set. */
Expr startSet = queryAttr(attrs, "startSet"); Bindings::iterator startSet =
if (!startSet) throw EvalError("attribute `startSet' required"); args[0]->attrs->find(state.symbols.create("startSet"));
ATermList startSet2 = evalList(state, startSet); if (startSet == args[0]->attrs->end())
throw EvalError("attribute `startSet' required");
state.forceList(startSet->second);
set<Expr> workSet; // !!! gc roots list<Value> workSet;
for (ATermIterator i(startSet2); i; ++i) workSet.insert(*i); for (unsigned int n = 0; n < startSet->second.list.length; ++n)
workSet.push_back(*startSet->second.list.elems[n]);
/* Get the operator. */ /* Get the operator. */
Expr op = queryAttr(attrs, "operator"); Bindings::iterator op =
if (!op) throw EvalError("attribute `operator' required"); args[0]->attrs->find(state.symbols.create("operator"));
if (op == args[0]->attrs->end())
throw EvalError("attribute `operator' required");
/* Construct the closure by applying the operator to element of /* Construct the closure by applying the operator to element of
`workSet', adding the result to `workSet', continuing until `workSet', adding the result to `workSet', continuing until
no new elements are found. */ no new elements are found. */
ATermList res = ATempty; list<Value> res;
set<Expr> doneKeys; // !!! gc roots set<Expr> doneKeys; // !!! gc roots
while (!workSet.empty()) { while (!workSet.empty()) {
Expr e = *(workSet.begin()); Expr e = *(workSet.begin());
@ -322,8 +327,8 @@ static void prim_derivationStrict(EvalState & state, Value * * args, Value & v)
string s = state.coerceToString(i->second, context, true); string s = state.coerceToString(i->second, context, true);
drv.env[key] = s; drv.env[key] = s;
if (key == "builder") drv.builder = s; if (key == "builder") drv.builder = s;
else if (key == "system") drv.platform = s; else if (i->first == state.sSystem) drv.platform = s;
else if (key == "name") drvName = s; else if (i->first == state.sName) drvName = s;
else if (key == "outputHash") outputHash = s; else if (key == "outputHash") outputHash = s;
else if (key == "outputHashAlgo") outputHashAlgo = s; else if (key == "outputHashAlgo") outputHashAlgo = s;
else if (key == "outputHashMode") { else if (key == "outputHashMode") {
@ -830,9 +835,7 @@ static void prim_map(EvalState & state, Value * * args, Value & v)
for (unsigned int n = 0; n < v.list.length; ++n) { for (unsigned int n = 0; n < v.list.length; ++n) {
v.list.elems[n] = &vs[n]; v.list.elems[n] = &vs[n];
vs[n].type = tApp; mkApp(vs[n], *args[0], *args[1]->list.elems[n]);
vs[n].app.left = args[0];
vs[n].app.right = args[1]->list.elems[n];
} }
} }

View file

@ -8,7 +8,6 @@
#include "help.txt.hh" #include "help.txt.hh"
#include "get-drvs.hh" #include "get-drvs.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "pathlocks.hh"
#include "common-opts.hh" #include "common-opts.hh"
#include "xml-writer.hh" #include "xml-writer.hh"
#include "store-api.hh" #include "store-api.hh"
@ -193,141 +192,6 @@ static Path getDefNixExprPath()
} }
/* Ensure exclusive access to a profile. Any command that modifies
the profile first acquires this lock. */
static void lockProfile(PathLocks & lock, const Path & profile)
{
lock.lockPaths(singleton<PathSet>(profile),
(format("waiting for lock on profile `%1%'") % profile).str());
lock.setDeletion(true);
}
/* Optimistic locking is used by long-running operations like `nix-env
-i'. Instead of acquiring the exclusive lock for the entire
duration of the operation, we just perform the operation
optimistically (without an exclusive lock), and check at the end
whether the profile changed while we were busy (i.e., the symlink
target changed). If so, the operation is restarted. Restarting is
generally cheap, since the build results are still in the Nix
store. Most of the time, only the user environment has to be
rebuilt. */
static string optimisticLockProfile(const Path & profile)
{
return pathExists(profile) ? readLink(profile) : "";
}
static bool createUserEnv(EvalState & state, DrvInfos & elems,
const Path & profile, bool keepDerivations,
const string & lockToken)
{
throw Error("not implemented");
#if 0
/* Build the components in the user environment, if they don't
exist already. */
PathSet drvsToBuild;
foreach (DrvInfos::const_iterator, i, elems)
/* Call to `isDerivation' is for compatibility with Nix <= 0.7
user environments. */
if (i->queryDrvPath(state) != "" &&
isDerivation(i->queryDrvPath(state)))
drvsToBuild.insert(i->queryDrvPath(state));
debug(format("building user environment dependencies"));
store->buildDerivations(drvsToBuild);
/* Get the environment builder expression. */
Expr envBuilder = parseExprFromFile(state,
nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */
/* Construct the whole top level derivation. */
PathSet references;
ATermList manifest = ATempty;
ATermList inputs = ATempty;
foreach (DrvInfos::iterator, i, elems) {
/* Create a pseudo-derivation containing the name, system,
output path, and optionally the derivation path, as well as
the meta attributes. */
Path drvPath = keepDerivations ? i->queryDrvPath(state) : "";
/* Round trip to get rid of "bad" meta values (like
functions). */
MetaInfo meta = i->queryMetaInfo(state);
i->setMetaInfo(meta);
ATermList as = ATmakeList5(
makeBind(toATerm("type"),
makeStr("derivation"), makeNoPos()),
makeBind(toATerm("name"),
makeStr(i->name), makeNoPos()),
makeBind(toATerm("system"),
makeStr(i->system), makeNoPos()),
makeBind(toATerm("outPath"),
makeStr(i->queryOutPath(state)), makeNoPos()),
makeBind(toATerm("meta"),
i->attrs->get(toATerm("meta")), makeNoPos()));
if (drvPath != "") as = ATinsert(as,
makeBind(toATerm("drvPath"),
makeStr(drvPath), makeNoPos()));
manifest = ATinsert(manifest, makeAttrs(as));
inputs = ATinsert(inputs, makeStr(i->queryOutPath(state)));
/* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */
store->addTempRoot(i->queryOutPath(state));
store->ensurePath(i->queryOutPath(state));
references.insert(i->queryOutPath(state));
if (drvPath != "") references.insert(drvPath);
}
/* Also write a copy of the list of inputs to the store; we need
it for future modifications of the environment. */
Path manifestFile = store->addTextToStore("env-manifest",
atPrint(canonicaliseExpr(makeList(ATreverse(manifest)))), references);
Expr topLevel = makeCall(envBuilder, makeAttrs(ATmakeList3(
makeBind(toATerm("system"),
makeStr(thisSystem), makeNoPos()),
makeBind(toATerm("derivations"),
makeList(ATreverse(manifest)), makeNoPos()),
makeBind(toATerm("manifest"),
makeStr(manifestFile, singleton<PathSet>(manifestFile)), makeNoPos())
)));
/* Instantiate it. */
debug(format("evaluating builder expression `%1%'") % topLevel);
DrvInfo topLevelDrv;
if (!getDerivation(state, topLevel, topLevelDrv))
abort();
/* Realise the resulting store expression. */
debug(format("building user environment"));
store->buildDerivations(singleton<PathSet>(topLevelDrv.queryDrvPath(state)));
/* Switch the current user environment to the output path. */
PathLocks lock;
lockProfile(lock, profile);
Path lockTokenCur = optimisticLockProfile(profile);
if (lockToken != lockTokenCur) {
printMsg(lvlError, format("profile `%1%' changed while we were busy; restarting") % profile);
return false;
}
debug(format("switching to new user environment"));
Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state));
switchLink(profile, generation);
return true;
#endif
}
static int getPriority(EvalState & state, const DrvInfo & drv) static int getPriority(EvalState & state, const DrvInfo & drv)
{ {
MetaValue value = drv.queryMetaInfo(state, "priority"); MetaValue value = drv.queryMetaInfo(state, "priority");

View file

@ -131,5 +131,19 @@ void switchLink(Path link, Path target)
} }
void lockProfile(PathLocks & lock, const Path & profile)
{
lock.lockPaths(singleton<PathSet>(profile),
(format("waiting for lock on profile `%1%'") % profile).str());
lock.setDeletion(true);
}
string optimisticLockProfile(const Path & profile)
{
return pathExists(profile) ? readLink(profile) : "";
}
} }

View file

@ -2,6 +2,7 @@
#define __PROFILES_H #define __PROFILES_H
#include "types.hh" #include "types.hh"
#include "pathlocks.hh"
#include <time.h> #include <time.h>
@ -37,6 +38,20 @@ void deleteGeneration(const Path & profile, unsigned int gen);
void switchLink(Path link, Path target); void switchLink(Path link, Path target);
/* Ensure exclusive access to a profile. Any command that modifies
the profile first acquires this lock. */
void lockProfile(PathLocks & lock, const Path & profile);
/* Optimistic locking is used by long-running operations like `nix-env
-i'. Instead of acquiring the exclusive lock for the entire
duration of the operation, we just perform the operation
optimistically (without an exclusive lock), and check at the end
whether the profile changed while we were busy (i.e., the symlink
target changed). If so, the operation is restarted. Restarting is
generally cheap, since the build results are still in the Nix
store. Most of the time, only the user environment has to be
rebuilt. */
string optimisticLockProfile(const Path & profile);
} }

View file

@ -1,5 +1,12 @@
#include "util.hh" #include "util.hh"
#include "get-drvs.hh" #include "get-drvs.hh"
#include "derivations.hh"
#include "store-api.hh"
#include "globals.hh"
#include "shared.hh"
#include "eval.hh"
#include "parser.hh"
#include "profiles.hh"
namespace nix { namespace nix {
@ -12,17 +19,137 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
{ {
DrvInfos elems; DrvInfos elems;
Path path = userEnv + "/manifest"; Path manifestFile = userEnv + "/manifest.nix";
Path oldManifestFile = userEnv + "/manifest";
if (!pathExists(path)) if (pathExists(manifestFile)) {
return DrvInfos(); /* not an error, assume nothing installed */ Value v;
state.eval(parseExprFromFile(state, manifestFile), v);
readLegacyManifest(path, elems); getDerivations(state, v, "", Bindings(), elems);
} else if (pathExists(oldManifestFile))
readLegacyManifest(oldManifestFile, elems);
return elems; return elems;
} }
bool createUserEnv(EvalState & state, DrvInfos & elems,
const Path & profile, bool keepDerivations,
const string & lockToken)
{
/* Build the components in the user environment, if they don't
exist already. */
PathSet drvsToBuild;
foreach (DrvInfos::const_iterator, i, elems)
if (i->queryDrvPath(state) != "")
drvsToBuild.insert(i->queryDrvPath(state));
debug(format("building user environment dependencies"));
store->buildDerivations(drvsToBuild);
/* Construct the whole top level derivation. */
PathSet references;
Value manifest;
state.mkList(manifest, elems.size());
unsigned int n = 0;
foreach (DrvInfos::iterator, i, elems) {
/* Create a pseudo-derivation containing the name, system,
output path, and optionally the derivation path, as well as
the meta attributes. */
Path drvPath = keepDerivations ? i->queryDrvPath(state) : "";
Value & v(*state.allocValues(1));
manifest.list.elems[n++] = &v;
state.mkAttrs(v);
mkString((*v.attrs)[state.sType], "derivation");
mkString((*v.attrs)[state.sName], i->name);
mkString((*v.attrs)[state.sSystem], i->system);
mkString((*v.attrs)[state.sOutPath], i->queryOutPath(state));
if (drvPath != "")
mkString((*v.attrs)[state.sDrvPath], i->queryDrvPath(state));
state.mkAttrs((*v.attrs)[state.sMeta]);
MetaInfo meta = i->queryMetaInfo(state);
foreach (MetaInfo::const_iterator, j, meta) {
Value & v2((*(*v.attrs)[state.sMeta].attrs)[state.symbols.create(j->first)]);
switch (j->second.type) {
case MetaValue::tpInt: mkInt(v2, j->second.intValue); break;
case MetaValue::tpString: mkString(v2, j->second.stringValue); break;
case MetaValue::tpStrings: {
state.mkList(v2, j->second.stringValues.size());
unsigned int m = 0;
foreach (Strings::const_iterator, k, j->second.stringValues) {
v2.list.elems[m] = state.allocValues(1);
mkString(*v2.list.elems[m++], *k);
}
break;
}
default: abort();
}
}
/* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */
store->addTempRoot(i->queryOutPath(state));
store->ensurePath(i->queryOutPath(state));
references.insert(i->queryOutPath(state));
if (drvPath != "") references.insert(drvPath);
}
/* Also write a copy of the list of user environment elements to
the store; we need it for future modifications of the
environment. */
Path manifestFile = store->addTextToStore("env-manifest.nix",
(format("%1%") % manifest).str(), references);
printMsg(lvlError, manifestFile);
/* Get the environment builder expression. */
Value envBuilder;
state.eval(parseExprFromFile(state, nixDataDir + "/nix/corepkgs/buildenv"), envBuilder);
/* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */
Value args, topLevel;
state.mkAttrs(args);
mkString((*args.attrs)[state.sSystem], thisSystem);
mkString((*args.attrs)[state.symbols.create("manifest")],
manifestFile, singleton<PathSet>(manifestFile));
(*args.attrs)[state.symbols.create("derivations")] = manifest;
mkApp(topLevel, envBuilder, args);
/* Evaluate it. */
debug("evaluating user environment builder");
DrvInfo topLevelDrv;
if (!getDerivation(state, topLevel, topLevelDrv))
abort();
/* Realise the resulting store expression. */
debug("building user environment");
store->buildDerivations(singleton<PathSet>(topLevelDrv.queryDrvPath(state)));
/* Switch the current user environment to the output path. */
PathLocks lock;
lockProfile(lock, profile);
Path lockTokenCur = optimisticLockProfile(profile);
if (lockToken != lockTokenCur) {
printMsg(lvlError, format("profile `%1%' changed while we were busy; restarting") % profile);
return false;
}
debug(format("switching to new user environment"));
Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state));
switchLink(profile, generation);
return true;
}
/* Code for parsing manifests in the old textual ATerm format. */ /* Code for parsing manifests in the old textual ATerm format. */
static string parseStr(std::istream & str) static string parseStr(std::istream & str)

View file

@ -7,6 +7,10 @@ namespace nix {
DrvInfos queryInstalled(EvalState & state, const Path & userEnv); DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
bool createUserEnv(EvalState & state, DrvInfos & elems,
const Path & profile, bool keepDerivations,
const string & lockToken);
} }
#endif /* !__USER_ENV_H */ #endif /* !__USER_ENV_H */