25.11 upgrade error: two given paths contain a conflicting subpath

I’m trying to upgrade my nixos config to 25.11,

and i’ve encountered an interesting error in one of my old wokardounds:

last 5 log lines:
       > pkgs.buildEnv error: two given paths contain a conflicting subpath:
       >   `/nix/store/hnzl29jyx2vr28sbmxigvc88nvdmjfrd-font-misc-misc-1.1.3/share/fonts/X11/misc/fonts.dir' and
       >   `/nix/store/qgv2ahjz1d4v5dyq499ii99l2h04fk9x-font-cursor-misc-1.0.4/share/fonts/X11/misc/fonts.dir'
       > hint: this may be caused by two different versions of the same package in buildEnv's `paths` parameter
       > hint: `pkgs.nix-diff` can be used to compare derivations
       For full logs, run:
         nix log /nix/store/ass7hvz9madvcw2pbpjfbnjjd8q5b0p7-system-fonts.drv

this happens when trying to build an old font hack

I’m not even sure this font hack is needed any more, but here it is

{ config, pkgs, ... }:
{
  system.fsPackages = [ pkgs.bindfs ];
  fileSystems = let
    mkRoSymBind = path: {
      device = path;
      fsType = "fuse.bindfs";
      options = [ "ro" "resolve-symlinks" "x-gvfs-hide" ];
    };
    aggregatedFonts = pkgs.buildEnv {
      name = "system-fonts";
      paths = config.fonts.packages ++ [
        pkgs.kdePackages.breeze
        pkgs.kdePackages.oxygen
      ];
      pathsToLink = [
        "/share/fonts"
        "/share/icons"
      ];
    };
  in {
    "/usr/share/icons" = mkRoSymBind (aggregatedFonts + "/share/icons");
    "/usr/share/fonts" = mkRoSymBind (aggregatedFonts + "/share/fonts");
  };
}

it’s based on an old version of this section of wiki: https://wiki.nixos.org/wiki/Fonts#Solution_3:_Configure_bindfs_for_fonts/cursors/icons_support

Can someone please explain what is happening and how to fix it?) thank you

Two packages in config.fonts.packages both contain a file called share/fonts/X11/misc/fonts.dir. This code tries to merge them into one, but since they contain the same file it’s ambiguous whether one or the other’s file should be used, so the builder exits with that error.

This is probably “caused” by a change in the packaging of basic X11 fonts; the icon and font directories probably were never intended to be merged like this. Just stop at the share level instead, it’s not like you’re using the fact that the two types of share dir are merged. The packages are both called font-.*-misc, maybe they used to be the same package (what’s this about a font package named cursors?). I don’t know how font packages are intended to be merged, but fonts.fontDir implements it differently; if you really want to continue doing this, mimic their implementation.

This generally seems like a pretty insane hack to me, even if the implementation was less broken. The point of NixOS is not to use fhs dirs, this is doing exactly that.

2 Likes

Thank you for the explanation.

As I remember, this hack had to be done to have fonts in flatpacked firefox. Without it a lot of unicode glyphs rendered as squares.

I’ll check how fonts.findDir and see if this font hack even needed still

It probably is “needed”, but I don’t think this is a good solution to that problem. Approaches using fontDir are also listed on the wiki page, and I’m convinced this could be better fixed either upstream or with some flatpak overrides to bind directories differently.

i like the idea of using fontDir and adding a flatpak override to bind that dir to /run/host/user-fontsinside flatpak container, but i struggle to find if flatpak can actually do that.

It should in theory be possible, it just uses bubblewrap under the hood. If an override isn’t possible ootb, maybe upstream support needs to be added, or we add some trivial downstream NixOS patch.

Either way, this seems like the kind of thing that should be quite easy to make work properly under NixOS, or at least not harder than building whatever this hack is.

do you think just slapping ignoreCollisions = true; in the old solution is good enough for the time being? basically ignoring fonts.dir file (i have no idea if anything uses it)

i’m slowly looking into fontsDir, but i’m not sure how to do it right: how fonts on linux work is a mystery to me for now, and why dir structure created by that hack is not flat, but still works, and will it work with the flat fontsDir approach - i don’t know.

I mean, pretty easy to try out. You can symlink the fonts.fontDir directory to your /etc instead, using systemd.tmpfiles. It’s just in /run/current-system/sw/share/X11/fonts. Nested symlinks aren’t great, but you’re hacking around anyway.

Way better to just split it into two packages if you want to continue to use that hack. There’s no reason to put these in one buildEnv, the directories are different anyway.

if this creates simple symlinks - i think this won’t work inside bubblewrap, it will need to have access to the nix store locations where the links lead

Ah, of course, hence the mkRoSymBind.

i’m not sure how to do that. I thought the point of buildEnv there is to aggregate many paths into a single one, and then bind that to a single location /usr/share/fonts . so then if i have multiple buildEnvs, or just many packages - how would I bind that to a single location?

If you look closely, it’s two locations. So:

