Skip to content

Commit 1ecc97b

Browse files
committed
* 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.
1 parent 54945a2 commit 1ecc97b

24 files changed

Lines changed: 98 additions & 9 deletions

doc/manual/release-notes.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
<para>TODO: “or” keyword.</para>
3737
</listitem>
3838

39+
<listitem>
40+
<para>TODO: Nix expression search path (<literal>import &lt;foo/bar.nix></literal>).</para>
41+
</listitem>
42+
3943
</itemizedlist>
4044

4145
</section>

scripts/nix-build.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ EOF
7676
$outLink = $ARGV[$n];
7777
}
7878

79-
elsif ($arg eq "--attr" or $arg eq "-A") {
79+
elsif ($arg eq "--attr" or $arg eq "-A" or $arg eq "-I") {
8080
$n++;
8181
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
82-
push @instArgs, ("--attr", $ARGV[$n]);
82+
push @instArgs, ($arg, $ARGV[$n]);
8383
}
8484

8585
elsif ($arg eq "--arg" || $arg eq "--argstr") {

src/libexpr/common-opts.cc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,15 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
3333
return true;
3434
}
3535

36-
36+
37+
bool parseSearchPathArg(const string & arg, Strings::iterator & i,
38+
const Strings::iterator & argsEnd, EvalState & state)
39+
{
40+
if (arg != "-I") return false;
41+
if (i == argsEnd) throw UsageError(format("`%1%' requires an argument") % arg);;
42+
state.addToSearchPath(*i++);
43+
return true;
44+
}
45+
46+
3747
}

src/libexpr/common-opts.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
1111
const Strings::iterator & argsEnd, EvalState & state,
1212
Bindings & autoArgs);
1313

14+
bool parseSearchPathArg(const string & arg, Strings::iterator & i,
15+
const Strings::iterator & argsEnd, EvalState & state);
16+
1417
}
1518

1619

src/libexpr/eval.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ EvalState::EvalState()
181181
gcInitialised = true;
182182
}
183183
#endif
184+
185+
/* Initialise the Nix expression search path. */
186+
searchPathInsertionPoint = searchPath.end();
187+
Strings paths = tokenizeString(getEnv("NIX_PATH", ""), ":");
188+
foreach (Strings::iterator, i, paths) addToSearchPath(*i);
189+
searchPathInsertionPoint = searchPath.begin();
184190
}
185191

186192

src/libexpr/eval.hh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,16 @@ private:
213213

214214
std::map<Path, Expr *> parseTrees;
215215

216+
Paths searchPath;
217+
Paths::iterator searchPathInsertionPoint;
218+
216219
public:
217220

218221
EvalState();
219222
~EvalState();
220223

224+
void addToSearchPath(const string & s);
225+
221226
/* Parse a Nix expression from the specified file. If `path'
222227
refers to a directory, then "/default.nix" is appended. */
223228
Expr * parseExprFromFile(Path path);
@@ -229,6 +234,9 @@ public:
229234
form. */
230235
void evalFile(const Path & path, Value & v);
231236

237+
/* Look up a file in the search path. */
238+
Path findFile(const string & path);
239+
232240
/* Evaluate an expression to normal form, storing the result in
233241
value `v'. */
234242
void eval(Expr * e, Value & v);

