That is, unless --file is specified, the Nix search path is synthesized into an attribute set. Thus you can say $ nix build nixpkgs.hello assuming $NIX_PATH contains an entry of the form "nixpkgs=...". This is more verbose than $ nix build hello but is less ambiguous.
		
			
				
	
	
		
			1427 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1427 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "attr-path.hh"
 | ||
| #include "common-opts.hh"
 | ||
| #include "derivations.hh"
 | ||
| #include "eval.hh"
 | ||
| #include "get-drvs.hh"
 | ||
| #include "globals.hh"
 | ||
| #include "names.hh"
 | ||
| #include "profiles.hh"
 | ||
| #include "shared.hh"
 | ||
| #include "store-api.hh"
 | ||
| #include "user-env.hh"
 | ||
| #include "util.hh"
 | ||
| #include "value-to-json.hh"
 | ||
| #include "xml-writer.hh"
 | ||
| 
 | ||
| #include <cerrno>
 | ||
| #include <ctime>
 | ||
| #include <algorithm>
 | ||
| #include <iostream>
 | ||
| #include <sstream>
 | ||
| 
 | ||
| #include <sys/types.h>
 | ||
| #include <sys/stat.h>
 | ||
| #include <unistd.h>
 | ||
| 
 | ||
| 
 | ||
| using namespace nix;
 | ||
| using std::cout;
 | ||
| 
 | ||
| 
 | ||
| typedef enum {
 | ||
|     srcNixExprDrvs,
 | ||
|     srcNixExprs,
 | ||
|     srcStorePaths,
 | ||
|     srcProfile,
 | ||
|     srcAttrPath,
 | ||
|     srcUnknown
 | ||
| } InstallSourceType;
 | ||
| 
 | ||
| 
 | ||
| struct InstallSourceInfo
 | ||
| {
 | ||
|     InstallSourceType type;
 | ||
|     Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
 | ||
|     Path profile; /* for srcProfile */
 | ||
|     string systemFilter; /* for srcNixExprDrvs */
 | ||
|     Bindings * autoArgs;
 | ||
| };
 | ||
| 
 | ||
| 
 | ||
| struct Globals
 | ||
| {
 | ||
|     InstallSourceInfo instSource;
 | ||
|     Path profile;
 | ||
|     std::shared_ptr<EvalState> state;
 | ||
|     bool dryRun;
 | ||
|     bool preserveInstalled;
 | ||
|     bool removeAll;
 | ||
|     string forceName;
 | ||
|     bool prebuiltOnly;
 | ||
| };
 | ||
| 
 | ||
| 
 | ||
| typedef void (* Operation) (Globals & globals,
 | ||
|     Strings opFlags, Strings opArgs);
 | ||
| 
 | ||
| 
 | ||
| static string needArg(Strings::iterator & i,
 | ||
|     Strings & args, const string & arg)
 | ||
