Conversation
With this commit, the user is forced to explicitly specify all required dependency names via `depsRequired`. `options.deps` contains generated options for all names of `depsRequired`. These options are populated with defaults from `depsFrom`, The examples under `./examples` show how this is used. The `coercedTo` type of `deps` had some weaknesses: 1. It was not possible to specify required dependencies explicitly, because it was not possible to create nested options like`options.deps.bash = mkOption ...`. 2. Because of (1.) I had to add a manual check in makeModule.nix to ensure that all required `deps` are populated by the user. Having actual options for all required dependencies allows to reduce complexity and hand the `checking` off to the module system. Also, when rendering the options for a package to a doc, all dependencies now appear as an option, which I think is great. It allows the user to see all inputs of a package without having to read the code.
roberth
left a comment
There was a problem hiding this comment.
I don't like killing the deps pattern. I think it does a pretty good job at steering towards a separation of concerns, steering package authors to a situation where all deps are overridable, potentially even improving on callPackage in this regard.
Of course it can be defeated, but package authors who never see pkgs outside of deps = { pkgs, ... }: will not think of referencing it directly. I'm also surprised to see that pkgs is even in the package module arguments. Was that needed for something? (before this PR)
| systemd | ||
| ; | ||
| # This must be complete, otherwise options would be missing from `deps`. | ||
| depsRequired = { |
There was a problem hiding this comment.
This rename seems like a step back.
| callDeps | ||
| (t.lazyAttrsOf t.raw); | ||
| # Unspecified `deps` are taken from `depsFrom`. It's a source for defaults. | ||
| depsFrom = l.mkOption { |
There was a problem hiding this comment.
Seems like it should be this then
| depsFrom = l.mkOption { | |
| defaultDeps = l.mkOption { |
| ; | ||
| # Define dependencies from the "outer world" only via `depsFrom`. | ||
| # This allows for easy overriding via `config.deps` later. | ||
| depsFrom = {inherit (pkgs) fetchurl python}; |
There was a problem hiding this comment.
Where does this pkgs come from?
The point of having a function here is to avoid bringing pkgs into the larger scope and seducing people into using it directly.
There was a problem hiding this comment.
This example is bad. depsFrom should never be specified from within the package module itself. It should be set from outside.
| deps = | ||
| l.mapAttrs | ||
| mkDepOpt | ||
| (l.filterAttrs (_: enabled: enabled) config.depsRequired); |
There was a problem hiding this comment.
makeModule implementation concerns are leaking into the general interface. That's not good.
Shouldn't this be in a makeModule module?
Should deps be a freeform module?
There was a problem hiding this comment.
Shouldn't this be in a makeModule module?
I thought explicitly specifying a list of required deps is something that we want everywhere. depsRequired seemed like the simplest way to do that. It's not supposed to be specific to makeModule.
Should deps be a freeform module?
Will this bring back the capability of specifying deps as a function?
I probably need to combine freeform and apply, I guess?
Did you try with |
Those could be understood to be flake inputs. Arguably those shouldn't be in scope though, so perhaps shadowing is a good thing. Still potentially confusing though. Especially |
Which
Not yet, but I will experiment with it now. |
I think I inferred this wrong when reading the code. Maybe got confused by what you said is a bad example. |
Which type would I pick that allows nested options? If I pick |
|
To explain my though process a bit better:One underlying question seems to be: Who gets to pick dependencies? The package maintainer, or the caller? Out in the decentralized wild, it might be more valuable to let the Therefore I thought it's a good idea to enforce an option for each dependency. Thinking about an end game where nix competes with language specific package managers, the decentralized fashion seems to be the more generic approach vs. nixpkgs is more specific. Maybe the logic of how dependencies are picked within nixpkgs, should be factored out into a separate module?
Genius! With that, I can get rid of Maybe one can get rid of A package module would then look like this: {config, lib, ...}: {
name = "test";
# all required deps must always be declared
depsRequired = {
foo = true;
bar = true;
bash = true;
};
# `deps` is only required for picking non-default versions
# This logic is specific to the repo the package resides in, but can easily
# be removed by a `mkForce`.
deps = {pkgs, ...}: {
# pick a special version for bar
bar = pkgs.bar_2;
};
} |
I don't think the change will be huge. A lot of people already live in a decentralized wild (without Nix) and they rely on distributions to provide such things as docker base images. Even a gentoo user doesn't stitch together all dependencies by hand. (I think? It's been over a decade.)
or
Not sure if going in circles, but what about I should probably merge this, shouldn't I: |
Even if you have a community maintained package set, where the community picks one Customizing dependency trees can be done by hand or by a resolver, but in any case, to support this well, the It would be nice if mixed approaches would be supported, so that users can satisfy their requirements, by taking some packages from nixpkgs and others from elsewhere (auto-generated, or different repos, etc...). Automatically. Expanding a bit on the {
depsRequired = {
foo = "^2.0.0";
bar = "*";
baz = "~2.0";
};
}If packages had that, mixing dynamic language2nix approaches with nixpkgs would be a charm. Knowing more about a package is beneficial for automation. And automation is key to scale.
depsRequired is creating options for Anyways, I might be biased by dealing with language2nix too much, and I do not want to pollute the core of drv-parts with anything that we don't agree is absolutely necessary. |
|
I now opened #11 which is a more minimal change, just allowing nested options for deps.
|
|
so this implementation / PR will be closed? |
@roberth It would be amazing if you could review this. I really want to get this right.
With this commit, the user is forced to explicitly specify all required dependency names via
depsRequired.options.depscontains generated options for all names ofdepsRequired. These options are populated with defaults fromdepsFrom, The examples under./examplesshow how this is used.The
coercedTotype ofdepshad some weaknesses:options.deps.bash = mkOption ....depsare populated by the user.Having actual options for all required dependencies allows to reduce complexity and hand the
checkingoff to the module system.Also, when rendering the options for a package to a doc, all dependencies now appear as an option, which I think is great. It allows the user to see all inputs of a package without having to read the code.
This whole
depsmechanism is generic enough to suit as a generalinputs. It probably should be renamed toinputsin the future, but I don't want to clutter this PR too much.EDIT:
options.stdenvcan now probably be moved tooptions.deps.stdenv. It is basically just a dependency of the derivation and I see no need for special treatment.