How to prepend to preInstallPhases?

I’m trying to debug some rust behavior where debug info isn’t working on lldb with aarch64-darwin: rust debug symbols don't work on aarch64-darwin · Issue #262131 · NixOS/nixpkgs · GitHub

As part of my debugging steps, I wanted to insert a few commands into the buildRustPackage build environment after the buildPhase.

I thought that I could just do something like .overrideAttrs (old: { installPhase = old.installPhase or "" + "ls -lAtr"; });

however, this clobbers the default installPhase='cargoInstallHook' (which is annoying).

I’m sure I could just define the default as "cargoInstallHook" instead of "" and be done, but I was hoping to learn an approach that would work with other builders, so I figured I could just prepend something to preInstallPhases and perhaps that would be a more general approach.

I was surprised that this fails miserably:

preInstallPhases = old.preInstallPhases or []
          ++ [ "ls -lAtr" ];

Seeming to run the ls and -lAtr as separate commands. I figured this was just an odd choice about word splitting behavior and tried [ '' "ls -lAtr" '' ] which fails even more miserably, trying to run "ls as a command.

I can’t find many examples of modifying preInstallPhases. It looks like it’s probably supposed to be a function name (which are ultimately joined as something like ${preInstallPhases[*]:-} in the builder.

Is there a “right way” to throw an arbitrary command into preInstallPhases, or otherwise “do something right before the default installPhase” without having to redefine the installPhase?

Gah, of course as soon as I post I figure something out.

.overrideAttrs (old: {
        doStuff = "ls -lAtr";
        preInstallPhases =
          old.preInstallPhases
          or []
          ++ [" doStuff"];
});
1 Like

Out of curiosity, why are you adding a whole new phase instead of setting/appending to preInstall?

1 Like

Honestly I didn’t know that existed; all I see is preInstallPhases in genericBuild:

$ declare -f genericBuild | grep preInstall
        phases="${prePhases[*]:-} unpackPhase patchPhase ${preConfigurePhases[*]:-}             configurePhase ${preBuildPhases[*]:-} buildPhase checkPhase             ${preInstallPhases[*]:-} installPhase ${preFixupPhases[*]:-} fixupPhase installCheckPhase             ${preDistPhases[*]:-} distPhase ${postPhases[*]:-}";

I see it mentioned in the manual with a fairly terse description:

Hook executed at the start of the install phase.

I guess I wouldn’t know whether modifying a hook is better or worse than modifying a phase. In Controlling phases in the manual preInstall isn’t mentioned at all, and it says

Usually, if you just want to add a few phases, it’s more convenient to set one of the variables below (such as preInstallPhases).

so I thought I was following best practices. I’d love to hear some feedback if you would recommend a different approach.

I don’t know if this is clearly documented anywhere, but from what I’ve gathered here and there a phase is a conceptually heavyweight thing, and the main reason that phases have their pre- and post- hooks is so that the existing phases can be extended in small ways instead of messing with the phase structure itself. (I don’t know in what situations one would want to add a new phase, but my guess is that it would involve expecting that phase to be manipulated by downstream code, independently of the other phases.)

So my recommendation would be to write

.overrideAttrs (old: {
  preInstall = old.preInstall or "" + ''
    ls -lAtr
  '';
})

(By convention, these hook scripts, if defined, should end in a newline. The multiline string literal includes a final newline for you; if you for some reason strongly want to one-line this as in the original post, you ought to write old.preInstall or "" + "ls -lAtr\n". For experimentation purposes, this doesn’t matter at all, but for learning best practices it does.)

1 Like