* 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.
This commit is contained in:
Eelco Dolstra 2011-08-06 16:05:24 +00:00
parent 54945a2950
commit 1ecc97b6bd
24 changed files with 98 additions and 9 deletions

View file

@ -36,6 +36,10 @@
<para>TODO: “or” keyword.</para> <para>TODO: “or” keyword.</para>
</listitem> </listitem>
<listitem>
<para>TODO: Nix expression search path (<literal>import &lt;foo/bar.nix></literal>).</para>
</listitem>
</itemizedlist> </itemizedlist>
</section> </section>

View file

@ -76,10 +76,10 @@ EOF
$outLink = $ARGV[$n]; $outLink = $ARGV[$n];
} }
elsif ($arg eq "--attr" or $arg eq "-A") { elsif ($arg eq "--attr" or $arg eq "-A" or $arg eq "-I") {
$n++; $n++;
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV; 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") { elsif ($arg eq "--arg" || $arg eq "--argstr") {

View file

@ -34,4 +34,14 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
} }
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;
}
} }

View file

@ -11,6 +11,9 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
const Strings::iterator & argsEnd, EvalState & state, const Strings::iterator & argsEnd, EvalState & state,
Bindings & autoArgs); Bindings & autoArgs);
bool parseSearchPathArg(const string & arg, Strings::iterator & i,
const Strings::iterator & argsEnd, EvalState & state);
} }

View file

@ -181,6 +181,12 @@ EvalState::EvalState()
gcInitialised = true; gcInitialised = true;
} }
#endif #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();
} }

View file

@ -213,11 +213,16 @@ private:
std::map<Path, Expr *> parseTrees; std::map<Path, Expr *> parseTrees;
Paths searchPath;
Paths::iterator searchPathInsertionPoint;
public: public:
EvalState(); EvalState();
~EvalState(); ~EvalState();
void addToSearchPath(const string & s);
/* Parse a Nix expression from the specified file. If `path' /* Parse a Nix expression from the specified file. If `path'
refers to a directory, then "/default.nix" is appended. */ refers to a directory, then "/default.nix" is appended. */
Expr * parseExprFromFile(Path path); Expr * parseExprFromFile(Path path);
@ -229,6 +234,9 @@ public:
form. */ form. */
void evalFile(const Path & path, Value & v); 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 /* Evaluate an expression to normal form, storing the result in
value `v'. */ value `v'. */
void eval(Expr * e, Value & v); void eval(Expr * e, Value & v);

View file

@ -81,6 +81,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
ID [a-zA-Z\_][a-zA-Z0-9\_\']* ID [a-zA-Z\_][a-zA-Z0-9\_\']*
INT [0-9]+ INT [0-9]+
PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-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\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+ 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 */ <IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */
{PATH} { yylval->path = strdup(yytext); return PATH; } {PATH} { yylval->path = strdup(yytext); return PATH; }
{SPATH} { yylval->path = strdup(yytext); return SPATH; }
{URI} { yylval->uri = strdup(yytext); return URI; } {URI} { yylval->uri = strdup(yytext); return URI; }
[ \t\r\n]+ /* eat up whitespace */ [ \t\r\n]+ /* eat up whitespace */

View file

@ -17,19 +17,22 @@
#include "util.hh" #include "util.hh"
#include "nixexpr.hh" #include "nixexpr.hh"
#include "eval.hh"
namespace nix { namespace nix {
struct ParseData struct ParseData
{ {
EvalState & state;
SymbolTable & symbols; SymbolTable & symbols;
Expr * result; Expr * result;
Path basePath; Path basePath;
Path path; Path path;
string error; string error;
Symbol sLetBody; Symbol sLetBody;
ParseData(SymbolTable & symbols) ParseData(EvalState & state)
: symbols(symbols) : state(state)
, symbols(state.symbols)
, sLetBody(symbols.create("<let-body>")) , 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 <id> ID ATTRPATH
%token <e> STR IND_STR %token <e> STR IND_STR
%token <n> INT %token <n> INT
%token <path> PATH %token <path> PATH SPATH
%token <uri> URI %token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token DOLLAR_CURLY /* == ${ */ %token DOLLAR_CURLY /* == ${ */
@ -350,6 +353,20 @@ expr_simple
$$ = stripIndentation(data->symbols, *$2); $$ = stripIndentation(data->symbols, *$2);
} }
| PATH { $$ = new ExprPath(absPath($1, data->basePath)); } | 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)); } | URI { $$ = new ExprString(data->symbols.create($1)); }
| '(' expr ')' { $$ = $2; } | '(' expr ')' { $$ = $2; }
/* Let expressions `let {..., body = ...}' are just desugared /* Let expressions `let {..., body = ...}' are just desugared
@ -454,7 +471,7 @@ Expr * EvalState::parse(const char * text,
const Path & path, const Path & basePath) const Path & path, const Path & basePath)
{ {
yyscan_t scanner; yyscan_t scanner;
ParseData data(symbols); ParseData data(*this);
data.basePath = basePath; data.basePath = basePath;
data.path = path; data.path = path;
@ -511,4 +528,24 @@ Expr * EvalState::parseExprFromString(const string & s, const Path & 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 "";
}
} }

