From 6325e7ff1406f7af435042429601dc29d74cbebe Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:04:05 -0400 Subject: [PATCH 01/11] lib.extendDerivation: inline variables --- lib/customisation.nix | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index ce00e364ba76..1a2815098420 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -409,12 +409,10 @@ rec { extendDerivation = condition: passthru: drv: let - outputs = drv.outputs or [ "out" ]; - commonAttrs = drv // (listToAttrs outputsList) // { all = map (x: x.value) outputsList; } // passthru; - outputToAttrListElement = outputName: { + outputsList = map (outputName: { name = outputName; value = commonAttrs @@ -436,9 +434,7 @@ rec { # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing. overrideAttrs = f: (passthru.overrideAttrs f).${outputName}; }; - }; - - outputsList = map outputToAttrListElement outputs; + }) (drv.outputs or [ "out" ]); in commonAttrs // { From 8ccea3bf6cc28d7754f2dd7d4028b96ee0c5212c Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:11:21 -0400 Subject: [PATCH 02/11] lib.extendDerivation: avoid optionalAttrs merge --- lib/customisation.nix | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 1a2815098420..d6a1eb8d9bab 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -414,26 +414,22 @@ rec { outputsList = map (outputName: { name = outputName; - value = - commonAttrs - // { - inherit (drv.${outputName}) type outputName; - outputSpecified = true; - drvPath = - assert condition; - drv.${outputName}.drvPath; - outPath = - assert condition; - drv.${outputName}.outPath; - } - // - # TODO: give the derivation control over the outputs. - # `overrideAttrs` may not be the only attribute that needs - # updating when switching outputs. - optionalAttrs (passthru ? overrideAttrs) { - # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing. - overrideAttrs = f: (passthru.overrideAttrs f).${outputName}; - }; + value = commonAttrs // { + inherit (drv.${outputName}) type outputName; + outputSpecified = true; + drvPath = + assert condition; + drv.${outputName}.drvPath; + outPath = + assert condition; + drv.${outputName}.outPath; + # TODO: give the derivation control over the outputs. + # `overrideAttrs` may not be the only attribute that needs + # updating when switching outputs. + # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing. + ${if passthru ? overrideAttrs then "overrideAttrs" else null} = + f: (passthru.overrideAttrs f).${outputName}; + }; }) (drv.outputs or [ "out" ]); in commonAttrs From 1e022acbf5861841e47e8fbac78fb483dc488e94 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:41:33 -0400 Subject: [PATCH 03/11] lib.makeOverridable: inline several variables --- lib/customisation.nix | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index d6a1eb8d9bab..fdaf80c9b6b8 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -159,28 +159,28 @@ rec { # When f is a callable attribute set, # it may contain its own `f.override` and additional attributes. # This helper function recovers those attributes and decorate the overrider. - recoverMetadata = + decorate = + f': if isAttrs f then - fDecorated: - # Preserve additional attributes for f - f - // fDecorated - # Decorate f.override if presented - // lib.optionalAttrs (f ? override) { - override = fdrv: makeOverridable (f.override fdrv); - } + ( + fDecorated: + # Preserve additional attributes for f + f + // fDecorated + # Decorate f.override if presented + // lib.optionalAttrs (f ? override) { + override = fdrv: makeOverridable (f.override fdrv); + } + ) + (mirrorArgs f') else - id; - decorate = f': recoverMetadata (mirrorArgs f'); + mirrorArgs f'; in decorate ( origArgs: let result = f origArgs; - # Changes the original arguments with (potentially a function that returns) a set of new attributes - overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs); - # Re-call the function but with different arguments overrideArgs = mirrorArgs ( /** @@ -190,7 +190,7 @@ rec { This function was provided by `lib.makeOverridable`. */ - newArgs: makeOverridable f (overrideWith newArgs) + newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs)) ); # Change the result of the function call by applying g to it overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs; From c33811716d42e163f76f6d2b5cbe0c1c5777e40a Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:06:55 -0400 Subject: [PATCH 04/11] lib.makeOverridable: avoid optionalAttrs merge --- lib/customisation.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index fdaf80c9b6b8..f20298cb8808 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -168,8 +168,8 @@ rec { f // fDecorated # Decorate f.override if presented - // lib.optionalAttrs (f ? override) { - override = fdrv: makeOverridable (f.override fdrv); + // { + ${if f ? override then "override" else null} = fdrv: makeOverridable (f.override fdrv); } ) (mirrorArgs f') From cb1edb818340d88e7bd33f061dfa59e058199e89 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:32:52 -0400 Subject: [PATCH 05/11] lib.makeOverridable: inline helper function into its usages Also perform some manual beta reductions. --- lib/customisation.nix | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index f20298cb8808..95f1d5ebf69e 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -192,14 +192,12 @@ rec { */ newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs)) ); - # Change the result of the function call by applying g to it - overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs; in if isAttrs result then result // { override = overrideArgs; - overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv); + overrideDerivation = fdrv: makeOverridable (mirrorArgs (args: overrideDerivation (f args) fdrv)) origArgs; ${if result ? overrideAttrs then "overrideAttrs" else null} = /** Override the attributes that were passed to `mkDerivation` in order to generate this derivation. @@ -211,7 +209,7 @@ rec { */ # NOTE: part of the above documentation had to be duplicated in `mkDerivation`'s `overrideAttrs`. # design/tech debt issue: https://github.com/NixOS/nixpkgs/issues/273815 - fdrv: overrideResult (x: x.overrideAttrs fdrv); + fdrv: makeOverridable (mirrorArgs (args: (f args).overrideAttrs fdrv)) origArgs; } else if isFunction result then # Transform the result into a functor while propagating its arguments From a72cd26ceb15e1f9091a72f3edef41dcf28e3589 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:48:31 -0400 Subject: [PATCH 06/11] lib.makeOverridable: avoid some function calls --- lib/customisation.nix | 114 ++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 95f1d5ebf69e..b2fa2cdebf33 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -155,71 +155,67 @@ rec { let # Creates a functor with the same arguments as f mirrorArgs = mirrorFunctionArgs f; - # Recover overrider and additional attributes for f - # When f is a callable attribute set, - # it may contain its own `f.override` and additional attributes. - # This helper function recovers those attributes and decorate the overrider. - decorate = - f': - if isAttrs f then - ( - fDecorated: - # Preserve additional attributes for f - f - // fDecorated - # Decorate f.override if presented - // { - ${if f ? override then "override" else null} = fdrv: makeOverridable (f.override fdrv); - } - ) - (mirrorArgs f') - else - mirrorArgs f'; - in - decorate ( - origArgs: - let - result = f origArgs; - # Re-call the function but with different arguments - overrideArgs = mirrorArgs ( - /** - Change the arguments with which a certain function is called. + f' = + origArgs: + let + result = f origArgs; - In some cases, you may find a list of possible attributes to pass in this function's `__functionArgs` attribute, but it will not be complete for an original function like `args@{foo, ...}: ...`, which accepts arbitrary attributes. - - This function was provided by `lib.makeOverridable`. - */ - newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs)) - ); - in - if isAttrs result then - result - // { - override = overrideArgs; - overrideDerivation = fdrv: makeOverridable (mirrorArgs (args: overrideDerivation (f args) fdrv)) origArgs; - ${if result ? overrideAttrs then "overrideAttrs" else null} = + # Re-call the function but with different arguments + overrideArgs = mirrorArgs ( /** - Override the attributes that were passed to `mkDerivation` in order to generate this derivation. + Change the arguments with which a certain function is called. - This function is provided by `lib.makeOverridable`, and indirectly by `callPackage` among others, in order to make the combination of `override` and `overrideAttrs` work. - Specifically, it re-adds the `override` attribute to the result of `overrideAttrs`. + In some cases, you may find a list of possible attributes to pass in this function's `__functionArgs` attribute, but it will not be complete for an original function like `args@{foo, ...}: ...`, which accepts arbitrary attributes. - The real implementation of `overrideAttrs` is provided by `stdenv.mkDerivation`. + This function was provided by `lib.makeOverridable`. */ - # NOTE: part of the above documentation had to be duplicated in `mkDerivation`'s `overrideAttrs`. - # design/tech debt issue: https://github.com/NixOS/nixpkgs/issues/273815 - fdrv: makeOverridable (mirrorArgs (args: (f args).overrideAttrs fdrv)) origArgs; - } - else if isFunction result then - # Transform the result into a functor while propagating its arguments - setFunctionArgs result (functionArgs result) - // { - override = overrideArgs; - } - else - result - ); + newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs)) + ); + in + if isAttrs result then + result + // { + override = overrideArgs; + overrideDerivation = + fdrv: makeOverridable (mirrorArgs (args: overrideDerivation (f args) fdrv)) origArgs; + ${if result ? overrideAttrs then "overrideAttrs" else null} = + /** + Override the attributes that were passed to `mkDerivation` in order to generate this derivation. + + This function is provided by `lib.makeOverridable`, and indirectly by `callPackage` among others, in order to make the combination of `override` and `overrideAttrs` work. + Specifically, it re-adds the `override` attribute to the result of `overrideAttrs`. + + The real implementation of `overrideAttrs` is provided by `stdenv.mkDerivation`. + */ + # NOTE: part of the above documentation had to be duplicated in `mkDerivation`'s `overrideAttrs`. + # design/tech debt issue: https://github.com/NixOS/nixpkgs/issues/273815 + fdrv: makeOverridable (mirrorArgs (args: (f args).overrideAttrs fdrv)) origArgs; + } + else if isFunction result then + # Transform the result into a functor while propagating its arguments + setFunctionArgs result (functionArgs result) + // { + override = overrideArgs; + } + else + result; + in + # Recover overrider and additional attributes for f + # When f is a callable attribute set, + # it may contain its own `f.override` and additional attributes. + # This recovers those attributes and decorates the overrider. + if isAttrs f then + # Preserve additional attributes for f + f + // (mirrorArgs f') + # Decorate f.override if presented + // { + ${if f ? override then "override" else null} = fdrv: makeOverridable (f.override fdrv); + } + + else + mirrorArgs f'; /** Call the package function in the file `fn` with the required From 6a07020e3c1734ff6f069a8f4ff33e6a2a0aefae Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:02:43 -0400 Subject: [PATCH 07/11] lib.overrideDerivation: rewrite to not be utterly ridiculous Why are we running a `flip` on arguments we can control? Why are we using `or` on one line only to use a full if/else on the next line? Why are we merging with {}, and why have we been doing it for three years? THese are the questions I ask myself. --- lib/customisation.nix | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index b2fa2cdebf33..4fbbfec41dff 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -20,7 +20,6 @@ let take length filterAttrs - flip head pipe isDerivation @@ -98,19 +97,18 @@ rec { */ overrideDerivation = drv: f: - let - newDrv = derivation (drv.drvAttrs // (f drv)); - in - flip (extendDerivation (seq drv.drvPath true)) newDrv ( + (extendDerivation (seq drv.drvPath true)) ( { meta = drv.meta or { }; - passthru = if drv ? passthru then drv.passthru else { }; + passthru = drv.passthru or { }; } // (drv.passthru or { }) - // optionalAttrs (drv ? __spliced) { - __spliced = { } // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced); + // { + ${if drv ? __spliced then "__spliced" else null} = mapAttrs ( + _: sDrv: overrideDerivation sDrv f + ) drv.__spliced; } - ); + ) (derivation (drv.drvAttrs // (f drv))); /** `makeOverridable` takes a function from attribute set to attribute set and From caab17b274c0bf5b1763315132af91a80b106582 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:00:20 -0400 Subject: [PATCH 08/11] lib.callPackageWith: move error message variables out of happy path --- lib/customisation.nix | 90 +++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 4fbbfec41dff..2cce885dde6f 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -264,6 +264,40 @@ rec { ``` */ callPackageWith = + let + makeErrorMessage = + autoArgs: fn: args: fargs: arg: + let + # Get a list of suggested argument names for a given missing one + getSuggestions = + arg: + pipe (autoArgs // args) [ + attrNames + # Only use ones that are at most 2 edits away. While mork would work, + # levenshteinAtMost is only fast for 2 or less. + (filter (levenshteinAtMost 2 arg)) + # Put strings with shorter distance first + (sortOn (levenshtein arg)) + # Only take the first couple results + (take 3) + # Quote all entries + (map (x: "\"" + x + "\"")) + ]; + + prettySuggestions = + suggestions: + if suggestions == [ ] then + "" + else if length suggestions == 1 then + ", did you mean ${elemAt suggestions 0}?" + else + ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?"; + + loc = unsafeGetAttrPos arg fargs; + loc' = if loc != null then loc.file + ":" + toString loc.line else ""; + in + "lib.customisation.callPackageWith: Function called without required argument \"${arg}\" at ${loc'}${prettySuggestions (getSuggestions arg)}"; + in autoArgs: fn: args: let f = if isFunction fn then fn else import fn; @@ -277,58 +311,20 @@ rec { # wouldn't be passed to it missingArgs = # Filter out arguments that have a default value - ( - filterAttrs (name: value: !value) - # Filter out arguments that would be passed - (removeAttrs fargs (attrNames allArgs)) - ); - - # Get a list of suggested argument names for a given missing one - getSuggestions = - arg: - pipe (autoArgs // args) [ - attrNames - # Only use ones that are at most 2 edits away. While mork would work, - # levenshteinAtMost is only fast for 2 or less. - (filter (levenshteinAtMost 2 arg)) - # Put strings with shorter distance first - (sortOn (levenshtein arg)) - # Only take the first couple results - (take 3) - # Quote all entries - (map (x: "\"" + x + "\"")) - ]; - - prettySuggestions = - suggestions: - if suggestions == [ ] then - "" - else if length suggestions == 1 then - ", did you mean ${elemAt suggestions 0}?" - else - ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?"; - - errorForArg = - arg: - let - loc = unsafeGetAttrPos arg fargs; - loc' = if loc != null then loc.file + ":" + toString loc.line else ""; - in - "Function called without required argument \"${arg}\" at " - + "${loc'}${prettySuggestions (getSuggestions arg)}"; - - # Only show the error for the first missing argument - error = errorForArg (head (attrNames missingArgs)); + filterAttrs (name: value: !value) + # Filter out arguments that would be passed + (removeAttrs fargs (attrNames allArgs)); in if missingArgs == { } then makeOverridable f allArgs - # This needs to be an abort so it can't be caught with `builtins.tryEval`, - # which is used by nix-env and ofborg to filter out packages that don't evaluate. - # This way we're forced to fix such errors in Nixpkgs, - # which is especially relevant with allowAliases = false else - abort "lib.customisation.callPackageWith: ${error}"; + # Only show the error for the first missing argument + # This needs to be an abort so it can't be caught with `builtins.tryEval`, + # which is used by nix-env and ofborg to filter out packages that don't evaluate. + # This way we're forced to fix such errors in Nixpkgs, + # which is especially relevant with allowAliases = false + abort (makeErrorMessage autoArgs fn args fargs (head (attrNames missingArgs))); /** Like `callPackage`, but for a function that returns an attribute From 54cbe35bf9b3513d5789c76a9be9a448bed24d2c Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:33:31 -0400 Subject: [PATCH 09/11] lib.callPackageWith: use custom version of filterAttrs Avoids a double-negation and passing the name every time. --- lib/customisation.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 2cce885dde6f..9ce5f1eece3e 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -19,7 +19,6 @@ let sortOn take length - filterAttrs head pipe isDerivation @@ -265,6 +264,7 @@ rec { */ callPackageWith = let + filterTrueAttrs = set: removeAttrs set (filter (name: set.${name}) (attrNames set)); makeErrorMessage = autoArgs: fn: args: fargs: arg: let @@ -311,7 +311,7 @@ rec { # wouldn't be passed to it missingArgs = # Filter out arguments that have a default value - filterAttrs (name: value: !value) + filterTrueAttrs # Filter out arguments that would be passed (removeAttrs fargs (attrNames allArgs)); From efd9c79a80ce8e4596bd60f72537f2c65a87b6fe Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:12:28 -0400 Subject: [PATCH 10/11] lib.callPackageWith: filter names instead of attributes --- lib/customisation.nix | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 9ce5f1eece3e..2801dafd59f2 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -264,7 +264,6 @@ rec { */ callPackageWith = let - filterTrueAttrs = set: removeAttrs set (filter (name: set.${name}) (attrNames set)); makeErrorMessage = autoArgs: fn: args: fargs: arg: let @@ -309,14 +308,14 @@ rec { # a list of argument names that the function requires, but # wouldn't be passed to it - missingArgs = + missingNames = # Filter out arguments that have a default value - filterTrueAttrs + filter (name: !fargs.${name}) # Filter out arguments that would be passed - (removeAttrs fargs (attrNames allArgs)); + (attrNames (removeAttrs fargs (attrNames allArgs))); in - if missingArgs == { } then + if missingNames == [ ] then makeOverridable f allArgs else # Only show the error for the first missing argument @@ -324,7 +323,7 @@ rec { # which is used by nix-env and ofborg to filter out packages that don't evaluate. # This way we're forced to fix such errors in Nixpkgs, # which is especially relevant with allowAliases = false - abort (makeErrorMessage autoArgs fn args fargs (head (attrNames missingArgs))); + abort (makeErrorMessage autoArgs fn args fargs (head missingNames)); /** Like `callPackage`, but for a function that returns an attribute From 0b29c3289edeb4730cb4b067edf03361cd8c9654 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Fri, 1 May 2026 10:55:14 -0400 Subject: [PATCH 11/11] lib.callPackageWith: check if args are already empty before handling defaults --- lib/customisation.nix | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index 2801dafd59f2..e9e88aff7cb5 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -6,6 +6,8 @@ let unsafeGetAttrPos ; inherit (lib) + all + attrValues functionArgs isFunction mirrorFunctionArgs @@ -265,8 +267,14 @@ rec { callPackageWith = let makeErrorMessage = - autoArgs: fn: args: fargs: arg: + autoArgs: fn: args: fargs: unpassedArgs: let + # The first missing arg + arg = head ( + # Filter out the default args. We did a similar computation in the + # happy path, but we're okay recomputing it in an error case + filter (name: !fargs.${name}) (attrNames unpassedArgs) + ); # Get a list of suggested argument names for a given missing one getSuggestions = arg: @@ -306,16 +314,12 @@ rec { # This includes automatic ones and ones passed explicitly allArgs = intersectAttrs fargs autoArgs // args; - # a list of argument names that the function requires, but - # wouldn't be passed to it - missingNames = - # Filter out arguments that have a default value - filter (name: !fargs.${name}) - # Filter out arguments that would be passed - (attrNames (removeAttrs fargs (attrNames allArgs))); + # arguments that weren't passed automatically to the function + unpassedArgs = removeAttrs fargs (attrNames allArgs); in - if missingNames == [ ] then + # if nonempty, check if the function has defaults for those other args + if unpassedArgs == { } || all (value: value) (attrValues unpassedArgs) then makeOverridable f allArgs else # Only show the error for the first missing argument @@ -323,7 +327,7 @@ rec { # which is used by nix-env and ofborg to filter out packages that don't evaluate. # This way we're forced to fix such errors in Nixpkgs, # which is especially relevant with allowAliases = false - abort (makeErrorMessage autoArgs fn args fargs (head missingNames)); + abort (makeErrorMessage autoArgs fn args fargs unpassedArgs); /** Like `callPackage`, but for a function that returns an attribute