snix/nix/readTree/README.md
vkryachko a04c73ca83 feat(readTree): Add special here argument.
It's convenient for depending on sub-tree members of the current file as
well as gives access to siblings.

Change-Id: I74234cec6566177d88d3bc8507fa3f6ec789adb8
Reviewed-on: https://cl.snix.dev/c/snix/+/30098
Reviewed-by: adis bladis <adisbladis@gmail.com>
Tested-by: besadii
2025-03-18 20:48:29 +00:00

101 lines
3.6 KiB
Markdown

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:
* 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 other than a set, else the children are *not traversed*.
* A folder can opt out from readTree completely by containing a
`.skip-tree` file. The content of the file is not read. These
folders will be missing completely from the readTree structure.
* 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.
* 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
`{depot, pkgs, lib, here, ... }:` where:
* `depot` is a fixed-point of the entire package tree (see the `default.nix`
at the root of the depot).
* `pkgs` is the nixpkgs used in the repo, see `third_party/nixpkgs`
* `lib` is essentially a shortcut to `pkgs.lib` exposed for convenience
* `here` is a special argument that points to the current location in the
tree. Useful to avoid specifying dependencies from the very top 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.