about summary refs log tree commit diff
path: root/nix/readTree/README.md
blob: f8bbe2255e5ecb2cad2cded05270ed2732fab2dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
readTree
========

This is a Nix program that builds up an attribute set tree for a large
repository based on the filesystem layout.

It is in fact the tool that lays out the attribute set of this repository.

As an example, consider a root (`.`) of a repository and a layout such as:

```
.
├── third_party
│   ├── default.nix
│   └── rustpkgs
│       ├── aho-corasick.nix
│       └── serde.nix
└── tools
    ├── cheddar
    │   └── default.nix
    └── roquefort.nix
```

When `readTree` is called on that tree, it will construct an attribute set with
this shape:

```nix
{
    tools = {
        cheddar = ...;
        roquefort = ...;
    };

    third_party = {
        # the `default.nix` of this folder might have had arbitrary other
        # attributes here, such as this:
        favouriteColour = "orange";

        rustpkgs = {
            aho-corasick = ...;
            serde = ...;
        };
    };
}
```

Every imported Nix file that yields an attribute set will have a `__readTree =
true;` attribute merged into it.

## Traversal logic

`readTree` will follow any subdirectories of a tree and import all Nix files,
with some exceptions:

* A folder can declare that its children are off-limit by containing a
  `.skip-subtree` file. Since the content of the file is not checked, it can be
  useful to leave a note for a human in the file.
* If a folder contains a `default.nix` file, no *sibling* Nix files will be
  imported - however children are traversed as normal.
* If a folder contains a `default.nix` it is loaded and, if it evaluates to a
  set, *merged* with the children. If it evaluates to anything else the children
  are *not traversed*.
* The `default.nix` of the top-level folder on which readTree is
  called is **not** read to avoid infinite recursion (as, presumably,
  this file is where readTree itself is called).

Traversal is lazy, `readTree` will only build up the tree as requested. This
currently has the downside that directories with no importable files end up in
the tree as empty nodes (`{}`).

## Import structure

`readTree` is called with an argument set containing a few parameters:

* `path`: Initial path at which to start the traversal.
* `args`: Arguments to pass to all imports.
* `filter`: (optional) A function to filter the argument set on each
  import based on the location in the tree. This can be used to, for
  example, implement a "visibility" system inside of a tree.
* `scopedArgs`: (optional) An argument set that is passed to all
  imported files via `builtins.scopedImport`. This will forcefully
  override the given values in the import scope, use with care!

The package headers in this repository follow the form `{ pkgs, ... }:` where
`pkgs` is a fixed-point of the entire package tree (see the `default.nix` at the
root of the depot).

In theory `readTree` can pass arguments of different shapes, but I have found
this to be a good solution for the most part.

Note that `readTree` does not currently make functions overridable, though it is
feasible that it could do that in the future.