about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-03-16T20·17+0000
committerWilliam Carroll <wpcarro@gmail.com>2020-03-16T20·17+0000
commit862c69590015782c01c93ce68ab493f7f91c9308 (patch)
tree55e2c8012f3f050c7fd8cc21b222b6059430a32d
parenteb402bca744e07a54a420900586064a6cbc41403 (diff)
Work on "Let's Learn Nix: Dotfiles" blog post
It has been awhile since I have written a tutorial. I have spent 2-3 hours
working on this post, but I think I need to spend another 2-3 hours before I
publish it.

I expect to be able to write these posts faster as I practice.

I would like to create a few resources that I can reuse in each article for
things like:
- "Let's Learn Nix" reproducibility: Where I list all of the tutorials'
  dependencies: nix version, <nixpkgs> version, OS type and version, etc.
- Haskell type signature convention for Nix
- Ad hoc vs. declarative configuration for Nix
- Troubleshooting Nix: <nixpkgs> search, nix repl, searching the Nix codebase
-rw-r--r--blog/content/english/lets-learn-nix-dotfiles.md396
1 files changed, 394 insertions, 2 deletions
diff --git a/blog/content/english/lets-learn-nix-dotfiles.md b/blog/content/english/lets-learn-nix-dotfiles.md
index 58524901d4c2..084fb19e4406 100644
--- a/blog/content/english/lets-learn-nix-dotfiles.md
+++ b/blog/content/english/lets-learn-nix-dotfiles.md
@@ -1,9 +1,401 @@
 ---
 title: "Let's Learn Nix: Dotfiles"
 date: 2020-03-13T22:23:02Z
-draft: false
+draft: true
 ---
 
 ## Let's Learn Nix: Dotfiles
 
