about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2003-12-22T16·04+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2003-12-22T16·04+0000
commitcf0287c09e8b5816c65dd265c4ef167865d70172 (patch)
tree47a646c1b929f224d1c1d42f58020ed381270e49 /src
parentf3c978384698a0b9b0f5dee41f98e6208f269347 (diff)
* Upgrade operation in `nix-env'. For instance, you can say
    nix-env -u foo.nix strategoxt

  to replace the installed `strategoxt' derivation with the one from `foo.nix', if 
  the latter has a higher version number.  This is a no-op if `strategoxt' is not 
  installed.  Wildcards are also accepted, so

    nix-env -u foo.nix '*'

  will replace any installed derivation with newer versions from `foo.nix', if 
  available.

  The notion of "version number" is somewhat ad hoc, but should be useful in most 
  cases, as evidenced by the following unit tests for the version comparator:

    TEST("1.0", "2.3", -1);
    TEST("2.1", "2.3", -1);
    TEST("2.3", "2.3", 0);
    TEST("2.5", "2.3", 1);
    TEST("3.1", "2.3", 1);
    TEST("2.3.1", "2.3", 1);
    TEST("2.3.1", "2.3a", 1);
    TEST("2.3pre1", "2.3", -1);
    TEST("2.3pre3", "2.3pre12", -1);
    TEST("2.3a", "2.3c", -1);
    TEST("2.3pre1", "2.3c", -1);
    TEST("2.3pre1", "2.3q", -1);

  (-1 = less, 0 = equal, 1 = greater)

* A new verbosity level `lvlInfo', between `lvlError' and `lvlTalkative'.  This is 
  the default for `nix-env', so without any `-v' flags users should get useful 
  output, e.g.,

$ nix-env -u foo.nix strategoxt
upgrading `strategoxt-0.9.2' to `strategoxt-0.9.3'


Diffstat (limited to 'src')
-rw-r--r--src/libutil/util.hh3
-rw-r--r--src/nix-env/main.cc186
2 files changed, 165 insertions, 24 deletions
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index cca93cdc737a..4126381d9e96 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -96,7 +96,8 @@ void writeStringToFile(const Path & path, const string & s);
 /* Messages. */
 
 typedef enum { 
-    lvlError, 
+    lvlError,
+    lvlInfo,
     lvlTalkative,
     lvlChatty,
     lvlDebug,
diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc
index fc37f01be0e5..06678fc518d7 100644
--- a/src/nix-env/main.cc
+++ b/src/nix-env/main.cc
@@ -230,15 +230,13 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs,
 }
 
 
-class DrvName
+struct DrvName
 {
     string fullName;
     string name;
     string version;
     unsigned int hits;
 
-public:
-
     /* Parse a derivation name.  The `name' part of a derivation name
        is everything up to but not including the first dash *not*
        followed by a letter.  The `version' part is the rest
@@ -248,6 +246,7 @@ public:
     {
         name = fullName = s;
         for (unsigned int i = 0; i < s.size(); ++i) {
+            /* !!! isalpha/isdigit are affected by the locale. */
             if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) {
                 name = string(s, 0, i);
                 version = string(s, i + 1);
@@ -262,22 +261,90 @@ public:
         if (version != "" && version != n.version) return false;
         return true;
     }
+};
 
-    void hit()
-    {
-        hits++;
-    }
 
-    unsigned int getHits()
-    {
-        return hits;
-    }
+string nextComponent(string::const_iterator & p,
+    const string::const_iterator end)
+{
+    /* Skip any dots and dashes (component separators). */
+    while (p != end && (*p == '.' || *p == '-')) ++p;
+
+    if (p == end) return "";
+
+    /* If the first character is a digit, consume the longest sequence
+       of digits.  Otherwise, consume the longest sequence of
+       non-digit, non-separator characters. */
+    string s;
+    if (isdigit(*p))
+        while (p != end && isdigit(*p)) s += *p++;
+    else
+        while (p != end && (!isdigit(*p) && *p != '.' && *p != '-'))
+            s += *p++;
+    
+    return s;
+}
 
-    string getFullName()
-    {
-        return fullName;
+
+#include <fstream>
+
+bool parseInt(const string & s, int & n)
+{
+    istringstream st(s);
+    st >> n;
+    return !st.fail();
+}
+
+
+static bool componentsLT(const string & c1, const string & c2)
+{
+    int n1, n2;
+    bool c1Num = parseInt(c1, n1), c2Num = parseInt(c2, n2);
+
+    if (c1Num && c2Num) return n1 < n2;
+    else if (c1 == "" && c2Num) return true;
+    else if (c1 == "pre" && c2 != "pre") return true;
+    else if (c2 == "pre") return false;
+    /* Assume that `2.3a' < `2.3.1'. */
+    else if (c2Num) return true;
+    else if (c1Num) return false;
+    else return c1 < c2;
+}
+
+
+static int compareVersions(const string & v1, const string & v2)
+{
+    string::const_iterator p1 = v1.begin();
+    string::const_iterator p2 = v2.begin();
+    
+    while (p1 != v1.end() || p2 != v2.end()) {
+        string c1 = nextComponent(p1, v1.end());
+        string c2 = nextComponent(p2, v2.end());
+        if (componentsLT(c1, c2)) return -1;
+        else if (componentsLT(c2, c1)) return 1;
     }
-};
+
+    return 0;
+}
+
+
+static void testCompareVersions()
+{
+#define TEST(v1, v2, n) assert( \
+    compareVersions(v1, v2) == n && compareVersions(v2, v1) == -n)
+    TEST("1.0", "2.3", -1);
+    TEST("2.1", "2.3", -1);
+    TEST("2.3", "2.3", 0);
+    TEST("2.5", "2.3", 1);
+    TEST("3.1", "2.3", 1);
+    TEST("2.3.1", "2.3", 1);
+    TEST("2.3.1", "2.3a", 1);
+    TEST("2.3pre1", "2.3", -1);
+    TEST("2.3pre3", "2.3pre12", -1);
+    TEST("2.3a", "2.3c", -1);
+    TEST("2.3pre1", "2.3c", -1);
+    TEST("2.3pre1", "2.3q", -1);
+}
 
 
 typedef list<DrvName> DrvNames;
