Skip to content

Conversation

@bengsparks
Copy link
Contributor

@bengsparks bengsparks commented May 31, 2025

lib.getExe prefers to use meta.mainProgram over pname. Not setting meta.mainProgram leads to lib.getExe emitting a warning, stating that this "behavior is deprecated, because it leads to surprising errors when the assumption does not hold."

if [[ -z "${pname-}" ]]; then

The above snippet shows that when versionCheckProgram is not set, versionCheckHook will simply select the package's pname and ignore the meta.mainProgram entry, which is counter-intuitive, and exhibits precisely the behaviour that lib.getExe describes as "deprecated".

To remedy this, this PR introduces a new environment variable called NIX_MAIN_PROGRAM, which derives its value from the meta.mainProgram.
With the newly introduced NIX_MAIN_PROGRAM, versionCheckHook prefers meta.mainProgram over pname, but fallbacks to the latter when the former is omitted.

This PR was tested by adding a temporary opt-in flag in make-derivation.nix (credits go to @emilazy for this cool trick!), opting in to this behaviour in the hello package, and changing its pname value to something nonsensical, but the package still builds. Attempting this on the master branch causes the package to fail, despite meta.mainProgram being set to hello.

(click to open) @LordGrimmauld contributed this script to detect potential package breakage
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p bash -p jq -p lixPackageSets.latest.nix-eval-jobs

flake_path="$HOME/coding/nixpkgs"

check_drv() {
  read -r drv_nej

  # no main program set, or not depending on version check hook
  if jq -e '.meta.mainProgram == null or .meta.mainProgram == .attr or .inputDrvs == null or (.inputDrvs | keys_unsorted | map(test("version-check-hook")) | any | not)' <<< "$drv_nej" > /dev/null ; then
    return 0
  fi

  drv_path=$(jq -r '.drvPath' <<< "$drv_nej" )
  main_program=$(jq -r '.meta.mainProgram' <<< "$drv_nej" )
  drv_json=$(nix derivation show "${drv_path}^*")

  # echo "checking hook usage: $drv_path"

  # version check hook does not apply or explicit version check program is set
  if jq -e --arg main_program "$main_program" 'to_entries[] | .value.env | (.doInstallCheck != "1" or .versionCheckProgram != null or .pname == $main_program)' <<< "$drv_json" > /dev/null ; then
    return 0
  fi

  echo "found problematic hook usage: $drv_path"
}

nix-eval-jobs "$flake_path" --meta --workers 4 2> /dev/null | while read -r drv_nej; do
  check_drv <<< "$drv_nej"
done

When run against d313d94, it outputs

