Nix (language): Difference between revisions

imported>Toraritte
Remove editor section but add that page as "see also" link
imported>Toraritte
Move section "Tips & Tricks" to their own page
Line 465: Line 465:
</syntaxHighlight>
</syntaxHighlight>


== Tips & Tricks ==
== See also ==
 
* [[Nix Expression Language: Learning resources|Learning resources]]
=== Finding the definition of a function or package ===
* [[Editor Modes for Nix Files]]
 
* [[Nix Expression Language: Tips & Tricks]]
Nix is often criticized that it has no working “jump to definition”. The good news is that you can have something very similar by using a regular expression:
 
If your package is named <code>hello</code>, searching for the regular expression <code>hello\ =</code> lists all nix symbol definitions that are named this way. In many cases there’s only two or three results. You should find what you are searching for easily.
 
This trick even works for functions, because their arguments come *after* the equals sign. If you search for <code>mkDerivation\ =</code> for example, you will see that there is more than one definition of that symbol in <code>nixpkgs</code>, but at least all definitions are shown.
 
You will also notice that searching with <code>grep</code> takes quite a while on a large repository like <code>nixpkgs</code>. Tools like <code>ag</code> (The Silver Searcher) and <code>rg</code> (ripgrep) are orders of magnitudes faster (especially on modern SSDs).
 
