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