/* Collection of functions useful for debugging broken nix expressions. * `trace`-like functions take two values, print the first to stderr and return the second. * `traceVal`-like functions take one argument which both printed and returned. * `traceSeq`-like functions fully evaluate their traced value before printing (not just to “weak head normal form” like trace does by default). * Functions that end in `-Fn` take an additional function as their first argument, which is applied to the traced value before it is printed. */ { lib }: let inherit (lib) isInt attrNames isList isAttrs substring addErrorContext attrValues concatLists concatStringsSep const elem generators head id isDerivation isFunction mapAttrs trace; in rec { # -- TRACING -- /* Conditionally trace the supplied message, based on a predicate. Type: traceIf :: bool -> string -> a -> a Example: traceIf true "hello" 3 trace: hello => 3 */ traceIf = # Predicate to check pred: # Message that should be traced msg: # Value to return x: if pred then trace msg x else x; /* Trace the supplied value after applying a function to it, and return the original value. Type: traceValFn :: (a -> b) -> a -> a Example: traceValFn (v: "mystring ${v}") "foo" trace: mystring foo => "foo" */ traceValFn = # Function to apply f: # Value to trace and return x: trace (f x) x; /* Trace the supplied value and return it. Type: traceVal :: a -> a Example: traceVal 42 # trace: 42 => 42 */ traceVal = traceValFn id; /* `builtins.trace`, but the value is `builtins.deepSeq`ed first. Type: traceSeq :: a -> b -> b Example: trace { a.b.c = 3; } null trace: { a = ; } => null traceSeq { a.b.c = 3; } null trace: { a = { b = { c = 3; }; }; } => null */ traceSeq = # The value to trace x: # The value to return y: trace (builtins.deepSeq x x) y; /* Like `traceSeq`, but only evaluate down to depth n. This is very useful because lots of `traceSeq` usages lead to an infinite recursion. Example: traceSeqN 2 { a.b.c = 3; } null trace: { a = { b = {…}; }; } => null */ traceSeqN = depth: x: y: let snip = v: if isList v then noQuotes "[…]" v else if isAttrs v then noQuotes "{…}" v else v; noQuotes = str: v: { __pretty = const str; val = v; }; modify = n: fn: v: if (n == 0) then fn v else if isList v then map (modify (n - 1) fn) v else if isAttrs v then mapAttrs (const (modify (n - 1) fn)) v else v; in trace (generators.toPretty { allowPrettyValues = true; } (modify depth snip x)) y; /* A combination of `traceVal` and `traceSeq` that applies a provided function to the value to be traced after `deepSeq`ing it. */ traceValSeqFn = # Function to apply f: # Value to trace v: traceValFn f (builtins.deepSeq v v); /* A combination of `traceVal` and `traceSeq`. */ traceValSeq = traceValSeqFn id; /* A combination of `traceVal` and `traceSeqN` that applies a provided function to the value to be traced. */ traceValSeqNFn = # Function to apply f: depth: # Value to trace v: traceSeqN depth (f v) v; /* A combination of `traceVal` and `traceSeqN`. */ traceValSeqN = traceValSeqNFn id; /* Trace the input and output of a function `f` named `name`, both down to `depth`. This is useful for adding around a function call, to see the before/after of values as they are transformed. Example: traceFnSeqN 2 "id" (x: x) { a.b.c = 3; } trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; } => { a.b.c = 3; } */ traceFnSeqN = depth: name: f: v: let res = f v; in lib.traceSeqN (depth + 1) { fn = name; from = v; to = res; } res; # -- TESTING -- /* Evaluate a set of tests. A test is an attribute set `{expr, expected}`, denoting an expression and its expected result. The result is a list of failed tests, each represented as `{name, expected, actual}`, denoting the attribute name of the failing test and its expected and actual results. Used for regression testing of the functions in lib; see tests.nix for an example. Only tests having names starting with "test" are run. Add attr { tests = ["testName"]; } to run these tests only. */ runTests = # Tests to run tests: concatLists (attrValues (mapAttrs (name: test: let testsToRun = if tests ? tests then tests.tests else []; in if (substring 0 4 name == "test" || elem name testsToRun) && ((testsToRun == []) || elem name tests.tests) && (test.expr != test.expected) then [ { inherit name; expected = test.expected; result = test.expr; } ] else [] ) tests)); /* Create a test assuming that list elements are `true`. Example: { testX = allTrue [ true ]; } */ testAllTrue = expr: { inherit expr; expected = map (x: true) expr; }; # -- DEPRECATED -- traceShowVal = x: trace (showVal x) x; traceShowValMarked = str: x: trace (str + showVal x) x; attrNamesToStr = a: trace ( "Warning: `attrNamesToStr` is deprecated " + "and will be removed in the next release. " + "Please use more specific concatenation " + "for your uses (`lib.concat(Map)StringsSep`)." ) (concatStringsSep "; " (map (x: "${x}=") (attrNames a))); showVal = trace ( "Warning: `showVal` is deprecated " + "and will be removed in the next release, " + "please use `traceSeqN`" ) (let modify = v: let pr = f: { __pretty = f; val = v; }; in if isDerivation v then pr (drv: "<δ:${drv.name}:${concatStringsSep "," (attrNames drv)}>") else if [] == v then pr (const "[]") else if isList v then pr (l: "[ ${go (head l)}, … ]") else if isAttrs v then pr (a: "{ ${ concatStringsSep ", " (attrNames a)} }") else v; go = x: generators.toPretty { allowPrettyValues = true; } (modify x); in go); traceXMLVal = x: trace ( "Warning: `traceXMLVal` is deprecated " + "and will be removed in the next release. " + "Please use `traceValFn builtins.toXML`." ) (trace (builtins.toXML x) x); traceXMLValMarked = str: x: trace ( "Warning: `traceXMLValMarked` is deprecated " + "and will be removed in the next release. " + "Please use `traceValFn (x: str + builtins.toXML x)`." ) (trace (str + builtins.toXML x) x); # trace the arguments passed to function and its result # maybe rewrite these functions in a traceCallXml like style. Then one function is enough traceCall = n: f: a: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a)); traceCall2 = n: f: a: b: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b)); traceCall3 = n: f: a: b: c: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b) (t "arg 3" c)); traceValIfNot = c: x: trace ( "Warning: `traceValIfNot` is deprecated " + "and will be removed in the next release. " + "Please use `if/then/else` and `traceValSeq 1`.") (if c x then true else traceSeq (showVal x) false); addErrorContextToAttrs = attrs: trace ( "Warning: `addErrorContextToAttrs` is deprecated " + "and will be removed in the next release. " + "Please use `builtins.addErrorContext` directly." ) (mapAttrs (a: v: addErrorContext "while evaluating ${a}" v) attrs); # example: (traceCallXml "myfun" id 3) will output something like # calling myfun arg 1: 3 result: 3 # this forces deep evaluation of all arguments and the result! # note: if result doesn't evaluate you'll get no trace at all (FIXME) # args should be printed in any case traceCallXml = a: trace ( "Warning: `traceCallXml` is deprecated " + "and will be removed in the next release. " + "Please complain if you use the function regularly." ) (if !isInt a then traceCallXml 1 "calling ${a}\n" else let nr = a; in (str: expr: if isFunction expr then (arg: traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (builtins.seq arg arg)}" (expr arg) ) else let r = builtins.seq expr expr; in trace "${str}\n result:\n${builtins.toXML r}" r )); }