There seems to be considerable opposition to writing an updateScript in nix. I don’t want to further annoy Sandro with my stubbornness. But is there a general consensus that writing the updateScript in nix is bad practice?
Well, what do we gain by doing it in nix? I’m not in any way defending the status quo, I just think it’s the responsibility of the party introducing the change to “sell it”.
(While it is clear from the post that this has to be an impurely evaluated Nix expression, the case in question also has this impure Nix evaluation during evaluation of updateScript, not while running it.)
I don’t want to get into the discussion if nix is better suited than bash for an update script (even though that is also an interesting discussion that we might later get into if you like).
What is the disadvantage of having an impure evaluation during evaluation of the updateScript?
I think, it is quite the opposite because firstly, if my update script fails, then it fails early (and failing early is good in my opinion) and secondly, it fails with a custom error message for better understanding (in this case either with Failed to extract versions from html. or Failed to extract sha256 from html.)
If I write the update script in bash on the other hand and it fails then the real reason of the failure is often swallowed by the update-source-version script and remains hidden.
As a result, I consider the nix version to even have an advantage over the bash version.
Indeed. But thanks to the laziness of nix this impurity has no effect on the derivation. Only when you run the update script, then the impurity will happen already at evaluation time instead of at runtime. Since an update script is in the end always impure, I don’t see any problems with this approach.
Checking that everything correctly evaluates (including meta, and ideally tests and update scripts) is a valuable check, and making it require any additional downloads creates an obstacle to improving CI eval-checks.
Is is not the opposite again? If the eval check of the update script fails, then we know something is broken and can go fix it, which is good.
Since we can eval and run the passthru.updateScript separately from the main derivation, we can also disable it on a case by case basis (e.g. if it downloads too much) during the CI eval-checks.
So in my opinion this flexibility gives us the best of both worlds when writing an update script in nix:
Earlier feedback at eval time
Easy to integrate with the derivation since both are written in nix
Is is not the opposite again? If the eval check of the update script fails, then we know something is broken and can go fix it, which is good.
No, it’s like our avoidance of IFD: there are clear parts of CI that can be run hermetically. If «does Nixpkgs eval cleanly» depends on «which hoster is down today», we have lost.
Since we can eval and run the passthru.updateScript separately from the main derivation, we can also disable it on a case by case basis (e.g. if it downloads too much) during the CI eval-checks.
The more case-by-case our CI definitions are, the less we can have a simple mental model
So in my opinion this flexibility gives us the best of both worlds when writing an update script in nix:
Earlier feedback at eval time
Breaking the very basic properties of eval in the process.
Easy to integrate with the derivation since both are written in nix
Not really integrating, as it needs to overwrite the source code.
No, it’s like our avoidance of IFD: there are clear parts of CI that can be run hermetically. If «does Nixpkgs eval cleanly» depends on «which hoster is down today», we have lost.
I agree.
The more case-by-case our CI definitions are, the less we can have a simple mental model
That is why I want to propose the following simple model:
Exclude the passthru.updateScript in the eval check because it is always impure. Then we can safely use nix in the updateScript without any disadvantages. Done.
The more case-by-case our CI definitions are, the less we can have a simple mental model
That is why I want to propose the following simple model:
Exclude the passthru.updateScript in the eval check because it is always impure. Then we can safely use nix in the updateScript without any disadvantages. Done.
Normally updateScript is just as impure as any derivation. It can be evaluated purely (and without network access), it can typically be built purely, but running it of course requires network access in unreproducible way. That’s how things in Nixpkgs behave and should behave.
Striving for purity and hermetic builds is why I use nix.
However, enforcing pure evaluation on every derivation seems to be a rather rigid and idealistic approach. In practice, that means that you prohibit the use of nix for impure actions at eval time.
In the special case of the updateScript we gain nothing by enforcing purity at eval time (currently, passthru.updateScript is not evaluated in any CI-check) but loose the above mentioned advantages that come with using nix.
What do we gain with a pure update-script? We don’t know if it works until we run it anyway.
passThru.updateScript generally is an unregulated mess, but the convention is that it is either a path to local file or a derivation that evaluates to an update script. I have (low priority) plans for making things a little more sane, but in the meantime please don’t make it even worse. Feel free to write your Nix update functions in a different attribute though, and then have a thin shell wrapper in updateScript or something like that for compatibility.
passThru.updateScript generally is an unregulated mess, but the convention is that it is either a path to local file or a derivation that evaluates to an update script. I have (low priority) plans for making things a little more sane, but in the meantime please don’t make it even worse. Feel free to write your Nix update functions in a different attribute though, and then have a thin shell wrapper in updateScript or something like that for compatibility.
It is already in a separate file anyway, and updateScript already calls nix-shell, so I guess the magic could indeed be just moved to running-the-script time.
(update-time impure-Nix version extraction looks weird enough to merit a comment at the top of the file that this is indeed an impure Nix expression using no-hash builtins.fetchurl, but as long as all the maintainers of the package in question are fine with it, that seems enough)
passthru.updateScript =
let
inherit (import ./update-utils.nix { inherit lib; })
getLatestStableVersion
getSha256;
newVersion = getLatestStableVersion;
newAarch64Sha256 = getSha256 dist."aarch64-darwin".url version newVersion;
newX86_64Sha256 = getSha256 dist."x86_64-darwin".url version newVersion;
currentFile = builtins.toString ./default.nix;
in
writeScript "update-libreoffice.sh"
where getSha256 and getLatestStableVersion are impure nix functions?
Me no, it is impure evaluation during the evaluation of updateScript. If the internal call to nix-shell was impure or something like that, this would be update-script-run-time, and way less concerning, indeed.