| {
 | ||
|     if (i == args.end()) throw UsageError(
 | ||
|         format("‘%1%’ requires an argument") % arg);
 | ||
|     return *i++;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool parseInstallSourceOptions(Globals & globals,
 | ||
|     Strings::iterator & i, Strings & args, const string & arg)
 | ||
| {
 | ||
|     if (arg == "--from-expression" || arg == "-E")
 | ||
|         globals.instSource.type = srcNixExprs;
 | ||
|     else if (arg == "--from-profile") {
 | ||
|         globals.instSource.type = srcProfile;
 | ||
|         globals.instSource.profile = needArg(i, args, arg);
 | ||
|     }
 | ||
|     else if (arg == "--attr" || arg == "-A")
 | ||
|         globals.instSource.type = srcAttrPath;
 | ||
|     else return false;
 | ||
|     return true;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool isNixExpr(const Path & path, struct stat & st)
 | ||
| {
 | ||
|     return S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && pathExists(path + "/default.nix"));
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void getAllExprs(EvalState & state,
 | ||
|     const Path & path, StringSet & attrs, Value & v)
 | ||
| {
 | ||
|     StringSet namesSorted;
 | ||
|     for (auto & i : readDirectory(path)) namesSorted.insert(i.name);
 | ||
| 
 | ||
|     for (auto & i : namesSorted) {
 | ||
|         /* Ignore the manifest.nix used by profiles.  This is
 | ||
|            necessary to prevent it from showing up in channels (which
 | ||
|            are implemented using profiles). */
 | ||
|         if (i == "manifest.nix") continue;
 | ||
| 
 | ||
|         Path path2 = path + "/" + i;
 | ||
| 
 | ||
|         struct stat st;
 | ||
|         if (stat(path2.c_str(), &st) == -1)
 | ||
|             continue; // ignore dangling symlinks in ~/.nix-defexpr
 | ||
| 
 | ||
|         if (isNixExpr(path2, st) && (!S_ISREG(st.st_mode) || hasSuffix(path2, ".nix"))) {
 | ||
|             /* Strip off the `.nix' filename suffix (if applicable),
 | ||
|                otherwise the attribute cannot be selected with the
 | ||
|                `-A' option.  Useful if you want to stick a Nix
 | ||
|                expression directly in ~/.nix-defexpr. */
 | ||
|             string attrName = i;
 | ||
|             if (hasSuffix(attrName, ".nix"))
 | ||
|                 attrName = string(attrName, 0, attrName.size() - 4);
 | ||
|             if (attrs.find(attrName) != attrs.end()) {
 | ||
|                 printMsg(lvlError, format("warning: name collision in input Nix expressions, skipping ‘%1%’") % path2);
 | ||
|                 continue;
 | ||
|             }
 | ||
|             attrs.insert(attrName);
 | ||
|             /* Load the expression on demand. */
 | ||
|             Value & vFun = state.getBuiltin("import");
 | ||
|             Value & vArg(*state.allocValue());
 | ||
|             mkString(vArg, path2);
 | ||
|             if (v.attrs->size() == v.attrs->capacity())
 | ||
|                 throw Error(format("too many Nix expressions in directory ‘%1%’") % path);
 | ||
|             mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
 | ||
|         }
 | ||
|         else if (S_ISDIR(st.st_mode))
 | ||
|             /* `path2' is a directory (with no default.nix in it);
 | ||
|                recurse into it. */
 | ||
|             getAllExprs(state, path2, attrs, v);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
 | ||
| {
 | ||
|     struct stat st;
 | ||
|     if (stat(path.c_str(), &st) == -1)
 | ||
|         throw SysError(format("getting information about ‘%1%’") % path);
 | ||
| 
 | ||
|     if (isNixExpr(path, st)) {
 | ||
|         state.evalFile(path, v);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     /* The path is a directory.  Put the Nix expressions in the
 | ||
|        directory in a set, with the file name of each expression as
 | ||
|        the attribute name.  Recurse into subdirectories (but keep the
 | ||
|        set flat, not nested, to make it easier for a user to have a
 | ||
|        ~/.nix-defexpr directory that includes some system-wide
 | ||
|        directory). */
 | ||
|     if (S_ISDIR(st.st_mode)) {
 | ||
|         state.mkAttrs(v, 1024);
 | ||
|         state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0);
 | ||
|         StringSet attrs;
 | ||
|         getAllExprs(state, path, attrs, v);
 | ||
|         v.attrs->sort();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void loadDerivations(EvalState & state, Path nixExprPath,
 | ||
|     string systemFilter, Bindings & autoArgs,
 | ||
|     const string & pathPrefix, DrvInfos & elems)
 | ||
| {
 | ||
|     Value vRoot;
 | ||
|     loadSourceExpr(state, nixExprPath, vRoot);
 | ||
| 
 | ||
|     Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
 | ||
| 
 | ||
|     getDerivations(state, v, pathPrefix, autoArgs, elems, true);
 | ||
| 
 | ||
|     /* Filter out all derivations not applicable to the current
 | ||
|        system. */
 | ||
|     for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
 | ||
|         j = i; j++;
 | ||
|         if (systemFilter != "*" && i->system != systemFilter)
 | ||
|             elems.erase(i);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static Path getHomeDir()
 | ||
| {
 | ||
|     Path homeDir(getEnv("HOME", ""));
 | ||
|     if (homeDir == "") throw Error("HOME environment variable not set");
 | ||
|     return homeDir;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static Path getDefNixExprPath()
 | ||
| {
 | ||
|     return getHomeDir() + "/.nix-defexpr";
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static int getPriority(EvalState & state, DrvInfo & drv)
 | ||
| {
 | ||
|     return drv.queryMetaInt("priority", 0);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static int comparePriorities(EvalState & state, DrvInfo & drv1, DrvInfo & drv2)
 | ||
| {
 | ||
|     return getPriority(state, drv2) - getPriority(state, drv1);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| // FIXME: this function is rather slow since it checks a single path
 | ||
| // at a time.
 | ||
| static bool isPrebuilt(EvalState & state, DrvInfo & elem)
 | ||
| {
 | ||
|     Path path = elem.queryOutPath();
 | ||
|     if (state.store->isValidPath(path)) return true;
 | ||
|     PathSet ps = state.store->querySubstitutablePaths({path});
 | ||
|     return ps.find(path) != ps.end();
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void checkSelectorUse(DrvNames & selectors)
 | ||
| {
 | ||
|     /* Check that all selectors have been used. */
 | ||
|     for (auto & i : selectors)
 | ||
|         if (i.hits == 0 && i.fullName != "*")
 | ||
|             throw Error(format("selector ‘%1%’ matches no derivations") % i.fullName);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
 | ||
|     const Strings & args, bool newestOnly)
 | ||
| {
 | ||
|     DrvNames selectors = drvNamesFromArgs(args);
 | ||
|     if (selectors.empty())
 | ||
|         selectors.push_back(DrvName("*"));
 | ||
| 
 | ||
|     DrvInfos elems;
 | ||
|     set<unsigned int> done;
 | ||
| 
 | ||
|     for (auto & i : selectors) {
 | ||
|         typedef list<std::pair<DrvInfo, unsigned int> > Matches;
 | ||
|         Matches matches;
 | ||
|         unsigned int n = 0;
 | ||
|         for (DrvInfos::const_iterator j = allElems.begin();
 | ||
|              j != allElems.end(); ++j, ++n)
 | ||
|         {
 | ||
|             DrvName drvName(j->name);
 | ||
|             if (i.matches(drvName)) {
 | ||
|                 i.hits++;
 | ||
|                 matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         /* If `newestOnly', if a selector matches multiple derivations
 | ||
|            with the same name, pick the one matching the current
 | ||
|            system.  If there are still multiple derivations, pick the
 | ||
|            one with the highest priority.  If there are still multiple
 | ||
|            derivations, pick the one with the highest version.
 | ||
|            Finally, if there are still multiple derivations,
 | ||
|            arbitrarily pick the first one. */
 | ||
|         if (newestOnly) {
 | ||
| 
 | ||
|             /* Map from package names to derivations. */
 | ||
|             typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
 | ||
|             Newest newest;
 | ||
|             StringSet multiple;
 | ||
| 
 | ||
|             for (auto & j : matches) {
 | ||
|                 DrvName drvName(j.first.name);
 | ||
|                 int d = 1;
 | ||
| 
 | ||
|                 Newest::iterator k = newest.find(drvName.name);
 | ||
| 
 | ||
|                 if (k != newest.end()) {
 | ||
|                     d = j.first.system == k->second.first.system ? 0 :
 | ||
|                         j.first.system == settings.thisSystem ? 1 :
 | ||
|                         k->second.first.system == settings.thisSystem ? -1 : 0;
 | ||
|                     if (d == 0)
 | ||
|                         d = comparePriorities(state, j.first, k->second.first);
 | ||
|                     if (d == 0)
 | ||
|                         d = compareVersions(drvName.version, DrvName(k->second.first.name).version);
 | ||
|                 }
 | ||
| 
 | ||
|                 if (d > 0) {
 | ||
|                     newest.erase(drvName.name);
 | ||
|                     newest.insert(Newest::value_type(drvName.name, j));
 | ||
|                     multiple.erase(j.first.name);
 | ||
|                 } else if (d == 0) {
 | ||
|                     multiple.insert(j.first.name);
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             matches.clear();
 | ||
|             for (auto & j : newest) {
 | ||
|                 if (multiple.find(j.second.first.name) != multiple.end())
 | ||
|                     printMsg(lvlInfo,
 | ||
|                         format("warning: there are multiple derivations named ‘%1%’; using the first one")
 | ||
|                         % j.second.first.name);
 | ||
|                 matches.push_back(j.second);
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         /* Insert only those elements in the final list that we
 | ||
|            haven't inserted before. */
 | ||
|         for (auto & j : matches)
 | ||
|             if (done.find(j.second) == done.end()) {
 | ||
|                 done.insert(j.second);
 | ||
|                 elems.push_back(j.first);
 | ||
|             }
 | ||
|     }
 | ||
| 
 | ||
|     checkSelectorUse(selectors);
 | ||
| 
 | ||
|     return elems;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool isPath(const string & s)
 | ||
| {
 | ||
|     return s.find('/') != string::npos;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void queryInstSources(EvalState & state,
 | ||
|     InstallSourceInfo & instSource, const Strings & args,
 | ||
|     DrvInfos & elems, bool newestOnly)
 | ||
| {
 | ||
|     InstallSourceType type = instSource.type;
 | ||
|     if (type == srcUnknown && args.size() > 0 && isPath(args.front()))
 | ||
|         type = srcStorePaths;
 | ||
| 
 | ||
|     switch (type) {
 | ||
| 
 | ||
|         /* Get the available user environment elements from the
 | ||
|            derivations specified in a Nix expression, including only
 | ||
|            those with names matching any of the names in `args'. */
 | ||
|         case srcUnknown:
 | ||
|         case srcNixExprDrvs: {
 | ||
| 
 | ||
|             /* Load the derivations from the (default or specified)
 | ||
|                Nix expression. */
 | ||
|             DrvInfos allElems;
 | ||
|             loadDerivations(state, instSource.nixExprPath,
 | ||
|                 instSource.systemFilter, *instSource.autoArgs, "", allElems);
 | ||
| 
 | ||
|             elems = filterBySelector(state, allElems, args, newestOnly);
 | ||
| 
 | ||
|             break;
 | ||
|         }
 | ||
| 
 | ||
|         /* Get the available user environment elements from the Nix
 | ||
|            expressions specified on the command line; these should be
 | ||
|            functions that take the default Nix expression file as
 | ||
|            argument, e.g., if the file is `./foo.nix', then the
 | ||
|            argument `x: x.bar' is equivalent to `(x: x.bar)
 | ||
|            (import ./foo.nix)' = `(import ./foo.nix).bar'. */
 | ||
|         case srcNixExprs: {
 | ||
| 
 | ||
|             Value vArg;
 | ||
|             loadSourceExpr(state, instSource.nixExprPath, vArg);
 | ||
| 
 | ||
|             for (auto & i : args) {
 | ||
|                 Expr * eFun = state.parseExprFromString(i, absPath("."));
 | ||
|                 Value vFun, vTmp;
 | ||
|                 state.eval(eFun, vFun);
 | ||
|                 mkApp(vTmp, vFun, vArg);
 | ||
|                 getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
 | ||
|             }
 | ||
| 
 | ||
|             break;
 | ||
|         }
 | ||
| 
 | ||
|         /* The available user environment elements are specified as a
 | ||
|            list of store paths (which may or may not be
 | ||
|            derivations). */
 | ||
|         case srcStorePaths: {
 | ||
| 
 | ||
|             for (auto & i : args) {
 | ||
|                 Path path = state.store->followLinksToStorePath(i);
 | ||
| 
 | ||
|                 string name = baseNameOf(path);
 | ||
|                 string::size_type dash = name.find('-');
 | ||
|                 if (dash != string::npos)
 | ||
|                     name = string(name, dash + 1);
 | ||
| 
 | ||
|                 DrvInfo elem(state, name, "", "", 0);
 | ||
| 
 | ||
|                 if (isDerivation(path)) {
 | ||
|                     elem.setDrvPath(path);
 | ||
|                     elem.setOutPath(state.store->derivationFromPath(path).findOutput("out"));
 | ||
|                     if (name.size() >= drvExtension.size() &&
 | ||
|                         string(name, name.size() - drvExtension.size()) == drvExtension)
 | ||
|                         name = string(name, 0, name.size() - drvExtension.size());
 | ||
|                 }
 | ||
|                 else elem.setOutPath(path);
 | ||
| 
 | ||
|                 elems.push_back(elem);
 | ||
|             }
 | ||
| 
 | ||
|             break;
 | ||
|         }
 | ||
| 
 | ||
|         /* Get the available user environment elements from another
 | ||
|            user environment.  These are then filtered as in the
 | ||
|            `srcNixExprDrvs' case. */
 | ||
|         case srcProfile: {
 | ||
|             elems = filterBySelector(state,
 | ||
|                 queryInstalled(state, instSource.profile),
 | ||
|                 args, newestOnly);
 | ||
|             break;
 | ||
|         }
 | ||
| 
 | ||
|         case srcAttrPath: {
 | ||
|             Value vRoot;
 | ||
|             loadSourceExpr(state, instSource.nixExprPath, vRoot);
 | ||
|             for (auto & i : args) {
 | ||
|                 Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot));
 | ||
|                 getDerivations(state, v, "", *instSource.autoArgs, elems, true);
 | ||
|             }
 | ||
|             break;
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void printMissing(EvalState & state, DrvInfos & elems)
 | ||
| {
 | ||
|     PathSet targets;
 | ||
|     for (auto & i : elems) {
 | ||
|         Path drvPath = i.queryDrvPath();
 | ||
|         if (drvPath != "")
 | ||
|             targets.insert(drvPath);
 | ||
|         else
 | ||
|             targets.insert(i.queryOutPath());
 | ||
|     }
 | ||
| 
 | ||
|     printMissing(state.store, targets);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool keep(DrvInfo & drv)
 | ||
| {
 | ||
|     return drv.queryMetaBool("keep", false);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void installDerivations(Globals & globals,
 | ||
|     const Strings & args, const Path & profile)
 | ||
| {
 | ||
|     debug(format("installing derivations"));
 | ||
| 
 | ||
|     /* Get the set of user environment elements to be installed. */
 | ||
|     DrvInfos newElems, newElemsTmp;
 | ||
|     queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true);
 | ||
| 
 | ||
|     /* If --prebuilt-only is given, filter out source-only packages. */
 | ||
|     for (auto & i : newElemsTmp)
 | ||
|         if (!globals.prebuiltOnly || isPrebuilt(*globals.state, i))
 | ||
|             newElems.push_back(i);
 | ||
| 
 | ||
|     StringSet newNames;
 | ||
|     for (auto & i : newElems) {
 | ||
|         /* `forceName' is a hack to get package names right in some
 | ||
|            one-click installs, namely those where the name used in the
 | ||
|            path is not the one we want (e.g., `java-front' versus
 | ||
|            `java-front-0.9pre15899'). */
 | ||
|         if (globals.forceName != "")
 | ||
|             i.name = globals.forceName;
 | ||
|         newNames.insert(DrvName(i.name).name);
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     while (true) {
 | ||
|         string lockToken = optimisticLockProfile(profile);
 | ||
| 
 | ||
|         DrvInfos allElems(newElems);
 | ||
| 
 | ||
|         /* Add in the already installed derivations, unless they have
 | ||
|            the same name as a to-be-installed element. */
 | ||
|         if (!globals.removeAll) {
 | ||
|             DrvInfos installedElems = queryInstalled(*globals.state, profile);
 | ||
| 
 | ||
|             for (auto & i : installedElems) {
 | ||
|                 DrvName drvName(i.name);
 | ||
|                 if (!globals.preserveInstalled &&
 | ||
|                     newNames.find(drvName.name) != newNames.end() &&
 | ||
|                     !keep(i))
 | ||
|                     printMsg(lvlInfo, format("replacing old ‘%1%’") % i.name);
 | ||
|                 else
 | ||
|                     allElems.push_back(i);
 | ||
|             }
 | ||
| 
 | ||
|             for (auto & i : newElems)
 | ||
|                 printMsg(lvlInfo, format("installing ‘%1%’") % i.name);
 | ||
|         }
 | ||
| 
 | ||
|         printMissing(*globals.state, newElems);
 | ||
| 
 | ||
|         if (globals.dryRun) return;
 | ||
| 
 | ||
|         if (createUserEnv(*globals.state, allElems,
 | ||
|                 profile, settings.envKeepDerivations, lockToken)) break;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opInstall(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
 | ||
|         string arg = *i++;
 | ||
|         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
 | ||
|         else if (arg == "--preserve-installed" || arg == "-P")
 | ||
|             globals.preserveInstalled = true;
 | ||
|         else if (arg == "--remove-all" || arg == "-r")
 | ||
|             globals.removeAll = true;
 | ||
|         else throw UsageError(format("unknown flag ‘%1%’") % arg);
 | ||
|     }
 | ||
| 
 | ||
|     installDerivations(globals, opArgs, globals.profile);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType;
 | ||
| 
 | ||
| 
 | ||
| static void upgradeDerivations(Globals & globals,
 | ||
|     const Strings & args, UpgradeType upgradeType)
 | ||
| {
 | ||
|     debug(format("upgrading derivations"));
 | ||
| 
 | ||
|     /* Upgrade works as follows: we take all currently installed
 | ||
|        derivations, and for any derivation matching any selector, look
 | ||
|        for a derivation in the input Nix expression that has the same
 | ||
|        name and a higher version number. */
 | ||
| 
 | ||
|     while (true) {
 | ||
|         string lockToken = optimisticLockProfile(globals.profile);
 | ||
| 
 | ||
|         DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
 | ||
| 
 | ||
|         /* Fetch all derivations from the input file. */
 | ||
|         DrvInfos availElems;
 | ||
|         queryInstSources(*globals.state, globals.instSource, args, availElems, false);
 | ||
| 
 | ||
|         /* Go through all installed derivations. */
 | ||
|         DrvInfos newElems;
 | ||
|         for (auto & i : installedElems) {
 | ||
|             DrvName drvName(i.name);
 | ||
| 
 | ||
|             try {
 | ||
| 
 | ||
|                 if (keep(i)) {
 | ||
|                     newElems.push_back(i);
 | ||
|                     continue;
 | ||
|                 }
 | ||
| 
 | ||
|                 /* Find the derivation in the input Nix expression
 | ||
|                    with the same name that satisfies the version
 | ||
|                    constraints specified by upgradeType.  If there are
 | ||
|                    multiple matches, take the one with the highest
 | ||
|                    priority.  If there are still multiple matches,
 | ||
|                    take the one with the highest version.
 | ||
|                    Do not upgrade if it would decrease the priority. */
 | ||
|                 DrvInfos::iterator bestElem = availElems.end();
 | ||
|                 string bestVersion;
 | ||
|                 for (auto j = availElems.begin(); j != availElems.end(); ++j) {
 | ||
|                     if (comparePriorities(*globals.state, i, *j) > 0)
 | ||
|                         continue;
 | ||
|                     DrvName newName(j->name);
 | ||
|                     if (newName.name == drvName.name) {
 | ||
|                         int d = compareVersions(drvName.version, newName.version);
 | ||
|                         if ((upgradeType == utLt && d < 0) ||
 | ||
|                             (upgradeType == utLeq && d <= 0) ||
 | ||
|                             (upgradeType == utEq && d == 0) ||
 | ||
|                             upgradeType == utAlways)
 | ||
|                         {
 | ||
|                             int d2 = -1;
 | ||
|                             if (bestElem != availElems.end()) {
 | ||
|                                 d2 = comparePriorities(*globals.state, *bestElem, *j);
 | ||
|                                 if (d2 == 0) d2 = compareVersions(bestVersion, newName.version);
 | ||
|                             }
 | ||
|                             if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) {
 | ||
|                                 bestElem = j;
 | ||
|                                 bestVersion = newName.version;
 | ||
|                             }
 | ||
|                         }
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|                 if (bestElem != availElems.end() &&
 | ||
|                     i.queryOutPath() !=
 | ||
|                     bestElem->queryOutPath())
 | ||
|                 {
 | ||
|                     const char * action = compareVersions(drvName.version, bestVersion) <= 0
 | ||
|                         ? "upgrading" : "downgrading";
 | ||
|                     printMsg(lvlInfo,
 | ||
|                         format("%1% ‘%2%’ to ‘%3%’")
 | ||
|                         % action % i.name % bestElem->name);
 | ||
|                     newElems.push_back(*bestElem);
 | ||
|                 } else newElems.push_back(i);
 | ||
| 
 | ||
|             } catch (Error & e) {
 | ||
|                 e.addPrefix(format("while trying to find an upgrade for ‘%1%’:\n") % i.name);
 | ||
|                 throw;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         printMissing(*globals.state, newElems);
 | ||
| 
 | ||
|         if (globals.dryRun) return;
 | ||
| 
 | ||
|         if (createUserEnv(*globals.state, newElems,
 | ||
|                 globals.profile, settings.envKeepDerivations, lockToken)) break;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     UpgradeType upgradeType = utLt;
 | ||
|     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
 | ||
|         string arg = *i++;
 | ||
|         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
 | ||
|         else if (arg == "--lt") upgradeType = utLt;
 | ||
|         else if (arg == "--leq") upgradeType = utLeq;
 | ||
|         else if (arg == "--eq") upgradeType = utEq;
 | ||
|         else if (arg == "--always") upgradeType = utAlways;
 | ||
|         else throw UsageError(format("unknown flag ‘%1%’") % arg);
 | ||
|     }
 | ||
| 
 | ||
|     upgradeDerivations(globals, opArgs, upgradeType);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void setMetaFlag(EvalState & state, DrvInfo & drv,
 | ||
|     const string & name, const string & value)
 | ||
| {
 | ||
|     Value * v = state.allocValue();
 | ||
|     mkString(*v, value.c_str());
 | ||
|     drv.setMeta(name, v);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     if (opArgs.size() < 2)
 | ||
|         throw UsageError("not enough arguments to ‘--set-flag’");
 | ||
| 
 | ||
|     Strings::iterator arg = opArgs.begin();
 | ||
|     string flagName = *arg++;
 | ||
|     string flagValue = *arg++;
 | ||
|     DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
 | ||
| 
 | ||
|     while (true) {
 | ||
|         string lockToken = optimisticLockProfile(globals.profile);
 | ||
| 
 | ||
|         DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
 | ||
| 
 | ||
|         /* Update all matching derivations. */
 | ||
|         for (auto & i : installedElems) {
 | ||
|             DrvName drvName(i.name);
 | ||
|             for (auto & j : selectors)
 | ||
|                 if (j.matches(drvName)) {
 | ||
|                     printMsg(lvlInfo, format("setting flag on ‘%1%’") % i.name);
 | ||
|                     j.hits++;
 | ||
|                     setMetaFlag(*globals.state, i, flagName, flagValue);
 | ||
|                     break;
 | ||
|                 }
 | ||
|         }
 | ||
| 
 | ||
|         checkSelectorUse(selectors);
 | ||
| 
 | ||
|         /* Write the new user environment. */
 | ||
|         if (createUserEnv(*globals.state, installedElems,
 | ||
|                 globals.profile, settings.envKeepDerivations, lockToken)) break;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     auto store2 = globals.state->store.dynamic_pointer_cast<LocalFSStore>();
 | ||
|     if (!store2) throw Error("--set is not supported for this Nix store");
 | ||
| 
 | ||
|     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
 | ||
|         string arg = *i++;
 | ||
|         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
 | ||
|         else throw UsageError(format("unknown flag ‘%1%’") % arg);
 | ||
|     }
 | ||
| 
 | ||
|     DrvInfos elems;
 | ||
|     queryInstSources(*globals.state, globals.instSource, opArgs, elems, true);
 | ||
| 
 | ||
|     if (elems.size() != 1)
 | ||
|         throw Error("--set requires exactly one derivation");
 | ||
| 
 | ||
|     DrvInfo & drv(elems.front());
 | ||
| 
 | ||
|     if (globals.forceName != "")
 | ||
|         drv.name = globals.forceName;
 | ||
| 
 | ||
|     if (drv.queryDrvPath() != "") {
 | ||
|         PathSet paths = {drv.queryDrvPath()};
 | ||
|         printMissing(globals.state->store, paths);
 | ||
|         if (globals.dryRun) return;
 | ||
|         globals.state->store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal);
 | ||
|     }
 | ||
|     else {
 | ||
|         printMissing(globals.state->store, {drv.queryOutPath()});
 | ||
|         if (globals.dryRun) return;
 | ||
|         globals.state->store->ensurePath(drv.queryOutPath());
 | ||
|     }
 | ||
| 
 | ||
|     debug(format("switching to new user environment"));
 | ||
|     Path generation = createGeneration(ref<LocalFSStore>(store2), globals.profile, drv.queryOutPath());
 | ||
|     switchLink(globals.profile, generation);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void uninstallDerivations(Globals & globals, Strings & selectors,
 | ||
|     Path & profile)
 | ||
| {
 | ||
|     while (true) {
 | ||
|         string lockToken = optimisticLockProfile(profile);
 | ||
| 
 | ||
|         DrvInfos installedElems = queryInstalled(*globals.state, profile);
 | ||
|         DrvInfos newElems;
 | ||
| 
 | ||
|         for (auto & i : installedElems) {
 | ||
|             DrvName drvName(i.name);
 | ||
|             bool found = false;
 | ||
|             for (auto & j : selectors)
 | ||
|                 /* !!! the repeated calls to followLinksToStorePath()
 | ||
|                    are expensive, should pre-compute them. */
 | ||
|                 if ((isPath(j) && i.queryOutPath() == globals.state->store->followLinksToStorePath(j))
 | ||
|                     || DrvName(j).matches(drvName))
 | ||
|                 {
 | ||
|                     printMsg(lvlInfo, format("uninstalling ‘%1%’") % i.name);
 | ||
|                     found = true;
 | ||
|                     break;
 | ||
|                 }
 | ||
|             if (!found) newElems.push_back(i);
 | ||
|         }
 | ||
| 
 | ||
|         if (globals.dryRun) return;
 | ||
| 
 | ||
|         if (createUserEnv(*globals.state, newElems,
 | ||
|                 profile, settings.envKeepDerivations, lockToken)) break;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     uninstallDerivations(globals, opArgs, globals.profile);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool cmpChars(char a, char b)
 | ||
| {
 | ||
|     return toupper(a) < toupper(b);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
 | ||
| {
 | ||
|     return lexicographical_compare(
 | ||
|         a.name.begin(), a.name.end(),
 | ||
|         b.name.begin(), b.name.end(), cmpChars);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| typedef list<Strings> Table;
 | ||
| 
 | ||
| 
 | ||
| void printTable(Table & table)
 | ||
| {
 | ||
|     unsigned int nrColumns = table.size() > 0 ? table.front().size() : 0;
 | ||
| 
 | ||
|     vector<unsigned int> widths;
 | ||
|     widths.resize(nrColumns);
 | ||
| 
 | ||
|     for (auto & i : table) {
 | ||
|         assert(i.size() == nrColumns);
 | ||
|         Strings::iterator j;
 | ||
|         unsigned int column;
 | ||
|         for (j = i.begin(), column = 0; j != i.end(); ++j, ++column)
 | ||
|             if (j->size() > widths[column]) widths[column] = j->size();
 | ||
|     }
 | ||
| 
 | ||
|     for (auto & i : table) {
 | ||
|         Strings::iterator j;
 | ||
|         unsigned int column;
 | ||
|         for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
 | ||
|             string s = *j;
 | ||
|             replace(s.begin(), s.end(), '\n', ' ');
 | ||
|             cout << s;
 | ||
|             if (column < nrColumns - 1)
 | ||
|                 cout << string(widths[column] - s.size() + 2, ' ');
 | ||
|         }
 | ||
|         cout << std::endl;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| /* This function compares the version of an element against the
 | ||
|    versions in the given set of elements.  `cvLess' means that only
 | ||
|    lower versions are in the set, `cvEqual' means that at most an
 | ||
|    equal version is in the set, and `cvGreater' means that there is at
 | ||
|    least one element with a higher version in the set.  `cvUnavail'
 | ||
|    means that there are no elements with the same name in the set. */
 | ||
| 
 | ||
| typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
 | ||
| 
 | ||
| static VersionDiff compareVersionAgainstSet(
 | ||
|     const DrvInfo & elem, const DrvInfos & elems, string & version)
 | ||
| {
 | ||
|     DrvName name(elem.name);
 | ||
| 
 | ||
|     VersionDiff diff = cvUnavail;
 | ||
|     version = "?";
 | ||
| 
 | ||
|     for (auto & i : elems) {
 | ||
|         DrvName name2(i.name);
 | ||
|         if (name.name == name2.name) {
 | ||
|             int d = compareVersions(name.version, name2.version);
 | ||
|             if (d < 0) {
 | ||
|                 diff = cvGreater;
 | ||
|                 version = name2.version;
 | ||
|             }
 | ||
|             else if (diff != cvGreater && d == 0) {
 | ||
|                 diff = cvEqual;
 | ||
|                 version = name2.version;
 | ||
|             }
 | ||
|             else if (diff != cvGreater && diff != cvEqual && d > 0) {
 | ||
|                 diff = cvLess;
 | ||
|                 if (version == "" || compareVersions(version, name2.version) < 0)
 | ||
|                     version = name2.version;
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     return diff;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
 | ||
| {
 | ||
|     JSONObject topObj(cout);
 | ||
|     for (auto & i : elems) {
 | ||
|         topObj.attr(i.attrPath);
 | ||
|         JSONObject pkgObj(cout);
 | ||
| 
 | ||
|         pkgObj.attr("name", i.name);
 | ||
|         pkgObj.attr("system", i.system);
 | ||
| 
 | ||
|         pkgObj.attr("meta");
 | ||
|         JSONObject metaObj(cout);
 | ||
|         StringSet metaNames = i.queryMetaNames();
 | ||
|         for (auto & j : metaNames) {
 | ||
|             metaObj.attr(j);
 | ||
|             Value * v = i.queryMeta(j);
 | ||
|             if (!v) {
 | ||
|                 printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j);
 | ||
|                 cout << "null";
 | ||
|             } else {
 | ||
|                 PathSet context;
 | ||
|                 printValueAsJSON(*globals.state, true, *v, cout, context);
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     Strings remaining;
 | ||
|     string attrPath;
 | ||
| 
 | ||
|     bool printStatus = false;
 | ||
|     bool printName = true;
 | ||
|     bool printAttrPath = false;
 | ||
|     bool printSystem = false;
 | ||
|     bool printDrvPath = false;
 | ||
|     bool printOutPath = false;
 | ||
|     bool printDescription = false;
 | ||
|     bool printMeta = false;
 | ||
|     bool compareVersions = false;
 | ||
|     bool xmlOutput = false;
 | ||
|     bool jsonOutput = false;
 | ||
| 
 | ||
|     enum { sInstalled, sAvailable } source = sInstalled;
 | ||
| 
 | ||
|     settings.readOnlyMode = true; /* makes evaluation a bit faster */
 | ||
| 
 | ||
|     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
 | ||
|         string arg = *i++;
 | ||
|         if (arg == "--status" || arg == "-s") printStatus = true;
 | ||
|         else if (arg == "--no-name") printName = false;
 | ||
|         else if (arg == "--system") printSystem = true;
 | ||
|         else if (arg == "--description") printDescription = true;
 | ||
|         else if (arg == "--compare-versions" || arg == "-c") compareVersions = true;
 | ||
|         else if (arg == "--drv-path") printDrvPath = true;
 | ||
|         else if (arg == "--out-path") printOutPath = true;
 | ||
|         else if (arg == "--meta") printMeta = true;
 | ||
|         else if (arg == "--installed") source = sInstalled;
 | ||
|         else if (arg == "--available" || arg == "-a") source = sAvailable;
 | ||
|         else if (arg == "--xml") xmlOutput = true;
 | ||
|         else if (arg == "--json") jsonOutput = true;
 | ||
|         else if (arg == "--attr-path" || arg == "-P") printAttrPath = true;
 | ||
|         else if (arg == "--attr" || arg == "-A")
 | ||
|             attrPath = needArg(i, opFlags, arg);
 | ||
|         else
 | ||
|             throw UsageError(format("unknown flag ‘%1%’") % arg);
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     /* Obtain derivation information from the specified source. */
 | ||
|     DrvInfos availElems, installedElems;
 | ||
| 
 | ||
|     if (source == sInstalled || compareVersions || printStatus)
 | ||
|         installedElems = queryInstalled(*globals.state, globals.profile);
 | ||
| 
 | ||
|     if (source == sAvailable || compareVersions)
 | ||
|         loadDerivations(*globals.state, globals.instSource.nixExprPath,
 | ||
|             globals.instSource.systemFilter, *globals.instSource.autoArgs,
 | ||
|             attrPath, availElems);
 | ||
| 
 | ||
|     DrvInfos elems_ = filterBySelector(*globals.state,
 | ||
|         source == sInstalled ? installedElems : availElems,
 | ||
|         opArgs, false);
 | ||
| 
 | ||
|     DrvInfos & otherElems(source == sInstalled ? availElems : installedElems);
 | ||
| 
 | ||
| 
 | ||
|     /* Sort them by name. */
 | ||
|     /* !!! */
 | ||
|     vector<DrvInfo> elems;
 | ||
|     for (auto & i : elems_) elems.push_back(i);
 | ||
|     sort(elems.begin(), elems.end(), cmpElemByName);
 | ||
| 
 | ||
| 
 | ||
|     /* We only need to know the installed paths when we are querying
 | ||
|        the status of the derivation. */
 | ||
|     PathSet installed; /* installed paths */
 | ||
| 
 | ||
|     if (printStatus) {
 | ||
|         for (auto & i : installedElems)
 | ||
|             installed.insert(i.queryOutPath());
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     /* Query which paths have substitutes. */
 | ||
|     PathSet validPaths, substitutablePaths;
 | ||
|     if (printStatus || globals.prebuiltOnly) {
 | ||
|         PathSet paths;
 | ||
|         for (auto & i : elems)
 | ||
|             try {
 | ||
|                 paths.insert(i.queryOutPath());
 | ||
|             } catch (AssertionError & e) {
 | ||
|                 printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name);
 | ||
|                 i.setFailed();
 | ||
|             }
 | ||
|         validPaths = globals.state->store->queryValidPaths(paths);
 | ||
|         substitutablePaths = globals.state->store->querySubstitutablePaths(paths);
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     /* Print the desired columns, or XML output. */
 | ||
|     if (jsonOutput) {
 | ||
|         queryJSON(globals, elems);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     bool tty = isatty(STDOUT_FILENO);
 | ||
|     RunPager pager;
 | ||
| 
 | ||
|     Table table;
 | ||
|     std::ostringstream dummy;
 | ||
|     XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
 | ||
|     XMLOpenElement xmlRoot(xml, "items");
 | ||
| 
 | ||
|     for (auto & i : elems) {
 | ||
|         try {
 | ||
|             if (i.hasFailed()) continue;
 | ||
| 
 | ||
|             Activity act(*logger, lvlDebug, format("outputting query result ‘%1%’") % i.attrPath);
 | ||
| 
 | ||
|             if (globals.prebuiltOnly &&
 | ||
|                 validPaths.find(i.queryOutPath()) == validPaths.end() &&
 | ||
|                 substitutablePaths.find(i.queryOutPath()) == substitutablePaths.end())
 | ||
|                 continue;
 | ||
| 
 | ||
|             /* For table output. */
 | ||
|             Strings columns;
 | ||
| 
 | ||
|             /* For XML output. */
 | ||
|             XMLAttrs attrs;
 | ||
| 
 | ||
|             if (printStatus) {
 | ||
|                 Path outPath = i.queryOutPath();
 | ||
|                 bool hasSubs = substitutablePaths.find(outPath) != substitutablePaths.end();
 | ||
|                 bool isInstalled = installed.find(outPath) != installed.end();
 | ||
|                 bool isValid = validPaths.find(outPath) != validPaths.end();
 | ||
|                 if (xmlOutput) {
 | ||
|                     attrs["installed"] = isInstalled ? "1" : "0";
 | ||
|                     attrs["valid"] = isValid ? "1" : "0";
 | ||
|                     attrs["substitutable"] = hasSubs ? "1" : "0";
 | ||
|                 } else
 | ||
|                     columns.push_back(
 | ||
|                         (string) (isInstalled ? "I" : "-")
 | ||
|                         + (isValid ? "P" : "-")
 | ||
|                         + (hasSubs ? "S" : "-"));
 | ||
|             }
 | ||
| 
 | ||
|             if (xmlOutput)
 | ||
|                 attrs["attrPath"] = i.attrPath;
 | ||
|             else if (printAttrPath)
 | ||
|                 columns.push_back(i.attrPath);
 | ||
| 
 | ||
|             if (xmlOutput)
 | ||
|                 attrs["name"] = i.name;
 | ||
|             else if (printName)
 | ||
|                 columns.push_back(i.name);
 | ||
| 
 | ||
|             if (compareVersions) {
 | ||
|                 /* Compare this element against the versions of the
 | ||
|                    same named packages in either the set of available
 | ||
|                    elements, or the set of installed elements.  !!!
 | ||
|                    This is O(N * M), should be O(N * lg M). */
 | ||
|                 string version;
 | ||
|                 VersionDiff diff = compareVersionAgainstSet(i, otherElems, version);
 | ||
| 
 | ||
|                 char ch;
 | ||
|                 switch (diff) {
 | ||
|                     case cvLess: ch = '>'; break;
 | ||
|                     case cvEqual: ch = '='; break;
 | ||
|                     case cvGreater: ch = '<'; break;
 | ||
|                     case cvUnavail: ch = '-'; break;
 | ||
|                     default: abort();
 | ||
|                 }
 | ||
| 
 | ||
|                 if (xmlOutput) {
 | ||
|                     if (diff != cvUnavail) {
 | ||
|                         attrs["versionDiff"] = ch;
 | ||
|                         attrs["maxComparedVersion"] = version;
 | ||
|                     }
 | ||
|                 } else {
 | ||
|                     string column = (string) "" + ch + " " + version;
 | ||
|                     if (diff == cvGreater && tty)
 | ||
|                         column = ANSI_RED + column + ANSI_NORMAL;
 | ||
|                     columns.push_back(column);
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             if (xmlOutput) {
 | ||
|                 if (i.system != "") attrs["system"] = i.system;
 | ||
|             }
 | ||
|             else if (printSystem)
 | ||
|                 columns.push_back(i.system);
 | ||
| 
 | ||
|             if (printDrvPath) {
 | ||
|                 string drvPath = i.queryDrvPath();
 | ||
|                 if (xmlOutput) {
 | ||
|                     if (drvPath != "") attrs["drvPath"] = drvPath;
 | ||
|                 } else
 | ||
|                     columns.push_back(drvPath == "" ? "-" : drvPath);
 | ||
|             }
 | ||
| 
 | ||
|             if (printOutPath && !xmlOutput) {
 | ||
|                 DrvInfo::Outputs outputs = i.queryOutputs();
 | ||
|                 string s;
 | ||
|                 for (auto & j : outputs) {
 | ||
|                     if (!s.empty()) s += ';';
 | ||
|                     if (j.first != "out") { s += j.first; s += "="; }
 | ||
|                     s += j.second;
 | ||
|                 }
 | ||
|                 columns.push_back(s);
 | ||
|             }
 | ||
| 
 | ||
|             if (printDescription) {
 | ||
|                 string descr = i.queryMetaString("description");
 | ||
|                 if (xmlOutput) {
 | ||
|                     if (descr != "") attrs["description"] = descr;
 | ||
|                 } else
 | ||
|                     columns.push_back(descr);
 | ||
|             }
 | ||
| 
 | ||
|             if (xmlOutput) {
 | ||
|                 if (printOutPath || printMeta) {
 | ||
|                     XMLOpenElement item(xml, "item", attrs);
 | ||
|                     if (printOutPath) {
 | ||
|                         DrvInfo::Outputs outputs = i.queryOutputs();
 | ||
|                         for (auto & j : outputs) {
 | ||
|                             XMLAttrs attrs2;
 | ||
|                             attrs2["name"] = j.first;
 | ||
|                             attrs2["path"] = j.second;
 | ||
|                             xml.writeEmptyElement("output", attrs2);
 | ||
|                         }
 | ||
|                     }
 | ||
|                     if (printMeta) {
 | ||
|                         StringSet metaNames = i.queryMetaNames();
 | ||
|                         for (auto & j : metaNames) {
 | ||
|                             XMLAttrs attrs2;
 | ||
|                             attrs2["name"] = j;
 | ||
|                             Value * v = i.queryMeta(j);
 | ||
|                             if (!v)
 | ||
|                                 printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j);
 | ||
|                             else {
 | ||
|                                 if (v->type == tString) {
 | ||
|                                     attrs2["type"] = "string";
 | ||
|                                     attrs2["value"] = v->string.s;
 | ||
|                                     xml.writeEmptyElement("meta", attrs2);
 | ||
|                                 } else if (v->type == tInt) {
 | ||
|                                     attrs2["type"] = "int";
 | ||
|                                     attrs2["value"] = (format("%1%") % v->integer).str();
 | ||
|                                     xml.writeEmptyElement("meta", attrs2);
 | ||
|                                 } else if (v->type == tFloat) {
 | ||
|                                     attrs2["type"] = "float";
 | ||
|                                     attrs2["value"] = (format("%1%") % v->fpoint).str();
 | ||
|                                     xml.writeEmptyElement("meta", attrs2);
 | ||
|                                 } else if (v->type == tBool) {
 | ||
|                                     attrs2["type"] = "bool";
 | ||
|                                     attrs2["value"] = v->boolean ? "true" : "false";
 | ||
|                                     xml.writeEmptyElement("meta", attrs2);
 | ||
|                                 } else if (v->isList()) {
 | ||
|                                     attrs2["type"] = "strings";
 | ||
|                                     XMLOpenElement m(xml, "meta", attrs2);
 | ||
|                                     for (unsigned int j = 0; j < v->listSize(); ++j) {
 | ||
|                                         if (v->listElems()[j]->type != tString) continue;
 | ||
|                                         XMLAttrs attrs3;
 | ||
|                                         attrs3["value"] = v->listElems()[j]->string.s;
 | ||
|                                         xml.writeEmptyElement("string", attrs3);
 | ||
|                                     }
 | ||
|                               } else if (v->type == tAttrs) {
 | ||
|                                   attrs2["type"] = "strings";
 | ||
|                                   XMLOpenElement m(xml, "meta", attrs2);
 | ||
|                                   Bindings & attrs = *v->attrs;
 | ||
|                                   for (auto &i : attrs) {
 | ||
|                                       Attr & a(*attrs.find(i.name));
 | ||
|                                       if(a.value->type != tString) continue;
 | ||
|                                       XMLAttrs attrs3;
 | ||
|                                       attrs3["type"] = i.name;
 | ||
|                                       attrs3["value"] = a.value->string.s;
 | ||
|                                       xml.writeEmptyElement("string", attrs3);
 | ||
|                                 }
 | ||
|                               }
 | ||
|                             }
 | ||
|                         }
 | ||
|                     }
 | ||
|                 } else
 | ||
|                     xml.writeEmptyElement("item", attrs);
 | ||
|             } else
 | ||
|                 table.push_back(columns);
 | ||
| 
 | ||
|             cout.flush();
 | ||
| 
 | ||
|         } catch (AssertionError & e) {
 | ||
|             printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name);
 | ||
|         } catch (Error & e) {
 | ||
|             e.addPrefix(format("while querying the derivation named ‘%1%’:\n") % i.name);
 | ||
|             throw;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     if (!xmlOutput) printTable(table);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     if (opArgs.size() != 1)
 | ||
|         throw UsageError(format("exactly one argument expected"));
 | ||
| 
 | ||
|     Path profile = absPath(opArgs.front());
 | ||
|     Path profileLink = getHomeDir() + "/.nix-profile";
 | ||
| 
 | ||
|     switchLink(profileLink, profile);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static const int prevGen = -2;
 | ||
| 
 | ||
| 
 | ||
| static void switchGeneration(Globals & globals, int dstGen)
 | ||
| {
 | ||
|     PathLocks lock;
 | ||
|     lockProfile(lock, globals.profile);
 | ||
| 
 | ||
|     int curGen;
 | ||
|     Generations gens = findGenerations(globals.profile, curGen);
 | ||
| 
 | ||
|     Generation dst;
 | ||
|     for (auto & i : gens)
 | ||
|         if ((dstGen == prevGen && i.number < curGen) ||
 | ||
|             (dstGen >= 0 && i.number == dstGen))
 | ||
|             dst = i;
 | ||
| 
 | ||
|     if (!dst) {
 | ||
|         if (dstGen == prevGen)
 | ||
|             throw Error(format("no generation older than the current (%1%) exists")
 | ||
|                 % curGen);
 | ||
|         else
 | ||
|             throw Error(format("generation %1% does not exist") % dstGen);
 | ||
|     }
 | ||
| 
 | ||
|     printMsg(lvlInfo, format("switching from generation %1% to %2%")
 | ||
|         % curGen % dst.number);
 | ||
| 
 | ||
|     if (globals.dryRun) return;
 | ||
| 
 | ||
|     switchLink(globals.profile, dst.path);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     if (opArgs.size() != 1)
 | ||
|         throw UsageError(format("exactly one argument expected"));
 | ||
| 
 | ||
|     int dstGen;
 | ||
|     if (!string2Int(opArgs.front(), dstGen))
 | ||
|         throw UsageError(format("expected a generation number"));
 | ||
| 
 | ||
|     switchGeneration(globals, dstGen);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     if (opArgs.size() != 0)
 | ||
|         throw UsageError(format("no arguments expected"));
 | ||
| 
 | ||
|     switchGeneration(globals, prevGen);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
|     if (opArgs.size() != 0)
 | ||
|         throw UsageError(format("no arguments expected"));
 | ||
| 
 | ||
|     PathLocks lock;
 | ||
|     lockProfile(lock, globals.profile);
 | ||
| 
 | ||
|     int curGen;
 | ||
|     Generations gens = findGenerations(globals.profile, curGen);
 | ||
| 
 | ||
|     RunPager pager;
 | ||
| 
 | ||
|     for (auto & i : gens) {
 | ||
|         tm t;
 | ||
|         if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
 | ||
|         cout << format("%|4|   %|4|-%|02|-%|02| %|02|:%|02|:%|02|   %||\n")
 | ||
|             % i.number
 | ||
|             % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday
 | ||
|             % t.tm_hour % t.tm_min % t.tm_sec
 | ||
|             % (i.number == curGen ? "(current)" : "");
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     if (opFlags.size() > 0)
 | ||
|         throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
 | ||
| 
 | ||
|     if (opArgs.size() == 1 && opArgs.front() == "old") {
 | ||
|         deleteOldGenerations(globals.profile, globals.dryRun);
 | ||
|     } else if (opArgs.size() == 1 && opArgs.front().find('d') != string::npos) {
 | ||
|         deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
 | ||
|     } else {
 | ||
|         std::set<unsigned int> gens;
 | ||
|         for (auto & i : opArgs) {
 | ||
|             unsigned int n;
 | ||
|             if (!string2Int(i, n))
 | ||
|                 throw UsageError(format("invalid generation number ‘%1%’") % i);
 | ||
|             gens.insert(n);
 | ||
|         }
 | ||
|         deleteGenerations(globals.profile, gens, globals.dryRun);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static void opVersion(Globals & globals, Strings opFlags, Strings opArgs)
 | ||
| {
 | ||
|     printVersion("nix-env");
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| int main(int argc, char * * argv)
 | ||
| {
 | ||
|     return handleExceptions(argv[0], [&]() {
 | ||
|         initNix();
 | ||
|         initGC();
 | ||
| 
 | ||
|         Strings opFlags, opArgs, searchPath;
 | ||
|         std::map<string, string> autoArgs_;
 | ||
|         Operation op = 0;
 | ||
|         bool repair = false;
 | ||
|         string file;
 | ||
| 
 | ||
|         Globals globals;
 | ||
| 
 | ||
|         globals.instSource.type = srcUnknown;
 | ||
|         globals.instSource.nixExprPath = getDefNixExprPath();
 | ||
|         globals.instSource.systemFilter = "*";
 | ||
| 
 | ||
|         globals.dryRun = false;
 | ||
|         globals.preserveInstalled = false;
 | ||
|         globals.removeAll = false;
 | ||
|         globals.prebuiltOnly = false;
 | ||
| 
 | ||
|         parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
 | ||
|             Operation oldOp = op;
 | ||
| 
 | ||
|             if (*arg == "--help")
 | ||
|                 showManPage("nix-env");
 | ||
|             else if (*arg == "--version")
 | ||
|                 op = opVersion;
 | ||
|             else if (*arg == "--install" || *arg == "-i")
 | ||
|                 op = opInstall;
 | ||
|             else if (parseAutoArgs(arg, end, autoArgs_))
 | ||
|                 ;
 | ||
|             else if (parseSearchPathArg(arg, end, searchPath))
 | ||
|                 ;
 | ||
|             else if (*arg == "--force-name") // undocumented flag for nix-install-package
 | ||
|                 globals.forceName = getArg(*arg, arg, end);
 | ||
|             else if (*arg == "--uninstall" || *arg == "-e")
 | ||
|                 op = opUninstall;
 | ||
|             else if (*arg == "--upgrade" || *arg == "-u")
 | ||
|                 op = opUpgrade;
 | ||
|             else if (*arg == "--set-flag")
 | ||
|                 op = opSetFlag;
 | ||
|             else if (*arg == "--set")
 | ||
|                 op = opSet;
 | ||
|             else if (*arg == "--query" || *arg == "-q")
 | ||
|                 op = opQuery;
 | ||
|             else if (*arg == "--profile" || *arg == "-p")
 | ||
|                 globals.profile = absPath(getArg(*arg, arg, end));
 | ||
|             else if (*arg == "--file" || *arg == "-f")
 | ||
|                 file = getArg(*arg, arg, end);
 | ||
|             else if (*arg == "--switch-profile" || *arg == "-S")
 | ||
|                 op = opSwitchProfile;
 | ||
|             else if (*arg == "--switch-generation" || *arg == "-G")
 | ||
|                 op = opSwitchGeneration;
 | ||
|             else if (*arg == "--rollback")
 | ||
|                 op = opRollback;
 | ||
|             else if (*arg == "--list-generations")
 | ||
|                 op = opListGenerations;
 | ||
|             else if (*arg == "--delete-generations")
 | ||
|                 op = opDeleteGenerations;
 | ||
|             else if (*arg == "--dry-run") {
 | ||
|                 printMsg(lvlInfo, "(dry run; not doing anything)");
 | ||
|                 globals.dryRun = true;
 | ||
|             }
 | ||
|             else if (*arg == "--system-filter")
 | ||
|                 globals.instSource.systemFilter = getArg(*arg, arg, end);
 | ||
|             else if (*arg == "--prebuilt-only" || *arg == "-b")
 | ||
|                 globals.prebuiltOnly = true;
 | ||
|             else if (*arg == "--repair")
 | ||
|                 repair = true;
 | ||
|             else if (*arg != "" && arg->at(0) == '-') {
 | ||
|                 opFlags.push_back(*arg);
 | ||
|                 /* FIXME: hacky */
 | ||
|                 if (*arg == "--from-profile" ||
 | ||
|                     (op == opQuery && (*arg == "--attr" || *arg == "-A")))
 | ||
|                     opFlags.push_back(getArg(*arg, arg, end));
 | ||
|             }
 | ||
|             else
 | ||
|                 opArgs.push_back(*arg);
 | ||
| 
 | ||
|             if (oldOp && oldOp != op)
 | ||
|                 throw UsageError("only one operation may be specified");
 | ||
| 
 | ||
|             return true;
 | ||
|         });
 | ||
| 
 | ||
|         if (!op) throw UsageError("no operation specified");
 | ||
| 
 | ||
|         auto store = openStore();
 | ||
| 
 | ||
|         globals.state = std::shared_ptr<EvalState>(new EvalState(searchPath, store));
 | ||
|         globals.state->repair = repair;
 | ||
| 
 | ||
|         if (file != "")
 | ||
|             globals.instSource.nixExprPath = lookupFileArg(*globals.state, file);
 | ||
| 
 | ||
|         globals.instSource.autoArgs = evalAutoArgs(*globals.state, autoArgs_);
 | ||
| 
 | ||
|         if (globals.profile == "")
 | ||
|             globals.profile = getEnv("NIX_PROFILE", "");
 | ||
| 
 | ||
|         if (globals.profile == "") {
 | ||
|             Path profileLink = getHomeDir() + "/.nix-profile";
 | ||
|             globals.profile = pathExists(profileLink)
 | ||
|                 ? absPath(readLink(profileLink), dirOf(profileLink))
 | ||
|                 : canonPath(settings.nixStateDir + "/profiles/default");
 | ||
|         }
 | ||
| 
 | ||
|         op(globals, opFlags, opArgs);
 | ||
| 
 | ||
|         globals.state->printStats();
 | ||
|     });
 | ||
| }
 |