#include "globals.hh" #include "normalise.hh" #include "shared.hh" #include "parser.hh" #include "eval.hh" #include "help.txt.hh" typedef void (* Operation) (EvalState & state, Strings opFlags, Strings opArgs); struct DrvInfo { string name; Path drvPath; Path outPath; Hash drvHash; }; typedef map DrvInfos; void printHelp() { cout << string((char *) helpText, sizeof helpText); } bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) { ATMatcher m; e = evalExpr(state, e); if (!(atMatch(m, e) >> "Attrs")) return false; Expr a = queryAttr(e, "type"); if (!a || evalString(state, a) != "derivation") return false; a = queryAttr(e, "name"); if (!a) throw badTerm("derivation name missing", e); drv.name = evalString(state, a); a = queryAttr(e, "drvPath"); if (!a) throw badTerm("derivation path missing", e); drv.drvPath = evalPath(state, a); a = queryAttr(e, "drvHash"); if (!a) throw badTerm("derivation hash missing", e); drv.drvHash = parseHash(evalString(state, a)); a = queryAttr(e, "outPath"); if (!a) throw badTerm("output path missing", e); drv.outPath = evalPath(state, a); return true; } bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) { ATMatcher m; ATermList es; DrvInfo drv; e = evalExpr(state, e); if (parseDerivation(state, e, drv)) drvs[drv.drvPath] = drv; else if (atMatch(m, e) >> "Attrs") { ATermMap drvMap; queryAllAttrs(e, drvMap); for (ATermIterator i(drvMap.keys()); i; ++i) { debug(format("evaluating attribute `%1%'") % *i); if (parseDerivation(state, drvMap.get(*i), drv)) drvs[drv.drvPath] = drv; } } else if (atMatch(m, e) >> "List" >> es) { for (ATermIterator i(es); i; ++i) { debug(format("evaluating list element")); if (parseDerivation(state, *i, drv)) drvs[drv.drvPath] = drv; } } return true; } void loadDerivations(EvalState & state, Path nePath, DrvInfos & drvs) { Expr e = parseExprFromFile(absPath(nePath)); if (!parseDerivations(state, e, drvs)) throw badTerm("expected set of derivations", e); } static Path getLinksDir() { return canonPath(nixStateDir + "/links"); } static Path getCurrentPath() { return getLinksDir() + "/current"; } void queryInstalled(EvalState & state, DrvInfos & drvs) { Path path = getCurrentPath() + "/manifest"; if (!pathExists(path)) return; /* not an error, assume nothing installed */ Expr e = ATreadFromNamedFile(path.c_str()); if (!e) throw Error(format("cannot read Nix expression from `%1%'") % path); if (!parseDerivations(state, e, drvs)) throw badTerm(format("expected set of derivations in `%1%'") % path, e); } Path createLink(Path outPath, Path drvPath) { Path linksDir = getLinksDir(); unsigned int num = 0; Strings names = readDirectory(linksDir); for (Strings::iterator i = names.begin(); i != names.end(); ++i) { istringstream s(*i); unsigned int n; if (s >> n && s.eof() && n >= num) num = n + 1; } Path linkPath; while (1) { linkPath = (format("%1%/%2%") % linksDir % num).str(); if (symlink(outPath.c_str(), linkPath.c_str()) == 0) break; if (errno != EEXIST) throw SysError(format("creating symlink `%1%'") % linkPath); /* Somebody beat us to it, retry with a higher number. */ num++; } writeStringToFile(linkPath + "-src.id", drvPath); return linkPath; } void switchLink(Path link, Path target) { Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link)); if (symlink(target.c_str(), tmp.c_str()) != 0) throw SysError(format("creating symlink `%1%'") % tmp); /* The rename() system call is supposed to be essentially atomic on Unix. That is, if we have links `current -> X' and `new_current -> Y', and we rename new_current to current, a process accessing current will see X or Y, but never a file-not-found or other error condition. This is sufficient to atomically switch user environments. */ if (rename(tmp.c_str(), link.c_str()) != 0) throw SysError(format("renaming `%1%' to `%2%'") % tmp % link); } void createUserEnv(EvalState & state, const DrvInfos & drvs) { /* Get the environment builder expression. */ Expr envBuilder = parseExprFromFile(nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ /* Construct the whole top level derivation. */ ATermList inputs = ATempty; for (DrvInfos::const_iterator i = drvs.begin(); i != drvs.end(); ++i) { ATerm t = ATmake( "Attrs([" "Bind(\"type\", Str(\"derivation\")), " "Bind(\"name\", Str()), " "Bind(\"drvPath\", Path()), " "Bind(\"drvHash\", Str()), " "Bind(\"outPath\", Path())" "])", i->second.name.c_str(), i->second.drvPath.c_str(), ((string) i->second.drvHash).c_str(), i->second.outPath.c_str()); inputs = ATinsert(inputs, t); } ATerm inputs2 = ATmake("List()", ATreverse(inputs)); /* Also write a copy of the list of inputs to the store; we need it for future modifications of the environment. */ Path inputsFile = writeTerm(inputs2, "-env-inputs"); Expr topLevel = ATmake( "Call(, Attrs([" "Bind(\"system\", Str()), " "Bind(\"derivations\", ), " // !!! redundant "Bind(\"manifest\", Path())" "]))", envBuilder, thisSystem.c_str(), inputs2, inputsFile.c_str()); /* Instantiate it. */ debug(format("evaluating builder expression `%1%'") % topLevel); DrvInfo topLevelDrv; if (!parseDerivation(state, topLevel, topLevelDrv)) abort(); /* Realise the resulting store expression. */ debug(format("realising user environment")); Path nfPath = normaliseStoreExpr(topLevelDrv.drvPath); realiseClosure(nfPath); /* Switch the current user environment to the output path. */ debug(format("switching to new user environment")); Path linkPath = createLink(topLevelDrv.outPath, topLevelDrv.drvPath); switchLink(getLinksDir() + "/current", linkPath); } typedef map NameMap; NameMap mapByNames(DrvInfos & drvs) { NameMap nameMap; for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) nameMap[i->second.name] = i->first; return nameMap; } void installDerivations(EvalState & state, Path nePath, Strings drvNames) { debug(format("installing derivations from `%1%'") % nePath); /* Fetch all derivations from the input file. */ DrvInfos availDrvs; loadDerivations(state, nePath, availDrvs); NameMap nameMap = mapByNames(availDrvs); /* Filter out the ones we're not interested in. */ DrvInfos selectedDrvs; if (drvNames.size() > 0 && drvNames.front() == "*") { /* !!! hack */ selectedDrvs = availDrvs; } else { for (Strings::iterator i = drvNames.begin(); i != drvNames.end(); ++i) { NameMap::iterator j = nameMap.find(*i); if (j == nameMap.end()) throw Error(format("unknown derivation `%1%'") % *i); else selectedDrvs[j->second] = availDrvs[j->second]; } } /* Add in the already installed derivations. */ DrvInfos installedDrvs; queryInstalled(state, installedDrvs); selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); createUserEnv(state, selectedDrvs); } static void opInstall(EvalState & state, Strings opFlags, Strings opArgs) { if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); installDerivations(state, nePath, Strings(opArgs.begin(), opArgs.end())); } void uninstallDerivations(EvalState & state, Strings drvNames) { DrvInfos installedDrvs; queryInstalled(state, installedDrvs); NameMap nameMap = mapByNames(installedDrvs); for (Strings::iterator i = drvNames.begin(); i != drvNames.end(); ++i) { NameMap::iterator j = nameMap.find(*i); if (j == nameMap.end()) throw Error(format("unknown derivation `%1%'") % *i); else installedDrvs.erase(j->second); } createUserEnv(state, installedDrvs); } static void opUninstall(EvalState & state, Strings opFlags, Strings opArgs) { uninstallDerivations(state, opArgs); } static void opQuery(EvalState & state, Strings opFlags, Strings opArgs) { enum { qName, qDrvPath, qStatus } query = qName; enum { sInstalled, sAvailable } source = sInstalled; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ++i) if (*i == "--name") query = qName; else if (*i == "--expr" || *i == "-e") query = qDrvPath; else if (*i == "--status" || *i == "-s") query = qStatus; else if (*i == "--installed") source = sInstalled; else if (*i == "--available" || *i == "-f") source = sAvailable; else throw UsageError(format("unknown flag `%1%'") % *i); /* Obtain derivation information from the specified source. */ DrvInfos drvs; switch (source) { case sInstalled: queryInstalled(state, drvs); break; case sAvailable: { if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); loadDerivations(state, nePath, drvs); break; } default: abort(); } if (opArgs.size() != 0) throw UsageError("no arguments expected"); /* Perform the specified query on the derivations. */ switch (query) { case qName: { for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) cout << format("%1%\n") % i->second.name; break; } case qDrvPath: { for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) cout << format("%1%\n") % i->second.drvPath; break; } case qStatus: { DrvInfos installed; queryInstalled(state, installed); for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { cout << format("%1%%2% %3%\n") % (installed.find(i->first) != installed.end() ? 'I' : '-') % (isValidPath(i->second.outPath) ? 'P' : '-') % i->second.name; } break; } default: abort(); } } void run(Strings args) { EvalState state; Strings opFlags, opArgs; Operation op = 0; for (Strings::iterator i = args.begin(); i != args.end(); ++i) { string arg = *i; Operation oldOp = op; if (arg == "--install" || arg == "-i") op = opInstall; else if (arg == "--uninstall" || arg == "-u") op = opUninstall; else if (arg == "--query" || arg == "-q") op = opQuery; else if (arg[0] == '-') opFlags.push_back(arg); else opArgs.push_back(arg); if (oldOp && oldOp != op) throw UsageError("only one operation may be specified"); } if (!op) throw UsageError("no operation specified"); openDB(); op(state, opFlags, opArgs); printEvalStats(state); } string programId = "nix-env";