From fe2d869e04372de69719c3989a75247ff44b8fd4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 21 Apr 2010 15:08:58 +0000 Subject: [PATCH] * 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...) --- corepkgs/buildenv/builder.pl.in | 2 +- src/libexpr/eval.cc | 11 ++- src/libexpr/eval.hh | 11 ++- src/libexpr/get-drvs.cc | 23 +----- src/libexpr/get-drvs.hh | 2 +- src/libexpr/primops.cc | 31 ++++---- src/nix-env/nix-env.cc | 136 ------------------------------- src/nix-env/profiles.cc | 16 +++- src/nix-env/profiles.hh | 15 ++++ src/nix-env/user-env.cc | 137 ++++++++++++++++++++++++++++++-- src/nix-env/user-env.hh | 4 + 11 files changed, 205 insertions(+), 183 deletions(-) diff --git a/corepkgs/buildenv/builder.pl.in b/corepkgs/buildenv/builder.pl.in index 9932ea577..9eb9f7bb0 100755 --- a/corepkgs/buildenv/builder.pl.in +++ b/corepkgs/buildenv/builder.pl.in @@ -160,4 +160,4 @@ while (scalar(keys %postponed) > 0) { 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"; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d259d58a3..f59ea99e5 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -98,6 +98,7 @@ EvalState::EvalState() , sType(symbols.create("type")) , sMeta(symbols.create("meta")) , sName(symbols.create("name")) + , sSystem(symbols.create("system")) , baseEnv(allocEnv(128)) , baseEnvDispl(0) , staticBaseEnv(false, 0) @@ -131,12 +132,13 @@ void EvalState::addPrimOp(const string & name, unsigned int arity, PrimOp primOp) { Value v; + string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name; v.type = tPrimOp; v.primOp.arity = arity; v.primOp.fun = primOp; + v.primOp.name = strdup(name2.c_str()); staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl; baseEnv.values[baseEnvDispl++] = v; - string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name; (*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; /* And call the primop. */ - primOp->primOp.fun(*this, vArgs, v); + try { + primOp->primOp.fun(*this, vArgs, v); + } catch (Error & e) { + addErrorPrefix(e, "while evaluating the builtin function `%1%':\n", primOp->primOp.name); + throw; + } } else { Value * v2 = allocValues(2); v2[0] = fun; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 6cdc171f5..a730dc297 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -92,6 +92,7 @@ struct Value Value * val; struct { PrimOp fun; + char * name; unsigned int arity; } primOp; 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 string & s, const PathSet & context = PathSet()); void mkPath(Value & v, const char * s); @@ -162,7 +171,7 @@ public: SymbolTable symbols; - const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName; + const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sSystem; private: SrcToStore srcToStore; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index e9f1063d9..e0ad91d8a 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -70,27 +70,6 @@ void DrvInfo::setMetaInfo(const MetaInfo & meta) { metaInfoRead = true; 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"); 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()) drv.system = "unknown"; else diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh index 6f3c381f8..ca7d98002 100644 --- a/src/libexpr/get-drvs.hh +++ b/src/libexpr/get-drvs.hh @@ -41,7 +41,7 @@ public: /* !!! make this private */ Bindings * attrs; - DrvInfo() : metaInfoRead(false) { }; + DrvInfo() : metaInfoRead(false), attrs(0) { }; string queryDrvPath(EvalState & state) const; string queryOutPath(EvalState & state) const; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index b28201593..a228398e0 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -89,24 +89,29 @@ static void prim_genericClosure(EvalState & state, Value * * args, Value & v) { startNest(nest, lvlDebug, "finding dependencies"); - Expr attrs = evalExpr(state, args[0]); + state.forceAttrs(*args[0]); /* Get the start set. */ - Expr startSet = queryAttr(attrs, "startSet"); - if (!startSet) throw EvalError("attribute `startSet' required"); - ATermList startSet2 = evalList(state, startSet); + Bindings::iterator startSet = + args[0]->attrs->find(state.symbols.create("startSet")); + if (startSet == args[0]->attrs->end()) + throw EvalError("attribute `startSet' required"); + state.forceList(startSet->second); - set workSet; // !!! gc roots - for (ATermIterator i(startSet2); i; ++i) workSet.insert(*i); + list workSet; + for (unsigned int n = 0; n < startSet->second.list.length; ++n) + workSet.push_back(*startSet->second.list.elems[n]); /* Get the operator. */ - Expr op = queryAttr(attrs, "operator"); - if (!op) throw EvalError("attribute `operator' required"); + Bindings::iterator op = + 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 `workSet', adding the result to `workSet', continuing until no new elements are found. */ - ATermList res = ATempty; + list res; set doneKeys; // !!! gc roots while (!workSet.empty()) { 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); drv.env[key] = s; if (key == "builder") drv.builder = s; - else if (key == "system") drv.platform = s; - else if (key == "name") drvName = s; + else if (i->first == state.sSystem) drv.platform = s; + else if (i->first == state.sName) drvName = s; else if (key == "outputHash") outputHash = s; else if (key == "outputHashAlgo") outputHashAlgo = s; 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) { v.list.elems[n] = &vs[n]; - vs[n].type = tApp; - vs[n].app.left = args[0]; - vs[n].app.right = args[1]->list.elems[n]; + mkApp(vs[n], *args[0], *args[1]->list.elems[n]); } } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index e298c4003..4a9df454d 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -8,7 +8,6 @@ #include "help.txt.hh" #include "get-drvs.hh" #include "attr-path.hh" -#include "pathlocks.hh" #include "common-opts.hh" #include "xml-writer.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(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(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(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) { MetaValue value = drv.queryMetaInfo(state, "priority"); diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc index 75585b1b2..60576f1ae 100644 --- a/src/nix-env/profiles.cc +++ b/src/nix-env/profiles.cc @@ -130,6 +130,20 @@ void switchLink(Path link, Path target) throw SysError(format("renaming `%1%' to `%2%'") % tmp % link); } - + +void lockProfile(PathLocks & lock, const Path & profile) +{ + lock.lockPaths(singleton(profile), + (format("waiting for lock on profile `%1%'") % profile).str()); + lock.setDeletion(true); +} + + +string optimisticLockProfile(const Path & profile) +{ + return pathExists(profile) ? readLink(profile) : ""; +} + + } diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh index 99c20f42d..a64258dae 100644 --- a/src/nix-env/profiles.hh +++ b/src/nix-env/profiles.hh @@ -2,6 +2,7 @@ #define __PROFILES_H #include "types.hh" +#include "pathlocks.hh" #include @@ -37,6 +38,20 @@ void deleteGeneration(const Path & profile, unsigned int gen); 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); } diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index f040f8c11..a0e51cae1 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -1,5 +1,12 @@ #include "util.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 { @@ -11,18 +18,138 @@ static void readLegacyManifest(const Path & path, DrvInfos & elems); DrvInfos queryInstalled(EvalState & state, const Path & userEnv) { DrvInfos elems; - - Path path = userEnv + "/manifest"; - if (!pathExists(path)) - return DrvInfos(); /* not an error, assume nothing installed */ + Path manifestFile = userEnv + "/manifest.nix"; + Path oldManifestFile = userEnv + "/manifest"; - readLegacyManifest(path, elems); + if (pathExists(manifestFile)) { + Value v; + state.eval(parseExprFromFile(state, manifestFile), v); + getDerivations(state, v, "", Bindings(), elems); + } else if (pathExists(oldManifestFile)) + readLegacyManifest(oldManifestFile, 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(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(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. */ static string parseStr(std::istream & str) diff --git a/src/nix-env/user-env.hh b/src/nix-env/user-env.hh index 6675014f1..4125d8217 100644 --- a/src/nix-env/user-env.hh +++ b/src/nix-env/user-env.hh @@ -7,6 +7,10 @@ namespace nix { 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 */