summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2011-08-06 16:05:24 +0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2011-08-06 16:05:24 +0000
commit1ecc97b6bdb27e56d832ca48cdafd3dbb5185a04 (patch)
tree4de27ee42f04bb50766f33a58d830677bd8fa80b
parent54945a2950174ded83d58336061b4a9990cdbbfd (diff)
downloadguix-1ecc97b6bdb27e56d832ca48cdafd3dbb5185a04.tar.gz
* Add a Nix expression search path feature. Paths between angle
  brackets, e.g.

    import <nixpkgs/pkgs/lib>

  are resolved by looking them up relative to the elements listed in
  the search path.  This allows us to get rid of hacks like

    import "${builtins.getEnv "NIXPKGS_ALL"}/pkgs/lib"

  The search path can be specified through the ‘-I’ command-line flag
  and through the colon-separated ‘NIX_PATH’ environment variable,
  e.g.,

    $ nix-build -I /etc/nixos ...

  If a file is not found in the search path, an error message is
  lazily thrown.

-rw-r--r--doc/manual/release-notes.xml4
-rw-r--r--scripts/nix-build.in4
-rw-r--r--src/libexpr/common-opts.cc12
-rw-r--r--src/libexpr/common-opts.hh3
-rw-r--r--src/libexpr/eval.cc6
-rw-r--r--src/libexpr/eval.hh8
-rw-r--r--src/libexpr/lexer.l2
-rw-r--r--src/libexpr/parser.y47
-rw-r--r--src/nix-env/nix-env.cc2
-rw-r--r--src/nix-instantiate/nix-instantiate.cc2
-rw-r--r--tests/lang.sh2
-rw-r--r--tests/lang/dir1/a.nix1
-rw-r--r--tests/lang/dir2/a.nix1
-rw-r--r--tests/lang/dir2/b.nix1
-rw-r--r--tests/lang/dir3/a.nix1
-rw-r--r--tests/lang/dir3/b.nix1
-rw-r--r--tests/lang/dir3/c.nix1
-rw-r--r--tests/lang/dir4/a.nix1
-rw-r--r--tests/lang/dir4/c.nix1
-rw-r--r--tests/lang/eval-okay-search-path.exp1
-rw-r--r--tests/lang/eval-okay-search-path.flags1
-rw-r--r--tests/lang/eval-okay-search-path.nix3
-rw-r--r--tests/lang/eval-okay-search-path.nix~1
-rw-r--r--tests/lang/eval-okay-search-path.out1
24 files changed, 98 insertions, 9 deletions
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index cf025aaf51..7d0bfe7fdc 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -36,6 +36,10 @@
     <para>TODO: “or” keyword.</para>
   </listitem>
 
+  <listitem>
+    <para>TODO: Nix expression search path (<literal>import &lt;foo/bar.nix></literal>).</para>
+  </listitem>
+
 </itemizedlist>
 
 </section>
diff --git a/scripts/nix-build.in b/scripts/nix-build.in
index f9d81b36c7..d9d1da73b1 100644
--- a/scripts/nix-build.in
+++ b/scripts/nix-build.in
@@ -76,10 +76,10 @@ EOF
         $outLink = $ARGV[$n];
     }
 
-    elsif ($arg eq "--attr" or $arg eq "-A") {
+    elsif ($arg eq "--attr" or $arg eq "-A" or $arg eq "-I") {
         $n++;
         die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
-        push @instArgs, ("--attr", $ARGV[$n]);
+        push @instArgs, ($arg, $ARGV[$n]);
     }
 
     elsif ($arg eq "--arg" || $arg eq "--argstr") {
diff --git a/src/libexpr/common-opts.cc b/src/libexpr/common-opts.cc
index bab31f4935..d029d2ec35 100644
--- a/src/libexpr/common-opts.cc
+++ b/src/libexpr/common-opts.cc
@@ -33,5 +33,15 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
     return true;
 }
 
- 
+
+bool parseSearchPathArg(const string & arg, Strings::iterator & i,
+    const Strings::iterator & argsEnd, EvalState & state)
+{
+    if (arg != "-I") return false;
+    if (i == argsEnd) throw UsageError(format("`%1%' requires an argument") % arg);;
+    state.addToSearchPath(*i++);
+    return true;
+}
+
+
 }
diff --git a/src/libexpr/common-opts.hh b/src/libexpr/common-opts.hh
index 80298ce55d..6b7247fc3d 100644
--- a/src/libexpr/common-opts.hh
+++ b/src/libexpr/common-opts.hh
@@ -11,6 +11,9 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
     const Strings::iterator & argsEnd, EvalState & state,
     Bindings & autoArgs);
     