-Coming soon...
+### Dependencies
+
+Speaking of dependencies, here's what you should know before reading this tutorial.
+
+- Basic Nix syntax: Nix 1p
+
+What version of Nix are we using? What version of `<nixpkgs>` are we using? What
+operating system are we using? So many variables...
+
+Cartesian product of all possibilities...
+
+TODO(wpcarro): Create a graphic of the options.
+
+### The problems of dotfiles
+
+How do you manage your dependencies?
+
+You can use `stow` to install the dotfiles.
+
+### home-manager
+
+What we are going to write is most likely less preferable to the following
+alternatives:
+- using Nix home-manager
+- committing your `.gitconfig` into your
+
+In the next tutorial, we will use [home-manager][wtf-home-mgr] to replace the
+functionality that we wrote.
+
+So why bother completing this?
+
+### Let's begin
+
+Welcome to the first tutorial in the [Let's Learn Nix][wtf-lln] series. Today we
+are going to create a Nix derivation for one of your dotfiles.
+
+"Dotfiles" refers to a user's collection of configuration files. Typically these
+files look like:
+- `.vimrc`
+- `.xsessionrc`
+- `.bashrc`
+
+The leading "dot" at the beginning gives dotfiles their name.
+
+You probably have amassed a collection of dotfiles whether or not you are
+aware. For example, if you use [git][wtf-git], the file `~/.gitconfig` should
+exist on your machine. You can verify this with:
+
+```shell
+$ stat ~/.gitconfig
+```
+
+When I was first learning `git`, I learned to configure it using commands I
+found in books and tutorials that often looked like:
+
+```shell
+$ git config user.email
+```
+
+The `~/.gitconfig` file on your machine may look something like this:
+
+```.gitconfig
+[user]
+	name = John Cleese
+	email = john@flying-circus.com
+	username = jcleese
+[core]
+	editor = emacs
+[web]
+	browser = google-chrome
+[rerere]
+	enabled = 1
+	autoupdate = 1
+[push]
+	default = matching
+[color]
+	ui = auto
+[alias]
+	a = add --all
+	ai = add -i
+	b = branch
+	cl = clone
+	cp = cherry-pick
+	d = diff
+	fo = fetch origin
+	lg = log --oneline --graph --decorate
+	ps = push
+	pb = pull --rebase
+	s = status
+```
+
+As I ran increasingly more `git config` commands to configure my `git`
+preferences, the size of my `.gitconfig` increased, and the less likely I was to
+remember which options I set to which values.
+
+Thankfully a coworker at the time, Ryan ([@rschmukler][who-ryan]), told me that
+he version-controlled his `.gitconfig` file along with his other configuration
+files (e.g. `.vimrc`) in a repository he called "dotfiles".
+
+Version-controlling your dotfiles improves upon a workflow where you have a
+variety of configuration files scattered around your machine.
+
+If you look at the above `.gitconfig`, can you spot the dependencies?
+
+We explicitly depend `emacs` and `google-chrome`. We also *implicitly* depend on
+`git`: there is not much value of having a `.gitconfig` file if you also do not
+have `git` installed on your machine.
+
+Dependencies:
+- `emacs`
+- `google-chrome`
+
+Let's use Nix to generate this `.gitconfig` file. Here is what I would like our
+API to be:
+
+Let's create a file `gitconfig.nix` and build our function section-by-section:
+
+TODO(wpcarro): Link to sections here
+- options.user
+- options.core
+- options.web
+- options.rerere
+- options.push
+- options.color
+- options.alias
+
+```shell
+$ touch gitconfig.nix
+```
+
+### options.user
+
+```haskell
+AttrSet -> String
+```
+
+```nix
+user = {
+  name = "John Cleese";
+  email = "john@flying-circus.com";
+  username = "jcleese";
+};
+```
+
+```.gitconfig
+[user]
+	name = John Cleese
+	email = john@flying-circus.com
+	username = jcleese
+```
+
+### options.core
+
+```nix
+core = {
+  editor = "${pkgs.emacs}/bin/emacs";
+};
+```
+
+```.gitconfig
+[core]
+	editor = /nix/store/<hash>-emacs-<version>/bin/emacs
+```
+
+### options.web
+
+```nix
+web.browser = "${pkgs.google-chrome}/bin/google-chrome";
+```
+
+```.gitconfig
+[web]
+	browser = /nix/store/<hash>-google-chrome-<version>/bin/google-chrome
+```
+
+### options.rerere
+
+```nix
+rerere = {
+  enabled = true;
+  autoupdate = true;
+};
+```
+
+```.gitconfig
+[rerere]
+	enabled = 1
+	autoupdate = 1
+```
+
+### options.push
+
+```nix
+push.default = "matching";
+```
+
+```.gitconfig
+[push]
+	default = matching
+```
+
+### options.color
+
+```nix
+color.ui = "auto";
+```
+
+```.gitconfig
+[color]
+	ui = auto
+```
+
+We need to define a function named `gitconfig` that creates a Nix [derivation][wtf-derivation]:
+
+```nix
+# file: gitconfig.nix
+let
+  # Import the <nixpkgs> package repository.
+  pkgs = import <nixpkgs> {};
+
+  # Stringify the attribute set, `xs`, as a multilined string formatted as "<key> = <value>".
+  # See attrsets.nix for more functions that work with attribute sets.
+  encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);
+
+  # Define out function name `gitconfig` that accepts an `options` argument.
+  gitconfig = options: pkgs.stdenv.mkDerivation {
+    # The gitconfig file that Nix builds will be located /nix/store/some-hash-gitconfig.
+    name = "gitconfig";
+    src = pkgs.writeTextFile ".gitconfig" ''
+      [user]
+          name = ${options.user.name}
+          email = ${options.user.email}
+          username = ${options.user.username}
+      [core]
+          editor = ${options.core.editor}
+      [web]
+          editor = ${options.web.browser}
+      [rerere]
+          enabled = ${if options.rerere.enabled "1" else "0"}
+          autoupdate = ${if options.rerere.autoupdate "1" else "0"}
+      [push]
+          default = ${options.push.default}
+      [color]
+          ui = ${options.color.ui}
+      [alias]
+          ${encodeAttrSet options.aliases}
+    '';
+    buildPhase = ''
+      ${pkgs.coreutils}/bin/cp $src $out
+    '';
+    installPhase = ''
+      ${pkgs.coreutils}/bin/ln -s $out ~/.gitconfig
+    '';
+  };
+} in gitconfig {
+  user = {
+    name = "John Cleese";
+    email = "john@flying-circus.com";
+    username = "jcleese";
+  };
+  core = {
+    editor = "${pkgs.emacs}/bin/emacs";
+  };
+  web.browser = "${pkgs.google-chrome}/bin/google-chrome";
+  rerere = {
+    enabled = true;
+    autoupdate = true;
+  };
+  push.default = "matching";
+  color.ui = "auto";
+  aliases = {
+	a  = "add --all";
+	ai = "add -i";
+	b  = "branch";
+	cl = "clone";
+	cp = "cherry-pick";
+	d  = "diff";
+	fo = "fetch origin";
+	lg = "log --oneline --graph --decorate";
+	ps = "push";
+	pb = "pull --rebase";
+	s  = "status";
+  };
+}
+```
+
+### options.alias
+
+We want to write a function that accepts an attribute set and returns a
+string. While Nix is a dynamically typed programming language, thinking in types
+helps me clarify what I'm trying to write.
+
+```haskell
+encodeAttrSet :: AttrSet -> String
+```
+
+I prefer using a Haskell-inspired syntax for describing type signatures. Even if
+you haven't written Haskell before, you may find the syntax intuitive.
+
+Here is a non comprehensive, but demonstrative list of example type signatures:
+- `[String]`: A list of strings (i.e. `[ "cogito" "ergo" "sum" ]`)
+- `AttrSet`: A nix attribute set (i.e. `{ name = "John Cleese"; age = 80; }`).
+- `add :: Integer -> Integer -> Integer`: A function named `add` that accepts
+  two integers and returns an integer.
+
+Specifically, we want to make sure that when we call:
+
+```nix
+encodeAttrSet {
+  a = "add --all";
+  b = "branch";
+}
+```
+
+...it returns a string that looks like this:
+
+```.gitconfig
+a = "add --all"
+b = "branch"
+```
+
+
+TODO(wpcarro): @tazjin's nix-1p mentions this. Link to it.
+Nix has useful functions scattered all over the place:
+- `lib.nix`
+- `list.nix`
+- `lib.attrSet`
+
+But I cannot recall exactly which functions we will need to write
+`encodeAttrSet`. In these cases, I do the following:
+1. Run `nix repl`.
+2. Browse the Nix source code.
+
+Google "nix attribute sets" and find the Github link to `attrsets.nix`.
+
+You should consider repeating this search but instead of searching for
+"attribute sets" search for "lists" and "strings". That is how I found the
+functions needed to write `encodeAttrSet`. Let's return to our `nix repl`.
+
+Load the nixpkgs set:
+
+```nix
+nix-repl> :l <nixpkgs>
+Added 11484 variables.
+```
+
+Define a test input called `attrs`:
+
+```nix
+nix-repl> attrs = { fname = "John"; lname = "Cleese"; }
+```
+
+Map the attribute set into `[String]` using `lib.mapAttrsToList`:
+
+```nix
+nix-repl> lib.mapAttrsToList (k: v: "${k} = ${toString v}") attrs
+[ "fname = John" "lname = Cleese" ]
+```
+
+Now join the `[String]` together using `lib.concatStringsSep`:
+
+```nix
+nix-repl> lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") attrs)
+"fname = John\nlname = Cleese"
+```
+
+Now let's use this to define our function `encodeAttrSet`:
+
+```nix
+# file: gitconfig.nix
+encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);
+```
+
+### Using nixpkgs search
+
+[Nixpkgs search][wtf-nixpkgs-search].
+
+### Conclusion
+
+We learned how to help ourselves.
+
+- Where does `emacs` exist? What about `google-chrome`? [nixpkgs search][wtf-nixpkgs-search]
+- Verify that I have it? [nix REPL][using-nix-repl]
+
+We used Nix to create our first derivation.
+
+[wtf-lln]: /lets-learn-nix
+[wtf-git]: https://git-scm.com/
+[wtf-derivation]: https://nixos.org/nixos/nix-pills/our-first-derivation.html
+[wtf-nixpkgs-search]: https://nixos.org/nixos/packages.html?channel=nixos-19.09
+[using-nix-repl]: /using-the-nix-repl
+[wtf-home-mgr]: https://github.com/rycee/home-manager
+[who-ryan]: https://twitter.com/rschmukler