From 633518628f48fb9c06bfd570eeca6f62696aba05 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 29 Nov 2007 16:18:24 +0000 Subject: [PATCH] * nix-env -e: support uninstalling by path, so that one can say $ nix-env -e $(which firefox) or $ nix-env -e /nix/store/nywzlygrkfcgz7dfmhm5xixlx1l0m60v-pan-0.132 * nix-env -i: if an argument contains a slash anywhere, treat it as a path and follow it through symlinks into the Nix store. This allows things like $ nix-build -A firefox $ nix-env -i ./result * nix-env -q/-i/-e: don't complain when the `*' selector doesn't match anything. In particular, `nix-env -q \*' doesn't fail anymore on an empty profile. --- src/libstore/gc.cc | 9 ++++---- src/libstore/store-api.cc | 20 ++++++++++++++++++ src/libstore/store-api.hh | 10 +++++++++ src/libutil/util.cc | 3 +-- src/nix-env/nix-env.cc | 42 +++++++++++++++++++++----------------- src/nix-store/nix-store.cc | 36 +++++++++++--------------------- tests/user-envs.sh | 11 ++++++++++ 7 files changed, 81 insertions(+), 50 deletions(-) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index c0acbab38..dab2b80aa 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -26,7 +26,7 @@ static string gcLockName = "gc.lock"; static string tempRootsDir = "temproots"; static string gcRootsDir = "gcroots"; -const unsigned int defaultGcLevel = 1000; +static const int defaultGcLevel = 1000; /* Acquire the global GC lock. This is used to prevent new Nix @@ -447,7 +447,7 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, queryBoolSetting("gc-keep-outputs", false); bool gcKeepDerivations = queryBoolSetting("gc-keep-derivations", true); - unsigned int gcKeepOutputsThreshold = + int gcKeepOutputsThreshold = queryIntSetting ("gc-keep-outputs-threshold", defaultGcLevel); /* Acquire the global GC root. This prevents @@ -503,13 +503,12 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, string gcLevelStr = drv.env["__gcLevel"]; int gcLevel; - if (!string2Int(gcLevelStr,gcLevel)) { + if (!string2Int(gcLevelStr, gcLevel)) gcLevel = defaultGcLevel; - } if (gcLevel >= gcKeepOutputsThreshold) for (DerivationOutputs::iterator j = drv.outputs.begin(); - j != drv.outputs.end(); ++j) + j != drv.outputs.end(); ++j) if (store->isValidPath(j->second.path)) computeFSClosure(j->second.path, livePaths); } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 9eb313da0..22a66ccab 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -48,6 +48,26 @@ Path toStorePath(const Path & path) } +Path followLinksToStore(const Path & _path) +{ + Path path = absPath(_path); + while (!isInStore(path)) { + if (!isLink(path)) break; + string target = readLink(path); + path = absPath(target, dirOf(path)); + } + if (!isInStore(path)) + throw Error(format("path `%1%' is not in the Nix store") % path); + return path; +} + + +Path followLinksToStorePath(const Path & path) +{ + return toStorePath(followLinksToStore(path)); +} + + void checkStoreName(const string & name) { string validChars = "+-._?="; diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index f133302b2..e44259dda 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -174,11 +174,21 @@ bool isStorePath(const Path & path); void checkStoreName(const string & name); + /* Chop off the parts after the top-level store name, e.g., /nix/store/abcd-foo/bar => /nix/store/abcd-foo. */ Path toStorePath(const Path & path); +/* Follow symlinks until we end up with a path in the Nix store. */ +Path followLinksToStore(const Path & path); + + +/* Same as followLinksToStore(), but apply toStorePath() to the + result. */ +Path followLinksToStorePath(const Path & path); + + /* Constructs a unique store path name. */ Path makeStorePath(const string & type, const Hash & hash, const string & suffix); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index ed095717e..37e158e4a 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -105,8 +105,7 @@ Path canonPath(const Path & path, bool resolveSymlinks) /* If s points to a symlink, resolve it and restart (since the symlink target might contain new symlinks). */ if (resolveSymlinks && isLink(s)) { - followCount++; - if (followCount >= maxFollow) + if (++followCount >= maxFollow) throw Error(format("infinite symlink recursion in path `%1%'") % path); temp = absPath(readLink(s), dirOf(s)) + string(i, end); diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 3396b191f..1af1a2f53 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -410,7 +410,7 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, /* Check that all selectors have been used. */ for (DrvNames::iterator i = selectors.begin(); i != selectors.end(); ++i) - if (i->hits == 0) + if (i->hits == 0 && i->fullName != "*") throw Error(format("selector `%1%' matches no derivations") % i->fullName); @@ -418,12 +418,18 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, } +static bool isPath(const string & s) +{ + return s.find('/') != string::npos; +} + + static void queryInstSources(EvalState & state, const InstallSourceInfo & instSource, const Strings & args, DrvInfos & elems, bool newestOnly) { InstallSourceType type = instSource.type; - if (type == srcUnknown && args.size() > 0 && args.front()[0] == '/') + if (type == srcUnknown && args.size() > 0 && isPath(args.front())) type = srcStorePaths; switch (type) { @@ -475,23 +481,23 @@ static void queryInstSources(EvalState & state, for (Strings::const_iterator i = args.begin(); i != args.end(); ++i) { - assertStorePath(*i); + Path path = followLinksToStorePath(*i); DrvInfo elem; elem.attrs = boost::shared_ptr(new ATermMap(0)); /* ugh... */ - string name = baseNameOf(*i); + string name = baseNameOf(path); string::size_type dash = name.find('-'); if (dash != string::npos) name = string(name, dash + 1); - if (isDerivation(*i)) { - elem.setDrvPath(*i); - elem.setOutPath(findOutput(derivationFromPath(*i), "out")); + if (isDerivation(path)) { + elem.setDrvPath(path); + elem.setOutPath(findOutput(derivationFromPath(path), "out")); if (name.size() >= drvExtension.size() && string(name, name.size() - drvExtension.size()) == drvExtension) name = string(name, 0, name.size() - drvExtension.size()); } - else elem.setOutPath(*i); + else elem.setOutPath(path); elem.name = name; @@ -811,7 +817,7 @@ static void opSet(Globals & globals, } -static void uninstallDerivations(Globals & globals, DrvNames & selectors, +static void uninstallDerivations(Globals & globals, Strings & selectors, Path & profile) { PathLocks lock; @@ -824,11 +830,13 @@ static void uninstallDerivations(Globals & globals, DrvNames & selectors, { DrvName drvName(i->name); bool found = false; - for (DrvNames::iterator j = selectors.begin(); - j != selectors.end(); ++j) - if (j->matches(drvName)) { - printMsg(lvlInfo, - format("uninstalling `%1%'") % i->name); + for (Strings::iterator j = selectors.begin(); j != selectors.end(); ++j) + /* !!! the repeated calls to followLinksToStorePath() are + expensive, should pre-compute them. */ + if ((isPath(*j) && i->queryOutPath(globals.state) == followLinksToStorePath(*j)) + || DrvName(*j).matches(drvName)) + { + printMsg(lvlInfo, format("uninstalling `%1%'") % i->name); found = true; break; } @@ -847,11 +855,7 @@ static void opUninstall(Globals & globals, { if (opFlags.size() > 0) throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - - DrvNames drvNames = drvNamesFromArgs(opArgs); - - uninstallDerivations(globals, drvNames, - globals.profile); + uninstallDerivations(globals, opArgs, globals.profile); } diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index c919b0f25..f0e36463d 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -31,18 +31,6 @@ static int rootNr = 0; static bool indirectRoot = false; -static Path fixPath(Path path) -{ - path = absPath(path); - while (!isInStore(path)) { - if (!isLink(path)) break; - string target = readLink(path); - path = absPath(target, dirOf(path)); - } - return toStorePath(path); -} - - static Path useDeriver(Path path) { if (!isDerivation(path)) { @@ -86,7 +74,7 @@ static void opRealise(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - *i = fixPath(*i); + *i = followLinksToStorePath(*i); if (opArgs.size() > 1) { PathSet drvPaths; @@ -296,7 +284,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - *i = fixPath(*i); + *i = followLinksToStorePath(*i); if (forceRealise) realisePath(*i); Derivation drv = derivationFromPath(*i); cout << format("%1%\n") % findOutput(drv, "out"); @@ -312,7 +300,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); + Path path = maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise); if (query == qRequisites) storePathRequisites(path, includeOutputs, paths); else if (query == qReferences) store->queryReferences(path, paths); @@ -330,7 +318,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path deriver = store->queryDeriver(fixPath(*i)); + Path deriver = store->queryDeriver(followLinksToStorePath(*i)); cout << format("%1%\n") % (deriver == "" ? "unknown-deriver" : deriver); } @@ -340,7 +328,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path path = useDeriver(fixPath(*i)); + Path path = useDeriver(followLinksToStorePath(*i)); Derivation drv = derivationFromPath(path); StringPairs::iterator j = drv.env.find(bindingName); if (j == drv.env.end()) @@ -354,7 +342,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); + Path path = maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise); Hash hash = store->queryPathHash(path); assert(hash.type == htSHA256); cout << format("sha256:%1%\n") % printHash32(hash); @@ -365,7 +353,7 @@ static void opQuery(Strings opFlags, Strings opArgs) PathSet done; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - printTree(fixPath(*i), "", "", done); + printTree(followLinksToStorePath(*i), "", "", done); break; } @@ -373,7 +361,7 @@ static void opQuery(Strings opFlags, Strings opArgs) PathSet roots; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - roots.insert(maybeUseOutput(fixPath(*i), useOutput, forceRealise)); + roots.insert(maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise)); printDotGraph(roots); break; } @@ -381,7 +369,7 @@ static void opQuery(Strings opFlags, Strings opArgs) case qResolve: { for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - cout << format("%1%\n") % fixPath(*i); + cout << format("%1%\n") % followLinksToStorePath(*i); break; } @@ -398,7 +386,7 @@ static void opReadLog(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path path = useDeriver(fixPath(*i)); + Path path = useDeriver(followLinksToStorePath(*i)); Path logPath = (format("%1%/%2%/%3%") % nixLogDir % drvsLogDir % baseNameOf(path)).str(); @@ -456,7 +444,7 @@ static void opCheckValidity(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - Path path = fixPath(*i); + Path path = followLinksToStorePath(*i); if (!store->isValidPath(path)) if (printInvalid) cout << format("%1%\n") % path; @@ -531,7 +519,7 @@ static void opDelete(Strings opFlags, Strings opArgs) PathSet pathsToDelete; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - pathsToDelete.insert(fixPath(*i)); + pathsToDelete.insert(followLinksToStorePath(*i)); PathSet dummy; PrintFreed freed(true, false); diff --git a/tests/user-envs.sh b/tests/user-envs.sh index 614c30ceb..edb6da0bf 100644 --- a/tests/user-envs.sh +++ b/tests/user-envs.sh @@ -2,6 +2,8 @@ source common.sh clearProfiles +set -x + # Query installed: should be empty. test "$($nixenv -p $profiles/test -q '*' | wc -l)" -eq 0 @@ -71,6 +73,15 @@ echo $outPath10 $nixenv -p $profiles/test -i "$outPath10" $nixenv -p $profiles/test -q '*' | grep -q foo-1.0 +# Uninstall foo-1.0, using a symlink to its store path. +ln -sfn $outPath10/bin/foo $TEST_ROOT/symlink +$nixenv -p $profiles/test -e $TEST_ROOT/symlink +if $nixenv -p $profiles/test -q '*' | grep -q foo; then false; fi + +# Install foo-1.0, now using a symlink to its store path. +$nixenv -p $profiles/test -i $TEST_ROOT/symlink +$nixenv -p $profiles/test -q '*' | grep -q foo + # Delete all old generations. $nixenv -p $profiles/test --delete-generations old