{ config, pkgs, ... }:
{
  system.fsPackages = [ pkgs.bindfs ];
  fileSystems = let
    mkRoSymBind = path: {
      device = path;
      fsType = "fuse.bindfs";
      options = [ "ro" "resolve-symlinks" "x-gvfs-hide" ];
    };
  in {
    "/usr/share/icons" = mkRoSymBind (pkgs.buildEnv {
      name = "system-icons";
      paths = config.fonts.packages ++ [
        pkgs.kdePackages.breeze
        pkgs.kdePackages.oxygen
      ];
      pathsToLink = [
        "/share/icons"
      ];
    });

    "/usr/share/fonts" = mkRoSymBind (pkgs.buildEnv {
      name = "system-fonts";
      paths = config.fonts.packages ++ [
        pkgs.kdePackages.breeze
        pkgs.kdePackages.oxygen
      ];
      pathsToLink = [
        "/share/fonts"
      ];
    });
  };
}

+/- some syntax errors

Edit:

… sorry, hit enter too quickly. You can DRY that out, of course, but why bother, this code shouldn’t exist.

hmmmmm, i think i did this, but the same error persists, as the collision was in fonts, not between fonts and icons

Oh, duh, you’re right:

Both under fonts/, and since both are in config.fonts.packages, it’s nontrivial to untangle that.

So at best you’d copy the function from nixpkgs (since unfortunately the fonts.fontDir package isn’t cleanly exposed anywhere), which is still duplication.

Well, I mean, using ignoreCollisions worst case will just break fonts you’re most likely not using anyway. What’s another hack?

ok, so coming back to this i came up with this now:

{ config, pkgs, ... }:
{
  system.fsPackages = [ pkgs.bindfs ];
  fileSystems = let
    mkRoSymBind = path: {
      device = path;
      fsType = "fuse.bindfs";
      options = [ "ro" "resolve-symlinks" "x-gvfs-hide" ];
    };
    fontsPkgs = config.fonts.packages ++ [
        pkgs.kdePackages.breeze
        pkgs.kdePackages.oxygen
      ];
    x11Fonts = pkgs.runCommand "X11-fonts"
      {
        preferLocalBuild = true;
        nativeBuildInputs = with pkgs; [
          gzip
          xorg.mkfontscale
          xorg.mkfontdir
        ];
      }
      (''
        mkdir -p "$out/share/fonts"
        font_regexp='.*\.\(ttf\|ttc\|otb\|otf\|pcf\|pfa\|pfb\|bdf\)\(\.gz\)?'
      ''
      + (builtins.concatStringsSep "\n" (builtins.map (pkg: ''
          find ${toString pkg} -regex "$font_regexp" \
            -exec ln -sf -t "$out/share/fonts" '{}' \;
        '') fontsPkgs
        ))
      + ''
        cd "$out/share/fonts"
        mkfontscale
        mkfontdir
        cat $(find ${pkgs.xorg.fontalias}/ -name fonts.alias) >fonts.alias
      '');
    aggregatedIcons = pkgs.buildEnv {
      name = "system-icons";
      paths = fontsPkgs;
      pathsToLink = [
        "/share/icons"
      ];
    };
  in {
    "/usr/share/icons" = mkRoSymBind (aggregatedIcons + "/share/icons");
    "/usr/share/fonts" = mkRoSymBind (x11Fonts + "/share/fonts");
  };
}

have not properly tested this yet, as I have other things to polish before proper system switch, but at least this builds and seem to produce correct fileSystems attributes.

I kept “icons” part as it was as I have no idea what needs it and how it should be structured, but it used to work before, and who knows, maybe i need it still :slight_smile:

1 Like

strange.
flatpaked firefox does not see fonts now.
but inside firefox’s bwrap everything seem to be mounted correctly. fonts are in the same /run/host/fonts/, but firefox does not display certain pages i use for testing correctly

ok, so I’ve danced around the fire with some chants, and got it working:
basically the whole config is fine,
but for some reason fonconfig inside container was not detecting changes to the /run/host/fonts and was not rebuilding font cache.
even if you run flatpak enter org.mozilla.firefox fc-cache -v - it’d say "oh, sure, /run/host/fonts? i’ve cached there - there are 2 files and 3 dirs“ (while in reality there are now 3k+ files and no dirs…)
but forcing font-cacheflatpak enter org.mozilla.firefox fc-cache -f -v did the font recaching, and after that fc-list started to shot proper fonts, and firefox started loading them!

with this I assume flatpaked firefox will not pick up changes to fonts host’s /usr/share/fonts dir hack on system update without manually calling fc-cache -f, which is daunting…

My blind guess here is that fc-cache uses dir timestamp maybe? and since there are no timestamps in nix/store which is bind-mounted font cache things there are no changes in the directory

Yeah, sounds like it. The optimal solution is probably to get the hosts’ cache mounted with a bind-mount as well.

How do other distros solve this? Is this even intended to work?

no idea…

but judging from man for fc-cache - it does use timestamps somehow (have not found any detailed info, and too lazy to go browse source code). So assuming it uses file creation timestamps that nix lacks - this would be a nix/nixos-specific problem

1 Like