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 @@ TODO: “or” keyword. + + TODO: Nix expression search path (import <foo/bar.nix>). + + 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 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; } . 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("")) { }; }; @@ -253,7 +256,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %token ID ATTRPATH %token STR IND_STR %token INT -%token PATH +%token PATH SPATH %token 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 + import + import + + 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 ) \ 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"