Override desktopItem from package

Hi,

I’m trying to override the desktopItem from a package, but it seems that my changes are not “applied”:

    (mypackage.overrideAttrs (oldAttrs: {
      desktopItem = (oldAttrs.desktopItem // {
        exec = "/my_exec";
        terminal = "true";
      });
}));

It builds fine, but the desktop item appears to be the original version.
I suspect this is because the makeDesktopItem is not being evaluated on the desktopItem I’ve added?
Any clues on this?

Thank you!

By inspecting the desktopItem derivation from the program’s derivation:

"desktopItem": "/nix/store/31f3rg37mn8myfhls2mnmnkxfss0hj60-mypackage.desktop"

I can indeed verify that the env[“buildCommand”] does not have my changes.

If this works is greatly dependent on how the package expression is written. Let’s take xournal for example, which uses the most common method of referring to the desktopItem using recursive attribute set.

This is actually the same issue that occurs with any overriding of attribute from recursive attribute sets as discussed in https://discourse.nixos.org/t/avoid-rec-expresions-in-nixpkgs/8293/4: Nix is a functional language and values in Nix are immutable – when you create a recursive attribute set, the references to its items refer to the original attribute set. Even when you create a new set replacing the value that is referenced:

nix-repl> ex = rec { a = "foo"; b = "${a}bar"; }

nix-repl> ex
{ a = "foo"; b = "foobar"; }

nix-repl> ex // { a = "baz"; }
{ a = "baz"; b = "foobar"; }

Since overrideAttrs internally does just this for the attribute set passed to stdenv.mkDerivation, the postInstall in the new attrset will keep referring to the old desktopItem.

One way to fix that would be just replacing the old references alongside attribute path in the postInstall

(mypackage.overrideAttrs (oldAttrs: rec {
  desktopItem = oldAttrs.desktopItem // { # ← this parts still does not actually work
    exec = "/my_exec";
    terminal = "true";
  };
  postInstall = builtins.replaceStrings [ "${oldAttrs.desktopItem}" ] [ "${desktopItem}" ] oldAttrs.postInstall;
}));

There is however another issue. oldAttrs.desktopItem is not just an attribute set that when changed a new desktop item is created. It is a derivation created by makeDesktopItem function. And just creating a new attribute with changed exec attribute will not propagate the change into a new derivation.

Unfortunately, looking at makeDesktopItem source it does not seem to be easily overridable. As far as I can tell, there is only overrideAttrs function from the derivation created by runCommandLocal (or rather stdenv.mkDerivation called by the trivial builder). This means you will have to resort to the following monstrosity:

(mypackage.overrideAttrs (oldAttrs: rec {
  desktopItem = oldAttrs.desktopItem.overrideAttrs (desktopAttrs: {
    buildCommand =
      let
        oldExec = builtins.match ".*(\nExec=[^\n]+\n).*" desktopAttrs.buildCommand;
        oldTerminal = builtins.match ".*(\nTerminal=[^\n]+\n).*" desktopAttrs.buildCommand;
        matches = oldExec ++ oldTerminal;
        replacements = [ "\nExec=/my_exec\n" "\nTerminal=true\n" ];
      in
        assert oldExec != null && oldTerminal != null;
        builtins.replaceStrings matches replacements desktopAttrs.buildCommand;
  });
  postInstall = builtins.replaceStrings [ "${oldAttrs.desktopItem}" ] [ "${desktopItem}" ] oldAttrs.postInstall;
}))

Note that there is a bug in builtins.replaceStrings that keeps the original desktop file in the string, which will cause it to be built as a dependency of your overridden mypackage, even though it is necessary. But that is pretty small concern so I did not bother getting around it here.

4 Likes

With the following patch to Nixpkgs:

diff --git a/pkgs/build-support/make-desktopitem/default.nix b/pkgs/build-support/make-desktopitem/default.nix
index 329286bd362..6853b0ada0c 100644
--- a/pkgs/build-support/make-desktopitem/default.nix
+++ b/pkgs/build-support/make-desktopitem/default.nix
@@ -1,7 +1,9 @@
 { lib, runCommandLocal, desktop-file-utils }:
 
+lib.makeOverridable
+
 # See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
-{ name # The name of the desktop file
+({ name # The name of the desktop file
 , type ? "Application"
 , exec
 , icon ? null
@@ -61,3 +63,4 @@ runCommandLocal "${name}.desktop"
     echo "Running desktop-file validation"
     desktop-file-validate "$out/share/applications/${name}.desktop"
   '')
+)

The makeDesktopItem arguments would become overridable, allowing us to simplify it to:

(mypackage.overrideAttrs (oldAttrs: rec {
  desktopItem = oldAttrs.desktopItem.override {
    exec = "/my_exec";
    terminal = true;
  };
  postInstall = builtins.replaceStrings [ "${oldAttrs.desktopItem}" ] [ "${desktopItem}" ] oldAttrs.postInstall;
}))
5 Likes

Wow thank you for such a detailed explanation @jtojnar !!
Your “mostrosity” almost works, the Exec is set properly but the Terminal remains as false. I’ll see if I now understand this enough to try and make it work :slight_smile:
Otherwise, the most sane thing might be to just redefine the desktopItem fully! Then it should work I guess.

Ok I got it working. The reason why the terminal was not being replaced was because the buildCommand is like: “Exec=exe\nTerminal=…” so the first and second replacement were both colliding on the “\n”. By removing the \n from the match it now works!

Thanks again!