If you don’t have a <code>nixpkgs</code> checkout at hand, you can use the repo search at [https://search.nix.gsc.io/ search.nix.gsc.io]. This even searches in all repositories of the [https://github.com/NixOS/ NixOS Github organization].
 
Another trick that only works for functions, is evaluating the function on the <code>nix repl</code>:
<pre>
nix-repl> pkgs.lib.strings.makeBinPath
«lambda @ /home/user/nixpkgs/lib/strings.nix:94:42»
</pre>
This doesn't work for non-functions or builtin functions, which show <code>«primop»</code>. It will always find the actual lambda, not an attribute that reexports a partial application, for example.
 
=== Convert a string to an (<code>import</code>-able) path ===
 
<syntaxHighlight lang=nix>
nix-repl> "/home/bernd/folder"
"/home/bernd/folder"
 
nix-repl> :t "/home/bernd/folder"
a string
 
nix-repl> builtins.toPath "/home/bernd/folder"
"/home/bernd/folder"
 
nix-repl> :t builtins.toPath "/home/bernd/folder"
a string
 
nix-repl> /. + builtins.toPath "/home/bernd/folder"
/home/bernd/folder
 
nix-repl> :t /. + builtins.toPath "/home/bernd/folder"
a path
</syntaxHighlight>
 
In contrast to what [https://nixos.org/nix/manual/#builtin-toPath <code>builtins.toPath</code>] suggests, it does not result in a path, but only checks whether the string is an absolute path, and normalizes it. The trick is to prepend the <code>/.</code> (“root”) path literal, which converts the result to a nix path (that will be copied to the store when used in a derivation).
 
Be careful not to confuse it with <code>./.</code>, which is the “directory of the current nix file” path literal, and will result in something like <code>/my/scripts/folder/home/bernd/folder</code> (provided you are in <code>/my/scripts/folder</code>).
 
This trick might be helpful in combination with [https://nixos.org/nix/manual/#builtin-getEnv <code>builtins.getEnv</code>], which returns a string (which might be a path). Be careful, depending on environment variables introduces heavy non-determinism and might lead to rebuilds!
 
If you need to '''build a path from a mix of paths and strings variables''', you can concatenate strings and paths, but you need to be careful of the evaluation order because Nix removes trailing  <code>/</code>.
 
For example if you need to concatenate <code>/data</code> with a variable call <code>my_var</code> you need to add parenthesis:
 
<syntaxHighlight lang=nix>
nix-repl> let my_var = "tmp"; in /data + "/" + my_var  # WRONG
/datatmp
 
nix-repl> let my_var = "tmp"; in /data + ("/" + my_var) # Better :)
/data/tmp
</syntaxHighlight>
 
=== Coercing a relative path with interpolated variables to an absolute path (for imports) ===
 
Sometimes you need to interpolate the value of a Nix variable into the path for an import, however these will not work:
* <code>./desktop-${desktop}.nix</code> (invalid curly, can't interpolate outside of a string in this location)
* <code>"./desktop-${desktop}.nix"</code> (nix paths must be absolute)
* <code>./. + "desktop-${desktop}.nix"</code> (missing slash at the start of the string part)
* <code>./. + "./desktop-${desktop}.nix"</code> (can't have the dot in front of that same slash)
 
Instead, use this construction:
* <code>./. + "/desktop-${desktop}.nix"</code>
 
As a fuller example:
 
<syntaxHighlight lang=nix>
let
  desktops = [ "elementary" "gnome" "plasma" "sway" ];
in
{
  config.specialisation =
    pkgs.lib.genAttrs desktops (desktop: {
      configuration = {
        boot.loader.grub.configurationName = "${desktop}";
        imports = [
          (./. + "/desktop-${desktop}.nix")
        ];
      };
    });
}
</syntaxHighlight>
 
Note that this requires <code>./.</code> to refer to the current directory, but also importantly requires the leading slash on the quoted-string-path part.
 
=== Writing update scripts / Referencing a relative path as string ===
 
Nix has relative path syntax that describes files relative to the current nix file, for example
 
<syntaxHighlight lang=nix>
with import <nixpkgs> {};
let textdata = ../foo.txt;
in runCommand "alldata" {} ''
  echo "=this is a header=" >> $out
  cat ${textdata} >> $out
''
</syntaxHighlight>
 
If the file <code>../foo.txt</code> are needed by evaluation, it is copied to the nix store first, so the script in the resulting <code>drv</code> file looks like this:
 
<code>
"echo \"=this is a header=\" >> $out\ncat /nix/store/dcaph3ib0vq0c27bqzw2vhrakk272mga-foo.txt >> $out\n"
</code>
 
Notice the <code>/nix/store</code> path of <code>foo.txt</code>. When we build the file:
 
<pre>
$ nix-build code.nix
these derivations will be built:
  /nix/store/bfv13hxqlwll398y5vi3wn44raw48yva-alldata.drv
building '/nix/store/bfv13hxqlwll398y5vi3wn44raw48yva-alldata.drv'...
/nix/store/9fav4aw2fs8ybaj06gg6cjzz7bkqf461-alldata
 
$ cat /nix/store/9fav4aw2fs8ybaj06gg6cjzz7bkqf461-alldata
=this is a header=
this
is
some
data
</pre>
 
Now, what if we don’t want to import the data file into the store, but still reference the absolute path of that file? We use <code>toString</code>:
 
<syntaxHighlight lang=nix>
with import <nixpkgs> {};
let textdata = toString ../foo.txt;
in writeScript "update-foo.sh" ''
  echo "updating foo.txt!"
  cat "additional new data" >> ${lib.escapeShellArg textdata}
''
</syntaxHighlight>
 
In this example we use the actual absolute path of the file to write a script (notice the change from <code>runCommand</code> to <code>writeScript</code>, which are both helper functions from <code>nixpkgs</code>). This script can update the <code>foo.txt</code> file when it is run by bash:
 
<pre>
$ cat $(nix-build code.nix)
echo "updating foo.txt!"
echo "additional new data" >> '/home/philip/tmp/foo.txt'
 
$ bash $(nix-build code.nix)
updating foo.txt!
 
$ cat foo.txt
this
is
some
data
additional new data
</pre>
 
Bear in mind that this makes the absolute path vary between different systems. The users Bob and Alice are going to get different scripts, because the paths of their home folders differ: <code>/home/bob/foo.txt</code> and  <code>/home/alice/foo.txt</code>; so it’s not reproducible.
 
We can use this trick to update the sources of nix expressions (for example by generating a script which updates a json file with the software’s hashes).


[[Category:Pedias]]
[[Category:Pedias]]
[[Category:Nix Expression Language]]
[[Category:Nix Expression Language]]
== See also ==
* [[Nix Expression Language: Learning resources|Learning resources]]
* [[Editor Modes for Nix Files]]