1 : #include "profiles.hh"
2 : #include "names.hh"
3 : #include "globals.hh"
4 : #include "build.hh"
5 : #include "misc.hh"
6 : #include "gc.hh"
7 : #include "shared.hh"
8 : #include "parser.hh"
9 : #include "eval.hh"
10 : #include "help.txt.hh"
11 : #include "nixexpr-ast.hh"
12 : #include "get-drvs.hh"
13 : #include "attr-path.hh"
14 : #include "pathlocks.hh"
15 : #include "xml-writer.hh"
16 : #include "store.hh"
17 : #include "db.hh"
18 : #include "util.hh"
19 :
20 : #include <cerrno>
21 : #include <ctime>
22 : #include <algorithm>
23 : #include <iostream>
24 : #include <sstream>
25 :
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 : ATermMap autoArgs;
50 100 : InstallSourceInfo() : autoArgs(128) { };
51 : };
52 :
53 :
54 : struct Globals
55 : {
56 : InstallSourceInfo instSource;
57 : Path profile;
58 : EvalState state;
59 : bool dryRun;
60 : bool preserveInstalled;
61 : bool keepDerivations;
62 : string forceName;
63 : };
64 :
65 :
66 : typedef void (* Operation) (Globals & globals,
67 : Strings opFlags, Strings opArgs);
68 :
69 :
70 : void printHelp()
71 1 : {
72 1 : cout << string((char *) helpText, sizeof helpText);
73 : }
74 :
75 :
76 : static void loadDerivations(EvalState & state, Path nixExprPath,
77 : string systemFilter, const ATermMap & autoArgs, DrvInfos & elems)
78 10 : {
79 10 : getDerivations(state,
80 : parseExprFromFile(state, absPath(nixExprPath)), "", autoArgs, elems);
81 :
82 : /* Filter out all derivations not applicable to the current
83 : system. */
84 56 : for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
85 46 : j = i; j++;
86 46 : if (systemFilter != "*" && i->system != systemFilter)
87 0 : elems.erase(i);
88 : }
89 : }
90 :
91 :
92 : static Path getHomeDir()
93 51 : {
94 51 : Path homeDir(getEnv("HOME", ""));
95 2457 : if (homeDir == "") throw Error("HOME environment variable not set");
96 0 : return homeDir;
97 : }
98 :
99 :
100 : static Path getDefNixExprPath()
101 50 : {
102 50 : return getHomeDir() + "/.nix-defexpr";
103 : }
104 :
105 :
106 : struct AddPos : TermFun
107 : {
108 : ATerm operator () (ATerm e)
109 1071 : {
110 1071 : ATerm x, y, z;
111 1071 : if (matchBind(e, x, y, z)) return e;
112 886 : if (matchBind2(e, x, y))
113 0 : return makeBind(x, y, makeNoPos());
114 886 : return e;
115 : }
116 204 : };
117 :
118 :
119 : static DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
120 41 : {
121 41 : Path path = userEnv + "/manifest";
122 :
123 41 : if (!pathExists(path))
124 439 : return DrvInfos(); /* not an error, assume nothing installed */
125 :
126 36 : Expr e = ATreadFromNamedFile(path.c_str());
127 36 : if (!e) throw Error(format("cannot read Nix expression from `%1%'") % path);
128 :
129 : /* Compatibility: Bind(x, y) -> Bind(x, y, NoPos). */
130 252 : AddPos addPos;
131 36 : e = bottomupRewrite(addPos, e);
132 :
133 36 : DrvInfos elems;
134 36 : getDerivations(state, e, "", ATermMap(1), elems);
135 36 : return elems;
136 : }
137 :
138 :
139 : static void createUserEnv(EvalState & state, const DrvInfos & elems,
140 : const Path & profile, bool keepDerivations)
141 16 : {
142 : /* Build the components in the user environment, if they don't
143 : exist already. */
144 2002 : PathSet drvsToBuild;
145 31 : for (DrvInfos::const_iterator i = elems.begin();
146 : i != elems.end(); ++i)
147 : /* Call to `isDerivation' is for compatibility with Nix <= 0.7
148 : user environments. */
149 15 : if (i->queryDrvPath(state) != "" &&
150 : isDerivation(i->queryDrvPath(state)))
151 10 : drvsToBuild.insert(i->queryDrvPath(state));
152 :
153 16 : debug(format("building user environment dependencies"));
154 16 : buildDerivations(drvsToBuild);
155 :
156 : /* Get the environment builder expression. */
157 : Expr envBuilder = parseExprFromFile(state,
158 16 : nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */
159 :
160 : /* Construct the whole top level derivation. */
161 16 : PathSet references;
162 16 : ATermList manifest = ATempty;
163 16 : ATermList inputs = ATempty;
164 31 : for (DrvInfos::const_iterator i = elems.begin();
165 : i != elems.end(); ++i)
166 : {
167 15 : Path drvPath = keepDerivations ? i->queryDrvPath(state) : "";
168 15 : ATerm t = makeAttrs(ATmakeList5(
169 : makeBind(toATerm("type"),
170 : makeStr(toATerm("derivation")), makeNoPos()),
171 : makeBind(toATerm("name"),
172 : makeStr(toATerm(i->name)), makeNoPos()),
173 : makeBind(toATerm("system"),
174 : makeStr(toATerm(i->system)), makeNoPos()),
175 : makeBind(toATerm("drvPath"),
176 : makePath(toATerm(drvPath)), makeNoPos()),
177 : makeBind(toATerm("outPath"),
178 : makePath(toATerm(i->queryOutPath(state))), makeNoPos())
179 : ));
180 15 : manifest = ATinsert(manifest, t);
181 15 : inputs = ATinsert(inputs, makeStr(toATerm(i->queryOutPath(state))));
182 :
183 : /* This is only necessary when installing store paths, e.g.,
184 : `nix-env -i /nix/store/abcd...-foo'. */
185 15 : addTempRoot(i->queryOutPath(state));
186 15 : ensurePath(i->queryOutPath(state));
187 :
188 15 : references.insert(i->queryOutPath(state));
189 15 : if (drvPath != "") references.insert(drvPath);
190 : }
191 :
192 : /* Also write a copy of the list of inputs to the store; we need
193 : it for future modifications of the environment. */
194 : Path manifestFile = addTextToStore("env-manifest",
195 16 : atPrint(makeList(ATreverse(manifest))), references);
196 :
197 16 : Expr topLevel = makeCall(envBuilder, makeAttrs(ATmakeList3(
198 : makeBind(toATerm("system"),
199 : makeStr(toATerm(thisSystem)), makeNoPos()),
200 : makeBind(toATerm("derivations"),
201 : makeList(ATreverse(inputs)), makeNoPos()),
202 : makeBind(toATerm("manifest"),
203 : makePath(toATerm(manifestFile)), makeNoPos())
204 : )));
205 :
206 : /* Instantiate it. */
207 16 : debug(format("evaluating builder expression `%1%'") % topLevel);
208 3106 : DrvInfo topLevelDrv;
209 16 : if (!getDerivation(state, topLevel, topLevelDrv))
210 0 : abort();
211 :
212 : /* Realise the resulting store expression. */
213 16 : debug(format("building user environment"));
214 16 : buildDerivations(singleton<PathSet>(topLevelDrv.queryDrvPath(state)));
215 :
216 : /* Switch the current user environment to the output path. */
217 15 : debug(format("switching to new user environment"));
218 15 : Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state));
219 15 : switchLink(profile, generation);
220 : }
221 :
222 :
223 : static DrvInfos filterBySelector(EvalState & state,
224 : const DrvInfos & allElems,
225 : const Strings & args, bool newestOnly)
226 35 : {
227 115 : DrvNames selectors = drvNamesFromArgs(args);
228 :
229 35 : DrvInfos elems;
230 105 : set<unsigned int> done;
231 :
232 71 : for (DrvNames::iterator i = selectors.begin();
233 : i != selectors.end(); ++i)
234 : {
235 : typedef list<std::pair<DrvInfo, unsigned int> > Matches;
236 108 : Matches matches;
237 36 : unsigned int n = 0;
238 115 : for (DrvInfos::const_iterator j = allElems.begin();
239 : j != allElems.end(); ++j, ++n)
240 : {
241 489 : DrvName drvName(j->name);
242 79 : if (i->matches(drvName)) {
243 55 : i->hits++;
244 2359 : matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
245 : }
246 : }
247 :
248 : /* If `newestOnly', if a selector matches multiple derivations
249 : with the same name, pick the one with the highest version.
250 : If there are multiple derivations with the same name *and*
251 : version, then pick the first one. */
252 36 : if (newestOnly) {
253 :
254 : /* Map from package names to derivations. */
255 : typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
256 24 : Newest newest;
257 8 : StringSet multiple;
258 :
259 22 : for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) {
260 14 : DrvName drvName(j->first.name);
261 14 : Newest::iterator k = newest.find(drvName.name);
262 14 : if (k != newest.end()) {
263 5 : int d = compareVersions(drvName.version, DrvName(k->second.first.name).version);
264 143 : if (d > 0) newest[drvName.name] = *j;
265 0 : else if (d == 0) multiple.insert(j->first.name);
266 : } else
267 9 : newest[drvName.name] = *j;
268 : }
269 :
270 8 : matches.clear();
271 17 : for (Newest::iterator j = newest.begin(); j != newest.end(); ++j) {
272 9 : if (multiple.find(j->second.first.name) != multiple.end())
273 0 : printMsg(lvlInfo,
274 : format("warning: there are multiple derivations named `%1%'; using the first one")
275 : % j->second.first.name);
276 9 : matches.push_back(j->second);
277 : }
278 : }
279 :
280 : /* Insert only those elements in the final list that we
281 : haven't inserted before. */
282 86 : for (Matches::iterator j = matches.begin(); j != matches.end(); ++j)
283 50 : if (done.find(j->second) == done.end()) {
284 50 : done.insert(j->second);
285 50 : elems.push_back(j->first);
286 : }
287 : }
288 :
289 : /* Check that all selectors have been used. */
290 69 : for (DrvNames::iterator i = selectors.begin();
291 : i != selectors.end(); ++i)
292 36 : if (i->hits == 0)
293 2 : throw Error(format("selector `%1%' matches no derivations")
294 : % i->fullName);
295 :
296 33 : return elems;
297 : }
298 :
299 :
300 : static void queryInstSources(EvalState & state,
301 : const InstallSourceInfo & instSource, const Strings & args,
302 : DrvInfos & elems, bool newestOnly)
303 11 : {
304 11 : InstallSourceType type = instSource.type;
305 11 : if (type == srcUnknown && args.size() > 0 && args.front()[0] == '/')
306 3 : type = srcStorePaths;
307 :
308 11 : switch (type) {
309 :
310 : /* Get the available user environment elements from the
311 : derivations specified in a Nix expression, including only
312 : those with names matching any of the names in `args'. */
313 : case srcUnknown:
314 : case srcNixExprDrvs: {
315 :
316 : /* Load the derivations from the (default or specified)
317 : Nix expression. */
318 8 : DrvInfos allElems;
319 8 : loadDerivations(state, instSource.nixExprPath,
320 : instSource.systemFilter, instSource.autoArgs, allElems);
321 :
322 8 : elems = filterBySelector(state, allElems, args, newestOnly);
323 :
324 8 : break;
325 : }
326 :
327 : /* Get the available user environment elements from the Nix
328 : expressions specified on the command line; these should be
329 : functions that take the default Nix expression file as
330 : argument, e.g., if the file is `./foo.nix', then the
331 : argument `x: x.bar' is equivalent to `(x: x.bar)
332 : (import ./foo.nix)' = `(import ./foo.nix).bar'. */
333 : case srcNixExprs: {
334 :
335 :
336 : Expr e1 = parseExprFromFile(state,
337 0 : absPath(instSource.nixExprPath));
338 :
339 0 : for (Strings::const_iterator i = args.begin();
340 : i != args.end(); ++i)
341 : {
342 0 : Expr e2 = parseExprFromString(state, *i, absPath("."));
343 0 : Expr call = makeCall(e2, e1);
344 0 : getDerivations(state, call, "", instSource.autoArgs, elems);
345 : }
346 :
347 3 : break;
348 : }
349 :
350 : /* The available user environment elements are specified as a
351 : list of store paths (which may or may not be
352 : derivations). */
353 : case srcStorePaths: {
354 :
355 6 : for (Strings::const_iterator i = args.begin();
356 : i != args.end(); ++i)
357 : {
358 3 : assertStorePath(*i);
359 :
360 3 : DrvInfo elem;
361 3 : elem.attrs = boost::shared_ptr<ATermMap>(new ATermMap(0)); /* ugh... */
362 3 : string name = baseNameOf(*i);
363 3 : string::size_type dash = name.find('-');
364 3 : if (dash != string::npos)
365 3 : name = string(name, dash + 1);
366 :
367 3 : if (isDerivation(*i)) {
368 0 : elem.setDrvPath(*i);
369 5836 : elem.setOutPath(findOutput(derivationFromPath(*i), "out"));
370 0 : if (name.size() >= drvExtension.size() &&
371 : string(name, name.size() - drvExtension.size()) == drvExtension)
372 0 : name = string(name, 0, name.size() - drvExtension.size());
373 : }
374 3 : else elem.setOutPath(*i);
375 :
376 3 : elem.name = name;
377 :
378 3 : elems.push_back(elem);
379 : }
380 :
381 0 : break;
382 : }
383 :
384 : /* Get the available user environment elements from another
385 : user environment. These are then filtered as in the
386 : `srcNixExprDrvs' case. */
387 : case srcProfile: {
388 0 : elems = filterBySelector(state,
389 : queryInstalled(state, instSource.profile),
390 : args, newestOnly);
391 0 : break;
392 : }
393 :
394 : case srcAttrPath: {
395 0 : for (Strings::const_iterator i = args.begin();
396 : i != args.end(); ++i)
397 0 : getDerivations(state,
398 : findAlongAttrPath(state, *i, instSource.autoArgs,
399 : parseExprFromFile(state, instSource.nixExprPath)),
400 : "", instSource.autoArgs, elems);
401 11 : break;
402 : }
403 : }
404 : }
405 :
406 :
407 : static void printMissing(EvalState & state, const DrvInfos & elems)
408 0 : {
409 0 : PathSet targets, willBuild, willSubstitute;
410 0 : for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) {
411 0 : Path drvPath = i->queryDrvPath(state);
412 0 : if (drvPath != "")
413 0 : targets.insert(drvPath);
414 : else
415 0 : targets.insert(i->queryOutPath(state));
416 : }
417 :
418 0 : queryMissing(targets, willBuild, willSubstitute);
419 :
420 0 : if (!willBuild.empty()) {
421 0 : printMsg(lvlInfo, format("the following derivations will be built:"));
422 0 : for (PathSet::iterator i = willBuild.begin(); i != willBuild.end(); ++i)
423 0 : printMsg(lvlInfo, format(" %1%") % *i);
424 : }
425 :
426 0 : if (!willSubstitute.empty()) {
427 0 : printMsg(lvlInfo, format("the following paths will be substituted:"));
428 0 : for (PathSet::iterator i = willSubstitute.begin(); i != willSubstitute.end(); ++i)
429 0 : printMsg(lvlInfo, format(" %1%") % *i);
430 : }
431 : }
432 :
433 :
434 : static void lockProfile(PathLocks & lock, const Path & profile)
435 21 : {
436 21 : lock.lockPaths(singleton<PathSet>(profile),
437 : (format("waiting for lock on profile `%1%'") % profile).str());
438 21 : lock.setDeletion(true);
439 : }
440 :
441 :
442 : static void installDerivations(Globals & globals,
443 : const Strings & args, const Path & profile)
444 10 : {
445 10 : debug(format("installing derivations"));
446 :
447 : /* Get the set of user environment elements to be installed. */
448 10 : DrvInfos newElems;
449 10 : queryInstSources(globals.state, globals.instSource, args, newElems, true);
450 :
451 10 : StringSet newNames;
452 22 : for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) {
453 : /* `forceName' is a hack to get package names right in some
454 : one-click installs, namely those where the name used in the
455 : path is not the one we want (e.g., `java-front' versus
456 : `java-front-0.9pre15899'). */
457 12 : if (globals.forceName != "")
458 2 : i->name = globals.forceName;
459 12 : newNames.insert(DrvName(i->name).name);
460 : }
461 :
462 : /* Add in the already installed derivations, unless they have the
463 : same name as a to-be-installed element. */
464 10 : PathLocks lock;
465 10 : lockProfile(lock, profile);
466 10 : DrvInfos installedElems = queryInstalled(globals.state, profile);
467 :
468 10 : DrvInfos allElems(newElems);
469 13 : for (DrvInfos::iterator i = installedElems.begin();
470 : i != installedElems.end(); ++i)
471 : {
472 3 : DrvName drvName(i->name);
473 3 : if (!globals.preserveInstalled &&
474 : newNames.find(drvName.name) != newNames.end())
475 2 : printMsg(lvlInfo,
476 : format("replacing old `%1%'") % i->name);
477 : else
478 1 : allElems.push_back(*i);
479 : }
480 :
481 22 : for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i)
482 12 : printMsg(lvlInfo,
483 : format("installing `%1%'") % i->name);
484 :
485 10 : if (globals.dryRun) {
486 0 : printMissing(globals.state, newElems);
487 0 : return;
488 : }
489 :
490 10 : createUserEnv(globals.state, allElems,
491 : profile, globals.keepDerivations);
492 : }
493 :
494 :
495 : static void opInstall(Globals & globals,
496 : Strings opFlags, Strings opArgs)
497 10 : {
498 10 : if (opFlags.size() > 0)
499 4 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
500 :
501 10 : installDerivations(globals, opArgs, globals.profile);
502 : }
503 :
504 :
505 : typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType;
506 :
507 :
508 : static void upgradeDerivations(Globals & globals,
509 : const Strings & args, const Path & profile,
510 : UpgradeType upgradeType)
511 1 : {
512 1 : debug(format("upgrading derivations"));
513 :
514 : /* Upgrade works as follows: we take all currently installed
515 : derivations, and for any derivation matching any selector, look
516 : for a derivation in the input Nix expression that has the same
517 : name and a higher version number. */
518 :
519 : /* Load the currently installed derivations. */
520 1 : PathLocks lock;
521 1 : lockProfile(lock, profile);
522 1 : DrvInfos installedElems = queryInstalled(globals.state, profile);
523 :
524 : /* Fetch all derivations from the input file. */
525 1 : DrvInfos availElems;
526 1 : queryInstSources(globals.state, globals.instSource, args, availElems, false);
527 :
528 : /* Go through all installed derivations. */
529 1 : DrvInfos newElems;
530 2 : for (DrvInfos::iterator i = installedElems.begin();
531 : i != installedElems.end(); ++i)
532 : {
533 1 : DrvName drvName(i->name);
534 :
535 : /* Find the derivation in the input Nix expression with the
536 : same name and satisfying the version constraints specified
537 : by upgradeType. If there are multiple matches, take the
538 : one with highest version. */
539 1 : DrvInfos::iterator bestElem = availElems.end();
540 1 : DrvName bestName;
541 4 : for (DrvInfos::iterator j = availElems.begin();
542 : j != availElems.end(); ++j)
543 : {
544 3 : DrvName newName(j->name);
545 3 : if (newName.name == drvName.name) {
546 3 : int d = compareVersions(drvName.version, newName.version);
547 3 : if (upgradeType == utLt && d < 0 ||
548 : upgradeType == utLeq && d <= 0 ||
549 : upgradeType == utEq && d == 0 ||
550 : upgradeType == utAlways)
551 : {
552 1 : if ((bestElem == availElems.end() ||
553 : compareVersions(
554 : bestName.version, newName.version) < 0))
555 : {
556 1 : bestElem = j;
557 3 : bestName = newName;
558 : }
559 : }
560 : }
561 : }
562 :
563 1 : if (bestElem != availElems.end() &&
564 : i->queryOutPath(globals.state) !=
565 : bestElem->queryOutPath(globals.state))
566 : {
567 1 : printMsg(lvlInfo,
568 : format("upgrading `%1%' to `%2%'")
569 : % i->name % bestElem->name);
570 1 : newElems.push_back(*bestElem);
571 0 : } else newElems.push_back(*i);
572 : }
573 :
574 1 : if (globals.dryRun) {
575 0 : printMissing(globals.state, newElems);
576 0 : return;
577 : }
578 :
579 1 : createUserEnv(globals.state, newElems,
580 : profile, globals.keepDerivations);
581 : }
582 :
583 :
584 : static void opUpgrade(Globals & globals,
585 : Strings opFlags, Strings opArgs)
586 1 : {
587 1 : UpgradeType upgradeType = utLt;
588 1 : for (Strings::iterator i = opFlags.begin();
589 : i != opFlags.end(); ++i)
590 0 : if (*i == "--lt") upgradeType = utLt;
591 0 : else if (*i == "--leq") upgradeType = utLeq;
592 0 : else if (*i == "--eq") upgradeType = utEq;
593 0 : else if (*i == "--always") upgradeType = utAlways;
594 0 : else throw UsageError(format("unknown flag `%1%'") % *i);
595 :
596 1 : upgradeDerivations(globals, opArgs, globals.profile, upgradeType);
597 : }
598 :
599 :
600 : static void uninstallDerivations(Globals & globals, DrvNames & selectors,
601 : Path & profile)
602 5 : {
603 5 : PathLocks lock;
604 5 : lockProfile(lock, profile);
605 5 : DrvInfos installedElems = queryInstalled(globals.state, profile);
606 5 : DrvInfos newElems;
607 :
608 10 : for (DrvInfos::iterator i = installedElems.begin();
609 : i != installedElems.end(); ++i)
610 : {
611 5 : DrvName drvName(i->name);
612 5 : bool found = false;
613 6 : for (DrvNames::iterator j = selectors.begin();
614 : j != selectors.end(); ++j)
615 5 : if (j->matches(drvName)) {
616 4 : printMsg(lvlInfo,
617 : format("uninstalling `%1%'") % i->name);
618 4 : found = true;
619 4 : break;
620 : }
621 5 : if (!found) newElems.push_back(*i);
622 : }
623 :
624 5 : if (globals.dryRun) return;
625 :
626 5 : createUserEnv(globals.state, newElems,
627 : profile, globals.keepDerivations);
628 : }
629 :
630 :
631 : static void opUninstall(Globals & globals,
632 : Strings opFlags, Strings opArgs)
633 5 : {
634 5 : if (opFlags.size() > 0)
635 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
636 :
637 5 : DrvNames drvNames = drvNamesFromArgs(opArgs);
638 :
639 5 : uninstallDerivations(globals, drvNames,
640 : globals.profile);
641 : }
642 :
643 :
644 : static bool cmpChars(char a, char b)
645 176 : {
646 176 : return toupper(a) < toupper(b);
647 : }
648 :
649 :
650 : static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
651 32 : {
652 32 : return lexicographical_compare(
653 : a.name.begin(), a.name.end(),
654 : b.name.begin(), b.name.end(), cmpChars);
655 : }
656 :
657 :
658 : typedef list<Strings> Table;
659 :
660 :
661 : void printTable(Table & table)
662 25 : {
663 25 : unsigned int nrColumns = table.size() > 0 ? table.front().size() : 0;
664 :
665 25 : vector<unsigned int> widths;
666 25 : widths.resize(nrColumns);
667 :
668 63 : for (Table::iterator i = table.begin(); i != table.end(); ++i) {
669 38 : assert(i->size() == nrColumns);
670 38 : Strings::iterator j;
671 38 : unsigned int column;
672 81 : for (j = i->begin(), column = 0; j != i->end(); ++j, ++column)
673 43 : if (j->size() > widths[column]) widths[column] = j->size();
674 : }
675 :
676 63 : for (Table::iterator i = table.begin(); i != table.end(); ++i) {
677 38 : Strings::iterator j;
678 38 : unsigned int column;
679 81 : for (j = i->begin(), column = 0; j != i->end(); ++j, ++column)
680 : {
681 43 : cout << *j;
682 43 : if (column < nrColumns - 1)
683 5 : cout << string(widths[column] - j->size() + 2, ' ');
684 : }
685 38 : cout << std::endl;
686 : }
687 : }
688 :
689 :
690 : /* This function compares the version of a element against the
691 : versions in the given set of elements. `cvLess' means that only
692 : lower versions are in the set, `cvEqual' means that at most an
693 : equal version is in the set, and `cvGreater' means that there is at
694 : least one element with a higher version in the set. `cvUnavail'
695 : means that there are no elements with the same name in the set. */
696 :
697 : typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
698 :
699 : static VersionDiff compareVersionAgainstSet(
700 : const DrvInfo & elem, const DrvInfos & elems, string & version)
701 0 : {
702 0 : DrvName name(elem.name);
703 :
704 0 : VersionDiff diff = cvUnavail;
705 0 : version = "?";
706 :
707 0 : for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) {
708 0 : DrvName name2(i->name);
709 0 : if (name.name == name2.name) {
710 0 : int d = compareVersions(name.version, name2.version);
711 0 : if (d < 0) {
712 0 : diff = cvGreater;
713 0 : version = name2.version;
714 : }
715 0 : else if (diff != cvGreater && d == 0) {
716 0 : diff = cvEqual;
717 0 : version = name2.version;
718 : }
719 0 : else if (diff != cvGreater && diff != cvEqual && d > 0) {
720 0 : diff = cvLess;
721 0 : if (version == "" || compareVersions(version, name2.version) < 0)
722 0 : version = name2.version;
723 : }
724 : }
725 : }
726 :
727 0 : return diff;
728 : }
729 :
730 :
731 : static string colorString(const string & s)
732 0 : {
733 0 : if (!isatty(STDOUT_FILENO)) return s;
734 0 : return "\e[1;31m" + s + "\e[0m";
735 : }
736 :
737 :
738 : static void opQuery(Globals & globals,
739 : Strings opFlags, Strings opArgs)
740 28 : {
741 : typedef vector< map<string, string> > ResultSet;
742 :
743 28 : bool printStatus = false;
744 28 : bool printName = true;
745 28 : bool printAttrPath = false;
746 28 : bool printSystem = false;
747 28 : bool printDrvPath = false;
748 28 : bool printOutPath = false;
749 28 : bool printDescription = false;
750 28 : bool compareVersions = false;
751 28 : bool xmlOutput = false;
752 :
753 28 : enum { sInstalled, sAvailable } source = sInstalled;
754 :
755 28 : readOnlyMode = true; /* makes evaluation a bit faster */
756 :
757 37 : for (Strings::iterator i = opFlags.begin();
758 : i != opFlags.end(); ++i)
759 10 : if (*i == "--status" || *i == "-s") printStatus = true;
760 10 : else if (*i == "--no-name") printName = false;
761 7 : else if (*i == "--system") printSystem = true;
762 7 : else if (*i == "--description") printDescription = true;
763 6 : else if (*i == "--compare-versions" || *i == "-c") compareVersions = true;
764 6 : else if (*i == "--drv-path") printDrvPath = true;
765 6 : else if (*i == "--out-path") printOutPath = true;
766 3 : else if (*i == "--installed") source = sInstalled;
767 3 : else if (*i == "--available" || *i == "-a") source = sAvailable;
768 1 : else if (*i == "--xml") xmlOutput = true;
769 1 : else throw UsageError(format("unknown flag `%1%'") % *i);
770 :
771 27 : if (globals.instSource.type == srcAttrPath) printAttrPath = true; /* hack */
772 :
773 27 : if (opArgs.size() == 0) {
774 0 : printMsg(lvlInfo, "warning: you probably meant to specify the argument '*' to show all packages");
775 : }
776 :
777 :
778 : /* Obtain derivation information from the specified source. */
779 27 : DrvInfos availElems, installedElems;
780 :
781 27 : if (source == sInstalled || compareVersions || printStatus) {
782 25 : installedElems = queryInstalled(globals.state, globals.profile);
783 : }
784 :
785 27 : if (source == sAvailable || compareVersions) {
786 2 : loadDerivations(globals.state, globals.instSource.nixExprPath,
787 : globals.instSource.systemFilter, globals.instSource.autoArgs,
788 : availElems);
789 : }
790 :
791 : DrvInfos elems = filterBySelector(globals.state,
792 : source == sInstalled ? installedElems : availElems,
793 27 : opArgs, false);
794 :
795 25 : DrvInfos & otherElems(source == sInstalled ? availElems : installedElems);
796 :
797 :
798 : /* Sort them by name. */
799 : /* !!! */
800 25 : vector<DrvInfo> elems2;
801 63 : for (DrvInfos::iterator i = elems.begin(); i != elems.end(); ++i)
802 38 : elems2.push_back(*i);
803 25 : sort(elems2.begin(), elems2.end(), cmpElemByName);
804 :
805 :
806 : /* We only need to know the installed paths when we are querying
807 : the status of the derivation. */
808 25 : PathSet installed; /* installed paths */
809 :
810 25 : if (printStatus) {
811 0 : for (DrvInfos::iterator i = installedElems.begin();
812 : i != installedElems.end(); ++i)
813 0 : installed.insert(i->queryOutPath(globals.state));
814 : }
815 :
816 :
817 : /* Print the desired columns, or XML output. */
818 75 : Table table;
819 25 : std::ostringstream dummy;
820 25 : XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
821 25 : XMLOpenElement xmlRoot(xml, "items");
822 :
823 63 : for (vector<DrvInfo>::iterator i = elems2.begin();
824 : i != elems2.end(); ++i)
825 : {
826 38 : try {
827 :
828 : /* For table output. */
829 38 : Strings columns;
830 :
831 : /* For XML output. */
832 38 : XMLAttrs attrs;
833 :
834 38 : if (printStatus) {
835 164 : Substitutes subs = querySubstitutes(noTxn, i->queryOutPath(globals.state));
836 0 : bool isInstalled = installed.find(i->queryOutPath(globals.state)) != installed.end();
837 0 : bool isValid = isValidPath(i->queryOutPath(globals.state));
838 0 : if (xmlOutput) {
839 0 : attrs["installed"] = isInstalled ? "1" : "0";
840 0 : attrs["valid"] = isValid ? "1" : "0";
841 0 : attrs["substitutable"] = !subs.empty() ? "1" : "0";
842 : } else
843 0 : columns.push_back(
844 : (string) (isInstalled ? "I" : "-")
845 : + (isValid ? "P" : "-")
846 : + (!subs.empty() ? "S" : "-"));
847 : }
848 :
849 38 : if (xmlOutput)
850 0 : attrs["attrPath"] = i->attrPath;
851 38 : else if (printAttrPath)
852 0 : columns.push_back(i->attrPath);
853 :
854 38 : if (xmlOutput)
855 0 : attrs["name"] = i->name;
856 38 : else if (printName)
857 35 : columns.push_back(i->name);
858 :
859 38 : if (compareVersions) {
860 : /* Compare this element against the versions of the
861 : same named packages in either the set of available
862 : elements, or the set of installed elements. !!!
863 : This is O(N * M), should be O(N * lg M). */
864 0 : string version;
865 0 : VersionDiff diff = compareVersionAgainstSet(*i, otherElems, version);
866 :
867 0 : char ch;
868 0 : switch (diff) {
869 0 : case cvLess: ch = '>'; break;
870 0 : case cvEqual: ch = '='; break;
871 0 : case cvGreater: ch = '<'; break;
872 0 : case cvUnavail: ch = '-'; break;
873 0 : default: abort();
874 : }
875 :
876 0 : if (xmlOutput) {
877 0 : if (diff != cvUnavail) {
878 0 : attrs["versionDiff"] = ch;
879 0 : attrs["maxComparedVersion"] = version;
880 : }
881 : } else {
882 0 : string column = (string) "" + ch + " " + version;
883 0 : if (diff == cvGreater) column = colorString(column);
884 0 : columns.push_back(column);
885 : }
886 : }
887 :
888 38 : if (xmlOutput) {
889 0 : if (i->system != "") attrs["system"] = i->system;
890 : }
891 38 : else if (printSystem)
892 0 : columns.push_back(i->system);
893 :
894 38 : if (printDrvPath) {
895 0 : string drvPath = i->queryDrvPath(globals.state);
896 0 : if (xmlOutput) {
897 0 : if (drvPath != "") attrs["drvPath"] = drvPath;
898 : } else
899 0 : columns.push_back(drvPath == "" ? "-" : drvPath);
900 : }
901 :
902 38 : if (printOutPath) {
903 3 : string outPath = i->queryOutPath(globals.state);
904 3 : if (xmlOutput) {
905 0 : if (outPath != "") attrs["outPath"] = outPath;
906 : } else
907 3 : columns.push_back(outPath);
908 : }
909 :
910 38 : if (printDescription) {
911 5 : MetaInfo meta = i->queryMetaInfo(globals.state);
912 5 : string descr = meta["description"];
913 5 : if (xmlOutput) {
914 0 : if (descr != "") attrs["description"] = descr;
915 : } else
916 5 : columns.push_back(descr);
917 : }
918 :
919 38 : if (xmlOutput)
920 0 : xml.writeEmptyElement("item", attrs);
921 : else
922 38 : table.push_back(columns);
923 :
924 0 : } catch (AssertionError & e) {
925 : /* !!! hm, maybe we should give some sort of warning here? */
926 : }
927 : }
928 :
929 25 : if (!xmlOutput) printTable(table);
930 : }
931 :
932 :
933 : static void opSwitchProfile(Globals & globals,
934 : Strings opFlags, Strings opArgs)
935 0 : {
936 0 : if (opFlags.size() > 0)
937 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
938 0 : if (opArgs.size() != 1)
939 0 : throw UsageError(format("exactly one argument expected"));
940 :
941 0 : Path profile = opArgs.front();
942 0 : Path profileLink = getHomeDir() + "/.nix-profile";
943 :
944 0 : SwitchToOriginalUser sw;
945 0 : switchLink(profileLink, profile);
946 : }
947 :
948 :
949 : static const int prevGen = -2;
950 :
951 :
952 : static void switchGeneration(Globals & globals, int dstGen)
953 2 : {
954 2 : PathLocks lock;
955 2 : lockProfile(lock, globals.profile);
956 :
957 2 : int curGen;
958 1732 : Generations gens = findGenerations(globals.profile, curGen);
959 :
960 222 : Generation dst;
961 12 : for (Generations::iterator i = gens.begin(); i != gens.end(); ++i)
962 10 : if ((dstGen == prevGen && i->number < curGen) ||
963 : (dstGen >= 0 && i->number == dstGen))
964 21 : dst = *i;
965 :
966 2 : if (!dst)
967 0 : if (dstGen == prevGen)
968 0 : throw Error(format("no generation older than the current (%1%) exists")
969 : % curGen);
970 : else
971 0 : throw Error(format("generation %1% does not exist") % dstGen);
972 :
973 2 : printMsg(lvlInfo, format("switching from generation %1% to %2%")
974 : % curGen % dst.number);
975 :
976 2 : if (globals.dryRun) return;
977 :
978 2 : switchLink(globals.profile, dst.path);
979 : }
980 :
981 :
982 : static void opSwitchGeneration(Globals & globals,
983 : Strings opFlags, Strings opArgs)
984 0 : {
985 0 : if (opFlags.size() > 0)
986 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
987 0 : if (opArgs.size() != 1)
988 0 : throw UsageError(format("exactly one argument expected"));
989 :
990 0 : int dstGen;
991 0 : if (!string2Int(opArgs.front(), dstGen))
992 0 : throw UsageError(format("expected a generation number"));
993 :
994 0 : switchGeneration(globals, dstGen);
995 : }
996 :
997 :
998 : static void opRollback(Globals & globals,
999 : Strings opFlags, Strings opArgs)
1000 2 : {
1001 2 : if (opFlags.size() > 0)
1002 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
1003 2 : if (opArgs.size() != 0)
1004 0 : throw UsageError(format("no arguments expected"));
1005 :
1006 2 : switchGeneration(globals, prevGen);
1007 : }
1008 :
1009 :
1010 : static void opListGenerations(Globals & globals,
1011 : Strings opFlags, Strings opArgs)
1012 1 : {
1013 1 : if (opFlags.size() > 0)
1014 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
1015 1 : if (opArgs.size() != 0)
1016 0 : throw UsageError(format("no arguments expected"));
1017 :
1018 1 : PathLocks lock;
1019 1 : lockProfile(lock, globals.profile);
1020 :
1021 1 : int curGen;
1022 1 : Generations gens = findGenerations(globals.profile, curGen);
1023 :
1024 6 : for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) {
1025 5 : tm t;
1026 5 : if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time");
1027 5 : cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n")
1028 : % i->number
1029 : % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday
1030 : % t.tm_hour % t.tm_min % t.tm_sec
1031 : % (i->number == curGen ? "(current)" : "");
1032 : }
1033 : }
1034 :
1035 :
1036 : static void deleteGeneration2(const Path & profile, unsigned int gen)
1037 6 : {
1038 6 : printMsg(lvlInfo, format("removing generation %1%") % gen);
1039 6 : deleteGeneration(profile, gen);
1040 : }
1041 :
1042 :
1043 : static void opDeleteGenerations(Globals & globals,
1044 : Strings opFlags, Strings opArgs)
1045 2 : {
1046 2 : if (opFlags.size() > 0)
1047 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
1048 :
1049 2 : PathLocks lock;
1050 2 : lockProfile(lock, globals.profile);
1051 :
1052 2 : int curGen;
1053 2 : Generations gens = findGenerations(globals.profile, curGen);
1054 :
1055 4 : for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) {
1056 :
1057 2 : if (*i == "old") {
1058 10 : for (Generations::iterator j = gens.begin(); j != gens.end(); ++j)
1059 8 : if (j->number != curGen)
1060 6 : deleteGeneration2(globals.profile, j->number);
1061 : }
1062 :
1063 : else {
1064 0 : int n;
1065 0 : if (!string2Int(*i, n) || n < 0)
1066 0 : throw UsageError(format("invalid generation specifier `%1%'") % *i);
1067 0 : bool found = false;
1068 0 : for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) {
1069 0 : if (j->number == n) {
1070 0 : deleteGeneration2(globals.profile, j->number);
1071 0 : found = true;
1072 0 : break;
1073 : }
1074 : }
1075 0 : if (!found)
1076 0 : printMsg(lvlError, format("generation %1% does not exist") % n);
1077 : }
1078 : }
1079 : }
1080 :
1081 :
1082 : static void opDefaultExpr(Globals & globals,
1083 : Strings opFlags, Strings opArgs)
1084 0 : {
1085 0 : if (opFlags.size() > 0)
1086 0 : throw UsageError(format("unknown flag `%1%'") % opFlags.front());
1087 0 : if (opArgs.size() != 1)
1088 0 : throw UsageError(format("exactly one argument expected"));
1089 :
1090 0 : Path defNixExpr = absPath(opArgs.front());
1091 0 : Path defNixExprLink = getDefNixExprPath();
1092 :
1093 0 : SwitchToOriginalUser sw;
1094 0 : switchLink(defNixExprLink, defNixExpr);
1095 : }
1096 :
1097 :
1098 : static string needArg(Strings::iterator & i,
1099 : Strings & args, const string & arg)
1100 64 : {
1101 64 : ++i;
1102 64 : if (i == args.end()) throw UsageError(
1103 : format("`%1%' requires an argument") % arg);
1104 64 : return *i;
1105 : }
1106 :
1107 :
1108 : void run(Strings args)
1109 50 : {
1110 50 : Strings opFlags, opArgs;
1111 50 : Operation op = 0;
1112 :
1113 586 : Globals globals;
1114 :
1115 50 : globals.instSource.type = srcUnknown;
1116 50 : globals.instSource.nixExprPath = getDefNixExprPath();
1117 50 : globals.instSource.systemFilter = thisSystem;
1118 :
1119 50 : globals.dryRun = false;
1120 50 : globals.preserveInstalled = false;
1121 :
1122 50 : globals.keepDerivations =
1123 : queryBoolSetting("env-keep-derivations", false);
1124 :
1125 220 : for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
1126 170 : string arg = *i;
1127 :
1128 170 : Operation oldOp = op;
1129 :
1130 170 : if (arg == "--install" || arg == "-i")
1131 10 : op = opInstall;
1132 160 : else if (arg == "--from-expression" || arg == "-E")
1133 0 : globals.instSource.type = srcNixExprs;
1134 160 : else if (arg == "--from-profile") {
1135 0 : globals.instSource.type = srcProfile;
1136 0 : globals.instSource.profile = needArg(i, args, arg);
1137 : }
1138 160 : else if (arg == "--attr" || arg == "-A")
1139 0 : globals.instSource.type = srcAttrPath;
1140 160 : else if (arg == "--arg") { /* !!! code duplication from nix-instantiate */
1141 0 : i++;
1142 0 : if (i == args.end())
1143 0 : throw UsageError("`--arg' requires two arguments");
1144 0 : string name = *i++;
1145 0 : if (i == args.end())
1146 0 : throw UsageError("`--arg' requires two arguments");
1147 0 : Expr value = parseExprFromString(globals.state, *i, absPath("."));
1148 0 : globals.instSource.autoArgs.set(toATerm(name), value);
1149 : }
1150 160 : else if (arg == "--force-name") // undocumented flag for nix-install-package
1151 2 : globals.forceName = needArg(i, args, arg);
1152 158 : else if (arg == "--uninstall" || arg == "-e")
1153 5 : op = opUninstall;
1154 153 : else if (arg == "--upgrade" || arg == "-u")
1155 1 : op = opUpgrade;
1156 152 : else if (arg == "--query" || arg == "-q")
1157 28 : op = opQuery;
1158 124 : else if (arg == "--import" || arg == "-I") /* !!! bad name */
1159 0 : op = opDefaultExpr;
1160 124 : else if (arg == "--profile" || arg == "-p") {
1161 48 : globals.profile = absPath(needArg(i, args, arg));
1162 : }
1163 76 : else if (arg == "--file" || arg == "-f") {
1164 14 : globals.instSource.nixExprPath = absPath(needArg(i, args, arg));
1165 : }
1166 62 : else if (arg == "--switch-profile" || arg == "-S")
1167 0 : op = opSwitchProfile;
1168 62 : else if (arg == "--switch-generation" || arg == "-G")
1169 0 : op = opSwitchGeneration;
1170 62 : else if (arg == "--rollback")
1171 2 : op = opRollback;
1172 60 : else if (arg == "--list-generations")
1173 1 : op = opListGenerations;
1174 59 : else if (arg == "--delete-generations")
1175 2 : op = opDeleteGenerations;
1176 57 : else if (arg == "--dry-run") {
1177 0 : printMsg(lvlInfo, "(dry run; not doing anything)");
1178 0 : globals.dryRun = true;
1179 : }
1180 57 : else if (arg == "--preserve-installed" || arg == "-P")
1181 0 : globals.preserveInstalled = true;
1182 57 : else if (arg == "--system-filter") {
1183 0 : globals.instSource.systemFilter = needArg(i, args, arg);
1184 : }
1185 57 : else if (arg[0] == '-')
1186 11 : opFlags.push_back(arg);
1187 : else
1188 46 : opArgs.push_back(arg);
1189 :
1190 170 : if (oldOp && oldOp != op)
1191 0 : throw UsageError("only one operation may be specified");
1192 : }
1193 :
1194 50 : if (!op) throw UsageError("no operation specified");
1195 :
1196 49 : if (globals.profile == "") {
1197 1 : SwitchToOriginalUser sw;
1198 1 : Path profileLink = getHomeDir() + "/.nix-profile";
1199 1 : globals.profile = pathExists(profileLink)
1200 : ? absPath(readLink(profileLink), dirOf(profileLink))
1201 : : canonPath(nixStateDir + "/profiles/default");
1202 : }
1203 :
1204 49 : openDB();
1205 :
1206 49 : op(globals, opFlags, opArgs);
1207 :
1208 45 : printEvalStats(globals.state);
1209 : }
1210 :
1211 :
1212 104 : string programId = "nix-env";
|