From d567baabbd99fdb92e67295a77aef76ef970e65c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Jul 2008 10:10:32 +0000 Subject: * Export the nix-env derivation name parsing and version comparison logic through the `parseDrvName' and `compareVersions' primops. This will allow expressions to easily check whether some dependency is a specific needed version or falls in some version range. See tests/lang/eval-okay-versions.nix for examples. --- doc/manual/release-notes.xml | 6 ++ src/libexpr/Makefile.am | 6 +- src/libexpr/names.cc | 105 ++++++++++++++++++++++++++++++++ src/libexpr/names.hh | 33 ++++++++++ src/libexpr/primops.cc | 63 ++++++++++++++----- src/nix-env/Makefile.am | 3 +- src/nix-env/names.cc | 124 -------------------------------------- src/nix-env/names.hh | 33 ---------- tests/lang/eval-okay-versions.exp | 1 + tests/lang/eval-okay-versions.nix | 40 ++++++++++++ tests/lang/lib.nix | 2 + 11 files changed, 241 insertions(+), 175 deletions(-) create mode 100644 src/libexpr/names.cc create mode 100644 src/libexpr/names.hh delete mode 100644 src/nix-env/names.cc delete mode 100644 src/nix-env/names.hh create mode 100644 tests/lang/eval-okay-versions.exp create mode 100644 tests/lang/eval-okay-versions.nix diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml index 906a772c81f8..a3c33b5f7f6d 100644 --- a/doc/manual/release-notes.xml +++ b/doc/manual/release-notes.xml @@ -12,8 +12,14 @@ + TODO: Berkeley DB no longer needed. + nix-store --dump-db / --load-db. + New primops: + builtins.parseDrvName and + builtins.compareVersions. + diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index b08c079f4d30..aacc1dcc3b16 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -2,11 +2,13 @@ pkglib_LTLIBRARIES = libexpr.la libexpr_la_SOURCES = \ nixexpr.cc eval.cc primops.cc lexer-tab.cc parser-tab.cc \ - get-drvs.cc attr-path.cc expr-to-xml.cc common-opts.cc + get-drvs.cc attr-path.cc expr-to-xml.cc common-opts.cc \ + names.cc pkginclude_HEADERS = \ nixexpr.hh eval.hh parser.hh lexer-tab.hh parser-tab.hh \ - get-drvs.hh attr-path.hh expr-to-xml.hh common-opts.hh + get-drvs.hh attr-path.hh expr-to-xml.hh common-opts.hh \ + names.hh libexpr_la_LIBADD = ../libutil/libutil.la ../libstore/libstore.la \ ../boost/format/libformat.la diff --git a/src/libexpr/names.cc b/src/libexpr/names.cc new file mode 100644 index 000000000000..29c7ddb4d70f --- /dev/null +++ b/src/libexpr/names.cc @@ -0,0 +1,105 @@ +#include "names.hh" +#include "util.hh" + + +namespace nix { + + +DrvName::DrvName() +{ + name = ""; +} + + +/* 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 (excluding the separating + dash). E.g., `apache-httpd-2.0.48' is parsed to (`apache-httpd', + '2.0.48'). */ +DrvName::DrvName(const string & s) : hits(0) +{ + 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); + break; + } + } +} + + +bool DrvName::matches(DrvName & n) +{ + if (name != "*" && name != n.name) return false; + if (version != "" && version != n.version) return false; + return true; +} + + +static 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; +} + + +static bool componentsLT(const string & c1, const string & c2) +{ + int n1, n2; + bool c1Num = string2Int(c1, n1), c2Num = string2Int(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; +} + + +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; +} + + +DrvNames drvNamesFromArgs(const Strings & opArgs) +{ + DrvNames result; + for (Strings::const_iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + result.push_back(DrvName(*i)); + return result; +} + + +} diff --git a/src/libexpr/names.hh b/src/libexpr/names.hh new file mode 100644 index 000000000000..e189302d6d94 --- /dev/null +++ b/src/libexpr/names.hh @@ -0,0 +1,33 @@ +#ifndef __NAMES_H +#define __NAMES_H + +#include "types.hh" + + +namespace nix { + + +struct DrvName +{ + string fullName; + string name; + string version; + unsigned int hits; + + DrvName(); + DrvName(const string & s); + bool matches(DrvName & n); +}; + + +typedef list DrvNames; + + +int compareVersions(const string & v1, const string & v2); +DrvNames drvNamesFromArgs(const Strings & opArgs); + + +} + + +#endif /* !__NAMES_H */ diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 864292d4a4b8..fbef227223dc 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -7,6 +7,7 @@ #include "expr-to-xml.hh" #include "nixexpr-ast.hh" #include "parser.hh" +#include "names.hh" #include #include @@ -817,7 +818,7 @@ static Expr prim_removeAttrs(EvalState & state, const ATermVector & args) } -/* Determine whether the argument is a list. */ +/* Determine whether the argument is an attribute set. */ static Expr prim_isAttrs(EvalState & state, const ATermVector & args) { ATermList list; @@ -950,23 +951,54 @@ static Expr prim_unsafeDiscardStringContext(EvalState & state, const ATermVector return makeStr(s, PathSet()); } -/* Expression serialization/deserialization */ -static Expr prim_ExprToString ( EvalState & state, const ATermVector & args) +/* Expression serialization/deserialization */ + + +static Expr prim_exprToString(EvalState & state, const ATermVector & args) { - return makeStr ( atPrint ( evalExpr ( state, args [ 0 ] ) ) ); + /* !!! this disregards context */ + return makeStr(atPrint(evalExpr(state, args[0]))); } -static Expr prim_StringToExpr ( EvalState & state, const ATermVector & args) + +static Expr prim_stringToExpr(EvalState & state, const ATermVector & args) { - string s; - PathSet l; - if (! matchStr ( evalExpr ( state, args[0] ), s, l )) { - throw EvalError("__stringToExpr needs string argument!"); - } - return ATreadFromString(s.c_str()); + /* !!! this can introduce arbitrary garbage terms in the + evaluator! */; + string s; + PathSet l; + if (!matchStr(evalExpr(state, args[0]), s, l)) + throw EvalError("stringToExpr needs string argument!"); + return ATreadFromString(s.c_str()); } + +/************************************************************* + * Versions + *************************************************************/ + + +static Expr prim_parseDrvName(EvalState & state, const ATermVector & args) +{ + string name = evalStringNoCtx(state, args[0]); + DrvName parsed(name); + ATermMap attrs(2); + attrs.set(toATerm("name"), makeAttrRHS(makeStr(parsed.name), makeNoPos())); + attrs.set(toATerm("version"), makeAttrRHS(makeStr(parsed.version), makeNoPos())); + return makeAttrs(attrs); +} + + +static Expr prim_compareVersions(EvalState & state, const ATermVector & args) +{ + string version1 = evalStringNoCtx(state, args[0]); + string version2 = evalStringNoCtx(state, args[1]); + int d = compareVersions(version1, version2); + return makeInt(d); +} + + /************************************************************* * Primop registration *************************************************************/ @@ -994,8 +1026,8 @@ void EvalState::addPrimOps() addPrimOp("__trace", 2, prim_trace); // Expr <-> String - addPrimOp("__exprToString", 1, prim_ExprToString); - addPrimOp("__stringToExpr", 1, prim_StringToExpr); + addPrimOp("__exprToString", 1, prim_exprToString); + addPrimOp("__stringToExpr", 1, prim_stringToExpr); addPrimOp("relativise", 2, prim_relativise); @@ -1039,7 +1071,10 @@ void EvalState::addPrimOps() addPrimOp("__substring", 3, prim_substring); addPrimOp("__stringLength", 1, prim_stringLength); addPrimOp("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); - + + // Versions + addPrimOp("__parseDrvName", 1, prim_parseDrvName); + addPrimOp("__compareVersions", 2, prim_compareVersions); } diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index a1e58cf006bc..4720dfe8b780 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -1,7 +1,6 @@ bin_PROGRAMS = nix-env -nix_env_SOURCES = nix-env.cc names.cc names.hh \ - profiles.cc profiles.hh help.txt +nix_env_SOURCES = nix-env.cc profiles.cc profiles.hh help.txt nix_env_LDADD = ../libmain/libmain.la ../libexpr/libexpr.la \ ../libstore/libstore.la ../libutil/libutil.la \ ../boost/format/libformat.la ${bdb_lib} ${aterm_lib} diff --git a/src/nix-env/names.cc b/src/nix-env/names.cc deleted file mode 100644 index a425b8d0762c..000000000000 --- a/src/nix-env/names.cc +++ /dev/null @@ -1,124 +0,0 @@ -#include "names.hh" -#include "util.hh" - - -namespace nix { - - -DrvName::DrvName() -{ - name = ""; -} - - -/* 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 (excluding the separating - dash). E.g., `apache-httpd-2.0.48' is parsed to (`apache-httpd', - '2.0.48'). */ -DrvName::DrvName(const string & s) : hits(0) -{ - 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); - break; - } - } -} - - -bool DrvName::matches(DrvName & n) -{ - if (name != "*" && name != n.name) return false; - if (version != "" && version != n.version) return false; - return true; -} - - -static 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; -} - - -static bool componentsLT(const string & c1, const string & c2) -{ - int n1, n2; - bool c1Num = string2Int(c1, n1), c2Num = string2Int(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; -} - - -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); -} - - -DrvNames drvNamesFromArgs(const Strings & opArgs) -{ - DrvNames result; - for (Strings::const_iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - result.push_back(DrvName(*i)); - return result; -} - - -} diff --git a/src/nix-env/names.hh b/src/nix-env/names.hh deleted file mode 100644 index e189302d6d94..000000000000 --- a/src/nix-env/names.hh +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __NAMES_H -#define __NAMES_H - -#include "types.hh" - - -namespace nix { - - -struct DrvName -{ - string fullName; - string name; - string version; - unsigned int hits; - - DrvName(); - DrvName(const string & s); - bool matches(DrvName & n); -}; - - -typedef list DrvNames; - - -int compareVersions(const string & v1, const string & v2); -DrvNames drvNamesFromArgs(const Strings & opArgs); - - -} - - -#endif /* !__NAMES_H */ diff --git a/tests/lang/eval-okay-versions.exp b/tests/lang/eval-okay-versions.exp new file mode 100644 index 000000000000..2015847b65e7 --- /dev/null +++ b/tests/lang/eval-okay-versions.exp @@ -0,0 +1 @@ +Bool(True) diff --git a/tests/lang/eval-okay-versions.nix b/tests/lang/eval-okay-versions.nix new file mode 100644 index 000000000000..e63c36586bb6 --- /dev/null +++ b/tests/lang/eval-okay-versions.nix @@ -0,0 +1,40 @@ +let + + name1 = "hello-1.0.2"; + name2 = "hello"; + name3 = "915resolution-0.5.2"; + name4 = "xf86-video-i810-1.7.4"; + + eq = 0; + lt = builtins.sub 0 1; + gt = 1; + + versionTest = v1: v2: expected: + let d1 = builtins.compareVersions v1 v2; + d2 = builtins.compareVersions v2 v1; + in d1 == builtins.sub 0 d2 && d1 == expected; + + tests = [ + ((builtins.parseDrvName name1).name == "hello") + ((builtins.parseDrvName name1).version == "1.0.2") + ((builtins.parseDrvName name2).name == "hello") + ((builtins.parseDrvName name2).version == "") + ((builtins.parseDrvName name3).name == "915resolution") + ((builtins.parseDrvName name3).version == "0.5.2") + ((builtins.parseDrvName name4).name == "xf86-video-i810") + ((builtins.parseDrvName name4).version == "1.7.4") + (versionTest "1.0" "2.3" lt) + (versionTest "2.1" "2.3" lt) + (versionTest "2.3" "2.3" eq) + (versionTest "2.5" "2.3" gt) + (versionTest "3.1" "2.3" gt) + (versionTest "2.3.1" "2.3" gt) + (versionTest "2.3.1" "2.3a" gt) + (versionTest "2.3pre1" "2.3" lt) + (versionTest "2.3pre3" "2.3pre12" lt) + (versionTest "2.3a" "2.3c" lt) + (versionTest "2.3pre1" "2.3c" lt) + (versionTest "2.3pre1" "2.3q" lt) + ]; + +in (import ./lib.nix).and tests diff --git a/tests/lang/lib.nix b/tests/lang/lib.nix index ec705299a7a9..e508f28ba53f 100644 --- a/tests/lang/lib.nix +++ b/tests/lang/lib.nix @@ -10,6 +10,8 @@ rec { concat = fold (x: y: x + y) ""; + and = fold (x: y: x && y) true; + flatten = x: if isList x then fold (x: y: (flatten x) ++ y) [] x -- cgit 1.4.1