@@ -293,7 +360,7 @@ static DrvNames drvNamesFromArgs(const Strings & opArgs)
 }
 
 
-void installDerivations(EvalState & state,
+static void installDerivations(EvalState & state,
     Path nePath, DrvNames & selectors, const Path & linkPath)
 {
     debug(format("installing derivations from `%1%'") % nePath);
@@ -312,7 +379,9 @@ void installDerivations(EvalState & state,
              j != selectors.end(); ++j)
         {
             if (j->matches(drvName)) {
-                j->hit();
+                printMsg(lvlInfo,
+                    format("installing `%1%'") % i->second.name);
+                j->hits++;
                 selectedDrvs.insert(*i);
             }
         }
@@ -321,9 +390,9 @@ void installDerivations(EvalState & state,
     /* Check that all selectors have been used. */
     for (DrvNames::iterator i = selectors.begin();
          i != selectors.end(); ++i)
-        if (i->getHits() == 0)
+        if (i->hits == 0)
             throw Error(format("selector `%1%' matches no derivations")
-                % i->getFullName());
+                % i->fullName);
     
     /* Add in the already installed derivations. */
     DrvInfos installedDrvs;
@@ -349,6 +418,69 @@ static void opInstall(Globals & globals,
 }
 
 
+static void upgradeDerivations(EvalState & state,
+    Path nePath, DrvNames & selectors, const Path & linkPath)
+{
+    debug(format("upgrading derivations from `%1%'") % nePath);
+
+    /* Upgrade works as follows: we take all currently installed
+       derivations, and for any derivation matching any selector, look
+       for a derivation in the input Nix expression that has the same
+       name and a higher version number. */
+
+    /* Load the currently installed derivations. */
+    DrvInfos installedDrvs;
+    queryInstalled(state, installedDrvs, linkPath);
+
+    /* Fetch all derivations from the input file. */
+    DrvInfos availDrvs;
+    loadDerivations(state, nePath, availDrvs);
+
+    /* Go through all installed derivations. */
+    for (DrvInfos::iterator i = installedDrvs.begin();
+         i != installedDrvs.end(); ++i)
+    {
+        DrvName drvName(i->second.name);
+
+        /* Do we want to upgrade this derivation? */
+        bool upgrade = false;
+        for (DrvNames::iterator j = selectors.begin();
+             j != selectors.end(); ++j)
+        {
+            if (j->matches(drvName)) {
+                j->hits++;
+                upgrade = true;
+                break;
+            }
+        }
+        if (!upgrade) continue;
+
+        /* If yes, find the derivation in the input Nix expression
+           with the same name and the highest version number. */
+        DrvInfos::iterator bestDrv = i;
+        DrvName bestName = drvName;
+        for (DrvInfos::iterator j = availDrvs.begin();
+             j != availDrvs.end(); ++j)
+        {
+            DrvName newName(j->second.name);
+            if (newName.name == bestName.name &&
+                compareVersions(newName.version, bestName.version) > 0)
+                bestDrv = j;
+        }
+
+        if (bestDrv != i) {
+            printMsg(lvlInfo,
+                format("upgrading `%1%' to `%2%'")
+                % i->second.name % bestDrv->second.name);
+            installedDrvs.erase(i);
+            installedDrvs.insert(*bestDrv);
+        }
+    }
+    
+    createUserEnv(state, installedDrvs, linkPath);
+}
+
+
 static void opUpgrade(Globals & globals,
     Strings opFlags, Strings opArgs)
 {
@@ -357,12 +489,14 @@ static void opUpgrade(Globals & globals,
     if (opArgs.size() < 1) throw UsageError("Nix file expected");
 
     Path nePath = opArgs.front();
-    opArgs.pop_front();
-
+    DrvNames drvNames = drvNamesFromArgs(
+        Strings(++opArgs.begin(), opArgs.end()));
+    
+    upgradeDerivations(globals.state, nePath, drvNames, globals.linkPath);
 }
 
 
-void uninstallDerivations(EvalState & state, DrvNames & selectors,
+static void uninstallDerivations(EvalState & state, DrvNames & selectors,
     Path & linkPath)
 {
     DrvInfos installedDrvs;
@@ -374,8 +508,11 @@ void uninstallDerivations(EvalState & state, DrvNames & selectors,
         DrvName drvName(i->second.name);
         for (DrvNames::iterator j = selectors.begin();
              j != selectors.end(); ++j)
-            if (j->matches(drvName))
+            if (j->matches(drvName)) {
+                printMsg(lvlInfo,
+                    format("uninstalling `%1%'") % i->second.name);
                 installedDrvs.erase(i);
+            }
     }
 
     createUserEnv(state, installedDrvs, linkPath);
@@ -466,6 +603,9 @@ static void opQuery(Globals & globals,
 
 void run(Strings args)
 {
+    /* Use a higher default verbosity (lvlInfo). */
+    verbosity = (Verbosity) ((int) verbosity + 1);
+
     Strings opFlags, opArgs;
     Operation op = 0;