forked from lix-project/lix
* 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:
parent
54945a2950
commit
1ecc97b6bd
|
@ -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 <foo/bar.nix></literal>).</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
1
tests/lang/dir1/a.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"a"
|
1
tests/lang/dir2/a.nix
Normal file
1
tests/lang/dir2/a.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"X"
|
1
tests/lang/dir2/b.nix
Normal file
1
tests/lang/dir2/b.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"b"
|
1
tests/lang/dir3/a.nix
Normal file
1
tests/lang/dir3/a.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"X"
|
1
tests/lang/dir3/b.nix
Normal file
1
tests/lang/dir3/b.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"X"
|
1
tests/lang/dir3/c.nix
Normal file
1
tests/lang/dir3/c.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"c"
|
1
tests/lang/dir4/a.nix
Normal file
1
tests/lang/dir4/a.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"X"
|
1
tests/lang/dir4/c.nix
Normal file
1
tests/lang/dir4/c.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"X"
|
1
tests/lang/eval-okay-search-path.exp
Normal file
1
tests/lang/eval-okay-search-path.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"abc"
|
1
tests/lang/eval-okay-search-path.flags
Normal file
1
tests/lang/eval-okay-search-path.flags
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-I lang/dir1 -I lang/dir2
|
3
tests/lang/eval-okay-search-path.nix
Normal file
3
tests/lang/eval-okay-search-path.nix
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import <a.nix> + import <b.nix> + import <c.nix>
|
||||||
|
|
||||||
|
|
1
tests/lang/eval-okay-search-path.nix~
Normal file
1
tests/lang/eval-okay-search-path.nix~
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(import <a.nix>)
|
1
tests/lang/eval-okay-search-path.out
Normal file
1
tests/lang/eval-okay-search-path.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"abc"
|
Loading…
Reference in a new issue