found problematic hook usage: /nix/store/r2abajkwmfcikjhdnmdgg38zff821za2-clash-rs-0.7.7.drv
found problematic hook usage: /nix/store/05pjqa09y3ci1wz4pc18yn2mdfmq95dm-clouddrive2-0.8.19.drv
found problematic hook usage: /nix/store/dcd2bz42k630vpnn83lwaq5j88w1hp7p-libcava-0.10.3.drv
found problematic hook usage: /nix/store/0mha517kyylapsvb8pmjc2wlv54ig0sq-picom-pijulius-8.2-unstable-2025-03-01.drv
found problematic hook usage: /nix/store/dfdhb7i0cc82zxhhk4q5db5cmm3q75j2-python3.12-mypy-boto3-builder-8.11.0.drv
found problematic hook usage: /nix/store/p0k9sijxa3n5bivl75iznbl1his48vn5-python3.13-mypy-boto3-builder-8.11.0.drv
  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • Nixpkgs 25.11 Release Notes (or backporting 24.11 and 25.05 Nixpkgs Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
  • NixOS 25.11 Release Notes (or backporting 24.11 and 25.05 NixOS Release notes)
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@github-actions github-actions bot added 6.topic: stdenv Standard environment 8.has: documentation This PR adds or changes documentation labels May 31, 2025
@bengsparks bengsparks marked this pull request as draft May 31, 2025 21:46
@bengsparks bengsparks changed the base branch from master to staging May 31, 2025 22:07
@nixpkgs-ci nixpkgs-ci bot closed this May 31, 2025
@nixpkgs-ci nixpkgs-ci bot reopened this May 31, 2025
@github-project-automation github-project-automation bot moved this to Done in Stdenv May 31, 2025
@ofborg ofborg bot added the 2.status: merge conflict This PR has merge conflicts with the target branch label May 31, 2025
@bengsparks bengsparks force-pushed the versionCheckHookTrick branch from efa5bab to 258f27f Compare May 31, 2025 22:40
Copy link
Contributor

@philiptaron philiptaron left a comment

Choose a reason for hiding this comment

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

I'm amenable to this change.

I see you've marked the PR as draft.

  1. What do you think the path to making it ready for review is?
  2. What do we need to do before merging it?
  3. Does anyone think we shouldn't do this?
  4. If we don't do this, what are some other options?

@bengsparks
Copy link
Contributor Author

bengsparks commented Jun 3, 2025

@philiptaron thanks for taking the time to review!

  1. After incorporating the suggested changes I'll need to tweak the docs to remove references to env.mainProgram, and make adjustments for pname.
    I'll be slightly changing the hook.sh to flatten the if statements (3 levels of ifs are too deep for my liking), but otherwise all is good I think.

  2. buildGraalvmNativeImage: switch to lib.extendMkDerivation #413663 fixes the eval errors stemming from buildGraalvmNativeImage; its defaulting of mainProgram = executable where executable ? args.pname breaks this PR's eval (I wonder if this is in fact an occurrence of Tracking issue: superfluous use of pname #277994?) (and if it is, it might just the kind of occurrence that this PR can remedy!) There is also the glibc eval error that can be found here, but your comment would fix this.
    We'll also need to rerun @LordGrimmauld's script and see if anything noteworthy breaks, but I am cautiously optimistic.

  3. As documented in the PR, meta.mainProgram now influences rebuilds. This is a departure from the status quo, where meta does not cause an effect on rebuilds. I don't think the ramifications of this are massive (why would one name a mainProgram that does not point to a valid program?) lib.getExe already influences rebuilds this way.
    In fact, versionCheckHook considering meta.mainProgram provides a better interface than versionCheckProgram, as this requires slightly awkward usage of ${placeholder "out"} to pick the path to the binary. Considering that meta.mainProgram already influences lib.getExe, I don't think it's an issue for this is happen. I don't think mainProgram changes very often anyhow 😄

  4. I'm out of ideas on this one 😄. I'm not aware of any alternate sources of mainProgram, and pname should not be considered due to the aforementioned issue (and the spirit of this PR!)

@bengsparks bengsparks force-pushed the versionCheckHookTrick branch from 258f27f to fd442d3 Compare June 3, 2025 23:57
@ofborg ofborg bot removed the 2.status: merge conflict This PR has merge conflicts with the target branch label Jun 4, 2025
@philiptaron
Copy link
Contributor

Merged #413663.

@bengsparks bengsparks force-pushed the versionCheckHookTrick branch from fd442d3 to e53fdd3 Compare June 11, 2025 09:14
@github-actions github-actions bot added 10.rebuild-darwin: 501+ This PR causes many rebuilds on Darwin and should normally target the staging branches. 10.rebuild-darwin: 5001+ This PR causes many rebuilds on Darwin and must target the staging branches. 10.rebuild-linux: 501+ This PR causes many rebuilds on Linux and should normally target the staging branches. 10.rebuild-linux: 5001+ This PR causes many rebuilds on Linux and must target the staging branches. labels Jun 11, 2025
@bengsparks bengsparks marked this pull request as ready for review June 11, 2025 09:24
@philiptaron philiptaron merged commit befb2be into NixOS:staging Jun 15, 2025
24 of 27 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in Stdenv Jun 15, 2025
@bengsparks bengsparks deleted the versionCheckHookTrick branch June 15, 2025 21:10
zackad added a commit to zackad/dotfiles that referenced this pull request Jul 8, 2025
@Defelo Defelo mentioned this pull request Jul 9, 2025
13 tasks
@niklaskorz niklaskorz mentioned this pull request Jul 14, 2025
13 tasks
@adisbladis
Copy link
Member

adisbladis commented Aug 3, 2025

I just spotted this for the first time in a build and find the behaviour of this really confusing.
When I saw NIX_MAIN_PROGRAM in my build environment I immediately thought it must be a bug.

I'm not a fan of introducing a new environment variable with questionable semantics: overriding a meta variable can have the unintended consequence of a rebuild.
Afaik this is not the case for anything else in nixpkgs, and is really not obvious or intuitive.
Nixpkgs is still missing a lot of meta.mainProgram entries and adding them via overrides should not mean that you're not getting binary cache hits.

I'm tempted to revert before this can make it out into a release.
At the very least I think we should revert until we can have a proper discussion around the expected semantics of meta and overrides.

@emilazy
Copy link
Member

emilazy commented Aug 4, 2025

FWIW, though I think it is correct that changing a package’s meta previously couldn’t result in rebuilds of that package – unless you count turning a throw into a derivation or vice versa as a rebuild, in which case license and so on already could (this isn’t totally academic, since it means changing a package’s licence can result in new Hydra jobs with the same costs as everything else the rebuild labels track) – in the case of meta.mainProgram specifically, the stronger property of “changing meta does not result in rebuilds of any package” already didn’t hold, because of lib.getExe; i.e. it already wasn’t safe to change the meta.mainProgram of a package with a large reverse closure.

So I think the weakening of the property here is more benign than it may appear. This might be more an argument for lifting mainProgram out of meta, though. It’s a bit tricky since it also makes sense to consume from the kinds of things that look at meta dumps.

(There are also clearly metadata‐ish things like pname that do inevitably cause rebuilds… and of course versionCheckHook defaulting to pname is a horrible hack that does need addressing, one way or the other. Using the same source of truth as lib.getExe makes sense to me for that.)

@adisbladis
Copy link
Member

...lib.getExe...

The fact that lib.getExe works as it does is not a problem. It's well documented and understood by it's consumers.

the weakening of the property

I think your point only makes sense in isolation.
This changes meta to be a derivation busting self reference. A huge semantic change that I'd like to avoid.
It sets a precedence not just for meta.mainProgram but weakens the expected properties of all of meta.

@emilazy
Copy link
Member

emilazy commented Aug 4, 2025

The fact that lib.getExe works as it does is not a problem. It's well documented and understood by it's consumers.

I personally think it’s surprising that we’d not be okay with meta causing a rebuild in the package it’s attached to, but okay with it causing rebuilds in potentially thousands of downstream packages. That seems like it eliminates the conceptual advantage of “metadata is just metadata, it doesn’t affect the ‘data plane’”, which I agree is a sensible property. So, insofar as we want to strongly enforce that property, I think that lib.getExe looking in meta is a problem.

At the same time, clearly a mainProgram declaration is both the correct thing to use both for lib.getExe and for things like versionCheckHook, and also useful for things that want to consume package metadata en masse. So I’m not sure how best to square this particular circle. The best idea I can come up with is to lift mainProgram to be a main derivation argument, make lib.getExe prefer pkg.mainProgram and versionCheckHook use $mainProgram, and then inherit it back into meta for compatibility and external consumers. What do you think of that plan?

@adisbladis
Copy link
Member

I personally think it’s surprising that we’d not be okay with meta causing a rebuild in the package it’s attached to, but okay with it causing rebuilds in potentially thousands of downstream packages

This is also not great, but at least it does what it says on the tin.
I was coming at this from the perspective of a nixpkgs user whose previous no-rebuild overrides suddenly started being heavy after a channel bump, and from that perspective I don't think getExe is problematic.
When I put on my nixpkgs developer hat it is of course an obvious downside in how getExe works.

The best idea I can come up with is to lift mainProgram to be a main derivation argument, make lib.getExe prefer pkg.mainProgram and versionCheckHook use $mainProgram, and then inherit it back into meta for compatibility and external consumers. What do you think of that plan?

Great plan that moves the option to where it actually makes semantic sense.
I'm all for it!

@RossSmyth
Copy link
Contributor

Also meta.mainProgram already can cause rebuilds of the package it is set on because of a peculiar pattern that this PR directly fixes.
rg "versionCheckProgram.*meta"

@vcunat
Copy link
Member

vcunat commented Aug 5, 2025

Yeah, but without this PR, getting rid of the getExe warning (by explicitly defining the same mainProgram) would not cause any rebuilds. Now it does.

dotlambda added a commit that referenced this pull request Oct 16, 2025
Not-cherry-picked-because: #412768 was not backported.
dotlambda added a commit that referenced this pull request Oct 16, 2025
We'd have to set `versionCheckProgram` manually.
But even then, running `bw` requires `$HOME` to be writable but we
cannot pass environment variables to the hook.

Not-cherry-picked-because: #412768 and #411609 were not backported.
dotlambda added a commit that referenced this pull request Oct 16, 2025
We'd have to set `versionCheckProgram` manually.
But even then, running `bw` requires `$HOME` to be writable but we
cannot pass environment variables to the hook.

Not-cherry-picked-because: #412768 and #411609 were not backported
dotlambda added a commit that referenced this pull request Oct 16, 2025
We'd have to set `versionCheckProgram` manually.
But even then, running `bw` requires `$HOME` to be writable but we
cannot pass environment variables to the hook.

Not-cherry-picked-because: #412768 and #411609 were not backported
@JohnRTitor JohnRTitor added the backport staging-25.05 Backport PR automatically label Oct 21, 2025
@nixpkgs-ci
Copy link
Contributor

nixpkgs-ci bot commented Oct 21, 2025

Successfully created backport PR for staging-25.05:

@github-actions github-actions bot added the 8.has: port to stable This PR already has a backport to the stable release. label Oct 21, 2025
@JohnRTitor
Copy link
Member

Without this, versionCheckHook is not respecting meta.mainProgram and thus leading to build failure in #454223 (comment)

Requesting a backport.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: stdenv Standard environment 8.has: documentation This PR adds or changes documentation 8.has: port to stable This PR already has a backport to the stable release. 10.rebuild-darwin: 501+ This PR causes many rebuilds on Darwin and should normally target the staging branches. 10.rebuild-darwin: 5001+ This PR causes many rebuilds on Darwin and must target the staging branches. 10.rebuild-linux: 501+ This PR causes many rebuilds on Linux and should normally target the staging branches. 10.rebuild-linux: 5001+ This PR causes many rebuilds on Linux and must target the staging branches. 12.approvals: 1 This PR was reviewed and approved by one person. backport staging-25.05 Backport PR automatically

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

9 participants