src/libexpr/lexer.l

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
8181
ID [a-zA-Z\_][a-zA-Z0-9\_\']*
8282
INT [0-9]+
8383
PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+
84+
SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
8485
URI [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
8586

8687

@@ -153,6 +154,7 @@ or { return OR_KW; }
153154
<IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */
154155

155156
{PATH} { yylval->path = strdup(yytext); return PATH; }
157+
{SPATH} { yylval->path = strdup(yytext); return SPATH; }
156158
{URI} { yylval->uri = strdup(yytext); return URI; }
157159

158160
[ \t\r\n]+ /* eat up whitespace */

src/libexpr/parser.y

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@
1717
#include "util.hh"
1818

1919
#include "nixexpr.hh"
20+
#include "eval.hh"
2021

2122
namespace nix {
2223

2324
struct ParseData
2425
{
26+
EvalState & state;
2527
SymbolTable & symbols;
2628
Expr * result;
2729
Path basePath;
2830
Path path;
2931
string error;
3032
Symbol sLetBody;
31-
ParseData(SymbolTable & symbols)
32-
: symbols(symbols)
33+
ParseData(EvalState & state)
34+
: state(state)
35+
, symbols(state.symbols)
3336
, sLetBody(symbols.create("<let-body>"))
3437
{ };
3538
};
@@ -253,7 +256,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
253256
%token <id> ID ATTRPATH
254257
%token <e> STR IND_STR
255258
%token <n> INT
256-
%token <path> PATH
259+
%token <path> PATH SPATH
257260
%token <uri> URI
258261
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
259262
%token DOLLAR_CURLY /* == ${ */
@@ -350,6 +353,20 @@ expr_simple
350353
$$ = stripIndentation(data->symbols, *$2);
351354
}
352355
| PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
356+
| SPATH {
357+
string path($1 + 1, strlen($1) - 2);
358+
Path path2 = data->state.findFile(path);
359+
/* The file wasn't found in the search path. However, we can't
360+
throw an error here, because the expression might never be
361+
evaluated. So return an expression that lazily calls
362+
‘abort’. */
363+
$$ = path2 == ""
364+
? (Expr * ) new ExprApp(
365+
new ExprVar(data->symbols.create("throw")),
366+
new ExprString(data->symbols.create(
367+
(format("file `%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)") % path).str())))
368+
: (Expr * ) new ExprPath(path2);
369+
}
353370
| URI { $$ = new ExprString(data->symbols.create($1)); }
354371
| '(' expr ')' { $$ = $2; }
355372
/* Let expressions `let {..., body = ...}' are just desugared
@@ -454,7 +471,7 @@ Expr * EvalState::parse(const char * text,
454471
const Path & path, const Path & basePath)
455472
{
456473
yyscan_t scanner;
457-
ParseData data(symbols);
474+
ParseData data(*this);
458475
data.basePath = basePath;
459476
data.path = path;
460477

@@ -510,5 +527,25 @@ Expr * EvalState::parseExprFromString(const string & s, const Path & basePath)
510527
return parse(s.c_str(), "(string)", basePath);
511528
}
512529

513-
530+
531+
void EvalState::addToSearchPath(const string & s)
532+
{
533+
Path path = absPath(s);
534+
if (pathExists(path)) {
535+
debug(format("adding path `%1%' to the search path") % path);
536+
searchPath.insert(searchPathInsertionPoint, path);
537+
}
538+
}
539+
540+
541+
Path EvalState::findFile(const string & path)
542+
{
543+
foreach (Paths::iterator, i, searchPath) {
544+
Path res = *i + "/" + path;
545+
if (pathExists(res)) return canonPath(res);
546+
}
547+
return "";
548+
}
549+
550+
514551
}

src/nix-env/nix-env.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,8 @@ void run(Strings args)
12531253
else if (parseOptionArg(arg, i, args.end(),
12541254
globals.state, globals.instSource.autoArgs))
12551255
;
1256+
else if (parseSearchPathArg(arg, i, args.end(), globals.state))
1257+
;
12561258
else if (arg == "--force-name") // undocumented flag for nix-install-package
12571259
globals.forceName = needArg(i, args, arg);
12581260
else if (arg == "--uninstall" || arg == "-e")

src/nix-instantiate/nix-instantiate.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ void run(Strings args)
107107
}
108108
else if (parseOptionArg(arg, i, args.end(), state, autoArgs))
109109
;
110+
else if (parseSearchPathArg(arg, i, args.end(), state))
111+
;
110112
else if (arg == "--add-root") {
111113
if (i == args.end())
112114
throw UsageError("`--add-root' requires an argument");

0 commit comments

Comments
 (0)