+bool parseSearchPathArg(const string & arg, Strings::iterator & i,
+    const Strings::iterator & argsEnd, EvalState & state);
+
 }
 
 
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 5701452f94..674fa96f0b 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -181,6 +181,12 @@ EvalState::EvalState()
         gcInitialised = true;
     }
 #endif
+
+    /* Initialise the Nix expression search path. */
+    searchPathInsertionPoint = searchPath.end();
+    Strings paths = tokenizeString(getEnv("NIX_PATH", ""), ":");
+    foreach (Strings::iterator, i, paths) addToSearchPath(*i);
+    searchPathInsertionPoint = searchPath.begin();
 }
 
 
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index e900217fa4..1583665bad 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -213,11 +213,16 @@ private:
 
     std::map<Path, Expr *> parseTrees;
 
+    Paths searchPath;
+    Paths::iterator searchPathInsertionPoint;
+
 public:
     
     EvalState();
     ~EvalState();
 
+    void addToSearchPath(const string & s);
+
     /* Parse a Nix expression from the specified file.  If `path'
        refers to a directory, then "/default.nix" is appended. */
     Expr * parseExprFromFile(Path path);
@@ -229,6 +234,9 @@ public:
        form. */
     void evalFile(const Path & path, Value & v);
 
+    /* Look up a file in the search path. */
+    Path findFile(const string & path);
+
     /* Evaluate an expression to normal form, storing the result in
        value `v'. */
     void eval(Expr * e, Value & v);
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index 330c2bd54d..d46b66d9ff 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -81,6 +81,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
 ID          [a-zA-Z\_][a-zA-Z0-9\_\']*
 INT         [0-9]+
 PATH        [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+
+SPATH       \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
 URI         [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
 
 
@@ -153,6 +154,7 @@ or          { return OR_KW; }
 <IND_STRING>.    return yytext[0]; /* just in case: shouldn't be reached */
 
 {PATH}      { yylval->path = strdup(yytext); return PATH; }
+{SPATH}     { yylval->path = strdup(yytext); return SPATH; }
 {URI}       { yylval->uri = strdup(yytext); return URI; }
 
 [ \t\r\n]+    /* eat up whitespace */
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index a64d327b45..cd63666dc5 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -17,19 +17,22 @@
 #include "util.hh"
     
 #include "nixexpr.hh"
+#include "eval.hh"
 
 namespace nix {
 
     struct ParseData 
     {
+        EvalState & state;
         SymbolTable & symbols;
         Expr * result;
         Path basePath;
         Path path;
         string error;
         Symbol sLetBody;
-        ParseData(SymbolTable & symbols)
-            : symbols(symbols)
+        ParseData(EvalState & state)
+            : state(state)
+            , symbols(state.symbols)
             , sLetBody(symbols.create("<let-body>"))
             { };
     };
@@ -253,7 +256,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %token <id> ID ATTRPATH
 %token <e> STR IND_STR
 %token <n> INT
-%token <path> PATH
+%token <path> PATH SPATH
 %token <uri> URI
 %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
 %token DOLLAR_CURLY /* == ${ */
@@ -350,6 +353,20 @@ expr_simple
       $$ = stripIndentation(data->symbols, *$2);
   }
   | PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
+  | SPATH {
+      string path($1 + 1, strlen($1) - 2);
+      Path path2 = data->state.findFile(path);
+      /* The file wasn't found in the search path.  However, we can't
+         throw an error here, because the expression might never be
+         evaluated.  So return an expression that lazily calls
+         ‘abort’. */
+      $$ = path2 == ""
+          ? (Expr * ) new ExprApp(
+              new ExprVar(data->symbols.create("throw")),
+              new ExprString(data->symbols.create(
+                      (format("file `%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)") % path).str())))
+          : (Expr * ) new ExprPath(path2);
+  }
   | URI { $$ = new ExprString(data->symbols.create($1)); }
   | '(' expr ')' { $$ = $2; }
   /* Let expressions `let {..., body = ...}' are just desugared
@@ -454,7 +471,7 @@ Expr * EvalState::parse(const char * text,
     const Path & path, const Path & basePath)
 {
     yyscan_t scanner;
-    ParseData data(symbols);
+    ParseData data(*this);
     data.basePath = basePath;
     data.path = path;
 
@@ -510,5 +527,25 @@ Expr * EvalState::parseExprFromString(const string & s, const Path & basePath)
     return parse(s.c_str(), "(string)", basePath);
 }
 
- 
+
+void EvalState::addToSearchPath(const string & s)
+{
+    Path path = absPath(s);
+    if (pathExists(path)) {
+        debug(format("adding path `%1%' to the search path") % path);
+        searchPath.insert(searchPathInsertionPoint, path);
+    }
+}
+
+
+Path EvalState::findFile(const string & path)
+{
+    foreach (Paths::iterator, i, searchPath) {
+        Path res = *i + "/" + path;
+        if (pathExists(res)) return canonPath(res);
+    }
+    return "";
+}
+
+
 }
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index 4ea301def0..731f91bba0 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -1253,6 +1253,8 @@ void run(Strings args)
         else if (parseOptionArg(arg, i, args.end(),
                      globals.state, globals.instSource.autoArgs))
             ;
+        else if (parseSearchPathArg(arg, i, args.end(), globals.state))
+            ;
         else if (arg == "--force-name") // undocumented flag for nix-install-package
             globals.forceName = needArg(i, args, arg);
         else if (arg == "--uninstall" || arg == "-e")
diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc
index 1f90595391..05b9d54797 100644
--- a/src/nix-instantiate/nix-instantiate.cc
+++ b/src/nix-instantiate/nix-instantiate.cc
@@ -107,6 +107,8 @@ void run(Strings args)
         }
         else if (parseOptionArg(arg, i, args.end(), state, autoArgs))
             ;
+        else if (parseSearchPathArg(arg, i, args.end(), state))
+            ;
         else if (arg == "--add-root") {
             if (i == args.end())
                 throw UsageError("`--add-root' requires an argument");
diff --git a/tests/lang.sh b/tests/lang.sh
index fab8c6e0d7..11267a23fd 100644
--- a/tests/lang.sh
+++ b/tests/lang.sh
@@ -40,7 +40,7 @@ for i in lang/eval-okay-*.nix; do
         if test -e lang/$i.flags; then
             flags=$(cat lang/$i.flags)
         fi
-        if ! $nixinstantiate $flags --eval-only --strict lang/$i.nix > lang/$i.out; then
+        if ! NIX_PATH=lang/dir3:lang/dir4 $nixinstantiate $flags --eval-only --strict lang/$i.nix > lang/$i.out; then
             echo "FAIL: $i should evaluate"
             fail=1
         elif ! diff lang/$i.out lang/$i.exp; then
diff --git a/tests/lang/dir1/a.nix b/tests/lang/dir1/a.nix
new file mode 100644
index 0000000000..231f150c57
--- /dev/null
+++ b/tests/lang/dir1/a.nix
@@ -0,0 +1 @@
+"a"
diff --git a/tests/lang/dir2/a.nix b/tests/lang/dir2/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tests/lang/dir2/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tests/lang/dir2/b.nix b/tests/lang/dir2/b.nix
new file mode 100644
index 0000000000..19010cc35c
--- /dev/null
+++ b/tests/lang/dir2/b.nix
@@ -0,0 +1 @@
+"b"
diff --git a/tests/lang/dir3/a.nix b/tests/lang/dir3/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tests/lang/dir3/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tests/lang/dir3/b.nix b/tests/lang/dir3/b.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tests/lang/dir3/b.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tests/lang/dir3/c.nix b/tests/lang/dir3/c.nix
new file mode 100644
index 0000000000..cdf158597e
--- /dev/null
+++ b/tests/lang/dir3/c.nix
@@ -0,0 +1 @@
+"c"
diff --git a/tests/lang/dir4/a.nix b/tests/lang/dir4/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tests/lang/dir4/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tests/lang/dir4/c.nix b/tests/lang/dir4/c.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tests/lang/dir4/c.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tests/lang/eval-okay-search-path.exp b/tests/lang/eval-okay-search-path.exp
new file mode 100644
index 0000000000..d1cc1b4e52
--- /dev/null
+++ b/tests/lang/eval-okay-search-path.exp
@@ -0,0 +1 @@
+"abc"
diff --git a/tests/lang/eval-okay-search-path.flags b/tests/lang/eval-okay-search-path.flags
new file mode 100644
index 0000000000..d7feb29e12
--- /dev/null
+++ b/tests/lang/eval-okay-search-path.flags
@@ -0,0 +1 @@
+-I lang/dir1 -I lang/dir2
\ No newline at end of file
diff --git a/tests/lang/eval-okay-search-path.nix b/tests/lang/eval-okay-search-path.nix
new file mode 100644
index 0000000000..cc1df08f01
--- /dev/null
+++ b/tests/lang/eval-okay-search-path.nix
@@ -0,0 +1,3 @@
+import <a.nix> + import <b.nix> + import <c.nix>
+
+
diff --git a/tests/lang/eval-okay-search-path.nix~ b/tests/lang/eval-okay-search-path.nix~
new file mode 100644
index 0000000000..da52a6d398
--- /dev/null
+++ b/tests/lang/eval-okay-search-path.nix~
@@ -0,0 +1 @@
+(import <a.nix>)
\ No newline at end of file
diff --git a/tests/lang/eval-okay-search-path.out b/tests/lang/eval-okay-search-path.out
new file mode 100644
index 0000000000..d1cc1b4e52
--- /dev/null
+++ b/tests/lang/eval-okay-search-path.out
@@ -0,0 +1 @@
+"abc"