Merge pull request #320266 from doronbehar/pkg/versionInstallHook

versionCheckHook: init
This commit is contained in:
Doron Behar 2024-07-03 01:37:49 +03:00 committed by GitHub
commit 32d57df8ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 136 additions and 38 deletions

View File

@ -120,9 +120,10 @@ It has two modes:
Checks that the output from running a command contains the specified version string in it as a whole word.
Although simplistic, this test assures that the main program can run.
While there's no substitute for a real test case, it does catch dynamic linking errors and such.
It also provides some protection against accidentally building the wrong version, for example when using an "old" hash in a fixed-output derivation.
NOTE: In most cases, [`versionCheckHook`](#versioncheckhook) should be preferred, but this function is provided and documented here anyway. The motivation for adding either tests would be:
- Catch dynamic linking errors and such and missing environment variables that should be added by wrapping.
- Probable protection against accidentally building the wrong version, for example when using an "old" hash in a fixed-output derivation.
By default, the command to be run will be inferred from the given `package` attribute:
it will check `meta.mainProgram` first, and fall back to `pname` or `name`.

View File

@ -29,6 +29,7 @@ scons.section.md
tetex-tex-live.section.md
unzip.section.md
validatePkgConfig.section.md
versionCheckHook.section.md
waf.section.md
zig.section.md
xcbuild.section.md

View File

@ -0,0 +1,35 @@
# versionCheckHook {#versioncheckhook}
This hook adds a `versionCheckPhase` to the [`preInstallCheckHooks`](#ssec-installCheck-phase) that runs the main program of the derivation with a `--help` or `--version` argument, and checks that the `${version}` string is found in that output. You use it like this:
```nix
{
lib,
stdenv,
versionCheckHook,
# ...
}:
stdenv.mkDerivation (finalAttrs: {
# ...
nativeInstallCheckInputs = [
versionCheckHook
];
doInstallCheck = true;
# ...
})
```
Note that for [`buildPythonPackage`](#buildpythonpackage-function) and [`buildPythonApplication`](#buildpythonapplication-function), `doInstallCheck` is enabled by default.
It does so in a clean environment (using `env --ignore-environment`), and it checks for the `${version}` string in both the `stdout` and the `stderr` of the command. It will report to you in the build log the output it received and it will fail the build if it failed to find `${version}`.
The variables that this phase control are:
- `dontVersionCheck`: Disable adding this hook to the [`preDistPhases`](#var-stdenv-preDist). Useful if you do want to load the bash functions of the hook, but run them differently.
- `versionCheckProgram`: The full path to the program that should print the `${version}` string. Defaults roughly to `${placeholder "out"}/bin/${pname}`. Using `$out` in the value of this variable won't work, as environment variables from this variable are not expanded by the hook. Hence using `placeholder` is unavoidable.
- `versionCheckProgramArg`: The argument that needs to be passed to `versionCheckProgram`. If undefined the hook tries first `--help` and then `--version`. Examples: `version`, `-V`, `-v`.
- `preVersionCheck`: A hook to run before the check is done.
- `postVersionCheck`: A hook to run after the check is done.

View File

@ -75,40 +75,17 @@ The Nixpkgs systems for continuous integration [Hydra](https://hydra.nixos.org/)
#### Package tests {#var-passthru-tests-packages}
[]{#var-meta-tests-packages} <!-- legacy anchor -->
Tests that are part of the source package, if they run quickly, are typically executed in the [`installCheckPhase`](#var-stdenv-phases).
This phase is also suitable for performing a `--version` test for packages that support such flag.
Most programs distributed by Nixpkgs support such a `--version` flag, and successfully calling the program with that flag indicates that the package at least got compiled properly.
Besides tests provided by upstream, that you run in the [`checkPhase`](#ssec-check-phase), you may want to define tests derivations in the `passthru.tests` attribute, which won't change the build. `passthru.tests` have several advantages over running tests during any of the [standard phases](#sec-stdenv-phases):
:::{.example #ex-checking-build-installCheckPhase}
- They access the package as consumers would, independently from the environment in which it was built
- They can be run and debugged without rebuilding the package, which is useful if that takes a long time
- They don't add overhead to each build, as opposed checks added to the [`distPhase`](#ssec-distribution-phase), such as [`versionCheckHook`](#versioncheckhook).
## Checking builds with `installCheckPhase`
It is also possible to use `passthru.tests` to test the version with [`testVersion`](#tester-testVersion), but since that is pretty trivial and recommended thing to do, we recommend using [`versionCheckHook`](#versioncheckhook) for that, which has the following advantages over `passthru.tests`:
When building `git`, a rudimentary test for successful compilation would be running `git --version`:
```nix
stdenv.mkDerivation (finalAttrs: {
pname = "git";
version = "1.2.3";
# ...
doInstallCheck = true;
installCheckPhase = ''
runHook preInstallCheck
echo checking if 'git --version' mentions ${finalAttrs.version}
$out/bin/git --version | grep ${finalAttrs.version}
runHook postInstallCheck
'';
# ...
})
```
:::
However, tests that are non-trivial will better fit into `passthru.tests` because they:
- Access the package as consumers would, independently from the environment in which it was built
- Can be run and debugged without rebuilding the package, which is useful if that takes a long time
- Don't add overhad to each build, as opposed to `installCheckPhase`
It is also possible to use `passthru.tests` to test the version with [`testVersion`](#tester-testVersion).
- If the `versionCheckPhase` (the phase defined by [`versionCheckHook`](#versioncheckhook)) fails, it triggers a failure which can't be ignored if you use the package, or if you find out about it in a [`nixpkgs-review`](https://github.com/Mic92/nixpkgs-review) report.
- Sometimes packages become silently broken - meaning they fail to launch but their build passes because they don't perform any tests in the `checkPhase`. If you use this tool infrequently, such a silent breakage may rot in your system / profile configuration, and you will not notice the failure until you will want to use this package. Testing such basic functionality ensures you have to deal with the failure when you update your system / profile.
- When you open a PR, [ofborg](https://github.com/NixOS/ofborg)'s CI _will_ run `passthru.tests` of [packages that are directly changed by your PR (according to your commits' messages)](https://github.com/NixOS/ofborg?tab=readme-ov-file#automatic-building), but if you'd want to use the [`@ofborg build`](https://github.com/NixOS/ofborg?tab=readme-ov-file#build) command for dependent packages, you won't have to specify in addition the `.tests` attribute of the packages you want to build, and no body will be able to avoid these tests.
<!-- NOTE(@fricklerhandwerk): one may argue whether that testing guide should rather be in the user's manual -->
For more on how to write and run package tests for Nixpkgs, see the [testing section in the package contributor guide](https://github.com/NixOS/nixpkgs/blob/master/pkgs/README.md#package-tests).

View File

@ -762,6 +762,8 @@ Before and after running `make`, the hooks `preBuild` and `postBuild` are called
The check phase checks whether the package was built correctly by running its test suite. The default `checkPhase` calls `make $checkTarget`, but only if the [`doCheck` variable](#var-stdenv-doCheck) is enabled.
It is highly recommended, for packages' sources that are not distributed with any tests, to at least use [`versionCheckHook`](#versioncheckhook) to test that the resulting executable is basically functional.
#### Variables controlling the check phase {#variables-controlling-the-check-phase}
##### `doCheck` {#var-stdenv-doCheck}

View File

@ -31,6 +31,7 @@ during runtime. Alternatively, one can edit the desktop file themselves after
it is generated See:
https://github.com/NixOS/nixpkgs/issues/199596#issuecomment-1310136382 */
, autostartExecPath ? "syncthingtray"
, versionCheckHook
}:
stdenv.mkDerivation (finalAttrs: {
@ -85,9 +86,10 @@ stdenv.mkDerivation (finalAttrs: {
# Make binary available in PATH like on other platforms
ln -s $out/Applications/syncthingtray.app/Contents/MacOS/syncthingtray $out/bin/syncthingtray
'';
installCheckPhase = ''
$out/bin/syncthingtray --help | grep ${finalAttrs.version}
'';
nativeInstallCheckInputs = [
versionCheckHook
];
doInstallCheck = true;
cmakeFlags = [
"-DQT_PACKAGE_PREFIX=Qt${lib.versions.major qtbase.version}"

View File

@ -4,6 +4,7 @@
, fetchurl
, nixos
, testers
, versionCheckHook
, hello
}:
@ -19,6 +20,9 @@ stdenv.mkDerivation (finalAttrs: {
doCheck = true;
doInstallCheck = true;
nativeInstallCheckInputs = [
versionCheckHook
];
# Give hello some install checks for testing purpose.
postInstallCheck = ''

View File

@ -0,0 +1,60 @@
_handleCmdOutput(){
local versionOutput
versionOutput="$(env --chdir=/ --argv0="$(basename "$1")" --ignore-environment "$@" 2>&1 || true)"
if [[ "$versionOutput" =~ "$version" ]]; then
echoPrefix="Successfully managed to"
else
echoPrefix="Did not"
fi
# The return value of this function is this variable:
echo "$echoPrefix"
# And in anycase we want these to be printed in the build log, useful for
# debugging, so we print these to stderr.
echo "$echoPrefix" find version "$version" in the output of the command \
"$@" >&2
echo "$versionOutput" >&2
}
versionCheckHook(){
runHook preVersionCheck
echo Executing versionCheckPhase
local cmdProgram cmdArg echoPrefix
if [[ -z "${versionCheckProgram-}" ]]; then
if [[ -z "${pname-}" ]]; then
echo "both \$pname and \$versionCheckProgram are empty, so" \
"we don't know which program to run the versionCheckPhase" \
"upon" >&2
exit 2
else
cmdProgram="${!outputBin}/bin/$pname"
fi
else
cmdProgram="$versionCheckProgram"
fi
if [[ ! -x "$cmdProgram" ]]; then
echo "versionCheckHook: $cmdProgram was not found, or is not an executable" >&2
exit 2
fi
if [[ -z "${versionCheckProgramArg}" ]]; then
for cmdArg in "--help" "--version"; do
echoPrefix="$(_handleCmdOutput "$cmdProgram" "$cmdArg")"
if [[ "$echoPrefix" == "Successfully managed to" ]]; then
break
fi
done
else
cmdArg="$versionCheckProgramArg"
echoPrefix="$(_handleCmdOutput "$cmdProgram" "$cmdArg")"
fi
if [[ "$echoPrefix" == "Did not" ]]; then
exit 2
fi
runHook postVersionCheck
echo Finished versionCheckPhase
}
if [[ -z "${dontVersionCheck-}" ]]; then
echo "Using versionCheckHook"
preInstallCheckHooks+=(versionCheckHook)
fi

View File

@ -0,0 +1,12 @@
{
lib,
makeSetupHook,
}:
makeSetupHook {
name = "version-check-hook";
meta = {
description = "Lookup for $version in the output of --help and --version";
maintainers = with lib.maintainers; [ doronbehar ];
};
} ./hook.sh

View File

@ -12,6 +12,7 @@
# tests
pytestCheckHook,
versionCheckHook,
}:
buildPythonPackage rec {
@ -30,7 +31,10 @@ buildPythonPackage rec {
propagatedBuildInputs = [ wcwidth ];
nativeCheckInputs = [ pytestCheckHook ];
nativeCheckInputs = [
versionCheckHook
pytestCheckHook
];
preCheck = ''
export PATH=$out/bin:$PATH