1 : #include "build.hh"
2 : #include "misc.hh"
3 : #include "eval.hh"
4 : #include "globals.hh"
5 : #include "store.hh"
6 : #include "util.hh"
7 : #include "expr-to-xml.hh"
8 : #include "nixexpr-ast.hh"
9 :
10 : #include <algorithm>
11 :
12 :
13 : namespace nix {
14 :
15 :
16 : static Expr unwrapContext(EvalState & state, Expr e, ATermList & context)
17 6 : {
18 6 : context = ATempty;
19 6 : e = evalExpr(state, e);
20 6 : if (matchContext(e, context, e))
21 0 : e = evalExpr(state, e);
22 6 : return e;
23 : }
24 :
25 :
26 : static Expr primBuiltins(EvalState & state, const ATermVector & args)
27 11 : {
28 : /* Return an attribute set containing all primops. This allows
29 : Nix expressions to test for new primops and take appropriate
30 : action if they're not available. For instance, rather than
31 : calling a primop `foo' directly, they could say `if builtins ?
32 : foo then builtins.foo ... else ...'. */
33 :
34 11 : ATermMap builtins(128);
35 :
36 341 : for (ATermMap::const_iterator i = state.primOps.begin();
37 : i != state.primOps.end(); ++i)
38 : {
39 330 : string name = aterm2String(i->key);
40 330 : if (string(name, 0, 2) == "__")
41 154 : name = string(name, 2);
42 : /* !!! should use makePrimOp here, I guess. */
43 330 : builtins.set(toATerm(name), makeAttrRHS(makeVar(i->key), makeNoPos()));
44 : }
45 :
46 11 : return makeAttrs(builtins);
47 : }
48 :
49 :
50 : /* Load and evaluate an expression from path specified by the
51 : argument. */
52 : static Expr primImport(EvalState & state, const ATermVector & args)
53 5 : {
54 5 : ATermList es;
55 5 : Path path;
56 5 : ATermList context; /* don't care the context */
57 :
58 5 : Expr arg = unwrapContext(state, args[0], context), arg2;
59 :
60 5 : if (matchPath(arg, arg2))
61 5 : path = aterm2String(arg2);
62 :
63 0 : else if (matchAttrs(arg, es)) {
64 0 : Expr a = queryAttr(arg, "type");
65 :
66 : /* If it is a derivation, we have to realise it and load the
67 : Nix expression created at the derivation's output path. */
68 0 : if (a && evalString(state, a) == "derivation") {
69 0 : a = queryAttr(arg, "drvPath");
70 0 : if (!a) throw EvalError("bad derivation in import");
71 0 : Path drvPath = evalPath(state, a);
72 :
73 0 : buildDerivations(singleton<PathSet>(drvPath));
74 :
75 0 : a = queryAttr(arg, "outPath");
76 0 : if (!a) throw EvalError("bad derivation in import");
77 0 : path = evalPath(state, a);
78 : }
79 : }
80 :
81 0 : else throw TypeError(format("argument of `import' is %1% while a path or derivation is required") % showType(arg));
82 :
83 5 : return evalFile(state, path);
84 : }
85 :
86 :
87 : static Expr primPathExists(EvalState & state, const ATermVector & args)
88 3 : {
89 3 : Expr arg = evalExpr(state, args[0]), arg2;
90 :
91 3 : if (!matchPath(arg, arg2))
92 0 : throw TypeError("`pathExists' requires a path as its argument");
93 :
94 3 : return makeBool(pathExists(aterm2String(arg2)));
95 : }
96 :
97 :
98 : static void flattenList(EvalState & state, Expr e, ATermList & result)
99 53 : {
100 53 : ATermList es;
101 53 : e = evalExpr(state, e);
102 53 : if (matchList(e, es))
103 53 : for (ATermIterator i(es); i; ++i)
104 27 : flattenList(state, *i, result);
105 : else
106 27 : result = ATinsert(result, e);
107 : }
108 :
109 :
110 : ATermList flattenList(EvalState & state, Expr e)
111 26 : {
112 26 : ATermList result = ATempty;
113 26 : flattenList(state, e, result);
114 26 : return ATreverse(result);
115 : }
116 :
117 :
118 : void toString(EvalState & state, Expr e,
119 : ATermList & context, string & result)
120 686 : {
121 686 : e = evalExpr(state, e);
122 :
123 686 : ATerm s;
124 686 : ATermList es;
125 686 : int n;
126 686 : Expr e2;
127 :
128 686 : bool isWrapped = false;
129 693 : while (matchContext(e, es, e2)) {
130 7 : isWrapped = true;
131 7 : e = e2;
132 14 : for (ATermIterator i(es); i; ++i)
133 7 : context = ATinsert(context, *i);
134 : }
135 :
136 : /* Note that `false' is represented as an empty string for shell
137 : scripting convenience, just like `null'. */
138 :
139 686 : if (matchStr(e, s)) result += aterm2String(s);
140 172 : else if (matchUri(e, s)) result += aterm2String(s);
141 172 : else if (e == eTrue) result += "1";
142 172 : else if (e == eFalse) ;
143 172 : else if (matchInt(e, n)) result += int2String(n);
144 172 : else if (matchNull(e)) ;
145 :
146 172 : else if (matchAttrs(e, es)) {
147 25 : Expr a = queryAttr(e, "type");
148 :
149 25 : if (a && evalString(state, a) == "derivation") {
150 25 : Expr a2 = queryAttr(e, "outPath");
151 25 : if (!a2) throw EvalError("output path missing");
152 25 : result += evalPath(state, a2);
153 25 : context = ATinsert(context, e);
154 : }
155 :
156 0 : else throw TypeError("cannot convert an attribute set to a string");
157 : }
158 :
159 147 : else if (matchPath(e, s)) {
160 121 : Path path(canonPath(aterm2String(s)));
161 :
162 121 : if (isStorePath(path) || (isWrapped && isInStore(path))) {
163 33 : result += path;
164 : /* !!! smells hacky. Check whether this is the Right
165 : Thing To Do. */
166 33 : if (!isWrapped)
167 26 : context = ATinsert(context, makePath(toATerm(toStorePath(path))));
168 : }
169 :
170 : else {
171 88 : if (isDerivation(path))
172 0 : throw EvalError(format("file names are not allowed to end in `%1%'")
173 : % drvExtension);
174 :
175 88 : Path dstPath;
176 88 : if (state.srcToStore[path] != "")
177 21 : dstPath = state.srcToStore[path];
178 : else {
179 67 : dstPath = addToStore(path);
180 66 : state.srcToStore[path] = dstPath;
181 66 : printMsg(lvlChatty, format("copied source `%1%' -> `%2%'")
182 : % path % dstPath);
183 : }
184 :
185 87 : result += dstPath;
186 87 : context = ATinsert(context, makePath(toATerm(dstPath)));
187 : }
188 : }
189 :
190 26 : else if (matchList(e, es)) {
191 26 : es = flattenList(state, e);
192 26 : bool first = true;
193 53 : for (ATermIterator i(es); i; ++i) {
194 27 : if (!first) result += " "; else first = false;
195 27 : toString(state, *i, context, result);
196 : }
197 : }
198 :
199 0 : else throw TypeError(format("cannot convert %1% to a string") % showType(e));
200 :
201 : }
202 :
203 :
204 : /* Returns the hash of a derivation modulo fixed-output
205 : subderivations. A fixed-output derivation is a derivation with one
206 : output (`out') for which an expected hash and hash algorithm are
207 : specified (using the `outputHash' and `outputHashAlgo'
208 : attributes). We don't want changes to such derivations to
209 : propagate upwards through the dependency graph, changing output
210 : paths everywhere.
211 :
212 : For instance, if we change the url in a call to the `fetchurl'
213 : function, we do not want to rebuild everything depending on it
214 : (after all, (the hash of) the file being downloaded is unchanged).
215 : So the *output paths* should not change. On the other hand, the
216 : *derivation store expression paths* should change to reflect the
217 : new dependency graph.
218 :
219 : That's what this function does: it returns a hash which is just the
220 : of the derivation ATerm, except that any input store expression
221 : paths have been replaced by the result of a recursive call to this
222 : function, and that for fixed-output derivations we return
223 : (basically) its outputHash. */
224 : static Hash hashDerivationModulo(EvalState & state, Derivation drv)
225 174 : {
226 : /* Return a fixed hash for fixed-output derivations. */
227 174 : if (drv.outputs.size() == 1) {
228 174 : DerivationOutputs::const_iterator i = drv.outputs.begin();
229 174 : if (i->first == "out" &&
230 : i->second.hash != "")
231 : {
232 16 : return hashString(htSHA256, "fixed:out:"
233 : + i->second.hashAlgo + ":"
234 : + i->second.hash + ":"
235 : + i->second.path);
236 : }
237 : }
238 :
239 : /* For other derivations, replace the inputs paths with recursive
240 : calls to this function.*/
241 158 : DerivationInputs inputs2;
242 222 : for (DerivationInputs::iterator i = drv.inputDrvs.begin();
243 : i != drv.inputDrvs.end(); ++i)
244 : {
245 64 : Hash h = state.drvHashes[i->first];
246 64 : if (h.type == htUnknown) {
247 1088 : Derivation drv2 = derivationFromPath(i->first);
248 0 : h = hashDerivationModulo(state, drv2);
249 0 : state.drvHashes[i->first] = h;
250 : }
251 64 : inputs2[printHash(h)] = i->second;
252 : }
253 158 : drv.inputDrvs = inputs2;
254 :
255 158 : return hashTerm(unparseDerivation(drv));
256 : }
257 :
258 :
259 : /* Construct (as a unobservable side effect) a Nix derivation
260 : expression that performs the derivation described by the argument
261 : set. Returns the original set extended with the following
262 : attributes: `outPath' containing the primary output path of the
263 : derivation; `drvPath' containing the path of the Nix expression;
264 : and `type' set to `derivation' to indicate that this is a
265 : derivation. */
266 : static Expr primDerivationStrict(EvalState & state, const ATermVector & args)
267 89 : {
268 89 : startNest(nest, lvlVomit, "evaluating derivation");
269 :
270 89 : ATermMap attrs(128); /* !!! */
271 89 : queryAllAttrs(evalExpr(state, args[0]), attrs, true);
272 :
273 : /* Figure out the name already (for stack backtraces). */
274 89 : Expr eDrvName = attrs.get(toATerm("name"));
275 89 : if (!eDrvName)
276 0 : throw EvalError("required attribute `name' missing");
277 89 : ATerm posDrvName;
278 89 : if (!matchAttrRHS(eDrvName, eDrvName, posDrvName)) abort();
279 89 : string drvName = evalString(state, eDrvName);
280 :
281 : /* Build the derivation expression by processing the attributes. */
282 371 : Derivation drv;
283 :
284 89 : string outputHash;
285 89 : string outputHashAlgo;
286 89 : bool outputHashRecursive = false;
287 :
288 619 : for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i) {
289 531 : string key = aterm2String(i->key);
290 531 : ATerm value;
291 531 : Expr pos;
292 531 : ATerm rhs = i->value;
293 531 : if (!matchAttrRHS(rhs, value, pos)) abort();
294 531 : startNest(nest, lvlVomit, format("processing attribute `%1%'") % key);
295 :
296 531 : try {
297 :
298 531 : ATermList context = ATempty;
299 :
300 : /* The `args' attribute is special: it supplies the
301 : command-line arguments to the builder. */
302 531 : if (key == "args") {
303 64 : ATermList es;
304 64 : value = evalExpr(state, value);
305 64 : if (!matchList(value, es)) {
306 0 : static bool haveWarned = false;
307 0 : warnOnce(haveWarned, "the `args' attribute should evaluate to a list");
308 0 : es = flattenList(state, value);
309 : }
310 255 : for (ATermIterator i(es); i; ++i) {
311 192 : string s;
312 192 : toString(state, *i, context, s);
313 191 : drv.args.push_back(s);
314 : }
315 : }
316 :
317 : /* All other attributes are passed to the builder through
318 : the environment. */
319 : else {
320 467 : string s;
321 467 : toString(state, value, context, s);
322 467 : drv.env[key] = s;
323 467 : if (key == "builder") drv.builder = s;
324 378 : else if (key == "system") drv.platform = s;
325 290 : else if (key == "name") drvName = s;
326 201 : else if (key == "outputHash") outputHash = s;
327 192 : else if (key == "outputHashAlgo") outputHashAlgo = s;
328 183 : else if (key == "outputHashMode") {
329 9 : if (s == "recursive") outputHashRecursive = true;
330 6 : else if (s == "flat") outputHashRecursive = false;
331 0 : else throw EvalError(format("invalid value `%1%' for `outputHashMode' attribute") % s);
332 : }
333 : }
334 :
335 : /* Everything in the context of the expression should be
336 : added as dependencies of the resulting derivation. */
337 :
338 675 : for (ATermIterator i(context); i; ++i) {
339 :
340 145 : ATerm s;
341 145 : ATermList as;
342 :
343 145 : if (matchPath(*i, s)) {
344 113 : assert(isStorePath(aterm2String(s)));
345 113 : drv.inputSrcs.insert(aterm2String(s));
346 : }
347 :
348 32 : else if (matchAttrs(*i, as)) {
349 32 : Expr a = queryAttr(*i, "type");
350 32 : assert(a && evalString(state, a) == "derivation");
351 :
352 32 : Expr a2 = queryAttr(*i, "drvPath");
353 32 : if (!a2) throw EvalError("derivation path missing");
354 :
355 32 : drv.inputDrvs[evalPath(state, a2)] = singleton<StringSet>("out");
356 : }
357 :
358 0 : else abort();
359 : }
360 :
361 0 : } catch (Error & e) {
362 1 : e.addPrefix(format("while processing the derivation attribute `%1%' at %2%:\n")
363 : % key % showPos(pos));
364 1 : e.addPrefix(format("while instantiating the derivation named `%1%' at %2%:\n")
365 : % drvName % showPos(posDrvName));
366 1 : throw;
367 : }
368 :
369 : }
370 :
371 : /* Do we have all required attributes? */
372 88 : if (drv.builder == "")
373 0 : throw EvalError("required attribute `builder' missing");
374 88 : if (drv.platform == "")
375 0 : throw EvalError("required attribute `system' missing");
376 :
377 : /* If an output hash was given, check it. */
378 88 : if (outputHash == "")
379 79 : outputHashAlgo = "";
380 : else {
381 9 : HashType ht = parseHashType(outputHashAlgo);
382 9 : if (ht == htUnknown)
383 0 : throw EvalError(format("unknown hash algorithm `%1%'") % outputHashAlgo);
384 9 : Hash h(ht);
385 9 : if (outputHash.size() == h.hashSize * 2)
386 : /* hexadecimal representation */
387 6 : h = parseHash(ht, outputHash);
388 3 : else if (outputHash.size() == hashLength32(h))
389 : /* base-32 representation */
390 2 : h = parseHash32(ht, outputHash);
391 : else
392 1 : throw Error(format("hash `%1%' has wrong length for hash type `%2%'")
393 : % outputHash % outputHashAlgo);
394 8 : string s = outputHash;
395 8 : outputHash = printHash(h);
396 8 : if (outputHashRecursive) outputHashAlgo = "r:" + outputHashAlgo;
397 : }
398 :
399 : /* Check whether the derivation name is valid. */
400 87 : checkStoreName(drvName);
401 87 : if (isDerivation(drvName))
402 0 : throw EvalError(format("derivation names are not allowed to end in `%1%'")
403 : % drvExtension);
404 :
405 : /* Construct the "masked" derivation store expression, which is
406 : the final one except that in the list of outputs, the output
407 : paths are empty, and the corresponding environment variables
408 : have an empty value. This ensures that changes in the set of
409 : output names do get reflected in the hash. */
410 87 : drv.env["out"] = "";
411 87 : drv.outputs["out"] =
412 2106 : DerivationOutput("", outputHashAlgo, outputHash);
413 :
414 : /* Use the masked derivation expression to compute the output
415 : path. */
416 : Path outPath = makeStorePath("output:out",
417 87 : hashDerivationModulo(state, drv), drvName);
418 :
419 : /* Construct the final derivation store expression. */
420 87 : drv.env["out"] = outPath;
421 87 : drv.outputs["out"] =
422 : DerivationOutput(outPath, outputHashAlgo, outputHash);
423 :
424 : /* Write the resulting term into the Nix store directory. */
425 87 : Path drvPath = writeDerivation(drv, drvName);
426 :
427 87 : printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'")
428 : % drvName % drvPath);
429 :
430 : /* Optimisation, but required in read-only mode! because in that
431 : case we don't actually write store expressions, so we can't
432 : read them later. */
433 87 : state.drvHashes[drvPath] = hashDerivationModulo(state, drv);
434 :
435 : /* !!! assumes a single output */
436 87 : ATermMap outAttrs(2);
437 87 : outAttrs.set(toATerm("outPath"),
438 : makeAttrRHS(makePath(toATerm(outPath)), makeNoPos()));
439 87 : outAttrs.set(toATerm("drvPath"),
440 : makeAttrRHS(makePath(toATerm(drvPath)), makeNoPos()));
441 :
442 87 : return makeAttrs(outAttrs);
443 : }
444 :
445 :
446 : static Expr primDerivationLazy(EvalState & state, const ATermVector & args)
447 125 : {
448 125 : Expr eAttrs = evalExpr(state, args[0]);
449 125 : ATermMap attrs(128); /* !!! */
450 125 : queryAllAttrs(eAttrs, attrs, true);
451 :
452 125 : attrs.set(toATerm("type"),
453 : makeAttrRHS(makeStr(toATerm("derivation")), makeNoPos()));
454 :
455 125 : Expr drvStrict = makeCall(makeVar(toATerm("derivation!")), eAttrs);
456 :
457 125 : attrs.set(toATerm("outPath"),
458 : makeAttrRHS(makeSelect(drvStrict, toATerm("outPath")), makeNoPos()));
459 125 : attrs.set(toATerm("drvPath"),
460 : makeAttrRHS(makeSelect(drvStrict, toATerm("drvPath")), makeNoPos()));
461 :
462 125 : return makeAttrs(attrs);
463 : }
464 :
465 :
466 : /* Return the base name of the given string, i.e., everything
467 : following the last slash. */
468 : static Expr primBaseNameOf(EvalState & state, const ATermVector & args)
469 0 : {
470 0 : return makeStr(toATerm(baseNameOf(evalString(state, args[0]))));
471 : }
472 :
473 :
474 : /* Return the directory of the given path, i.e., everything before the
475 : last slash. */
476 : static Expr primDirOf(EvalState & state, const ATermVector & args)
477 1 : {
478 1 : return makePath(toATerm(dirOf(evalPath(state, args[0]))));
479 : }
480 :
481 :
482 : ATerm coerceToString(Expr e)
483 15 : {
484 15 : ATerm s;
485 15 : if (matchStr(e, s) || matchPath(e, s) || matchUri(e, s))
486 15 : return s;
487 0 : return 0;
488 : }
489 :
490 :
491 : /* Convert the argument (which can be a path or a uri) to a string. */
492 : static Expr primToString(EvalState & state, const ATermVector & args)
493 1 : {
494 1 : ATermList context = ATempty;
495 1 : bool dummy;
496 1 : string s = coerceToStringWithContext(state, context, args[0], dummy);
497 1 : return wrapInContext(context, makeStr(toATerm(s)));
498 : }
499 :
500 :
501 : /* Convert the argument to a path. */
502 : static Expr primToPath(EvalState & state, const ATermVector & args)
503 10 : {
504 10 : Expr e = evalExpr(state, args[0]);
505 10 : ATerm t = coerceToString(e);
506 10 : if (!t) throw TypeError(format("cannot coerce %1% to a path in `toPath'") % showType(e));
507 10 : Path path = aterm2String(t);
508 10 : if (path == "" || path[0] != '/')
509 0 : throw EvalError("string doesn't represent an absolute path in `toPath'");
510 10 : return makePath(toATerm(canonPath(path)));
511 : }
512 :
513 :
514 : /* Convert the argument (which can be any Nix expression) to an XML
515 : representation returned in a string. Not all Nix expressions can
516 : be sensibly or completely represented (e.g., functions). */
517 : static Expr primToXML(EvalState & state, const ATermVector & args)
518 1 : {
519 1 : std::ostringstream out;
520 1 : ATermList context = ATempty;
521 1 : printTermAsXML(strictEvalExpr(state, args[0]), out, context);
522 1 : return wrapInContext(context, makeStr(toATerm(out.str())));
523 : }
524 :
525 :
526 : /* Store a string in the Nix store as a source file that can be used
527 : as an input by derivations. */
528 : static Expr primToFile(EvalState & state, const ATermVector & args)
529 1 : {
530 1 : ATermList context;
531 1 : string name = evalString(state, args[0]);
532 : string contents = evalString(state,
533 1 : unwrapContext(state, args[1], context));
534 :
535 1 : PathSet refs;
536 :
537 1 : for (ATermIterator i(context); i; ++i) {
538 0 : ATerm s;
539 0 : if (matchPath(*i, s)) {
540 0 : assert(isStorePath(aterm2String(s)));
541 0 : refs.insert(aterm2String(s));
542 : }
543 0 : else throw EvalError("in `toFile': the file cannot contain references to derivation outputs");
544 : }
545 :
546 1 : Path storePath = addTextToStore(name, contents, refs);
547 :
548 : /* Note: we don't need to wrap the result in a context, since
549 : `storePath' itself has references to the paths used in
550 : args[1]. */
551 1 : return makePath(toATerm(storePath));
552 : }
553 :
554 :
555 : /* Boolean constructors. */
556 : static Expr primTrue(EvalState & state, const ATermVector & args)
557 4 : {
558 4 : return eTrue;
559 : }
560 :
561 :
562 : static Expr primFalse(EvalState & state, const ATermVector & args)
563 3 : {
564 3 : return eFalse;
565 : }
566 :
567 :
568 : /* Return the null value. */
569 : static Expr primNull(EvalState & state, const ATermVector & args)
570 0 : {
571 0 : return makeNull();
572 : }
573 :
574 :
575 : /* Determine whether the argument is the null value. */
576 : static Expr primIsNull(EvalState & state, const ATermVector & args)
577 0 : {
578 0 : return makeBool(matchNull(evalExpr(state, args[0])));
579 : }
580 :
581 :
582 : /* Determine whether the argument is a list. */
583 : static Expr primIsList(EvalState & state, const ATermVector & args)
584 11 : {
585 11 : ATermList list;
586 11 : return makeBool(matchList(evalExpr(state, args[0]), list));
587 : }
588 :
589 :
590 : static Path findDependency(Path dir, string dep)
591 0 : {
592 0 : if (dep[0] == '/') throw EvalError(
593 : format("illegal absolute dependency `%1%'") % dep);
594 :
595 0 : Path p = canonPath(dir + "/" + dep);
596 :
597 0 : if (pathExists(p))
598 0 : return p;
599 : else
600 0 : return "";
601 : }
602 :
603 :
604 : /* Make path `p' relative to directory `pivot'. E.g.,
605 : relativise("/a/b/c", "a/b/x/y") => "../x/y". Both input paths
606 : should be in absolute canonical form. */
607 : static string relativise(Path pivot, Path p)
608 0 : {
609 0 : assert(pivot.size() > 0 && pivot[0] == '/');
610 0 : assert(p.size() > 0 && p[0] == '/');
611 :
612 0 : if (pivot == p) return ".";
613 :
614 : /* `p' is in `pivot'? */
615 0 : Path pivot2 = pivot + "/";
616 0 : if (p.substr(0, pivot2.size()) == pivot2) {
617 0 : return p.substr(pivot2.size());
618 : }
619 :
620 : /* Otherwise, `p' is in a parent of `pivot'. Find up till which
621 : path component `p' and `pivot' match, and add an appropriate
622 : number of `..' components. */
623 0 : string::size_type i = 1;
624 0 : while (1) {
625 0 : string::size_type j = pivot.find('/', i);
626 0 : if (j == string::npos) break;
627 0 : j++;
628 0 : if (pivot.substr(0, j) != p.substr(0, j)) break;
629 0 : i = j;
630 : }
631 :
632 0 : string prefix;
633 0 : unsigned int slashes = count(pivot.begin() + i, pivot.end(), '/') + 1;
634 0 : while (slashes--) {
635 0 : prefix += "../";
636 : }
637 :
638 0 : return prefix + p.substr(i);
639 : }
640 :
641 :
642 : static Expr primDependencyClosure(EvalState & state, const ATermVector & args)
643 0 : {
644 0 : startNest(nest, lvlDebug, "finding dependencies");
645 :
646 0 : Expr attrs = evalExpr(state, args[0]);
647 :
648 : /* Get the start set. */
649 0 : Expr startSet = queryAttr(attrs, "startSet");
650 0 : if (!startSet) throw EvalError("attribute `startSet' required");
651 0 : ATermList startSet2 = evalList(state, startSet);
652 :
653 0 : Path pivot;
654 0 : PathSet workSet;
655 0 : for (ATermIterator i(startSet2); i; ++i) {
656 0 : Path p = evalPath(state, *i);
657 0 : workSet.insert(p);
658 0 : pivot = dirOf(p);
659 : }
660 :
661 : /* Get the search path. */
662 0 : PathSet searchPath;
663 0 : Expr e = queryAttr(attrs, "searchPath");
664 0 : if (e) {
665 0 : ATermList list = evalList(state, e);
666 0 : for (ATermIterator i(list); i; ++i) {
667 0 : Path p = evalPath(state, *i);
668 0 : searchPath.insert(p);
669 : }
670 : }
671 :
672 0 : Expr scanner = queryAttr(attrs, "scanner");
673 0 : if (!scanner) throw EvalError("attribute `scanner' required");
674 :
675 : /* Construct the dependency closure by querying the dependency of
676 : each path in `workSet', adding the dependencies to
677 : `workSet'. */
678 0 : PathSet doneSet;
679 0 : while (!workSet.empty()) {
680 0 : Path path = *(workSet.begin());
681 0 : workSet.erase(path);
682 :
683 0 : if (doneSet.find(path) != doneSet.end()) continue;
684 0 : doneSet.insert(path);
685 :
686 0 : try {
687 :
688 : /* Call the `scanner' function with `path' as argument. */
689 0 : debug(format("finding dependencies in `%1%'") % path);
690 0 : ATermList deps = evalList(state, makeCall(scanner, makePath(toATerm(path))));
691 :
692 : /* Try to find the dependencies relative to the `path'. */
693 0 : for (ATermIterator i(deps); i; ++i) {
694 0 : string s = evalString(state, *i);
695 :
696 0 : Path dep = findDependency(dirOf(path), s);
697 :
698 0 : if (dep == "") {
699 0 : for (PathSet::iterator j = searchPath.begin();
700 : j != searchPath.end(); ++j)
701 : {
702 0 : dep = findDependency(*j, s);
703 0 : if (dep != "") break;
704 : }
705 : }
706 :
707 0 : if (dep == "")
708 0 : debug(format("did NOT find dependency `%1%'") % s);
709 : else {
710 0 : debug(format("found dependency `%1%'") % dep);
711 0 : workSet.insert(dep);
712 : }
713 : }
714 :
715 0 : } catch (Error & e) {
716 0 : e.addPrefix(format("while finding dependencies in `%1%':\n")
717 : % path);
718 0 : throw;
719 : }
720 : }
721 :
722 : /* Return a list of the dependencies we've just found. */
723 0 : ATermList deps = ATempty;
724 0 : for (PathSet::iterator i = doneSet.begin(); i != doneSet.end(); ++i) {
725 0 : deps = ATinsert(deps, makeStr(toATerm(relativise(pivot, *i))));
726 0 : deps = ATinsert(deps, makePath(toATerm(*i)));
727 : }
728 :
729 0 : debug(format("dependency list is `%1%'") % makeList(deps));
730 :
731 0 : return makeList(deps);
732 : }
733 :
734 :
735 : static Expr primAbort(EvalState & state, const ATermVector & args)
736 1 : {
737 1 : throw Abort(format("evaluation aborted with the following error message: `%1%'") %
738 2 : evalString(state, args[0]));
739 : }
740 :
741 :
742 : /* Return the first element of a list. */
743 : static Expr primHead(EvalState & state, const ATermVector & args)
744 74 : {
745 74 : ATermList list = evalList(state, args[0]);
746 74 : if (ATisEmpty(list))
747 0 : throw Error("`head' called on an empty list");
748 74 : return evalExpr(state, ATgetFirst(list));
749 : }
750 :
751 :
752 : /* Return a list consisting of everything but the the first element of
753 : a list. */
754 : static Expr primTail(EvalState & state, const ATermVector & args)
755 74 : {
756 74 : ATermList list = evalList(state, args[0]);
757 74 : if (ATisEmpty(list))
758 0 : throw Error("`tail' called on an empty list");
759 74 : return makeList(ATgetNext(list));
760 : }
761 :
762 :
763 : /* Return an environment variable. Use with care. */
764 : static Expr primGetEnv(EvalState & state, const ATermVector & args)
765 2 : {
766 2 : string name = evalString(state, args[0]);
767 2 : return makeStr(toATerm(getEnv(name)));
768 : }
769 :
770 :
771 : /* Apply a function to every element of a list. */
772 : static Expr primMap(EvalState & state, const ATermVector & args)
773 1 : {
774 1 : Expr fun = evalExpr(state, args[0]);
775 1 : ATermList list = evalList(state, args[1]);
776 :
777 1 : ATermList res = ATempty;
778 4 : for (ATermIterator i(list); i; ++i)
779 3 : res = ATinsert(res, makeCall(fun, *i));
780 :
781 1 : return makeList(ATreverse(res));
782 : }
783 :
784 :
785 : /* Return a string constant representing the current platform. Note!
786 : that differs between platforms, so Nix expressions using
787 : `__currentSystem' can evaluate to different values on different
788 : platforms. */
789 : static Expr primCurrentSystem(EvalState & state, const ATermVector & args)
790 0 : {
791 0 : return makeStr(toATerm(thisSystem));
792 : }
793 :
794 :
795 : static Expr primCurrentTime(EvalState & state, const ATermVector & args)
796 0 : {
797 0 : return ATmake("Int(<int>)", time(0));
798 : }
799 :
800 :
801 : /* Dynamic version of the `.' operator. */
802 : static Expr primGetAttr(EvalState & state, const ATermVector & args)
803 1 : {
804 1 : string attr = evalString(state, args[0]);
805 1 : return evalExpr(state, makeSelect(args[1], toATerm(attr)));
806 : }
807 :
808 :
809 : /* Dynamic version of the `?' operator. */
810 : static Expr primHasAttr(EvalState & state, const ATermVector & args)
811 2 : {
812 2 : string attr = evalString(state, args[0]);
813 2 : return evalExpr(state, makeOpHasAttr(args[1], toATerm(attr)));
814 : }
815 :
816 :
817 : static Expr primRemoveAttrs(EvalState & state, const ATermVector & args)
818 2 : {
819 2 : ATermMap attrs(128); /* !!! */
820 2 : queryAllAttrs(evalExpr(state, args[0]), attrs, true);
821 :
822 2 : ATermList list = evalList(state, args[1]);
823 :
824 4 : for (ATermIterator i(list); i; ++i)
825 : /* It's not an error for *i not to exist. */
826 2 : attrs.remove(toATerm(evalString(state, *i)));
827 :
828 2 : return makeAttrs(attrs);
829 : }
830 :
831 :
832 : static Expr primRelativise(EvalState & state, const ATermVector & args)
833 0 : {
834 0 : Path pivot = evalPath(state, args[0]);
835 0 : Path path = evalPath(state, args[1]);
836 0 : return makeStr(toATerm(relativise(pivot, path)));
837 : }
838 :
839 :
840 : static Expr primAdd(EvalState & state, const ATermVector & args)
841 100 : {
842 100 : int i1 = evalInt(state, args[0]);
843 100 : int i2 = evalInt(state, args[1]);
844 100 : return makeInt(i1 + i2);
845 : }
846 :
847 :
848 : static Expr primLessThan(EvalState & state, const ATermVector & args)
849 51 : {
850 51 : int i1 = evalInt(state, args[0]);
851 51 : int i2 = evalInt(state, args[1]);
852 51 : return makeBool(i1 < i2);
853 : }
854 :
855 :
856 : void EvalState::addPrimOps()
857 118 : {
858 118 : addPrimOp("builtins", 0, primBuiltins);
859 :
860 118 : addPrimOp("true", 0, primTrue);
861 118 : addPrimOp("false", 0, primFalse);
862 118 : addPrimOp("null", 0, primNull);
863 118 : addPrimOp("__currentSystem", 0, primCurrentSystem);
864 118 : addPrimOp("__currentTime", 0, primCurrentTime);
865 :
866 118 : addPrimOp("import", 1, primImport);
867 118 : addPrimOp("__pathExists", 1, primPathExists);
868 118 : addPrimOp("derivation!", 1, primDerivationStrict);
869 118 : addPrimOp("derivation", 1, primDerivationLazy);
870 118 : addPrimOp("baseNameOf", 1, primBaseNameOf);
871 118 : addPrimOp("dirOf", 1, primDirOf);
872 118 : addPrimOp("toString", 1, primToString);
873 118 : addPrimOp("__toPath", 1, primToPath);
874 118 : addPrimOp("__toXML", 1, primToXML);
875 118 : addPrimOp("isNull", 1, primIsNull);
876 118 : addPrimOp("__isList", 1, primIsList);
877 118 : addPrimOp("dependencyClosure", 1, primDependencyClosure);
878 118 : addPrimOp("abort", 1, primAbort);
879 118 : addPrimOp("__head", 1, primHead);
880 118 : addPrimOp("__tail", 1, primTail);
881 118 : addPrimOp("__getEnv", 1, primGetEnv);
882 :
883 118 : addPrimOp("map", 2, primMap);
884 118 : addPrimOp("__getAttr", 2, primGetAttr);
885 118 : addPrimOp("__hasAttr", 2, primHasAttr);
886 118 : addPrimOp("removeAttrs", 2, primRemoveAttrs);
887 118 : addPrimOp("relativise", 2, primRelativise);
888 118 : addPrimOp("__add", 2, primAdd);
889 118 : addPrimOp("__lessThan", 2, primLessThan);
890 118 : addPrimOp("__toFile", 2, primToFile);
891 : }
892 :
893 :
894 121 : }
|