Warning This RFC draft is being discussed as RFC 140, please give feedback in that PR instead of this repository.
Auto-generate trivial top-level attribute definitions in pkgs/top-level/all-packages.nix
from a directory structure that matches the attribute name.
This makes it much easier to contribute new packages packages, since there's no more guessing needed as to where the package should go, both in the ad-hoc directory categories and in pkgs/top-level/all-packages.nix
.
all-packages.nix
file is too big to be displayed by GitHubnix edit -f . package-attr
works, though that's not yet stable (it relies on the nix-command
feature being enabled) and doesn't work with packages that don't set meta.position
correctly).all-packages.nix
frequently causes merge conflicts. It's a point of contention for all new packagesThis RFC establishes the standard of using pkgs/unit/${shard}/${name}
"unit" directories for the definitions of the Nix packages pkgs.${name}
in nixpkgs, where shard = toLower (substring 0 2 name)
.
All unit directories are automatically discovered and incorporated into the pkgs
set using pkgs.${name} = pkgs.callPackage pkgs/unit/${shard}/${name}/pkg-fun.nix { }
.
The following requirements will be checked by CI. This standard must be followed for newly added packages that can satisfy these requirements. A treewide migration to this standard will be performed for existing packages that can satisfy these requirements.
The pkgs/unit
directory must only contain unit directories, and only in subdirectories of the form ${shard}/${name}
.
Each unit directory must contain at least a pkg-fun.nix
file, but may contain arbitrary other files and directories.
This ensures that maintainers don't have to verify this structure manually, which is prone to mistakes.
If pkgs/unit/${shard}/${name}
exists, pkgs.${name}
must be a derivation that can be built directly with nix-build
.
This ensures that people can expect the unit directories to correspond to buildable packages and not functions like pkgs.fetchFromGitHub
or pkgs.buildRustCrate
.
Unit directories may only interact with the rest of nixpkgs via the stable pkgs.${name}
attributes, not with file references:
pkg-fun.nix
arguments injected by callPackage
.
This ensures that files in nixpkgs can be moved around without breaking this package.pkgs.${name}
.
This ensures that files within unit directories (except pkg-fun.nix
) can be freely moved and changed without breaking any other packages.The only notable exception to this rule is the pkgs/top-level/all-packages.nix
file which may reference the pkg-fun.nix
file according to the next requirement.
If pkgs/top-level/all-packages.nix
contains a definition for the attribute ${name}
and the unit directory pkgs/unit/${shard}/${name}
exists, then the attribute value must be defined as pkgs.callPackage pkgs/unit/${shard}/${name}/pkg-fun.nix args
, where args
may be a freely chosen expression.
This ensures that even if a package initially doesn't require a custom args
, if it later does, it doesn't have to be moved out of the pkgs/unit
directory to pass custom arguments.
To add a new package pkgs.foobar
to nixpkgs, one only needs to create the file pkgs/unit/fo/foobar/pkg-fun.nix
.
No need to find an appropriate category nor to modify pkgs/top-level/all-packages.nix
anymore.
With many packages, the pkgs/unit
directory may look like this:
pkgs
└── unit
├── _0
│ ├── _0verkill
│ └── _0x
┊
├── ch
│ ├── ChowPhaser
│ ├── CHOWTapeModel
│ ├── chroma
│ ┊
┊
├── t
│ └── t
┊
nix edit
and search.nixos.org are unaffected, since they rely on meta.position
to get the file to edit, which still worksgit blame
locally and on GitHub is unaffected, since it follows file renames properly.nix-build -E 'with import <nixpkgs> {}; callPackage pkgs/applications/misc/hello {}'
.
Since the path changes pkg-fun.nix
is now used, this becomes like nix-build -E 'with import <nixpkgs> {}; callPackage pkgs/unit/he/hello/pkg-fun.nix {}'
, which is harder for users.
However, calling a path like this is an anti-pattern anyways, because it doesn't use the correct nixpkgs version and it doesn't use the correct argument overrides.
The correct way of doing it was to add the package to pkgs/top-level/all-packages.nix
, then calling nix-build -A hello
.
This nix-build -E
workaround is partially motivated by the difficulty of knowing the mapping from attributes to package paths, which is what this RFC improves upon.
By teaching users that pkgs/unit/*/<name>
corresponds to nix-build -A <name>
, the need for such nix-build -E
workarounds should disappear.builtins.unsafeGetAttrPos "hello" pkgs
. Counter-arguments:
unsafe
part of the name.builtins.readDir
propagate file as a position)pkgs/unit
structurepkgs.hello
would be in pkgs/unit/hello
.
builtins.readDir
on instead of manyreadDir
this isn't much of a problemgit
slower (TODO: By how much?)substring 0 3 name
or substring 0 4 name
. This was not done because it still leads to directories in pkgs/unit
containing more than 1'000 entries, leading to the same problems.hello
is in pkgs/unit/he/ll/hello
,
if packages are less than 4 characters long, we will it out with -
, e.g. z
is in pkgs/unit/z-/--/z
.
This is not great because it's more complicated and it would improve git performance only marginally.pkgs.foobar
could be in pkgs/unit/f/foobar
initially.
But when there's more than 1'000 packages starting with f
, all packages starting with f
are distributed under 2-letter prefixes, moving foobar
to pkgs/unit/fo/foobar
.
This is not great because it's very complex to determine which directory to put a package in, making it bad for contributors.pkg-fun.nix
filenamedefault.nix
: Bad because:
all-packages.nix
.nix build -f pkgs/unit/hell/hello
equally broken regardless of file name.default.nix
frees up default.nix
for a short expression that is actually buildable, e.g. (import ../..).hello
, although at that point it might better be auto-generated or implicit in the CLIpackage.nix
/pkg.nix
: Bad, because it makes the migration to a non-function form of overridable packages harder in the future.pkgs/unit
locationunit
(at the nixpkgs root) instead of pkgs/unit
.
This is future proof in case we want to make the directory structure more general purpose, but this is out of scopepkg
, component
, part
, mod
, comp
Additionally have a backwards-compatibility layer for moved paths, such as a symlink pointing from the old to the new location, or for Nix files even a builtins.trace "deprecated" (import ../new/path)
.
The reference requirement could be removed, which would allow unit directories to reference files outside themselves, and the other way around. This is not great because it encourages the use of file paths as an API, rather than explicitly exposing functionality from Nix expressions.
We perceived some uncertainty around package variants that led us to scope these out at first, but we did not identify a real problem that would arise from allowing non-auto-called attributes to reference pkgs/unit
files. However, imposing unnecessary restrictions would be counterproductive because:
The contributor experience would suffer, because it won't be obvious to everyone whether their package is allowed to go into pkgs/unit
. This means that we'd fail to solve the goal "Which directory should my package definition go in?", leading to unnecessary requests for changes in pull requests.
Changes in dependencies can require dependents to add an override, causing packages to be moved back and forth between unit directories and the general pkgs
tree, worsening the problem as people have to decide categories again.
When lifting the restriction, the reviewers have to adapt, again leading to unnecessary requests for changes in pull requests.
We'd be protracting the migration by unnecessary gatekeeping or discovering some problem late.
That said, we did identify risks:
We might get something wrong, and while we plan to incrementally migrate Nixpkgs to this new system anyway, starting with fewer units is good.
callPackage path { }
) calls, to keep the initial change smallWe might not focus enough on the foundation, while we could more easily relax requirements later.
callPackage
calls are unlikely to cause issues that we wouldn't otherwise have.callPackage
pattern with default arguments
- While this RFC doesn't address expressions where the second
callPackage
argument isn't{}
, there is an easy way to transition to an argument of{}
: For every attribute of the formname = attrs.value;
in the argument, make sureattrs
is in the arguments of the file, then addname ? attrs.value
to the arguments. Then the expression inall-packages.nix
can too be auto-called
- Don't do this for
name = value
pairs though, that's an alias-like thing
callPackage
does not favor the default argument when both a default argument and a value in pkgs
exist. Changing the semantics of callPackage
is out of scope.
callPackage
arguments to be specified in <unit>/args.nix
The idea was to expand the auto-calling logic according to:
Unit directories are automatically discovered and transformed to a definition of the form
# If args.nix doesn't exist
pkgs.${name} = pkgs.callPackage ${unitDir}/pkg-fun.nix {}
# If args.nix does exists
pkgs.${name} = pkgs.callPackage ${unitDir}/pkg-fun.nix (import ${unitDir}/args.nix pkgs);
Pro:
Con:
args.nix
is another pattern that contributors need to learn how to use, as we have seen that it is not immediately obvious to everyone how it works.args.nix
files.All of these questions are in scope to be addressed in future discussions in the Nixpkgs Architecture Team:
pkgs.<name>
, it doesn't do anything about package sets like pkgs.python3Packages.<name>
, pkgs.haskell.packages.ghc942.<name>
, which may or may not also benefit from a similar auto-callingcallPackage
and/or apply a better solution, such as a module-like solutionwlroots = wlroots_0_14
? This goes into version resolution, a different problem to fixlibsForQt5.callPackage
? This goes into overrides, a different problem to fixjami-daemon = jami.jami-daemon
?recurseIntoAttrs
? Not single packages, package sets, another problem