View file

@ -1253,6 +1253,8 @@ void run(Strings args)
else if (parseOptionArg(arg, i, args.end(), else if (parseOptionArg(arg, i, args.end(),
globals.state, globals.instSource.autoArgs)) 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 else if (arg == "--force-name") // undocumented flag for nix-install-package
globals.forceName = needArg(i, args, arg); globals.forceName = needArg(i, args, arg);
else if (arg == "--uninstall" || arg == "-e") else if (arg == "--uninstall" || arg == "-e")

View file

@ -107,6 +107,8 @@ void run(Strings args)
} }
else if (parseOptionArg(arg, i, args.end(), state, autoArgs)) else if (parseOptionArg(arg, i, args.end(), state, autoArgs))
; ;
else if (parseSearchPathArg(arg, i, args.end(), state))
;
else if (arg == "--add-root") { else if (arg == "--add-root") {
if (i == args.end()) if (i == args.end())
throw UsageError("`--add-root' requires an argument"); throw UsageError("`--add-root' requires an argument");

View file

@ -40,7 +40,7 @@ for i in lang/eval-okay-*.nix; do
if test -e lang/$i.flags; then if test -e lang/$i.flags; then
flags=$(cat lang/$i.flags) flags=$(cat lang/$i.flags)
fi 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" echo "FAIL: $i should evaluate"
fail=1 fail=1
elif ! diff lang/$i.out lang/$i.exp; then elif ! diff lang/$i.out lang/$i.exp; then

1
tests/lang/dir1/a.nix Normal file
View file

@ -0,0 +1 @@
"a"

1
tests/lang/dir2/a.nix Normal file
View file

@ -0,0 +1 @@
"X"

1
tests/lang/dir2/b.nix Normal file
View file

@ -0,0 +1 @@
"b"

1
tests/lang/dir3/a.nix Normal file
View file

@ -0,0 +1 @@
"X"

1
tests/lang/dir3/b.nix Normal file
View file

@ -0,0 +1 @@
"X"

1
tests/lang/dir3/c.nix Normal file
View file

@ -0,0 +1 @@
"c"

1
tests/lang/dir4/a.nix Normal file
View file

@ -0,0 +1 @@
"X"

1
tests/lang/dir4/c.nix Normal file
View file

@ -0,0 +1 @@
"X"

View file

@ -0,0 +1 @@
"abc"

View file

@ -0,0 +1 @@
-I lang/dir1 -I lang/dir2

View file

@ -0,0 +1,3 @@
import <a.nix> + import <b.nix> + import <c.nix>

View file

@ -0,0 +1 @@
(import <a.nix>)

View file

@ -0,0 +1 @@
"abc"