From d260a4d01f33c6ef5a4fb2dd1d752418dcfa9a70 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 31 Oct 2025 20:36:20 +0000 Subject: [PATCH 1/2] Fix `warnAlias` mangling attrsets --- pkgs/top-level/aliases.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index dda5476de33bf..3f51cba974550 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -225,7 +225,7 @@ let if lib.isDerivation v then lib.warnOnInstantiate msg v else if lib.isAttrs v then - lib.mapAttrs (lib.warn msg) v + lib.mapAttrs (_: lib.warn msg) v else if lib.isFunction v then arg: lib.warn msg (v arg) else if lib.isList v then From 1d20296fc17924d5f9d81ecc31c2fabc227de39a Mon Sep 17 00:00:00 2001 From: PerchunPak Date: Sat, 1 Nov 2025 22:45:13 +0100 Subject: [PATCH 2/2] warnAlias -> lib.warnings.warnAlias `warnAlias` was added exclusively to `pkgs/top-level/aliases.nix` but I think it will be useful in stdlib. Also comes with tests! --- lib/README.md | 3 + lib/default.nix | 7 +- lib/derivations.nix | 39 ----------- lib/tests/misc.nix | 2 +- lib/tests/test-with-nix.nix | 3 + lib/tests/warnings.nix | 32 +++++++++ lib/tests/warnings.sh | 132 ++++++++++++++++++++++++++++++++++++ lib/warnings.nix | 97 ++++++++++++++++++++++++++ pkgs/top-level/aliases.nix | 22 +----- 9 files changed, 276 insertions(+), 61 deletions(-) create mode 100644 lib/tests/warnings.nix create mode 100755 lib/tests/warnings.sh create mode 100644 lib/warnings.nix diff --git a/lib/README.md b/lib/README.md index d7c757a15c8ca..a577f2f272849 100644 --- a/lib/README.md +++ b/lib/README.md @@ -131,6 +131,9 @@ tests/sources.sh # Run the lib.filesystem tests tests/filesystem.sh +# Run the lib.warnings tests +tests/warnings.sh + # Run the lib.path property tests path/tests/prop.sh diff --git a/lib/default.nix b/lib/default.nix index e10332ca58dd4..9e5ab5bbc5c73 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -84,6 +84,7 @@ let # misc asserts = callLibs ./asserts.nix; + warnings = callLibs ./warnings.nix; debug = callLibs ./debug.nix; misc = callLibs ./deprecated/misc.nix; @@ -401,7 +402,7 @@ let renameCrossIndexTo mapCrossIndex ; - inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; + inherit (self.derivations) lazyDerivation optionalDrvAttr; inherit (self.generators) mkLuaInline; inherit (self.meta) addMetaAttrs @@ -516,6 +517,10 @@ let assertMsg assertOneOf ; + inherit (self.warnings) + warnOnInstantiate + warnAlias + ; inherit (self.debug) traceIf traceVal diff --git a/lib/derivations.nix b/lib/derivations.nix index 574b01695d867..49906abec5c74 100644 --- a/lib/derivations.nix +++ b/lib/derivations.nix @@ -212,43 +212,4 @@ in ::: */ optionalDrvAttr = cond: value: if cond then value else null; - - /** - Wrap a derivation such that instantiating it produces a warning. - - All attributes will be wrapped with `lib.warn` except from `.meta`, `.name`, - and `.type` which are used by `nix search`, and `.outputName` which avoids - double warnings with `nix-instantiate` and `nix-build`. - - # Inputs - - `msg` - : The warning message to emit (via `lib.warn`). - - `drv` - : The derivation to wrap. - - # Examples - :::{.example} - ## `lib.derivations.warnOnInstantiate` usage example - - ```nix - { - myPackage = warnOnInstantiate "myPackage has been renamed to my-package" my-package; - } - ``` - - ::: - */ - warnOnInstantiate = - msg: drv: - let - drvToWrap = removeAttrs drv [ - "meta" - "name" - "type" - "outputName" - ]; - in - drv // mapAttrs (_: lib.warn msg) drvToWrap; } diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 2ec670262649d..e060c240714a3 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -6,7 +6,7 @@ which is `throw`'s and `abort`'s, without error messages. If you need to test error messages or more complex evaluations, see - `lib/tests/modules.sh`, `lib/tests/sources.sh` or `lib/tests/filesystem.sh` as examples. + `.sh` files in this directory as examples. To run these tests: diff --git a/lib/tests/test-with-nix.nix b/lib/tests/test-with-nix.nix index 4fc65010b8787..3c354be7b4320 100644 --- a/lib/tests/test-with-nix.nix +++ b/lib/tests/test-with-nix.nix @@ -63,6 +63,9 @@ pkgs.runCommand "nixpkgs-lib-tests-nix-${nix.version}" echo "Running lib/tests/network.sh" TEST_LIB=$PWD/lib bash lib/tests/network.sh + echo "Running lib/tests/warnings.sh" + TEST_LIB=$PWD/lib bash lib/tests/warnings.sh + echo "Running lib/fileset/tests.sh" TEST_LIB=$PWD/lib bash lib/fileset/tests.sh diff --git a/lib/tests/warnings.nix b/lib/tests/warnings.nix new file mode 100644 index 0000000000000..df15e3dc61896 --- /dev/null +++ b/lib/tests/warnings.nix @@ -0,0 +1,32 @@ +# Test cases for `lib.warnings.warnAlias`, see `lib/tests/warnings.sh` for more details +{ + pkgs ? import { }, +}: +let + lib = pkgs.lib; + + # renames all attr keys to xDest, e.g. drv -> drvDest + renameDestinations = + destinations: lib.mapAttrs' (name: value: lib.nameValuePair (name + "Dest") value) destinations; + mkAliases = + destinations: + # create a lib.warnAlias for each attribute + (lib.mapAttrs (name: value: lib.warnAlias (name + " alias") value) destinations) + // (renameDestinations destinations); +in +(mkAliases { + drv = pkgs.hello; + attrs = { + a = "b"; + }; + list = [ + 1 + 2 + 3 + ]; + arbitrary = "abc"; +}) +// rec { + func = (lib.warnAlias "func alias" (_: funcDest)) "123"; + funcDest = "abc"; +} diff --git a/lib/tests/warnings.sh b/lib/tests/warnings.sh new file mode 100755 index 0000000000000..69b5fd129c9d1 --- /dev/null +++ b/lib/tests/warnings.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +# Tests lib/warnings.nix +# Run: +# [nixpkgs]$ lib/tests/warnings.sh +# or: +# [nixpkgs]$ nix-build lib/tests/release.nix + +set -euo pipefail +shopt -s inherit_errexit + +die() { + printf ' test case failed: %s\n' "$1" >&2 + printf '%s\n' "${@:2}" >&2 + exit 1 +} + +if test -n "${TEST_LIB:-}"; then + NIX_PATH=nixpkgs="$(dirname "$TEST_LIB")" +else + NIX_PATH=nixpkgs="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.."; pwd)" +fi +export NIX_PATH + +PREFIX_IMPORT="(import $NIX_PATH/lib/tests/warnings.nix { })" + +firstLine() { + echo "$1" | head -n 1 +} + +# Completely tests a type for warnAlias function +testAliasCase() { + local attributeName=$1 + echo "Testing attribute '$attributeName'..." + + local eval_arg + # --eval on derivations tries to evaluate all attributes recursively, which + # always results in some kind of error, which is not what we want at all + if [ "$attributeName" != "drv" ]; then + eval_arg="1" + fi + + # Evaluate attribute and checks for warning using regex + echo " Evaluating for warning..." + if result=$( + NIX_ABORT_ON_WARN=1 \ + nix-instantiate ${eval_arg:+"--eval"} --strict \ + --expr "$PREFIX_IMPORT.$attributeName" \ + 2>&1 + ); then + die "'$attributeName' did not produce warning, but it was expected to:" "$result" + fi + + local expectedWarningRegex="^trace: evaluation warning: $attributeName alias$" + if [[ ! $(firstLine "$result") =~ $expectedWarningRegex ]]; then + die "Result is '$(firstLine "$result")', but '$expectedWarningRegex' was expected" "$result" + fi + + # Evaluate attribute for value, and check it with the value it is aliasing to + # but do not error on warning + echo " Evaluating for value..." + if ! result_attr=$( + nix-instantiate ${eval_arg:+"--eval"} --strict \ + --expr "$PREFIX_IMPORT.$attributeName" \ + 2>/dev/null + ); then + die "'$attributeName' failed to evaluate for value, but it was expected to succeed" "$result" + fi + # Evaluating the original value to check it with alias + echo " Getting expected value..." + if ! expected_attr=$( + NIX_ABORT_ON_WARN=1 \ + nix-instantiate ${eval_arg:+"--eval"} --strict \ + --expr "$PREFIX_IMPORT.${attributeName}Dest" \ + 2>/dev/null + ); then + die "'${attributeName}Dest' failed to evaluate with message, but it was expected to succeed" "$result" + fi + + if [[ "$result_attr" != "$expected_attr" ]]; then + die "'$attributeName' expected to be '$expected_attr', but it is '$result_attr'" + fi + echo " Done testing $attributeName!" +} + +expectSuccess() { + local expr=$1 + local expectedResultRegex=$2 + echo "Testing '$expr'..." + if ! result=$( + NIX_ABORT_ON_WARN=1 \ + nix-instantiate --eval --strict \ + --expr "with (import { }); $expr" \ + 2>&1 + ); then + die "'$expr' failed to evaluate, but it was expected to succeed:" "$result" + fi + if [[ ! $(firstLine "$result") =~ $expectedResultRegex ]]; then + die "Mismatched regex; expected '$expectedResultRegex' but result is:" "$result" + fi +} + +expectFailure() { + local expr=$1 + local expectedErrorRegex=$2 + echo "Testing '$expr'..." + if result=$( + NIX_ABORT_ON_WARN=1 \ + nix-instantiate --eval --strict \ + --expr "with (import { }); $expr" \ + 2>&1 + ); then + die "'$expr' evaluated successfully, but it was expected to fail:" "$result" + fi + if [[ ! $(firstLine "$result") =~ $expectedErrorRegex ]]; then + die "Mismatched regex; expected '$expectedErrorRegex' but result is:" "$result" + fi +} + +# tests for warnAlias +testAliasCase drv +testAliasCase attrs +testAliasCase func +testAliasCase list +testAliasCase arbitrary + +# tests for warnOnInstantiate +expectFailure 'lib.warnOnInstantiate "error message" hello' '^trace: evaluation warning: error message$' +expectSuccess '(lib.warnOnInstantiate "error message" hello).meta' 'description = "Program that produces a familiar, friendly greeting";' +expectSuccess '(lib.warnOnInstantiate "error message" hello).name' '^"hello-2.12.2"$' +expectSuccess '(lib.warnOnInstantiate "error message" hello).type' '^"derivation"$' +expectSuccess '(lib.warnOnInstantiate "error message" hello).outputName' '^"out"$' diff --git a/lib/warnings.nix b/lib/warnings.nix new file mode 100644 index 0000000000000..fc585252e7851 --- /dev/null +++ b/lib/warnings.nix @@ -0,0 +1,97 @@ +{ lib }: + +let + inherit (lib) + isAttrs + isDerivation + isFunction + isList + mapAttrs + removeAttrs + warn + ; +in +rec { + /** + Raise warning on an arbitrary value, when it is accessed + and alias it to something else. + + # Inputs + + `msg` + : The warning message to emit (via `lib.warn`). + + `v` + : The value to wrap. Can be derivation/attribute set/function/list. + + # Examples + :::{.example} + ## `lib.derivations.warnAlias` usage example + + ```nix + { + myFunc = warnAlias "myFunc has been renamed to my-func" my-func; + } + ``` + + ::: + */ + warnAlias = + msg: v: + if isDerivation v then + warnOnInstantiate msg v + else if isAttrs v then + mapAttrs (_: warn msg) v + else if isFunction v then + arg: warn msg (v arg) + else if isList v then + map (warn msg) v + else + # Can’t do better than this, and a `throw` would be more + # disruptive for users… + # + # `nix search` flags up warnings already, so hopefully this won’t + # make things much worse until we have proper CI for aliases, + # especially since aliases of paths and numbers are presumably + # not common. + warn msg v; + + /** + Wrap a derivation such that instantiating it produces a warning. + + All attributes will be wrapped with `lib.warn` except from `.meta`, `.name`, + and `.type` which are used by `nix search`, and `.outputName` which avoids + double warnings with `nix-instantiate` and `nix-build`. + + # Inputs + + `msg` + : The warning message to emit (via `lib.warn`). + + `drv` + : The derivation to wrap. + + # Examples + :::{.example} + ## `lib.derivations.warnOnInstantiate` usage example + + ```nix + { + myPackage = warnOnInstantiate "myPackage has been renamed to my-package" my-package; + } + ``` + + ::: + */ + warnOnInstantiate = + msg: drv: + let + drvToWrap = removeAttrs drv [ + "meta" + "name" + "type" + "outputName" + ]; + in + drv // mapAttrs (_: lib.warn msg) drvToWrap; +} diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 3f51cba974550..f57b670d96285 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -20,6 +20,8 @@ lib: self: super: with self; let + inherit (lib) warnAlias; + # Removing recurseForDerivation prevents derivations of aliased attribute set # to appear while listing all the packages available. removeRecurseForDerivations = @@ -219,26 +221,6 @@ let aliases: lib.mapAttrs (n: alias: removeRecurseForDerivations (checkInPkgs n alias)) aliases; plasma5Throws = mapAliases (lib.mapAttrs (k: _: makePlasma5Throw k) deprecatedPlasma5Packages); - - warnAlias = - msg: v: - if lib.isDerivation v then - lib.warnOnInstantiate msg v - else if lib.isAttrs v then - lib.mapAttrs (_: lib.warn msg) v - else if lib.isFunction v then - arg: lib.warn msg (v arg) - else if lib.isList v then - map (lib.warn msg) v - else - # Can’t do better than this, and a `throw` would be more - # disruptive for users… - # - # `nix search` flags up warnings already, so hopefully this won’t - # make things much worse until we have proper CI for aliases, - # especially since aliases of paths and numbers are presumably - # not common. - lib.warn msg v; in mapAliases {