Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions rfcs/0192-by-name-version-pins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
feature: by-name-version-pins
start-date: 2025-11-25
author: Quantenzitrone
co-authors: (find a buddy later to help out with the RFC)
shepherd-team: (names, to be nominated and accepted by RFC steering committee)
shepherd-leader: (name to be appointed by RFC steering committee)
related-issues: https://github.com/NixOS/nixpkgs/pull/464396
---

# Summary
[summary]: #summary

Add an additional file `pins.nix` to the `pkgs/by-name` structure that allows
overriding/pinning versions of package dependencies.

# Motivation
[motivation]: #motivation

> Why are we doing this?

- The `pkgs/by-name` structure doesn't have support for overriding packages yet
leading to a lot of remaining entries in `pkgs/top-level/all-packages.nix`.
- Packages with dependency version pins often get neglected and stay on the old
version for a long time, even though it already supports the new version. By
having dependency version pins in a defined location with a defined structure,
package update bots or scripts could check if the package works without the
pin and remove it automatically on update.

> What use cases does it support?

- Pinning dependency versions of packages.

> What is the expected outcome?

- Less confusion if dependency pins should go into `all-packages.nix` or be
inlined in the `package.nix`.
- Less maintenance burden due to possible automation of pin removal.

# Detailed design
[design]: #detailed-design

In addition to `pkgs/by-name/{shard}/{pname}/package.nix` there can also be
`pkgs/by-name/{shard}/{pname}/pins.nix`. This file will have the following structure:

```nix
{
package-a_version,
package-b_version,
# ...
}:
{
package-a = package-a_version;
package-b = package-b_version;
# ...
}
```

- Every attrName in the resulting attribute set has to be a valid package
attribute name.
- Every attrValue should be the pinned version of the respectice attrName
package, this is however hard to check I think.
- Every attrName has to be a functionArg of the `package.nix`.
Comment on lines +59 to +63
Copy link

Choose a reason for hiding this comment

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

Maybe this is out of scope, but can we also pin dependencies that not just differ in their semantic versions like nodejs = nodejs_22, but have other overrides or logic?
Some example pins.nix:

{
  darwin,
  stdenv,
}@args:
{
  stdenv = if args.stdenv.hostPlatform.isDarwin then darwin.bootstrapStdenv else args.stdenv;
}
{
  luajit,
}@args:
{
  luajit = args.luajit.override { enable52Compat = true; };
}

This would be useful to keep compatibility with previous overrides of a package when removing by-name overrides.


I think you know where this is going.

Packages with a `pin.nix` will have the versions of dependencies pinned
accordingly. This can be easily achieved with:

```nix
if lib.pathExists (packageDirectory + "/pins.nix") then
let
pins = lib.removeAttrs (callPackage (packageDirectory + "/pins.nix") { }) [
"override"
"overrideDerivation"
];
in
callPackage (packageDirectory + "/package.nix") pins
else
callPackage (packageDirectory + "/package.nix") { }
```

# Examples and Interactions
[examples-and-interactions]: #examples-and-interactions

# Drawbacks
[drawbacks]: #drawbacks

Probably longer eval time, this has to be tested however.

# Alternatives
Copy link
Member

Choose a reason for hiding this comment

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

IMO versions should be a first-class feature of nixpkgs such that, to an end user, searching for a package should give you just one package with all of its versions listed as a part of it as opposed to each pinned version being its own exposed package (this argument could extend to package variants too but that goes out of scope).

This could go hand-in-hand with the current pins.nix approach too where the pins in it are not exposed at the top-level but simply as alternative versions to the existing package. As an end user, that's a far better UX than having multiple top-level packages for the same thing.

I know that this would involve significantly more work than the current proposal, but given that Nix's usability has been a concern for a long time, I'd want to push this along with this change since it might be harder down the line.

Copy link
Member

Choose a reason for hiding this comment

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

The packages will still need to have default versions, doing otherwise is a usability disaster, and for pin.nix nothing changes if sbcl_2_0_0 is replaced with sbcl.versions."2.0.0"

So the change you propose is not reasonably tied to this one.

Copy link
Member

Choose a reason for hiding this comment

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

There aren't just different .version numbers. You also have e.g. the *Full variants.

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 this adds complexity to the version unification proposal but not to its interaction with the pins!

Copy link
Member

Choose a reason for hiding this comment

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

I agree that this is not quite intersecting with the proposed change. I hoped to push this along since it's sort of related by increasing the scope of the RFC in case folks were interested. Sigh. Perhaps another day, in another RFC.

Copy link
Author

Choose a reason for hiding this comment

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

I changed the rfc text so it's more clear that it's about pinning dependency versions and not the packages themselves.
I also added pinning versions of the packages themselves under future work.

[alternatives]: #alternatives

- Keep the current situation: version pins are either in `all-packages.nix` or
directly in the `package.nix`.

- Some more generalized `overrides.nix` without the strict requirements.
- This would be less useful for automation of dependency pin removal.

- Inline dependency pins in `package.nix` as default values in the function arguments.
```nix
{
package-a_version,
package-a ? package-a_version,
# ...
}:
# ...
```
This would require a custom `callPackage` for `pkgs/by-name` that prefers default values over
values from the `pkgs`.
- Confusing for contributors if something works differently through the normal `callPackage`.

- Inline dependency pins in `package.nix` with `let in` syntax.
```nix
{
package-a_version,
# ...
}:
let
package-a = package-a_version;
in
# ...
```
- Possibly harder to automate dependency pin removal.

- Your suggestion here.

# Prior art
[prior-art]: #prior-art

- [RFC 140](https://github.com/NixOS/rfcs/blob/master/rfcs/0140-simple-package-paths.md)
lists the problem this RFC is trying to solve under "Future work".

# Unresolved questions
[unresolved]: #unresolved-questions
Copy link

Choose a reason for hiding this comment

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

When overriding a package, will it override the the arguments of pins.nix or the arguments of package.nix?
Perhaps it could override package.nix and we could have an overridePins function.


# Future work
[future]: #future-work

- pinning versions of packages, e.g. having ffmpeg_7 as a subattribute of ffmpeg
(not dependency pinning, what this rfc is about)