Skip to content

lib.makeAlias: init with by-name support#442066

Closed
wolfgangwalther wants to merge 6 commits intoNixOS:masterfrom
wolfgangwalther:aliases-structured
Closed

lib.makeAlias: init with by-name support#442066
wolfgangwalther wants to merge 6 commits intoNixOS:masterfrom
wolfgangwalther:aliases-structured

Conversation

@wolfgangwalther
Copy link
Contributor

@wolfgangwalther wolfgangwalther commented Sep 11, 2025

WIP for "structured aliases in by-name".

Features:

  • Aliases are managed via by-name/.
  • Aliases are release-bound, not date-bound.
  • Conversion to throw / removal is forced via CI.
  • Throwing aliases can be filtered out with x.type != "error" or lib.isDerivation x
  • callPackage prevents alias arguments.
  • lib.meta.availableOn ... pkgs.<alias> fails, too, if somebody works around the callPackage limitation.
  • Should support non-by-name package sets (not tested, yet)

The first commit converts some aliases - two of these fail CI immediately, The last two commits fix that.

TODO:

  • Improve the API / custom messages / default messages etc.
  • Documentation
  • Script for conversion / removal of aliases
  • Implement example for non-by-name package set
  • Performance testing / optimization

The design of "by-name returns an attrset" is extensible. I'm thinking we could represent package sets in by-name in the future with something like type = "scope" or so - working towards unified APIs / tools / design of package sets.

Supersedes #441492. More discussion in #441108.

WDYT, @emilazy?

Things done

  • Built on platform:
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Tested, as applicable:
  • Ran nixpkgs-review on this PR. See nixpkgs-review usage.
  • Tested basic functionality of all binary files, usually in ./result/bin/.
  • Nixpkgs Release Notes
    • Package update: when the change is major or breaking.
  • NixOS Release Notes
    • Module addition: when adding a new NixOS module.
    • Module update: when the change is significant.
  • Fits CONTRIBUTING.md, pkgs/README.md, maintainers/README.md and other READMEs.

Add a 👍 reaction to pull requests you find important.

@nixpkgs-ci nixpkgs-ci bot added 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 6.topic: lib The Nixpkgs function library labels Sep 11, 2025
@emilazy
Copy link
Member

emilazy commented Sep 11, 2025

Thanks, I will take a look at this soon. I expect to have many opinions :)

@acid-bong
Copy link
Contributor

Looks really promising

@wolfgangwalther
Copy link
Contributor Author

Latest push makes performance (cpuTime) of the "alias in arguments" check for callPackage much better.

This allows adding more error conditions easily.
This is not a scope, so the callPackage protection doesn't apply. Also,
since these aliases are defined in the `linuxKernel.kernels` attribute
set and not at the top-level, the inherited top-level aliases are not
protected against inclusion in callPackage either. For this to work,
these aliases would just need to be moved to the top-level instead.

However, evaluation with and without config.allowAliases will be the
same here, providing the same safety against actually using these
attributes. Also, of course, they have metadata.

This is mostly to show how a package set could use `lib.mkAlias` - it
will work the same in a proper scope.
@wolfgangwalther
Copy link
Contributor Author

Also added an example how to use lib.makeAlias without by-name, for linuxKernel.kernels.

Copy link
Member

@emilazy emilazy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you know, I am very much in favour of the general approach :) This doesn’t quite look like what I hope for yet, but I am optimistic we can get there.

It would be nice to see pkgs/test/default.nix adapted for this as an example of how you can consume the structured information here.

Comment on lines +327 to +331
# These need to be abort so they can't be caught with `builtins.tryEval`,
# which is used by nix-env and CI to filter out packages that don't evaluate.
# This way we're forced to fix such errors in Nixpkgs.
if aliasArgs != { } then
abort "lib.customisation.callPackageWith: ${aliasError}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we made aliases become throws when !config.allowAliases, would we need this? i.e., they would be filtered out by CI, but would it also just filter out packages that use those aliases? (Just thinking about if we can minimize the number of places that have to know about aliases, for both cleanliness and performance.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still be possible to add aliases to the package arguments, if eval doesn't hit them.

We had this case for python3Packages, where this was already tried - somone tried to somehow override python3Packages in the python scope with aliases turned on or so, to prevent this from being used - but it didn't work, because Ci is swallowing those errors.

This could be hacked by putting abort in that place, I guess - but here I just demonstrated how we could use the structured alias information to implement such a check. This has a tiny effect on performance, so we can also entirely leave this part out, if we want to.

else
mapAttrs mkAttrOverridable pkgs;

makeAlias =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We should call this mkAlias for consistency with mkDerivation, I think.

