UpdateScript written in nix considered bad practice (vs. bash)?

I had an interesting discussion with @Sandro about writing the updateScript for libreoffice in nix vs. bash in https://github.com/NixOS/nixpkgs/pull/174578 and https://github.com/NixOS/nixpkgs/pull/175160.

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).

My main point is the following: There are also update scripts and updaters written in perl and python and probably sed and awk script as @Sandro writes. Why is it a problem if I write parts of the update script in nix? nixpkgs/update-utils.nix at fe949d64ef417ec254fea7d2ae022b1f78f4ad1e · NixOS/nixpkgs · GitHub

Should this not be up to the maintainer to decide in which language he wants to write his update script?

1 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.

2 Likes

This adds a thing that has a direct attribute name in Nixpkgs and is impurely evaluated.

4 Likes

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.

1 Like

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.

3 Likes

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
1 Like

The problem is that impure evaluations will break in pure evaluation mode which is used by flakes.

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.

3 Likes

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.

However, enforcing pure evaluation on every derivation seems to be a rather rigid and idealistic approach.

Enforcing this for Nixpkgs is quite practical.

In practice, that means that you prohibit the use of nix for impure actions at eval time.

In Nixpkgs — yes.

What do we gain with a pure update-script? We don’t know if it works until we run it anyway.

For one thing, we maintain the separation of what happens in which phase (and how the phases can be offloaded).

2 Likes

Enforcing this for Nixpkgs is quite practical.

In general, agreed. Enforcing this for the update script, not really.

For one thing, we maintain the separation of what happens in which phase (and how the phases can be offloaded).

Could you elaborate on that? I don’t understand why this is useful for an updateScript.

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.

2 Likes

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)

So are you ok with the following?

  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"
      ''
        #!/usr/bin/env nix-shell
        #!nix-shell -i bash -p common-updater-scripts
        set -eou pipefail

        # reset version first so that both platforms are always updated and in sync
        update-source-version libreoffice-bin 0 ${lib.fakeSha256} --file=${currentFile} --system=aarch64-darwin
        update-source-version libreoffice-bin ${newVersion} ${newAarch64Sha256} --file=${currentFile} --system=aarch64-darwin
        update-source-version libreoffice-bin 0 ${lib.fakeSha256} --file=${currentFile} --system=x86_64-darwin
        update-source-version libreoffice-bin ${newVersion} ${newX86_64Sha256} --file=${currentFile} --system=x86_64-darwin
      '';

where getSha256 and getLatestStableVersion are impure nix functions?

So are you ok with the following?

 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.