Pre-RFC: Standardization of feature parameters

The actual packages or their use don’t really matter to my argument, you could substitute foo and bar if you like.

I think we should not care about “high-level” flags such as withQt at the individual package level. Global overrides should rather be solved one layer higher by providing a mechanism which injects with{,Py}Qt[456] as the value of the global withQt.

Edit: Just had a thought: You could probably even do that using a simple overlay; defining with* as attrs of pkgs which callPackage would automatically inject.

There is a ruling about this as you cannot have “enable” options that are not boolean:

A more important point is to avoid writing a definition that makes it withPython.

Ad for uniformity, the point of the uniformity is tooling, so maybe for each step in terms of uniformity there should be a specific tooling need for this? Enforcing something that nobody considers less ugly than the alternative in the name of tooling that won’t even need that sounds unfortunate.

Yeah, you are right. I’ll have to come with convention for string/number feature parameters too, otherwise this RFC would outlaw withSpooldir parameter without proposing a replacement.

Yeah, to have withPython that would mean I want pyQt would be very unfortunate. I’ll refine the wording.

Uniformity is not only about tooling, it is also about programmer productivity and happiness. Tools are just fine with infamous PHP’s filter and map, programmers are not.

https://www.php.net/manual/en/function.array-filter.php
https://www.php.net/manual/en/function.array-map.php

On other hand, xcb C library is arguably ugly

This RFC covers quite well how boolean options ought to be named, but other types are not considered and this design might thus interfere with future work. As such I’d like this discussion to at least look into the other types as to not squash future possibilities.

If we do go ahead with this prefix-based approach, what prefixes could we use for numbers, lists, and attribute sets?

(Example of an attribute config in nixpkgs: https://github.com/nixos/nixpkgs/blob/629dd2dd4c4eb53a43316043279696edf9f597f2/pkgs/development/mobile/androidenv/emulate-app.nix#L7-L15)

A problem I also see with the other types it is that is no good way to declare, document and enforce feature parameter types, unless we resort to using the module system.


Things I’d like added to the RFC:

  • You MUST note the date / nixos version the deprecation happened in a comment, like in aliases.nix

Questions I’d like see answered in this RFC or at least explored and listed for future work:

  1. If we do go ahead with this prefix-based approach, what prefixes COULD we use for numbers, lists, and attribute sets?
  2. How to we address bitrot? Should ofborg and/or nixpkgs-review build every combination of bool flags?
  3. Which configurations should be built on hydra and added to the cache? What are the criteria for considering additional configurations to be built?
  4. How should alternative configurations be added to the cache? See What is the right way to add optional pkgs to nixos binary cache?

Updated the whole text, incorporating suggestions and clarifying different scenarios. Changelog is on the bottom.

2 Likes

While I’d like this to be possible, it’s simply not feasible to handle all of this at eval time. Packaging large, highly configurable software projects such as ffmpeg will already result in a huge derivation; adding all of those assertions on top would make it explode and we’d effectively re-implement the project’s configure script.
That’s one step too far IMHO.

Asserting mutual exclusivity should be a recommendation at best. Asserting dependencies between configuration options such as enableSSL -> withOpenSSL || withLibreSSL should not be required.

withLibreSSL -> enableSSL should not be a thing at all. You should be able to disable a feature without researching its dependencies and disabling each of those individually too.

Configuring defaults of feature flags depending on enabled dependencies is easy enough to do though.

My proposal:

{ lib
, stdenv
  # Global flags
, withOpenSSL ? false
, withLibreSSL ? false
  # Feature flags
, enableSSL ? withOpenSSL || withLibreSSL
  # Deps
, openssl
, libressl
}:

# Preferred but optional at the maintainer's discretion
assert !(withOpenSSL && withLibreSSL);

stdenv.mkDerivation {
  ...

  # Asserts above may make sure that at most one SSL implementation will be in
  # the list but if the user intentionally enables both, that's for them to deal with.
  buildInputs = lib.optionals withLibreSSL [ libressl ]
                ++ lib.optionals withOpenSSL [ openssl ];

  configureFlags = [
    (lib.enableFeature enableSSL "ssl")
  ];

  ...
}

I think for the build dependencies it is also «intended to be optional».

Maybe even say «only one configuration», but with «SHOULD» rather than «SHALL»? We have various single-use overrides that are actually built by CI due to the corresponding rev-dep, but really shouldn’t be used outside the context where they were considered and allowed.

There are some pure-Python things in pythonPackages that also need to avoid withPython fate! The difference between qt6/winePackages and pythonPackages/sbcl.pkgs is more about project-or-ecosystem.

(There are of course interesting naming collisions lurking around, but I agree those need to be handled via manual linting suppression.)

Should «C library» be replaced with «lower-level library»? It’s not like Qt is a C library! (It would be much easier to wrap it if it were…)

I definitely agree with point of abandoning withLibreSSL -> enableSSL.

I don’t really see much problem with enableSSL -> withOpenSSL || withLibreSSL.
Alternatives are not that common.

And I really would be sad to part with !(withOpenSSL && withLibreSSL) Without it you can configure big mess.

That said, we can do alternatives completely diffently.

{ lib, stdenv, openssl, libressl, providerSSL ? null }:
assert lib.oneof providerSSL [null, openssl, libressl];

stdenv.mkDerivation {
   buildInputs = [ providerSSL ];
   configureFlags = lib.optionals (providerSSL == openssl) ["--with-openssl"]
                  ++ lib.optionals (providerSSL = libressl) ["--with-libressl"]
}

Now there is less of asserts and it scales better if there are more providers. Not sure how well derivation equality plays with CA derivations. Probably okay, CA derivation has .drv path after all.

So if I want to override openssl to use a patched version, I need to pass it twice? Sounds suboptimal, too.

For forks … maybe. When the alternatives have different header naming, most build systems are capable of surviving in presence of both.

Can you elaborate on withPython? When rules will paint me into the corner?

Vim has optional support for python scripting, withPython fits just fine.

If a package has dependency on pythonPackages.pillow specifically, withPython is really not what the flag should be called.

Right. Looks like it should be withPillow, but (4) implies withPython. I’ll come with better guideline tomorrow.

As mentioned, there are two reasons:

  • These Assertions can grow out of control. Please take a look at the ffmpeg/generic.nix to get an idea of how many configuration options decently complex software can have. As the maintainer of ffmpeg, I can tell you that I won’t be figuring out all of the mutually exclusive options; it’s simply not feasible.
  • This logic already exists/should exist: You’d effectively be re-implementing the configure script.

Additionally, I think that, while we should provide reasonable defaults, if the user whishes to meddle with the package at this level, it’s up to them to provide a set of options that actually make sense together.

The main problem is that this is often simply not how the packages’ configure scripts work. It may only have the --with-ssl flag which then looks for either openssl or libressl internally.

Even if we had a choice between these two, you would once again lose the ability to simple turn an undesired feature off. You would have to look into every possible provider of the feature again and turn them all off in order to turn off one feature. That’s not good UX IMHO.

1 Like

Even without forks, say you have --with-gtk3, --with-qt. So uses passes withGTK3 = true, withQt = true and get something that uses whatever happened to be last in the configureFlags list.

No, that is not the plan. You would just .override { providerSSL = null; }.

I think we should explicitly accept that stupid configurations will be possible and that we should not put significant effort towards actively preventing the user from shooting themselves in the foot. If that’s what they want to do, that’s on them.

With a good build script, you can actually get both versions in the output (and then use one under Gnome and the other under KDE). And no, maintainer has the idea how to build in general, but a user of a specific weitd combo will know better how upstream handles that specific weird combo.

1 Like