Also, I feel that “alias” may be a confusing word in general. llvmPackages = llvmPackages_21; is an “alias”, but it’s distinct from both the end‐user aliases.nix aliases we have for convenience (like llvmPackages_latest), temporary compatibility aliases, and from removed‐package throws. I do not know what a better word would be; maybe having helper functions will reduce the need here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I started in customisation.nix, I had makeOverridablenext door, so... but yeah, I'm fine with mkX, too.

{
type,
package ? null,
reason ? null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to support multiple reasons, if we can ensure the interface remains convenient. I also think that we should ensure the reasons are structured by default rather than free‐form strings, because our existing throws are wildly inconsistent, and it’s much more convenient to write unmaintained than unmaintained in Nixpkgs but can give the wrong idea about upstream status.

Vague draft: reason.broken = true; reason.unmaintained = true; → “it was broken and unmaintained in Nixpkgs”? And then we could also have reason.insecure = true; or reason.insecure = ["CVE1" "CVE2"]; or such, and of course reason.custom = "I just really don’t like it";.

reason ? null,
release ? null,
}:
assert type == "alias";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like busywork to have to set this? It should be relatively low‐ceremony to stub out a package. lib.optionalAttrs (!config.allowAliases) is already annoying.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's the part that we need to keep the typing a bit stronger than freeform. If we ever want to extend this to other things like scopes or so, we better have type here, so we're not "an attrset means alias" deadend.

self.${package}
else if release != currentRelease then
if package == null then
abort "Throw for '${name}' was added in ${release}. The alias must now be removed."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A warning seems like it may be better? Then you can scoop them all up at once, and perhaps do something with unsafeGetAttrPos to list all the files that need changing.

Comment on lines +1 to +5
{
type = "alias";
release = "25.11";
package = "opentimelineio";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah… so the reason for the mandatory type = "alias"; is that we don’t have a wrapper function here at all.

But this is no good: you can have fooFull replaced with foo.override { withStuff = true; } and this does happen.

I know this ties in to supporting more non‐package stuff in by-name, but I think this would at least have to be package = { opentimelineio }: opentimelineio; or something. (Also, we should probably call it target or something rather than package, because we can have aliases for package sets too.)

Is the idea that this makes .override work properly, compared to { mkAlias, opentimelineio }: mkAlias { release = "25.11"; target = opentimelineio; }? Or is there a more fundamental reason that won’t work with this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are a couple of things going on here:

  • We can't callPackage, because then the "argument is an alias" check won't work - it hits infinite recursion, because we have no way of telling whether something is an alias before we do callPackage. If we're happy to give up on that check, which I am not sold on, yet, we can probably also put this behind callPackage.
  • If we wanted to have overrides, we could still have this as an attrset, but have a function inside, like override = { foo, bar }: bar.override { foo } or whatever (don't worry the details, just meant to say that we can have a function in the attrset, too)
  • Yes, this will cause fewer complications with .override if we want overriden aliases to be done via this system as well.

type,
package ? null,
reason ? null,
release ? null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a mild preference for since over release here – it seems less ambiguous that it’s when the alias was added, rather than when it’s due for complete removal.

pgroonga = throw "'pgroonga' has been removed. Use 'postgresqlPackages.pgroonga' instead."; # Added 2025-07-19
pgtap = throw "'pgtap' has been removed. Use 'postgresqlPackages.pgtap' instead."; # Added 2025-07-19
plv8 = throw "'plv8' has been removed. Use 'postgresqlPackages.plv8' instead."; # Added 2025-07-19
postcss-cli = throw "postcss-cli has been removed because it was broken"; # added 2025-03-24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that this was removed in this PR without replacement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because it's exactly the case that would hit abort. The throw as added in 2025-03, so needs to be removed in 25.11 - so right now. This is done in separate commits, it is first moved to an alias, which is then confirmed to hit the abort - and then later removed in a second commit to clean that up. This shows how this workflow would work at branch-off.

# and ideally https://github.com/NixOS/nix/pull/8895
self: super:
{
_isAlias = name: packages.${name}.type or null == "alias";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use double underscores for this, to match __spliced and the like.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think double underscores are wrong, my understanding is that double underscores mean "internal to Nix", while single underscore means "internal to Nixpkgs". __spliced is just.. bad.

};
};
in
(lib.mapAttrs (lib.makeAlias kernels) aliases) // { _isAlias = name: aliases ? ${name}; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yuck. This is too much ceremony – I think we have to find something better here. Whether that’s just defining these under an __aliases sub‐attribute‐set, or having these in line as mkAlias calls adjacent to the regular packages, or whatever. (I haven’t reviewed the implementation strategy enough to predict the issues these will cause, but I think this is no good, and I believe we can do better by making our scope creation functions etc. smarter.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am not happy with this, yet, either. I added this to have a look at how this would look like... but yeah, don't like it, yet.

My current idea would be: These kinds of aliases don't need to support non by-name structures at all. Let's implement them with by-name in mind, and then work on migrating everything to by-name. This renders this case void.

Copy link
Contributor Author

@wolfgangwalther wolfgangwalther left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your feedback, so far.

Comment on lines +327 to +331
# These need to be abort so they can't be caught with `builtins.tryEval`,
# which is used by nix-env and CI to filter out packages that don't evaluate.
# This way we're forced to fix such errors in Nixpkgs.
if aliasArgs != { } then
abort "lib.customisation.callPackageWith: ${aliasError}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still be possible to add aliases to the package arguments, if eval doesn't hit them.

We had this case for python3Packages, where this was already tried - somone tried to somehow override python3Packages in the python scope with aliases turned on or so, to prevent this from being used - but it didn't work, because Ci is swallowing those errors.

This could be hacked by putting abort in that place, I guess - but here I just demonstrated how we could use the structured alias information to implement such a check. This has a tiny effect on performance, so we can also entirely leave this part out, if we want to.

else
mapAttrs mkAttrOverridable pkgs;

makeAlias =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I started in customisation.nix, I had makeOverridablenext door, so... but yeah, I'm fine with mkX, too.

reason ? null,
release ? null,
}:
assert type == "alias";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's the part that we need to keep the typing a bit stronger than freeform. If we ever want to extend this to other things like scopes or so, we better have type here, so we're not "an attrset means alias" deadend.

Comment on lines +398 to +399
else
abort "Warning for '${name}' was added in ${release}. The alias must now be converted to a throw."
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consider Nixpkgs versions mostly freeform, so we can only say "still matches the current release" or "was added in a previous release".

Now, if we don't want to stick with these forever, then we need two points where things change:

  • One from alias+warning to error
  • One removing the error

If we only have one piece of information, we can't identify both.

To be able to identify both, we must put "structure" into the Nixpkgs version, too. Aka we must give it a semantic meaning, saying "25.11" is "25.05 + 1" and "26.05" is "25.05 + 2". We can do that, but I'm not sure if and how, yet.

abort "Warning for '${name}' was added in ${release}. The alias must now be converted to a throw."
else if package == null || !self.config.allowAliases then
{
type = "error";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to say we already have type = "derivation", so let's stick to that?

Aka we never want something like a derivation with a different _type. So arguably _type would weaken the typing.

else if package == null || !self.config.allowAliases then
{
type = "error";
meta = throw "'${name}' was removed because it was ${reason}.";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta is specifically for lib.meta.availableOn to fail.

(we should probably also add __toString, just to make sure)

Comment on lines +1 to +5
{
type = "alias";
release = "25.11";
package = "opentimelineio";
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are a couple of things going on here:

  • We can't callPackage, because then the "argument is an alias" check won't work - it hits infinite recursion, because we have no way of telling whether something is an alias before we do callPackage. If we're happy to give up on that check, which I am not sold on, yet, we can probably also put this behind callPackage.
  • If we wanted to have overrides, we could still have this as an attrset, but have a function inside, like override = { foo, bar }: bar.override { foo } or whatever (don't worry the details, just meant to say that we can have a function in the attrset, too)
  • Yes, this will cause fewer complications with .override if we want overriden aliases to be done via this system as well.

pgroonga = throw "'pgroonga' has been removed. Use 'postgresqlPackages.pgroonga' instead."; # Added 2025-07-19
pgtap = throw "'pgtap' has been removed. Use 'postgresqlPackages.pgtap' instead."; # Added 2025-07-19
plv8 = throw "'plv8' has been removed. Use 'postgresqlPackages.plv8' instead."; # Added 2025-07-19
postcss-cli = throw "postcss-cli has been removed because it was broken"; # added 2025-03-24
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because it's exactly the case that would hit abort. The throw as added in 2025-03, so needs to be removed in 25.11 - so right now. This is done in separate commits, it is first moved to an alias, which is then confirmed to hit the abort - and then later removed in a second commit to clean that up. This shows how this workflow would work at branch-off.

# and ideally https://github.com/NixOS/nix/pull/8895
self: super:
{
_isAlias = name: packages.${name}.type or null == "alias";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think double underscores are wrong, my understanding is that double underscores mean "internal to Nix", while single underscore means "internal to Nixpkgs". __spliced is just.. bad.

};
};
in
(lib.mapAttrs (lib.makeAlias kernels) aliases) // { _isAlias = name: aliases ? ${name}; }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am not happy with this, yet, either. I added this to have a look at how this would look like... but yeah, don't like it, yet.

My current idea would be: These kinds of aliases don't need to support non by-name structures at all. Let's implement them with by-name in mind, and then work on migrating everything to by-name. This renders this case void.

@wolfgangwalther
Copy link
Contributor Author

Not going to pursue this anymore myself.

@wolfgangwalther wolfgangwalther deleted the aliases-structured branch December 10, 2025 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.status: merge conflict This PR has merge conflicts with the target branch 6.topic: lib The Nixpkgs function library 10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants