* 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'
This commit is contained in:
parent
f3c9783846
commit
cf0287c09e
2 changed files with 168 additions and 27 deletions
|
@ -97,6 +97,7 @@ void writeStringToFile(const Path & path, const string & s);
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
lvlError,
|
lvlError,
|
||||||
|
lvlInfo,
|
||||||
lvlTalkative,
|
lvlTalkative,
|
||||||
lvlChatty,
|
lvlChatty,
|
||||||
lvlDebug,
|
lvlDebug,
|
||||||
|
|
|
@ -230,15 +230,13 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DrvName
|
struct DrvName
|
||||||
{
|
{
|
||||||
string fullName;
|
string fullName;
|
||||||
string name;
|
string name;
|
||||||
string version;
|
string version;
|
||||||
unsigned int hits;
|
unsigned int hits;
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
/* Parse a derivation name. The `name' part of a derivation name
|
/* Parse a derivation name. The `name' part of a derivation name
|
||||||
is everything up to but not including the first dash *not*
|
is everything up to but not including the first dash *not*
|
||||||
followed by a letter. The `version' part is the rest
|
followed by a letter. The `version' part is the rest
|
||||||
|
@ -248,6 +246,7 @@ public:
|
||||||
{
|
{
|
||||||
name = fullName = s;
|
name = fullName = s;
|
||||||
for (unsigned int i = 0; i < s.size(); ++i) {
|
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])) {
|
if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) {
|
||||||
name = string(s, 0, i);
|
name = string(s, 0, i);
|
||||||
version = string(s, i + 1);
|
version = string(s, i + 1);
|
||||||
|
@ -262,24 +261,92 @@ public:
|
||||||
if (version != "" && version != n.version) return false;
|
if (version != "" && version != n.version) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void hit()
|
|
||||||
{
|
|
||||||
hits++;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int getHits()
|
|
||||||
{
|
|
||||||
return hits;
|
|
||||||
}
|
|
||||||
|
|
||||||
string getFullName()
|
|
||||||
{
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#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;
|
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)
|
Path nePath, DrvNames & selectors, const Path & linkPath)
|
||||||
{
|
{
|
||||||
debug(format("installing derivations from `%1%'") % nePath);
|
debug(format("installing derivations from `%1%'") % nePath);
|
||||||
|
@ -312,7 +379,9 @@ void installDerivations(EvalState & state,
|
||||||
j != selectors.end(); ++j)
|
j != selectors.end(); ++j)
|
||||||
{
|
{
|
||||||
if (j->matches(drvName)) {
|
if (j->matches(drvName)) {
|
||||||
j->hit();
|
printMsg(lvlInfo,
|
||||||
|
format("installing `%1%'") % i->second.name);
|
||||||
|
j->hits++;
|
||||||
selectedDrvs.insert(*i);
|
selectedDrvs.insert(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,9 +390,9 @@ void installDerivations(EvalState & state,
|
||||||
/* Check that all selectors have been used. */
|
/* Check that all selectors have been used. */
|
||||||
for (DrvNames::iterator i = selectors.begin();
|
for (DrvNames::iterator i = selectors.begin();
|
||||||
i != selectors.end(); ++i)
|
i != selectors.end(); ++i)
|
||||||
if (i->getHits() == 0)
|
if (i->hits == 0)
|
||||||
throw Error(format("selector `%1%' matches no derivations")
|
throw Error(format("selector `%1%' matches no derivations")
|
||||||
% i->getFullName());
|
% i->fullName);
|
||||||
|
|
||||||
/* Add in the already installed derivations. */
|
/* Add in the already installed derivations. */
|
||||||
DrvInfos installedDrvs;
|
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,
|
static void opUpgrade(Globals & globals,
|
||||||
Strings opFlags, Strings opArgs)
|
Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
|
@ -357,12 +489,14 @@ static void opUpgrade(Globals & globals,
|
||||||
if (opArgs.size() < 1) throw UsageError("Nix file expected");
|
if (opArgs.size() < 1) throw UsageError("Nix file expected");
|
||||||
|
|
||||||
Path nePath = opArgs.front();
|
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)
|
Path & linkPath)
|
||||||
{
|
{
|
||||||
DrvInfos installedDrvs;
|
DrvInfos installedDrvs;
|
||||||
|
@ -374,9 +508,12 @@ void uninstallDerivations(EvalState & state, DrvNames & selectors,
|
||||||
DrvName drvName(i->second.name);
|
DrvName drvName(i->second.name);
|
||||||
for (DrvNames::iterator j = selectors.begin();
|
for (DrvNames::iterator j = selectors.begin();
|
||||||
j != selectors.end(); ++j)
|
j != selectors.end(); ++j)
|
||||||
if (j->matches(drvName))
|
if (j->matches(drvName)) {
|
||||||
|
printMsg(lvlInfo,
|
||||||
|
format("uninstalling `%1%'") % i->second.name);
|
||||||
installedDrvs.erase(i);
|
installedDrvs.erase(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createUserEnv(state, installedDrvs, linkPath);
|
createUserEnv(state, installedDrvs, linkPath);
|
||||||
}
|
}
|
||||||
|
@ -466,6 +603,9 @@ static void opQuery(Globals & globals,
|
||||||
|
|
||||||
void run(Strings args)
|
void run(Strings args)
|
||||||
{
|
{
|
||||||
|
/* Use a higher default verbosity (lvlInfo). */
|
||||||
|
verbosity = (Verbosity) ((int) verbosity + 1);
|
||||||
|
|
||||||
Strings opFlags, opArgs;
|
Strings opFlags, opArgs;
|
||||||
Operation op = 0;
|
Operation op = 0;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue