aboutsummaryrefslogtreecommitdiff
path: root/src/legacy/nix-env.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/legacy/nix-env.cc')
-rw-r--r--src/legacy/nix-env.cc1553
1 files changed, 1553 insertions, 0 deletions
diff --git a/src/legacy/nix-env.cc b/src/legacy/nix-env.cc
new file mode 100644
index 000000000..4674fd783
--- /dev/null
+++ b/src/legacy/nix-env.cc
@@ -0,0 +1,1553 @@
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "terminal.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "globals.hh"
+#include "names.hh"
+#include "profiles.hh"
+#include "path-with-outputs.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "local-fs-store.hh"
+#include "user-env.hh"
+#include "users.hh"
+#include "value-to-json.hh"
+#include "xml-writer.hh"
+#include "legacy.hh"
+#include "eval-settings.hh" // for defexpr
+#include "nix-env.hh"
+
+#include <ctime>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <nlohmann/json.hpp>
+
+using std::cout;
+
+namespace nix {
+
+
+typedef enum {
+ srcNixExprDrvs,
+ srcNixExprs,
+ srcStorePaths,
+ srcProfile,
+ srcAttrPath,
+ srcUnknown
+} InstallSourceType;
+
+
+struct InstallSourceInfo
+{
+ InstallSourceType type;
+ std::shared_ptr<SourcePath> nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
+ Path profile; /* for srcProfile */
+ std::string systemFilter; /* for srcNixExprDrvs */
+ Bindings * autoArgs;
+};
+
+
+struct Globals
+{
+ InstallSourceInfo instSource;
+ Path profile;
+ std::shared_ptr<EvalState> state;
+ bool dryRun;
+ bool preserveInstalled;
+ bool removeAll;
+ std::string forceName;
+ bool prebuiltOnly;
+};
+
+
+typedef void (* Operation) (Globals & globals,
+ Strings opFlags, Strings opArgs);
+
+
+static std::string needArg(Strings::iterator & i,
+ Strings & args, const std::string & arg)
+{
+ if (i == args.end()) throw UsageError("'%1%' requires an argument", arg);
+ return *i++;
+}
+
+
+static bool parseInstallSourceOptions(Globals & globals,
+ Strings::iterator & i, Strings & args, const std::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 SourcePath & path, struct InputAccessor::Stat & st)
+{
+ return
+ st.type == InputAccessor::tRegular
+ || (st.type == InputAccessor::tDirectory && (path + "default.nix").pathExists());
+}
+
+
+static constexpr size_t maxAttrs = 1024;
+
+
+static void getAllExprs(EvalState & state,
+ const SourcePath & path, StringSet & seen, BindingsBuilder & attrs)
+{
+ StringSet namesSorted;
+ for (auto & [name, _] : path.readDirectory()) namesSorted.insert(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;
+
+ SourcePath path2 = path + i;
+
+ InputAccessor::Stat st;
+ try {
+ st = path2.resolveSymlinks().lstat();
+ } catch (Error &) {
+ continue; // ignore dangling symlinks in ~/.nix-defexpr
+ }
+
+ if (isNixExpr(path2, st) && (st.type != InputAccessor::tRegular || path2.baseName().ends_with(".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. */
+ std::string attrName = i;
+ if (attrName.ends_with(".nix"))
+ attrName = std::string(attrName, 0, attrName.size() - 4);
+ if (!seen.insert(attrName).second) {
+ std::string suggestionMessage = "";
+ if (path2.path.abs().find("channels") != std::string::npos && path.path.abs().find("channels") != std::string::npos)
+ suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName);
+ printError("warning: name collision in input Nix expressions, skipping '%1%'"
+ "%2%", path2, suggestionMessage);
+ continue;
+ }
+ /* Load the expression on demand. */
+ auto vArg = state.allocValue();
+ vArg->mkString(path2.path.abs());
+ if (seen.size() == maxAttrs)
+ throw Error("too many Nix expressions in directory '%1%'", path);
+ attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg);
+ }
+ else if (st.type == InputAccessor::tDirectory)
+ /* `path2' is a directory (with no default.nix in it);
+ recurse into it. */
+ getAllExprs(state, path2, seen, attrs);
+ }
+}
+
+
+
+static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v)
+{
+ auto st = path.resolveSymlinks().lstat();
+
+ if (isNixExpr(path, st))
+ state.evalFile(path, v);
+
+ /* 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). */
+ else if (st.type == InputAccessor::tDirectory) {
+ auto attrs = state.buildBindings(maxAttrs);
+ attrs.alloc("_combineChannels").mkList(0);
+ StringSet seen;
+ getAllExprs(state, path, seen, attrs);
+ v.mkAttrs(attrs);
+ }
+
+ else throw Error("path '%s' is not a directory or a Nix expression", path);
+}
+
+
+static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
+ std::string systemFilter, Bindings & autoArgs,
+ const std::string & pathPrefix, DrvInfos & elems)
+{
+ Value vRoot;
+ loadSourceExpr(state, nixExprPath, vRoot);
+
+ Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
+
+ 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->querySystem() != systemFilter)
+ elems.erase(i);
+ }
+}
+
+
+static NixInt getPriority(EvalState & state, DrvInfo & drv)
+{
+ return drv.queryMetaInt("priority", NixInt(0));
+}
+
+
+static std::strong_ordering 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)
+{
+ auto path = elem.queryOutPath();
+ if (state.store->isValidPath(path)) return true;
+ return state.store->querySubstitutablePaths({path}).count(path);
+}
+
+
+static void checkSelectorUse(DrvNames & selectors)
+{
+ /* Check that all selectors have been used. */
+ for (auto & i : selectors)
+ if (i.hits == 0 && i.fullName != "*")
+ throw Error("selector '%1%' matches no derivations", i.fullName);
+}
+
+
+namespace {
+
+std::set<std::string> searchByPrefix(DrvInfos & allElems, std::string_view prefix) {
+ constexpr std::size_t maxResults = 3;
+ std::set<std::string> result;
+ for (auto & drvInfo : allElems) {
+ const auto drvName = DrvName { drvInfo.queryName() };
+ if (drvName.name.starts_with(prefix)) {
+ result.emplace(drvName.name);
+
+ if (result.size() >= maxResults) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+struct Match
+{
+ DrvInfo drvInfo;
+ std::size_t index;
+
+ Match(DrvInfo drvInfo_, std::size_t index_)
+ : drvInfo{std::move(drvInfo_)}
+ , index{index_}
+ {}
+};
+
+/* 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. */
+std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches) {
+ /* Map from package names to derivations. */
+ std::map<std::string, Match> newest;
+ StringSet multiple;
+
+ for (auto & match : matches) {
+ auto & oneDrv = match.drvInfo;
+
+ const auto drvName = DrvName { oneDrv.queryName() };
+ std::strong_ordering comparison = std::strong_ordering::greater;
+
+ const auto itOther = newest.find(drvName.name);
+
+ if (itOther != newest.end()) {
+ auto & newestDrv = itOther->second.drvInfo;
+
+ comparison =
+ oneDrv.querySystem() == newestDrv.querySystem() ? std::strong_ordering::equal :
+ oneDrv.querySystem() == settings.thisSystem ? std::strong_ordering::greater :
+ newestDrv.querySystem() == settings.thisSystem ? std::strong_ordering::less : std::strong_ordering::equal;
+ if (comparison == 0)
+ comparison = comparePriorities(state, oneDrv, newestDrv);
+ if (comparison == 0)
+ comparison = compareVersions(drvName.version, DrvName { newestDrv.queryName() }.version);
+ }
+
+ if (comparison > 0) {
+ newest.erase(drvName.name);
+ newest.emplace(drvName.name, match);
+ multiple.erase(drvName.fullName);
+ } else if (comparison == 0) {
+ multiple.insert(drvName.fullName);
+ }
+ }
+
+ matches.clear();
+ for (auto & [name, match] : newest) {
+ if (multiple.find(name) != multiple.end())
+ warn(
+ "there are multiple derivations named '%1%'; using the first one",
+ name);
+ matches.push_back(match);
+ }
+
+ return matches;
+}
+
+} // end namespace
+
+static DrvInfos filterBySelector(EvalState & state, DrvInfos & allElems,
+ const Strings & args, bool newestOnly)
+{
+ DrvNames selectors = drvNamesFromArgs(args);
+ if (selectors.empty())
+ selectors.emplace_back("*");
+
+ DrvInfos elems;
+ std::set<std::size_t> done;
+
+ for (auto & selector : selectors) {
+ std::vector<Match> matches;
+ for (auto && [index, drvInfo] : enumerate(allElems)) {
+ const auto drvName = DrvName { drvInfo.queryName() };
+ if (selector.matches(drvName)) {
+ ++selector.hits;
+ matches.emplace_back(drvInfo, index);
+ }
+ }
+
+ if (newestOnly) {
+ matches = pickNewestOnly(state, std::move(matches));
+ }
+
+ /* Insert only those elements in the final list that we
+ haven't inserted before. */
+ for (auto & match : matches)
+ if (done.insert(match.index).second)
+ elems.push_back(match.drvInfo);
+
+ if (selector.hits == 0 && selector.fullName != "*") {
+ const auto prefixHits = searchByPrefix(allElems, selector.name);
+
+ if (prefixHits.empty()) {
+ throw Error("selector '%1%' matches no derivations", selector.fullName);
+ } else {
+ std::string suggestionMessage = ", maybe you meant:";
+ for (const auto & drvName : prefixHits) {
+ suggestionMessage += fmt("\n%s", drvName);
+ }
+ throw Error("selector '%1%' matches no derivations" + suggestionMessage, selector.fullName);
+ }
+ }
+ }
+
+ return elems;
+}
+
+
+static bool isPath(std::string_view s)
+{
+ return s.find('/') != std::string_view::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, state.rootPath(CanonPath::fromCwd()));
+ Value vFun, vTmp;
+ state.eval(eFun, vFun);
+ vTmp.mkApp(&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) {
+ auto path = state.store->followLinksToStorePath(i);
+
+ std::string name(path.name());
+
+ DrvInfo elem(state, "", nullptr);
+ elem.setName(name);
+
+ if (path.isDerivation()) {
+ elem.setDrvPath(path);
+ auto outputs = state.store->queryDerivationOutputMap(path);
+ elem.setOutPath(outputs.at("out"));
+ if (name.size() >= drvExtension.size() &&
+ std::string(name, name.size() - drvExtension.size()) == drvExtension)
+ name = name.substr(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: {
+ auto installed = queryInstalled(state, instSource.profile);
+ elems = filterBySelector(
+ state,
+ installed,
+ args, newestOnly
+ );
+ break;
+ }
+
+ case srcAttrPath: {
+ Value vRoot;
+ loadSourceExpr(state, *instSource.nixExprPath, vRoot);
+ for (auto & i : args) {
+ Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
+ getDerivations(state, v, "", *instSource.autoArgs, elems, true);
+ }
+ break;
+ }
+ }
+}
+
+
+static void printMissing(EvalState & state, DrvInfos & elems)
+{
+ std::vector<DerivedPath> targets;
+ for (auto & i : elems)
+ if (auto drvPath = i.queryDrvPath())
+ targets.emplace_back(DerivedPath::Built{
+ .drvPath = makeConstantStorePathRef(*drvPath),
+ .outputs = OutputsSpec::All { },
+ });
+ else
+ targets.emplace_back(DerivedPath::Opaque{
+ .path = 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("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.setName(globals.forceName);
+ newNames.insert(DrvName(i.queryName()).name);
+ }
+
+
+ while (true) {
+ auto 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.queryName());
+ if (!globals.preserveInstalled &&
+ newNames.find(drvName.name) != newNames.end() &&
+ !keep(i))
+ printInfo("replacing old '%s'", i.queryName());
+ else
+ allElems.push_back(i);
+ }
+
+ for (auto & i : newElems)
+ printInfo("installing '%s'", i.queryName());
+ }
+
+ 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(); ) {
+ auto 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("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("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) {
+ auto 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.queryName());
+
+ 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();
+ std::string bestVersion;
+ for (auto j = availElems.begin(); j != availElems.end(); ++j) {
+ if (comparePriorities(*globals.state, i, *j) > 0)
+ continue;
+ DrvName newName(j->queryName());
+ if (newName.name == drvName.name) {
+ std::strong_ordering d = compareVersions(drvName.version, newName.version);
+ if ((upgradeType == utLt && d < 0) ||
+ (upgradeType == utLeq && d <= 0) ||
+ (upgradeType == utEq && d == 0) ||
+ upgradeType == utAlways)
+ {
+ std::strong_ordering d2 = std::strong_ordering::less;
+ 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";
+ printInfo("%1% '%2%' to '%3%'",
+ action, i.queryName(), bestElem->queryName());
+ newElems.push_back(*bestElem);
+ } else newElems.push_back(i);
+
+ } catch (Error & e) {
+ e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName());
+ 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(); ) {
+ std::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("unknown flag '%1%'", arg);
+ }
+
+ upgradeDerivations(globals, opArgs, upgradeType);
+}
+
+
+static void setMetaFlag(EvalState & state, DrvInfo & drv,
+ const std::string & name, const std::string & value)
+{
+ auto v = state.allocValue();
+ v->mkString(value);
+ drv.setMeta(name, v);
+}
+
+
+static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("unknown flag '%1%'", opFlags.front());
+ if (opArgs.size() < 2)
+ throw UsageError("not enough arguments to '--set-flag'");
+
+ Strings::iterator arg = opArgs.begin();
+ std::string flagName = *arg++;
+ std::string flagValue = *arg++;
+ DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
+
+ while (true) {
+ std::string lockToken = optimisticLockProfile(globals.profile);
+
+ DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
+
+ /* Update all matching derivations. */
+ for (auto & i : installedElems) {
+ DrvName drvName(i.queryName());
+ for (auto & j : selectors)
+ if (j.matches(drvName)) {
+ printInfo("setting flag on '%1%'", i.queryName());
+ 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(); ) {
+ std::string arg = *i++;
+ if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
+ else throw UsageError("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.setName(globals.forceName);
+
+ auto drvPath = drv.queryDrvPath();
+ std::vector<DerivedPath> paths {
+ drvPath
+ ? (DerivedPath) (DerivedPath::Built {
+ .drvPath = makeConstantStorePathRef(*drvPath),
+ .outputs = OutputsSpec::All { },
+ })
+ : (DerivedPath) (DerivedPath::Opaque {
+ .path = drv.queryOutPath(),
+ }),
+ };
+ printMissing(globals.state->store, paths);
+ if (globals.dryRun) return;
+ globals.state->store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal);
+
+ debug("switching to new user environment");
+ Path generation = createGeneration(
+ *store2,
+ globals.profile,
+ drv.queryOutPath());
+ switchLink(globals.profile, generation);
+}
+
+
+static void uninstallDerivations(Globals & globals, Strings & selectors,
+ Path & profile)
+{
+ while (true) {
+ auto lockToken = optimisticLockProfile(profile);
+
+ DrvInfos workingElems = queryInstalled(*globals.state, profile);
+
+ for (auto & selector : selectors) {
+ DrvInfos::iterator split = workingElems.begin();
+ if (isPath(selector)) {
+ StorePath selectorStorePath = globals.state->store->followLinksToStorePath(selector);
+ split = std::partition(
+ workingElems.begin(), workingElems.end(),
+ [&selectorStorePath, globals](auto &elem) {
+ return selectorStorePath != elem.queryOutPath();
+ }
+ );
+ } else {
+ DrvName selectorName(selector);
+ split = std::partition(
+ workingElems.begin(), workingElems.end(),
+ [&selectorName](auto &elem){
+ DrvName elemName(elem.queryName());
+ return !selectorName.matches(elemName);
+ }
+ );
+ }
+ if (split == workingElems.end())
+ warn("selector '%s' matched no installed derivations", selector);
+ for (auto removedElem = split; removedElem != workingElems.end(); removedElem++) {
+ printInfo("uninstalling '%s'", removedElem->queryName());
+ }
+ workingElems.erase(split, workingElems.end());
+ }
+
+ if (globals.dryRun) return;
+
+ if (createUserEnv(*globals.state, workingElems,
+ profile, settings.envKeepDerivations, lockToken)) break;
+ }
+}
+
+
+static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("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(DrvInfo & a, DrvInfo & b)
+{
+ auto a_name = a.queryName();
+ auto b_name = b.queryName();
+ return lexicographical_compare(
+ a_name.begin(), a_name.end(),
+ b_name.begin(), b_name.end(), cmpChars);
+}
+
+
+typedef std::list<Strings> Table;
+
+
+void printTable(Table & table)
+{
+ auto nrColumns = table.size() > 0 ? table.front().size() : 0;
+
+ std::vector<size_t> widths;
+ widths.resize(nrColumns);
+
+ for (auto & i : table) {
+ assert(i.size() == nrColumns);
+ Strings::iterator j;
+ size_t 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;
+ size_t column;
+ for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
+ std::string s = *j;
+ replace(s.begin(), s.end(), '\n', ' ');
+ cout << s;
+ if (column < nrColumns - 1)
+ cout << std::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(
+ DrvInfo & elem, DrvInfos & elems, std::string & version)
+{
+ DrvName name(elem.queryName());
+
+ VersionDiff diff = cvUnavail;
+ version = "?";
+
+ for (auto & i : elems) {
+ DrvName name2(i.queryName());
+ if (name.name == name2.name) {
+ std::strong_ordering 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, std::vector<DrvInfo> & elems, bool printOutPath, bool printDrvPath, bool printMeta)
+{
+ using nlohmann::json;
+ json topObj = json::object();
+ for (auto & i : elems) {
+ try {
+ if (i.hasFailed()) continue;
+
+
+ auto drvName = DrvName(i.queryName());
+ json &pkgObj = topObj[i.attrPath];
+ pkgObj = {
+ {"name", drvName.fullName},
+ {"pname", drvName.name},
+ {"version", drvName.version},
+ {"system", i.querySystem()},
+ {"outputName", i.queryOutputName()},
+ };
+
+ {
+ DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
+ json &outputObj = pkgObj["outputs"];
+ outputObj = json::object();
+ for (auto & j : outputs) {
+ if (j.second)
+ outputObj[j.first] = globals.state->store->printStorePath(*j.second);
+ else
+ outputObj[j.first] = nullptr;
+ }
+ }
+
+ if (printDrvPath) {
+ auto drvPath = i.queryDrvPath();
+ if (drvPath) pkgObj["drvPath"] = globals.state->store->printStorePath(*drvPath);
+ }
+
+ if (printMeta) {
+ json &metaObj = pkgObj["meta"];
+ metaObj = json::object();
+ StringSet metaNames = i.queryMetaNames();
+ for (auto & j : metaNames) {
+ Value * v = i.queryMeta(j);
+ if (!v) {
+ printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
+ metaObj[j] = nullptr;
+ } else {
+ NixStringContext context;
+ metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context);
+ }
+ }
+ }
+ } catch (AssertionError & e) {
+ printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
+ } catch (Error & e) {
+ e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
+ throw;
+ }
+ }
+ std::cout << topObj.dump(2);
+}
+
+
+static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ auto & store { *globals.state->store };
+
+ Strings remaining;
+ std::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(); ) {
+ auto 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("unknown flag '%1%'", arg);
+ }
+
+ if (printAttrPath && source != sAvailable)
+ throw UsageError("--attr-path(-P) only works with --available");
+
+ /* 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. */
+ /* !!! */
+ std::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. */
+ StorePathSet installed; /* installed paths */
+
+ if (printStatus)
+ for (auto & i : installedElems)
+ installed.insert(i.queryOutPath());
+
+
+ /* Query which paths have substitutes. */
+ StorePathSet validPaths;
+ StorePathSet substitutablePaths;
+ if (printStatus || globals.prebuiltOnly) {
+ StorePathSet paths;
+ for (auto & i : elems)
+ try {
+ paths.insert(i.queryOutPath());
+ } catch (AssertionError & e) {
+ printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
+ i.setFailed();
+ }
+ validPaths = store.queryValidPaths(paths);
+ substitutablePaths = store.querySubstitutablePaths(paths);
+ }
+
+
+ /* Print the desired columns, or XML output. */
+ if (jsonOutput) {
+ queryJSON(globals, elems, printOutPath, printDrvPath, printMeta);
+ cout << '\n';
+ return;
+ }
+
+ 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, "outputting query result '%1%'", i.attrPath);
+
+ if (globals.prebuiltOnly &&
+ !validPaths.count(i.queryOutPath()) &&
+ !substitutablePaths.count(i.queryOutPath()))
+ continue;
+
+ /* For table output. */
+ Strings columns;
+
+ /* For XML output. */
+ XMLAttrs attrs;
+
+ if (printStatus) {
+ auto outPath = i.queryOutPath();
+ bool hasSubs = substitutablePaths.count(outPath);
+ bool isInstalled = installed.count(outPath);
+ bool isValid = validPaths.count(outPath);
+ if (xmlOutput) {
+ attrs["installed"] = isInstalled ? "1" : "0";
+ attrs["valid"] = isValid ? "1" : "0";
+ attrs["substitutable"] = hasSubs ? "1" : "0";
+ } else
+ columns.push_back(
+ (std::string) (isInstalled ? "I" : "-")
+ + (isValid ? "P" : "-")
+ + (hasSubs ? "S" : "-"));
+ }
+
+ if (xmlOutput)
+ attrs["attrPath"] = i.attrPath;
+ else if (printAttrPath)
+ columns.push_back(i.attrPath);
+
+ if (xmlOutput) {
+ auto drvName = DrvName(i.queryName());
+ attrs["name"] = drvName.fullName;
+ attrs["pname"] = drvName.name;
+ attrs["version"] = drvName.version;
+ } else if (printName) {
+ columns.push_back(i.queryName());
+ }
+
+ 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). */
+ std::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 {
+ auto column = (std::string) "" + ch + " " + version;
+ if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout))
+ column = ANSI_RED + column + ANSI_NORMAL;
+ columns.push_back(column);
+ }
+ }
+
+ if (xmlOutput) {
+ if (i.querySystem() != "") attrs["system"] = i.querySystem();
+ }
+ else if (printSystem)
+ columns.push_back(i.querySystem());
+
+ if (printDrvPath) {
+ auto drvPath = i.queryDrvPath();
+ if (xmlOutput) {
+ if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath);
+ } else
+ columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
+ }
+
+ if (xmlOutput)
+ attrs["outputName"] = i.queryOutputName();
+
+ if (printOutPath && !xmlOutput) {
+ DrvInfo::Outputs outputs = i.queryOutputs();
+ std::string s;
+ for (auto & j : outputs) {
+ if (!s.empty()) s += ';';
+ if (j.first != "out") { s += j.first; s += "="; }
+ s += store.printStorePath(*j.second);
+ }
+ columns.push_back(s);
+ }
+
+ if (printDescription) {
+ auto descr = i.queryMetaString("description");
+ if (xmlOutput) {
+ if (descr != "") attrs["description"] = descr;
+ } else
+ columns.push_back(descr);
+ }
+
+ if (xmlOutput) {
+ XMLOpenElement item(xml, "item", attrs);
+ DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
+ for (auto & j : outputs) {
+ XMLAttrs attrs2;
+ attrs2["name"] = j.first;
+ if (j.second)
+ attrs2["path"] = store.printStorePath(*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)
+ printError(
+ "derivation '%s' has invalid meta attribute '%s'",
+ i.queryName(), j);
+ else {
+ if (v->type() == nString) {
+ attrs2["type"] = "string";
+ attrs2["value"] = v->string.s;
+ xml.writeEmptyElement("meta", attrs2);
+ } else if (v->type() == nInt) {
+ attrs2["type"] = "int";
+ attrs2["value"] = fmt("%1%", v->integer);
+ xml.writeEmptyElement("meta", attrs2);
+ } else if (v->type() == nFloat) {
+ attrs2["type"] = "float";
+ attrs2["value"] = fmt("%1%", v->fpoint);
+ xml.writeEmptyElement("meta", attrs2);
+ } else if (v->type() == nBool) {
+ attrs2["type"] = "bool";
+ attrs2["value"] = v->boolean ? "true" : "false";
+ xml.writeEmptyElement("meta", attrs2);
+ } else if (v->type() == nList) {
+ attrs2["type"] = "strings";
+ XMLOpenElement m(xml, "meta", attrs2);
+ for (auto elem : v->listItems()) {
+ if (elem->type() != nString) continue;
+ XMLAttrs attrs3;
+ attrs3["value"] = elem->string.s;
+ xml.writeEmptyElement("string", attrs3);
+ }
+ } else if (v->type() == nAttrs) {
+ 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() != nString) continue;
+ XMLAttrs attrs3;
+ attrs3["type"] = globals.state->symbols[i.name];
+ attrs3["value"] = a.value->string.s;
+ xml.writeEmptyElement("string", attrs3);
+ }
+ }
+ }
+ }
+ }
+ } else
+ table.push_back(columns);
+
+ cout.flush();
+
+ } catch (AssertionError & e) {
+ printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
+ } catch (Error & e) {
+ e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
+ throw;
+ }
+ }
+
+ if (!xmlOutput) printTable(table);
+}
+
+
+static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("unknown flag '%1%'", opFlags.front());
+ if (opArgs.size() != 1)
+ throw UsageError("exactly one argument expected");
+
+ Path profile = absPath(opArgs.front());
+ Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
+
+ switchLink(profileLink, profile);
+}
+
+
+static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("unknown flag '%1%'", opFlags.front());
+ if (opArgs.size() != 1)
+ throw UsageError("exactly one argument expected");
+
+ if (auto dstGen = string2Int<GenerationNumber>(opArgs.front()))
+ switchGeneration(globals.profile, *dstGen, globals.dryRun);
+ else
+ throw UsageError("expected a generation number");
+}
+
+
+static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("unknown flag '%1%'", opFlags.front());
+ if (opArgs.size() != 0)
+ throw UsageError("no arguments expected");
+
+ switchGeneration(globals.profile, {}, globals.dryRun);
+}
+
+
+static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ if (opFlags.size() > 0)
+ throw UsageError("unknown flag '%1%'", opFlags.front());
+ if (opArgs.size() != 0)
+ throw UsageError("no arguments expected");
+
+ PathLocks lock;
+ lockProfile(lock, globals.profile);
+
+ auto [gens, curGen] = findGenerations(globals.profile);
+
+ RunPager pager;
+
+ for (auto & i : gens) {
+ tm t;
+ if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
+ logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||",
+ 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("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') != std::string::npos) {
+ auto t = parseOlderThanTimeSpec(opArgs.front());
+ deleteGenerationsOlderThan(globals.profile, t, globals.dryRun);
+ } else if (opArgs.size() == 1 && opArgs.front().find('+') != std::string::npos) {
+ if (opArgs.front().size() < 2)
+ throw Error("invalid number of generations '%1%'", opArgs.front());
+ auto str_max = opArgs.front().substr(1);
+ auto max = string2Int<GenerationNumber>(str_max);
+ if (!max)
+ throw Error("invalid number of generations to keep '%1%'", opArgs.front());
+ deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun);
+ } else {
+ std::set<GenerationNumber> gens;
+ for (auto & i : opArgs) {
+ if (auto n = string2Int<GenerationNumber>(i))
+ gens.insert(*n);
+ else
+ throw UsageError("invalid generation number '%1%'", i);
+ }
+ deleteGenerations(globals.profile, gens, globals.dryRun);
+ }
+}
+
+
+static void opVersion(Globals & globals, Strings opFlags, Strings opArgs)
+{
+ printVersion("nix-env");
+}
+
+
+static int main_nix_env(int argc, char * * argv)
+{
+ {
+ Strings opFlags, opArgs;
+ Operation op = 0;
+ std::string opName;
+ bool showHelp = false;
+ std::string file;
+
+ Globals globals;
+
+ globals.instSource.type = srcUnknown;
+ globals.instSource.systemFilter = "*";
+
+ Path nixExprPath = getNixDefExpr();
+
+ if (!pathExists(nixExprPath)) {
+ try {
+ createDirs(nixExprPath);
+ replaceSymlink(
+ defaultChannelsDir(),
+ nixExprPath + "/channels");
+ if (getuid() != 0)
+ replaceSymlink(
+ rootChannelsDir(),
+ nixExprPath + "/channels_root");
+ } catch (Error &) { }
+ }
+
+ globals.dryRun = false;
+ globals.preserveInstalled = false;
+ globals.removeAll = false;
+ globals.prebuiltOnly = false;
+
+ struct MyArgs : LegacyArgs, MixEvalArgs
+ {
+ using LegacyArgs::LegacyArgs;
+ };
+
+ MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) {
+ Operation oldOp = op;
+
+ if (*arg == "--help")
+ showHelp = true;
+ else if (*arg == "--version")
+ op = opVersion;
+ else if (*arg == "--install" || *arg == "-i") {
+ op = opInstall;
+ opName = "-install";
+ }
+ 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;
+ opName = "-uninstall";
+ }
+ else if (*arg == "--upgrade" || *arg == "-u") {
+ op = opUpgrade;
+ opName = "-upgrade";
+ }
+ else if (*arg == "--set-flag") {
+ op = opSetFlag;
+ opName = arg->substr(1);
+ }
+ else if (*arg == "--set") {
+ op = opSet;
+ opName = arg->substr(1);
+ }
+ else if (*arg == "--query" || *arg == "-q") {
+ op = opQuery;
+ opName = "-query";
+ }
+ 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;
+ opName = "-switch-profile";
+ }
+ else if (*arg == "--switch-generation" || *arg == "-G") {
+ op = opSwitchGeneration;
+ opName = "-switch-generation";
+ }
+ else if (*arg == "--rollback") {
+ op = opRollback;
+ opName = arg->substr(1);
+ }
+ else if (*arg == "--list-generations") {
+ op = opListGenerations;
+ opName = arg->substr(1);
+ }
+ else if (*arg == "--delete-generations") {
+ op = opDeleteGenerations;
+ opName = arg->substr(1);
+ }
+ else if (*arg == "--dry-run") {
+ printInfo("(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 != "" && 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;
+ });
+
+ myArgs.parseCmdline(argvToStrings(argc, argv));
+
+ if (showHelp) showManPage("nix-env" + opName);
+ if (!op) throw UsageError("no operation specified");
+
+ auto store = openStore();
+
+ globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store));
+ globals.state->repair = myArgs.repair;
+
+ globals.instSource.nixExprPath = std::make_shared<SourcePath>(
+ file != ""
+ ? lookupFileArg(*globals.state, file)
+ : globals.state->rootPath(CanonPath(nixExprPath)));
+
+ globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);
+
+ if (globals.profile == "")
+ globals.profile = getEnv("NIX_PROFILE").value_or("");
+
+ if (globals.profile == "")
+ globals.profile = getDefaultProfile();
+
+ op(globals, std::move(opFlags), std::move(opArgs));
+
+ globals.state->maybePrintStats();
+
+ return 0;
+ }
+}
+
+void registerNixEnv() {
+ LegacyCommands::add("nix-env", main_nix_env);
+}
+
+}