1 #include "attr-path.hh"
2 #include "common-eval-args.hh"
3 #include "derivations.hh"
4 #include "eval.hh"
5 #include "get-drvs.hh"
6 #include "globals.hh"
7 #include "names.hh"
8 #include "profiles.hh"
9 #include "shared.hh"
10 #include "store-api.hh"
11 #include "user-env.hh"
12 #include "util.hh"
13 #include "json.hh"
14 #include "value-to-json.hh"
15 #include "xml-writer.hh"
16 #include "legacy.hh"
17
18 #include <cerrno>
19 #include <ctime>
20 #include <algorithm>
21 #include <iostream>
22 #include <sstream>
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27
28
29 using namespace nix;
30 using std::cout;
31
32
33 typedef enum {
34 srcNixExprDrvs,
35 srcNixExprs,
36 srcStorePaths,
37 srcProfile,
38 srcAttrPath,
39 srcUnknown
40 } InstallSourceType;
41
42
43 struct InstallSourceInfo
44 {
45 InstallSourceType type;
46 Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
47 Path profile; /* for srcProfile */
48 string systemFilter; /* for srcNixExprDrvs */
49 Bindings * autoArgs;
50 };
51
52
53 struct Globals
54 {
55 InstallSourceInfo instSource;
56 Path profile;
57 std::shared_ptr<EvalState> state;
58 bool dryRun;
59 bool preserveInstalled;
60 bool removeAll;
61 string forceName;
62 bool prebuiltOnly;
63 };
64
65
66 typedef void (* Operation) (Globals & globals,
67 Strings opFlags, Strings opArgs);
68
69
needArg(Strings::iterator & i,Strings & args,const string & arg)70 static string needArg(Strings::iterator & i,
71 Strings & args, const string & arg)
72 {
73 if (i == args.end()) throw UsageError(
74 format("'%1%' requires an argument") % arg);
75 return *i++;
76 }
77
78
parseInstallSourceOptions(Globals & globals,Strings::iterator & i,Strings & args,const string & arg)79 static bool parseInstallSourceOptions(Globals & globals,
80 Strings::iterator & i, Strings & args, const string & arg)
81 {
82 if (arg == "--from-expression" || arg == "-E")
83 globals.instSource.type = srcNixExprs;
84 else if (arg == "--from-profile") {
85 globals.instSource.type = srcProfile;
86 globals.instSource.profile = needArg(i, args, arg);
87 }
88 else if (arg == "--attr" || arg == "-A")
89 globals.instSource.type = srcAttrPath;
90 else return false;
91 return true;
92 }
93
94
isNixExpr(const Path & path,struct stat & st)95 static bool isNixExpr(const Path & path, struct stat & st)
96 {
97 return S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && pathExists(path + "/default.nix"));
98 }
99
100
getAllExprs(EvalState & state,const Path & path,StringSet & attrs,Value & v)101 static void getAllExprs(EvalState & state,
102 const Path & path, StringSet & attrs, Value & v)
103 {
104 StringSet namesSorted;
105 for (auto & i : readDirectory(path)) namesSorted.insert(i.name);
106
107 for (auto & i : namesSorted) {
108 /* Ignore the manifest.nix used by profiles. This is
109 necessary to prevent it from showing up in channels (which
110 are implemented using profiles). */
111 if (i == "manifest.nix") continue;
112
113 Path path2 = path + "/" + i;
114
115 struct stat st;
116 if (stat(path2.c_str(), &st) == -1)
117 continue; // ignore dangling symlinks in ~/.nix-defexpr
118
119 if (isNixExpr(path2, st) && (!S_ISREG(st.st_mode) || hasSuffix(path2, ".nix"))) {
120 /* Strip off the `.nix' filename suffix (if applicable),
121 otherwise the attribute cannot be selected with the
122 `-A' option. Useful if you want to stick a Nix
123 expression directly in ~/.nix-defexpr. */
124 string attrName = i;
125 if (hasSuffix(attrName, ".nix"))
126 attrName = string(attrName, 0, attrName.size() - 4);
127 if (attrs.find(attrName) != attrs.end()) {
128 printError(format("warning: name collision in input Nix expressions, skipping '%1%'") % path2);
129 continue;
130 }
131 attrs.insert(attrName);
132 /* Load the expression on demand. */
133 Value & vFun = state.getBuiltin("import");
134 Value & vArg(*state.allocValue());
135 mkString(vArg, path2);
136 if (v.attrs->size() == v.attrs->capacity())
137 throw Error(format("too many Nix expressions in directory '%1%'") % path);
138 mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
139 }
140 else if (S_ISDIR(st.st_mode))
141 /* `path2' is a directory (with no default.nix in it);
142 recurse into it. */
143 getAllExprs(state, path2, attrs, v);
144 }
145 }
146
147
loadSourceExpr(EvalState & state,const Path & path,Value & v)148 static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
149 {
150 struct stat st;
151 if (stat(path.c_str(), &st) == -1)
152 throw SysError(format("getting information about '%1%'") % path);
153
154 if (isNixExpr(path, st))
155 state.evalFile(path, v);
156
157 /* The path is a directory. Put the Nix expressions in the
158 directory in a set, with the file name of each expression as
159 the attribute name. Recurse into subdirectories (but keep the
160 set flat, not nested, to make it easier for a user to have a
161 ~/.nix-defexpr directory that includes some system-wide
162 directory). */
163 else if (S_ISDIR(st.st_mode)) {
164 state.mkAttrs(v, 1024);
165 state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0);
166 StringSet attrs;
167 getAllExprs(state, path, attrs, v);
168 v.attrs->sort();
169 }
170
171 else throw Error("path '%s' is not a directory or a Nix expression", path);
172 }
173
174
loadDerivations(EvalState & state,Path nixExprPath,string systemFilter,Bindings & autoArgs,const string & pathPrefix,DrvInfos & elems)175 static void loadDerivations(EvalState & state, Path nixExprPath,
176 string systemFilter, Bindings & autoArgs,
177 const string & pathPrefix, DrvInfos & elems)
178 {
179 Value vRoot;
180 loadSourceExpr(state, nixExprPath, vRoot);
181
182 Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
183
184 getDerivations(state, v, pathPrefix, autoArgs, elems, true);
185
186 /* Filter out all derivations not applicable to the current
187 system. */
188 for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
189 j = i; j++;
190 if (systemFilter != "*" && i->querySystem() != systemFilter)
191 elems.erase(i);
192 }
193 }
194
195
getPriority(EvalState & state,DrvInfo & drv)196 static long getPriority(EvalState & state, DrvInfo & drv)
197 {
198 return drv.queryMetaInt("priority", 0);
199 }
200
201
comparePriorities(EvalState & state,DrvInfo & drv1,DrvInfo & drv2)202 static long comparePriorities(EvalState & state, DrvInfo & drv1, DrvInfo & drv2)
203 {
204 return getPriority(state, drv2) - getPriority(state, drv1);
205 }
206
207
208 // FIXME: this function is rather slow since it checks a single path
209 // at a time.
isPrebuilt(EvalState & state,DrvInfo & elem)210 static bool isPrebuilt(EvalState & state, DrvInfo & elem)
211 {
212 Path path = elem.queryOutPath();
213 if (state.store->isValidPath(path)) return true;
214 PathSet ps = state.store->querySubstitutablePaths({path});
215 return ps.find(path) != ps.end();
216 }
217
218
checkSelectorUse(DrvNames & selectors)219 static void checkSelectorUse(DrvNames & selectors)
220 {
221 /* Check that all selectors have been used. */
222 for (auto & i : selectors)
223 if (i.hits == 0 && i.fullName != "*")
224 throw Error(format("selector '%1%' matches no derivations") % i.fullName);
225 }
226
227
filterBySelector(EvalState & state,const DrvInfos & allElems,const Strings & args,bool newestOnly)228 static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
229 const Strings & args, bool newestOnly)
230 {
231 DrvNames selectors = drvNamesFromArgs(args);
232 if (selectors.empty())
233 selectors.push_back(DrvName("*"));
234
235 DrvInfos elems;
236 set<unsigned int> done;
237
238 for (auto & i : selectors) {
239 typedef list<std::pair<DrvInfo, unsigned int> > Matches;
240 Matches matches;
241 unsigned int n = 0;
242 for (DrvInfos::const_iterator j = allElems.begin();
243 j != allElems.end(); ++j, ++n)
244 {
245 DrvName drvName(j->queryName());
246 if (i.matches(drvName)) {
247 i.hits++;
248 matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
249 }
250 }
251
252 /* If `newestOnly', if a selector matches multiple derivations
253 with the same name, pick the one matching the current
254 system. If there are still multiple derivations, pick the
255 one with the highest priority. If there are still multiple
256 derivations, pick the one with the highest version.
257 Finally, if there are still multiple derivations,
258 arbitrarily pick the first one. */
259 if (newestOnly) {
260
261 /* Map from package names to derivations. */
262 typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
263 Newest newest;
264 StringSet multiple;
265
266 for (auto & j : matches) {
267 DrvName drvName(j.first.queryName());
268 long d = 1;
269
270 Newest::iterator k = newest.find(drvName.name);
271
272 if (k != newest.end()) {
273 d = j.first.querySystem() == k->second.first.querySystem() ? 0 :
274 j.first.querySystem() == settings.thisSystem ? 1 :
275 k->second.first.querySystem() == settings.thisSystem ? -1 : 0;
276 if (d == 0)
277 d = comparePriorities(state, j.first, k->second.first);
278 if (d == 0)
279 d = compareVersions(drvName.version, DrvName(k->second.first.queryName()).version);
280 }
281
282 if (d > 0) {
283 newest.erase(drvName.name);
284 newest.insert(Newest::value_type(drvName.name, j));
285 multiple.erase(j.first.queryName());
286 } else if (d == 0) {
287 multiple.insert(j.first.queryName());
288 }
289 }
290
291 matches.clear();
292 for (auto & j : newest) {
293 if (multiple.find(j.second.first.queryName()) != multiple.end())
294 printInfo(
295 "warning: there are multiple derivations named '%1%'; using the first one",
296 j.second.first.queryName());
297 matches.push_back(j.second);
298 }
299 }
300
301 /* Insert only those elements in the final list that we
302 haven't inserted before. */
303 for (auto & j : matches)
304 if (done.find(j.second) == done.end()) {
305 done.insert(j.second);
306 elems.push_back(j.first);
307 }
308 }
309
310 checkSelectorUse(selectors);
311
312 return elems;
313 }
314
315
isPath(const string & s)316 static bool isPath(const string & s)
317 {
318 return s.find('/') != string::npos;
319 }
320
321
queryInstSources(EvalState & state,InstallSourceInfo & instSource,const Strings & args,DrvInfos & elems,bool newestOnly)322 static void queryInstSources(EvalState & state,
323 InstallSourceInfo & instSource, const Strings & args,
324 DrvInfos & elems, bool newestOnly)
325 {
326 InstallSourceType type = instSource.type;
327 if (type == srcUnknown && args.size() > 0 && isPath(args.front()))
328 type = srcStorePaths;
329
330 switch (type) {
331
332 /* Get the available user environment elements from the
333 derivations specified in a Nix expression, including only
334 those with names matching any of the names in `args'. */
335 case srcUnknown:
336 case srcNixExprDrvs: {
337
338 /* Load the derivations from the (default or specified)
339 Nix expression. */
340 DrvInfos allElems;
341 loadDerivations(state, instSource.nixExprPath,
342 instSource.systemFilter, *instSource.autoArgs, "", allElems);
343
344 elems = filterBySelector(state, allElems, args, newestOnly);
345
346 break;
347 }
348
349 /* Get the available user environment elements from the Nix
350 expressions specified on the command line; these should be
351 functions that take the default Nix expression file as
352 argument, e.g., if the file is `./foo.nix', then the
353 argument `x: x.bar' is equivalent to `(x: x.bar)
354 (import ./foo.nix)' = `(import ./foo.nix).bar'. */
355 case srcNixExprs: {
356
357 Value vArg;
358 loadSourceExpr(state, instSource.nixExprPath, vArg);
359
360 for (auto & i : args) {
361 Expr * eFun = state.parseExprFromString(i, absPath("."));
362 Value vFun, vTmp;
363 state.eval(eFun, vFun);
364 mkApp(vTmp, vFun, vArg);
365 getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
366 }
367
368 break;
369 }
370
371 /* The available user environment elements are specified as a
372 list of store paths (which may or may not be
373 derivations). */
374 case srcStorePaths: {
375
376 for (auto & i : args) {
377 Path path = state.store->followLinksToStorePath(i);
378
379 string name = baseNameOf(path);
380 string::size_type dash = name.find('-');
381 if (dash != string::npos)
382 name = string(name, dash + 1);
383
384 DrvInfo elem(state, "", nullptr);
385 elem.setName(name);
386
387 if (isDerivation(path)) {
388 elem.setDrvPath(path);
389 elem.setOutPath(state.store->derivationFromPath(path).findOutput("out"));
390 if (name.size() >= drvExtension.size() &&
391 string(name, name.size() - drvExtension.size()) == drvExtension)
392 name = string(name, 0, name.size() - drvExtension.size());
393 }
394 else elem.setOutPath(path);
395
396 elems.push_back(elem);
397 }
398
399 break;
400 }
401
402 /* Get the available user environment elements from another
403 user environment. These are then filtered as in the
404 `srcNixExprDrvs' case. */
405 case srcProfile: {
406 elems = filterBySelector(state,
407 queryInstalled(state, instSource.profile),
408 args, newestOnly);
409 break;
410 }
411
412 case srcAttrPath: {
413 Value vRoot;
414 loadSourceExpr(state, instSource.nixExprPath, vRoot);
415 for (auto & i : args) {
416 Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot));
417 getDerivations(state, v, "", *instSource.autoArgs, elems, true);
418 }
419 break;
420 }
421 }
422 }
423
424
printMissing(EvalState & state,DrvInfos & elems)425 static void printMissing(EvalState & state, DrvInfos & elems)
426 {
427 PathSet targets;
428 for (auto & i : elems) {
429 Path drvPath = i.queryDrvPath();
430 if (drvPath != "")
431 targets.insert(drvPath);
432 else
433 targets.insert(i.queryOutPath());
434 }
435
436 printMissing(state.store, targets);
437 }
438
439
keep(DrvInfo & drv)440 static bool keep(DrvInfo & drv)
441 {
442 return drv.queryMetaBool("keep", false);
443 }
444
445
installDerivations(Globals & globals,const Strings & args,const Path & profile)446 static void installDerivations(Globals & globals,
447 const Strings & args, const Path & profile)
448 {
449 debug(format("installing derivations"));
450
451 /* Get the set of user environment elements to be installed. */
452 DrvInfos newElems, newElemsTmp;
453 queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true);
454
455 /* If --prebuilt-only is given, filter out source-only packages. */
456 for (auto & i : newElemsTmp)
457 if (!globals.prebuiltOnly || isPrebuilt(*globals.state, i))
458 newElems.push_back(i);
459
460 StringSet newNames;
461 for (auto & i : newElems) {
462 /* `forceName' is a hack to get package names right in some
463 one-click installs, namely those where the name used in the
464 path is not the one we want (e.g., `java-front' versus
465 `java-front-0.9pre15899'). */
466 if (globals.forceName != "")
467 i.setName(globals.forceName);
468 newNames.insert(DrvName(i.queryName()).name);
469 }
470
471
472 while (true) {
473 string lockToken = optimisticLockProfile(profile);
474
475 DrvInfos allElems(newElems);
476
477 /* Add in the already installed derivations, unless they have
478 the same name as a to-be-installed element. */
479 if (!globals.removeAll) {
480 DrvInfos installedElems = queryInstalled(*globals.state, profile);
481
482 for (auto & i : installedElems) {
483 DrvName drvName(i.queryName());
484 if (!globals.preserveInstalled &&
485 newNames.find(drvName.name) != newNames.end() &&
486 !keep(i))
487 printInfo("replacing old '%s'", i.queryName());
488 else
489 allElems.push_back(i);
490 }
491
492 for (auto & i : newElems)
493 printInfo("installing '%s'", i.queryName());
494 }
495
496 printMissing(*globals.state, newElems);
497
498 if (globals.dryRun) return;
499
500 if (createUserEnv(*globals.state, allElems,
501 profile, settings.envKeepDerivations, lockToken)) break;
502 }
503 }
504
505
opInstall(Globals & globals,Strings opFlags,Strings opArgs)506 static void opInstall(Globals & globals, Strings opFlags, Strings opArgs)
507 {
508 for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
509 string arg = *i++;
510 if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
511 else if (arg == "--preserve-installed" || arg == "-P")
512 globals.preserveInstalled = true;
513 else if (arg == "--remove-all" || arg == "-r")
514 globals.removeAll = true;
515 else throw UsageError(format("unknown flag '%1%'") % arg);
516 }
517
518 installDerivations(globals, opArgs, globals.profile);
519 }
520
521
522 typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType;
523
524
upgradeDerivations(Globals & globals,const Strings & args,UpgradeType upgradeType)525 static void upgradeDerivations(Globals & globals,
526 const Strings & args, UpgradeType upgradeType)
527 {
528 debug(format("upgrading derivations"));
529
530 /* Upgrade works as follows: we take all currently installed
531 derivations, and for any derivation matching any selector, look
532 for a derivation in the input Nix expression that has the same
533 name and a higher version number. */
534
535 while (true) {
536 string lockToken = optimisticLockProfile(globals.profile);
537
538 DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
539
540 /* Fetch all derivations from the input file. */
541 DrvInfos availElems;
542 queryInstSources(*globals.state, globals.instSource, args, availElems, false);
543
544 /* Go through all installed derivations. */
545 DrvInfos newElems;
546 for (auto & i : installedElems) {
547 DrvName drvName(i.queryName());
548
549 try {
550
551 if (keep(i)) {
552 newElems.push_back(i);
553 continue;
554 }
555
556 /* Find the derivation in the input Nix expression
557 with the same name that satisfies the version
558 constraints specified by upgradeType. If there are
559 multiple matches, take the one with the highest
560 priority. If there are still multiple matches,
561 take the one with the highest version.
562 Do not upgrade if it would decrease the priority. */
563 DrvInfos::iterator bestElem = availElems.end();
564 string bestVersion;
565 for (auto j = availElems.begin(); j != availElems.end(); ++j) {
566 if (comparePriorities(*globals.state, i, *j) > 0)
567 continue;
568 DrvName newName(j->queryName());
569 if (newName.name == drvName.name) {
570 int d = compareVersions(drvName.version, newName.version);
571 if ((upgradeType == utLt && d < 0) ||
572 (upgradeType == utLeq && d <= 0) ||
573 (upgradeType == utEq && d == 0) ||
574 upgradeType == utAlways)
575 {
576 long d2 = -1;
577 if (bestElem != availElems.end()) {
578 d2 = comparePriorities(*globals.state, *bestElem, *j);
579 if (d2 == 0) d2 = compareVersions(bestVersion, newName.version);
580 }
581 if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) {
582 bestElem = j;
583 bestVersion = newName.version;
584 }
585 }
586 }
587 }
588
589 if (bestElem != availElems.end() &&
590 i.queryOutPath() !=
591 bestElem->queryOutPath())
592 {
593 const char * action = compareVersions(drvName.version, bestVersion) <= 0
594 ? "upgrading" : "downgrading";
595 printInfo("%1% '%2%' to '%3%'",
596 action, i.queryName(), bestElem->queryName());
597 newElems.push_back(*bestElem);
598 } else newElems.push_back(i);
599
600 } catch (Error & e) {
601 e.addPrefix(fmt("while trying to find an upgrade for '%s':\n", i.queryName()));
602 throw;
603 }
604 }
605
606 printMissing(*globals.state, newElems);
607
608 if (globals.dryRun) return;
609
610 if (createUserEnv(*globals.state, newElems,
611 globals.profile, settings.envKeepDerivations, lockToken)) break;
612 }
613 }
614
615
opUpgrade(Globals & globals,Strings opFlags,Strings opArgs)616 static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
617 {
618 UpgradeType upgradeType = utLt;
619 for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
620 string arg = *i++;
621 if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
622 else if (arg == "--lt") upgradeType = utLt;
623 else if (arg == "--leq") upgradeType = utLeq;
624 else if (arg == "--eq") upgradeType = utEq;
625 else if (arg == "--always") upgradeType = utAlways;
626 else throw UsageError(format("unknown flag '%1%'") % arg);
627 }
628
629 upgradeDerivations(globals, opArgs, upgradeType);
630 }
631
632
setMetaFlag(EvalState & state,DrvInfo & drv,const string & name,const string & value)633 static void setMetaFlag(EvalState & state, DrvInfo & drv,
634 const string & name, const string & value)
635 {
636 Value * v = state.allocValue();
637 mkString(*v, value.c_str());
638 drv.setMeta(name, v);
639 }
640
641
opSetFlag(Globals & globals,Strings opFlags,Strings opArgs)642 static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
643 {
644 if (opFlags.size() > 0)
645 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
646 if (opArgs.size() < 2)
647 throw UsageError("not enough arguments to '--set-flag'");
648
649 Strings::iterator arg = opArgs.begin();
650 string flagName = *arg++;
651 string flagValue = *arg++;
652 DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
653
654 while (true) {
655 string lockToken = optimisticLockProfile(globals.profile);
656
657 DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
658
659 /* Update all matching derivations. */
660 for (auto & i : installedElems) {
661 DrvName drvName(i.queryName());
662 for (auto & j : selectors)
663 if (j.matches(drvName)) {
664 printInfo("setting flag on '%1%'", i.queryName());
665 j.hits++;
666 setMetaFlag(*globals.state, i, flagName, flagValue);
667 break;
668 }
669 }
670
671 checkSelectorUse(selectors);
672
673 /* Write the new user environment. */
674 if (createUserEnv(*globals.state, installedElems,
675 globals.profile, settings.envKeepDerivations, lockToken)) break;
676 }
677 }
678
679
opSet(Globals & globals,Strings opFlags,Strings opArgs)680 static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
681 {
682 auto store2 = globals.state->store.dynamic_pointer_cast<LocalFSStore>();
683 if (!store2) throw Error("--set is not supported for this Nix store");
684
685 for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
686 string arg = *i++;
687 if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
688 else throw UsageError(format("unknown flag '%1%'") % arg);
689 }
690
691 DrvInfos elems;
692 queryInstSources(*globals.state, globals.instSource, opArgs, elems, true);
693
694 if (elems.size() != 1)
695 throw Error("--set requires exactly one derivation");
696
697 DrvInfo & drv(elems.front());
698
699 if (globals.forceName != "")
700 drv.setName(globals.forceName);
701
702 if (drv.queryDrvPath() != "") {
703 PathSet paths = {drv.queryDrvPath()};
704 printMissing(globals.state->store, paths);
705 if (globals.dryRun) return;
706 globals.state->store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal);
707 }
708 else {
709 printMissing(globals.state->store, {drv.queryOutPath()});
710 if (globals.dryRun) return;
711 globals.state->store->ensurePath(drv.queryOutPath());
712 }
713
714 debug(format("switching to new user environment"));
715 Path generation = createGeneration(ref<LocalFSStore>(store2), globals.profile, drv.queryOutPath());
716 switchLink(globals.profile, generation);
717 }
718
719
uninstallDerivations(Globals & globals,Strings & selectors,Path & profile)720 static void uninstallDerivations(Globals & globals, Strings & selectors,
721 Path & profile)
722 {
723 while (true) {
724 string lockToken = optimisticLockProfile(profile);
725
726 DrvInfos installedElems = queryInstalled(*globals.state, profile);
727 DrvInfos newElems;
728
729 for (auto & i : installedElems) {
730 DrvName drvName(i.queryName());
731 bool found = false;
732 for (auto & j : selectors)
733 /* !!! the repeated calls to followLinksToStorePath()
734 are expensive, should pre-compute them. */
735 if ((isPath(j) && i.queryOutPath() == globals.state->store->followLinksToStorePath(j))
736 || DrvName(j).matches(drvName))
737 {
738 printInfo("uninstalling '%s'", i.queryName());
739 found = true;
740 break;
741 }
742 if (!found) newElems.push_back(i);
743 }
744
745 if (globals.dryRun) return;
746
747 if (createUserEnv(*globals.state, newElems,
748 profile, settings.envKeepDerivations, lockToken)) break;
749 }
750 }
751
752
opUninstall(Globals & globals,Strings opFlags,Strings opArgs)753 static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
754 {
755 if (opFlags.size() > 0)
756 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
757 uninstallDerivations(globals, opArgs, globals.profile);
758 }
759
760
cmpChars(char a,char b)761 static bool cmpChars(char a, char b)
762 {
763 return toupper(a) < toupper(b);
764 }
765
766
cmpElemByName(const DrvInfo & a,const DrvInfo & b)767 static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
768 {
769 auto a_name = a.queryName();
770 auto b_name = b.queryName();
771 return lexicographical_compare(
772 a_name.begin(), a_name.end(),
773 b_name.begin(), b_name.end(), cmpChars);
774 }
775
776
777 typedef list<Strings> Table;
778
779
printTable(Table & table)780 void printTable(Table & table)
781 {
782 auto nrColumns = table.size() > 0 ? table.front().size() : 0;
783
784 vector<size_t> widths;
785 widths.resize(nrColumns);
786
787 for (auto & i : table) {
788 assert(i.size() == nrColumns);
789 Strings::iterator j;
790 size_t column;
791 for (j = i.begin(), column = 0; j != i.end(); ++j, ++column)
792 if (j->size() > widths[column]) widths[column] = j->size();
793 }
794
795 for (auto & i : table) {
796 Strings::iterator j;
797 size_t column;
798 for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
799 string s = *j;
800 replace(s.begin(), s.end(), '\n', ' ');
801 cout << s;
802 if (column < nrColumns - 1)
803 cout << string(widths[column] - s.size() + 2, ' ');
804 }
805 cout << std::endl;
806 }
807 }
808
809
810 /* This function compares the version of an element against the
811 versions in the given set of elements. `cvLess' means that only
812 lower versions are in the set, `cvEqual' means that at most an
813 equal version is in the set, and `cvGreater' means that there is at
814 least one element with a higher version in the set. `cvUnavail'
815 means that there are no elements with the same name in the set. */
816
817 typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
818
compareVersionAgainstSet(const DrvInfo & elem,const DrvInfos & elems,string & version)819 static VersionDiff compareVersionAgainstSet(
820 const DrvInfo & elem, const DrvInfos & elems, string & version)
821 {
822 DrvName name(elem.queryName());
823
824 VersionDiff diff = cvUnavail;
825 version = "?";
826
827 for (auto & i : elems) {
828 DrvName name2(i.queryName());
829 if (name.name == name2.name) {
830 int d = compareVersions(name.version, name2.version);
831 if (d < 0) {
832 diff = cvGreater;
833 version = name2.version;
834 }
835 else if (diff != cvGreater && d == 0) {
836 diff = cvEqual;
837 version = name2.version;
838 }
839 else if (diff != cvGreater && diff != cvEqual && d > 0) {
840 diff = cvLess;
841 if (version == "" || compareVersions(version, name2.version) < 0)
842 version = name2.version;
843 }
844 }
845 }
846
847 return diff;
848 }
849
850
queryJSON(Globals & globals,vector<DrvInfo> & elems)851 static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
852 {
853 JSONObject topObj(cout, true);
854 for (auto & i : elems) {
855 JSONObject pkgObj = topObj.object(i.attrPath);
856
857 auto drvName = DrvName(i.queryName());
858 pkgObj.attr("name", drvName.fullName);
859 pkgObj.attr("pname", drvName.name);
860 pkgObj.attr("version", drvName.version);
861 pkgObj.attr("system", i.querySystem());
862
863 JSONObject metaObj = pkgObj.object("meta");
864 StringSet metaNames = i.queryMetaNames();
865 for (auto & j : metaNames) {
866 auto placeholder = metaObj.placeholder(j);
867 Value * v = i.queryMeta(j);
868 if (!v) {
869 printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
870 placeholder.write(nullptr);
871 } else {
872 PathSet context;
873 printValueAsJSON(*globals.state, true, *v, placeholder, context);
874 }
875 }
876 }
877 }
878
879
opQuery(Globals & globals,Strings opFlags,Strings opArgs)880 static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
881 {
882 Strings remaining;
883 string attrPath;
884
885 bool printStatus = false;
886 bool printName = true;
887 bool printAttrPath = false;
888 bool printSystem = false;
889 bool printDrvPath = false;
890 bool printOutPath = false;
891 bool printDescription = false;
892 bool printMeta = false;
893 bool compareVersions = false;
894 bool xmlOutput = false;
895 bool jsonOutput = false;
896
897 enum { sInstalled, sAvailable } source = sInstalled;
898
899 settings.readOnlyMode = true; /* makes evaluation a bit faster */
900
901 for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
902 string arg = *i++;
903 if (arg == "--status" || arg == "-s") printStatus = true;
904 else if (arg == "--no-name") printName = false;
905 else if (arg == "--system") printSystem = true;
906 else if (arg == "--description") printDescription = true;
907 else if (arg == "--compare-versions" || arg == "-c") compareVersions = true;
908 else if (arg == "--drv-path") printDrvPath = true;
909 else if (arg == "--out-path") printOutPath = true;
910 else if (arg == "--meta") printMeta = true;
911 else if (arg == "--installed") source = sInstalled;
912 else if (arg == "--available" || arg == "-a") source = sAvailable;
913 else if (arg == "--xml") xmlOutput = true;
914 else if (arg == "--json") jsonOutput = true;
915 else if (arg == "--attr-path" || arg == "-P") printAttrPath = true;
916 else if (arg == "--attr" || arg == "-A")
917 attrPath = needArg(i, opFlags, arg);
918 else
919 throw UsageError(format("unknown flag '%1%'") % arg);
920 }
921
922
923 /* Obtain derivation information from the specified source. */
924 DrvInfos availElems, installedElems;
925
926 if (source == sInstalled || compareVersions || printStatus)
927 installedElems = queryInstalled(*globals.state, globals.profile);
928
929 if (source == sAvailable || compareVersions)
930 loadDerivations(*globals.state, globals.instSource.nixExprPath,
931 globals.instSource.systemFilter, *globals.instSource.autoArgs,
932 attrPath, availElems);
933
934 DrvInfos elems_ = filterBySelector(*globals.state,
935 source == sInstalled ? installedElems : availElems,
936 opArgs, false);
937
938 DrvInfos & otherElems(source == sInstalled ? availElems : installedElems);
939
940
941 /* Sort them by name. */
942 /* !!! */
943 vector<DrvInfo> elems;
944 for (auto & i : elems_) elems.push_back(i);
945 sort(elems.begin(), elems.end(), cmpElemByName);
946
947
948 /* We only need to know the installed paths when we are querying
949 the status of the derivation. */
950 PathSet installed; /* installed paths */
951
952 if (printStatus) {
953 for (auto & i : installedElems)
954 installed.insert(i.queryOutPath());
955 }
956
957
958 /* Query which paths have substitutes. */
959 PathSet validPaths, substitutablePaths;
960 if (printStatus || globals.prebuiltOnly) {
961 PathSet paths;
962 for (auto & i : elems)
963 try {
964 paths.insert(i.queryOutPath());
965 } catch (AssertionError & e) {
966 printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
967 i.setFailed();
968 }
969 validPaths = globals.state->store->queryValidPaths(paths);
970 substitutablePaths = globals.state->store->querySubstitutablePaths(paths);
971 }
972
973
974 /* Print the desired columns, or XML output. */
975 if (jsonOutput) {
976 queryJSON(globals, elems);
977 return;
978 }
979
980 bool tty = isatty(STDOUT_FILENO);
981 RunPager pager;
982
983 Table table;
984 std::ostringstream dummy;
985 XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
986 XMLOpenElement xmlRoot(xml, "items");
987
988 for (auto & i : elems) {
989 try {
990 if (i.hasFailed()) continue;
991
992 //Activity act(*logger, lvlDebug, format("outputting query result '%1%'") % i.attrPath);
993
994 if (globals.prebuiltOnly &&
995 validPaths.find(i.queryOutPath()) == validPaths.end() &&
996 substitutablePaths.find(i.queryOutPath()) == substitutablePaths.end())
997 continue;
998
999 /* For table output. */
1000 Strings columns;
1001
1002 /* For XML output. */
1003 XMLAttrs attrs;
1004
1005 if (printStatus) {
1006 Path outPath = i.queryOutPath();
1007 bool hasSubs = substitutablePaths.find(outPath) != substitutablePaths.end();
1008 bool isInstalled = installed.find(outPath) != installed.end();
1009 bool isValid = validPaths.find(outPath) != validPaths.end();
1010 if (xmlOutput) {
1011 attrs["installed"] = isInstalled ? "1" : "0";
1012 attrs["valid"] = isValid ? "1" : "0";
1013 attrs["substitutable"] = hasSubs ? "1" : "0";
1014 } else
1015 columns.push_back(
1016 (string) (isInstalled ? "I" : "-")
1017 + (isValid ? "P" : "-")
1018 + (hasSubs ? "S" : "-"));
1019 }
1020
1021 if (xmlOutput)
1022 attrs["attrPath"] = i.attrPath;
1023 else if (printAttrPath)
1024 columns.push_back(i.attrPath);
1025
1026 if (xmlOutput) {
1027 auto drvName = DrvName(i.queryName());
1028 attrs["name"] = drvName.fullName;
1029 attrs["pname"] = drvName.name;
1030 attrs["version"] = drvName.version;
1031 } else if (printName) {
1032 columns.push_back(i.queryName());
1033 }
1034
1035 if (compareVersions) {
1036 /* Compare this element against the versions of the
1037 same named packages in either the set of available
1038 elements, or the set of installed elements. !!!
1039 This is O(N * M), should be O(N * lg M). */
1040 string version;
1041 VersionDiff diff = compareVersionAgainstSet(i, otherElems, version);
1042
1043 char ch;
1044 switch (diff) {
1045 case cvLess: ch = '>'; break;
1046 case cvEqual: ch = '='; break;
1047 case cvGreater: ch = '<'; break;
1048 case cvUnavail: ch = '-'; break;
1049 default: abort();
1050 }
1051
1052 if (xmlOutput) {
1053 if (diff != cvUnavail) {
1054 attrs["versionDiff"] = ch;
1055 attrs["maxComparedVersion"] = version;
1056 }
1057 } else {
1058 string column = (string) "" + ch + " " + version;
1059 if (diff == cvGreater && tty)
1060 column = ANSI_RED + column + ANSI_NORMAL;
1061 columns.push_back(column);
1062 }
1063 }
1064
1065 if (xmlOutput) {
1066 if (i.querySystem() != "") attrs["system"] = i.querySystem();
1067 }
1068 else if (printSystem)
1069 columns.push_back(i.querySystem());
1070
1071 if (printDrvPath) {
1072 string drvPath = i.queryDrvPath();
1073 if (xmlOutput) {
1074 if (drvPath != "") attrs["drvPath"] = drvPath;
1075 } else
1076 columns.push_back(drvPath == "" ? "-" : drvPath);
1077 }
1078
1079 if (printOutPath && !xmlOutput) {
1080 DrvInfo::Outputs outputs = i.queryOutputs();
1081 string s;
1082 for (auto & j : outputs) {
1083 if (!s.empty()) s += ';';
1084 if (j.first != "out") { s += j.first; s += "="; }
1085 s += j.second;
1086 }
1087 columns.push_back(s);
1088 }
1089
1090 if (printDescription) {
1091 string descr = i.queryMetaString("description");
1092 if (xmlOutput) {
1093 if (descr != "") attrs["description"] = descr;
1094 } else
1095 columns.push_back(descr);
1096 }
1097
1098 if (xmlOutput) {
1099 if (printOutPath || printMeta) {
1100 XMLOpenElement item(xml, "item", attrs);
1101 if (printOutPath) {
1102 DrvInfo::Outputs outputs = i.queryOutputs();
1103 for (auto & j : outputs) {
1104 XMLAttrs attrs2;
1105 attrs2["name"] = j.first;
1106 attrs2["path"] = j.second;
1107 xml.writeEmptyElement("output", attrs2);
1108 }
1109 }
1110 if (printMeta) {
1111 StringSet metaNames = i.queryMetaNames();
1112 for (auto & j : metaNames) {
1113 XMLAttrs attrs2;
1114 attrs2["name"] = j;
1115 Value * v = i.queryMeta(j);
1116 if (!v)
1117 printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
1118 else {
1119 if (v->type == tString) {
1120 attrs2["type"] = "string";
1121 attrs2["value"] = v->string.s;
1122 xml.writeEmptyElement("meta", attrs2);
1123 } else if (v->type == tInt) {
1124 attrs2["type"] = "int";
1125 attrs2["value"] = (format("%1%") % v->integer).str();
1126 xml.writeEmptyElement("meta", attrs2);
1127 } else if (v->type == tFloat) {
1128 attrs2["type"] = "float";
1129 attrs2["value"] = (format("%1%") % v->fpoint).str();
1130 xml.writeEmptyElement("meta", attrs2);
1131 } else if (v->type == tBool) {
1132 attrs2["type"] = "bool";
1133 attrs2["value"] = v->boolean ? "true" : "false";
1134 xml.writeEmptyElement("meta", attrs2);
1135 } else if (v->isList()) {
1136 attrs2["type"] = "strings";
1137 XMLOpenElement m(xml, "meta", attrs2);
1138 for (unsigned int j = 0; j < v->listSize(); ++j) {
1139 if (v->listElems()[j]->type != tString) continue;
1140 XMLAttrs attrs3;
1141 attrs3["value"] = v->listElems()[j]->string.s;
1142 xml.writeEmptyElement("string", attrs3);
1143 }
1144 } else if (v->type == tAttrs) {
1145 attrs2["type"] = "strings";
1146 XMLOpenElement m(xml, "meta", attrs2);
1147 Bindings & attrs = *v->attrs;
1148 for (auto &i : attrs) {
1149 Attr & a(*attrs.find(i.name));
1150 if(a.value->type != tString) continue;
1151 XMLAttrs attrs3;
1152 attrs3["type"] = i.name;
1153 attrs3["value"] = a.value->string.s;
1154 xml.writeEmptyElement("string", attrs3);
1155 }
1156 }
1157 }
1158 }
1159 }
1160 } else
1161 xml.writeEmptyElement("item", attrs);
1162 } else
1163 table.push_back(columns);
1164
1165 cout.flush();
1166
1167 } catch (AssertionError & e) {
1168 printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
1169 } catch (Error & e) {
1170 e.addPrefix(fmt("while querying the derivation named '%1%':\n", i.queryName()));
1171 throw;
1172 }
1173 }
1174
1175 if (!xmlOutput) printTable(table);
1176 }
1177
1178
opSwitchProfile(Globals & globals,Strings opFlags,Strings opArgs)1179 static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
1180 {
1181 if (opFlags.size() > 0)
1182 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1183 if (opArgs.size() != 1)
1184 throw UsageError(format("exactly one argument expected"));
1185
1186 Path profile = absPath(opArgs.front());
1187 Path profileLink = getHome() + "/.nix-profile";
1188
1189 switchLink(profileLink, profile);
1190 }
1191
1192
1193 static const int prevGen = -2;
1194
1195
switchGeneration(Globals & globals,int dstGen)1196 static void switchGeneration(Globals & globals, int dstGen)
1197 {
1198 PathLocks lock;
1199 lockProfile(lock, globals.profile);
1200
1201 int curGen;
1202 Generations gens = findGenerations(globals.profile, curGen);
1203
1204 Generation dst;
1205 for (auto & i : gens)
1206 if ((dstGen == prevGen && i.number < curGen) ||
1207 (dstGen >= 0 && i.number == dstGen))
1208 dst = i;
1209
1210 if (!dst) {
1211 if (dstGen == prevGen)
1212 throw Error(format("no generation older than the current (%1%) exists")
1213 % curGen);
1214 else
1215 throw Error(format("generation %1% does not exist") % dstGen);
1216 }
1217
1218 printInfo(format("switching from generation %1% to %2%")
1219 % curGen % dst.number);
1220
1221 if (globals.dryRun) return;
1222
1223 switchLink(globals.profile, dst.path);
1224 }
1225
1226
opSwitchGeneration(Globals & globals,Strings opFlags,Strings opArgs)1227 static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
1228 {
1229 if (opFlags.size() > 0)
1230 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1231 if (opArgs.size() != 1)
1232 throw UsageError(format("exactly one argument expected"));
1233
1234 int dstGen;
1235 if (!string2Int(opArgs.front(), dstGen))
1236 throw UsageError(format("expected a generation number"));
1237
1238 switchGeneration(globals, dstGen);
1239 }
1240
1241
opRollback(Globals & globals,Strings opFlags,Strings opArgs)1242 static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
1243 {
1244 if (opFlags.size() > 0)
1245 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1246 if (opArgs.size() != 0)
1247 throw UsageError(format("no arguments expected"));
1248
1249 switchGeneration(globals, prevGen);
1250 }
1251
1252
opListGenerations(Globals & globals,Strings opFlags,Strings opArgs)1253 static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
1254 {
1255 if (opFlags.size() > 0)
1256 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1257 if (opArgs.size() != 0)
1258 throw UsageError(format("no arguments expected"));
1259
1260 PathLocks lock;
1261 lockProfile(lock, globals.profile);
1262
1263 int curGen;
1264 Generations gens = findGenerations(globals.profile, curGen);
1265
1266 RunPager pager;
1267
1268 for (auto & i : gens) {
1269 tm t;
1270 if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
1271 cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n")
1272 % i.number
1273 % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday
1274 % t.tm_hour % t.tm_min % t.tm_sec
1275 % (i.number == curGen ? "(current)" : "");
1276 }
1277 }
1278
1279
opDeleteGenerations(Globals & globals,Strings opFlags,Strings opArgs)1280 static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
1281 {
1282 if (opFlags.size() > 0)
1283 throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1284
1285 if (opArgs.size() == 1 && opArgs.front() == "old") {
1286 deleteOldGenerations(globals.profile, globals.dryRun);
1287 } else if (opArgs.size() == 1 && opArgs.front().find('d') != string::npos) {
1288 deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
1289 } else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) {
1290 if(opArgs.front().size() < 2)
1291 throw Error(format("invalid number of generations ‘%1%’") % opArgs.front());
1292 string str_max = string(opArgs.front(), 1, opArgs.front().size());
1293 int max;
1294 if (!string2Int(str_max, max) || max == 0)
1295 throw Error(format("invalid number of generations to keep ‘%1%’") % opArgs.front());
1296 deleteGenerationsGreaterThan(globals.profile, max, globals.dryRun);
1297 } else {
1298 std::set<unsigned int> gens;
1299 for (auto & i : opArgs) {
1300 unsigned int n;
1301 if (!string2Int(i, n))
1302 throw UsageError(format("invalid generation number '%1%'") % i);
1303 gens.insert(n);
1304 }
1305 deleteGenerations(globals.profile, gens, globals.dryRun);
1306 }
1307 }
1308
1309
opVersion(Globals & globals,Strings opFlags,Strings opArgs)1310 static void opVersion(Globals & globals, Strings opFlags, Strings opArgs)
1311 {
1312 printVersion("nix-env");
1313 }
1314
1315
_main(int argc,char ** argv)1316 static int _main(int argc, char * * argv)
1317 {
1318 {
1319 Strings opFlags, opArgs;
1320 Operation op = 0;
1321 RepairFlag repair = NoRepair;
1322 string file;
1323
1324 Globals globals;
1325
1326 globals.instSource.type = srcUnknown;
1327 globals.instSource.nixExprPath = getHome() + "/.nix-defexpr";
1328 globals.instSource.systemFilter = "*";
1329
1330 if (!pathExists(globals.instSource.nixExprPath)) {
1331 try {
1332 createDirs(globals.instSource.nixExprPath);
1333 replaceSymlink(
1334 fmt("%s/profiles/per-user/%s/channels", settings.nixStateDir, getUserName()),
1335 globals.instSource.nixExprPath + "/channels");
1336 if (getuid() != 0)
1337 replaceSymlink(
1338 fmt("%s/profiles/per-user/root/channels", settings.nixStateDir),
1339 globals.instSource.nixExprPath + "/channels_root");
1340 } catch (Error &) { }
1341 }
1342
1343 globals.dryRun = false;
1344 globals.preserveInstalled = false;
1345 globals.removeAll = false;
1346 globals.prebuiltOnly = false;
1347
1348 struct MyArgs : LegacyArgs, MixEvalArgs
1349 {
1350 using LegacyArgs::LegacyArgs;
1351 };
1352
1353 MyArgs myArgs(baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
1354 Operation oldOp = op;
1355
1356 if (*arg == "--help")
1357 showManPage("nix-env");
1358 else if (*arg == "--version")
1359 op = opVersion;
1360 else if (*arg == "--install" || *arg == "-i")
1361 op = opInstall;
1362 else if (*arg == "--force-name") // undocumented flag for nix-install-package
1363 globals.forceName = getArg(*arg, arg, end);
1364 else if (*arg == "--uninstall" || *arg == "-e")
1365 op = opUninstall;
1366 else if (*arg == "--upgrade" || *arg == "-u")
1367 op = opUpgrade;
1368 else if (*arg == "--set-flag")
1369 op = opSetFlag;
1370 else if (*arg == "--set")
1371 op = opSet;
1372 else if (*arg == "--query" || *arg == "-q")
1373 op = opQuery;
1374 else if (*arg == "--profile" || *arg == "-p")
1375 globals.profile = absPath(getArg(*arg, arg, end));
1376 else if (*arg == "--file" || *arg == "-f")
1377 file = getArg(*arg, arg, end);
1378 else if (*arg == "--switch-profile" || *arg == "-S")
1379 op = opSwitchProfile;
1380 else if (*arg == "--switch-generation" || *arg == "-G")
1381 op = opSwitchGeneration;
1382 else if (*arg == "--rollback")
1383 op = opRollback;
1384 else if (*arg == "--list-generations")
1385 op = opListGenerations;
1386 else if (*arg == "--delete-generations")
1387 op = opDeleteGenerations;
1388 else if (*arg == "--dry-run") {
1389 printInfo("(dry run; not doing anything)");
1390 globals.dryRun = true;
1391 }
1392 else if (*arg == "--system-filter")
1393 globals.instSource.systemFilter = getArg(*arg, arg, end);
1394 else if (*arg == "--prebuilt-only" || *arg == "-b")
1395 globals.prebuiltOnly = true;
1396 else if (*arg == "--repair")
1397 repair = Repair;
1398 else if (*arg != "" && arg->at(0) == '-') {
1399 opFlags.push_back(*arg);
1400 /* FIXME: hacky */
1401 if (*arg == "--from-profile" ||
1402 (op == opQuery && (*arg == "--attr" || *arg == "-A")))
1403 opFlags.push_back(getArg(*arg, arg, end));
1404 }
1405 else
1406 opArgs.push_back(*arg);
1407
1408 if (oldOp && oldOp != op)
1409 throw UsageError("only one operation may be specified");
1410
1411 return true;
1412 });
1413
1414 myArgs.parseCmdline(argvToStrings(argc, argv));
1415
1416 initPlugins();
1417
1418 if (!op) throw UsageError("no operation specified");
1419
1420 auto store = openStore();
1421
1422 globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store));
1423 globals.state->repair = repair;
1424
1425 if (file != "")
1426 globals.instSource.nixExprPath = lookupFileArg(*globals.state, file);
1427
1428 globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);
1429
1430 if (globals.profile == "")
1431 globals.profile = getEnv("NIX_PROFILE", "");
1432
1433 if (globals.profile == "") {
1434 Path profileLink = getHome() + "/.nix-profile";
1435 try {
1436 if (!pathExists(profileLink)) {
1437 replaceSymlink(
1438 getuid() == 0
1439 ? settings.nixStateDir + "/profiles/default"
1440 : fmt("%s/profiles/per-user/%s/profile", settings.nixStateDir, getUserName()),
1441 profileLink);
1442 }
1443 globals.profile = absPath(readLink(profileLink), dirOf(profileLink));
1444 } catch (Error &) {
1445 globals.profile = profileLink;
1446 }
1447 }
1448
1449 op(globals, opFlags, opArgs);
1450
1451 globals.state->printStats();
1452
1453 return 0;
1454 }
1455 }
1456
1457 static RegisterLegacyCommand s1("nix-env", _main);
1458