Python not seeing my Gtk dependency (works in nix-shell, not in configuration.nix nor a package)

I have a simple Python script that uses Gtk internally:

https://gitlab.com/moy/recent-files-cli/-/blob/main/recent-files?ref_type=heads

I want to run it on my nixos system (and I’m trying to understand what’s going on as a side effect :wink: ).

What works: a shell.nix file

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.gobject-introspection
    pkgs.gtk3
    (pkgs.python3.withPackages (p: with p; [
      pygobject3
    ]))
  ];
}

then

$ nix-shell
$ ./recent-files
# displays my recent files, good

What doesn’t work: configuration.nix

I tried adding the same dependencies to configuration.nix and sudo nixos-rebuild switch:

  environment.systemPackages = with pkgs; [
    # ...
    gobject-introspection
    gtk3
    (python3.withPackages (p: with p; [
       pygobject3
       pip
    ]))

The script finds the Python dependency import gi, but fails to load Gtk:

$ ./recent-files 
Traceback (most recent call last):
  File "/home/moy/dev/recent-files-cli/./recent-files", line 16, in <module>
    gi.require_version("Gtk", "3.0")
  File "/nix/store/7bpbfiaksacdzp7n2ycm408djqjjp13a-python3-3.12.11-env/lib/python3.12/site-packages/gi/__init__.py", line 122, in require_version
    raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gtk not available

What doesn’t work either: packaging

I also tried to write a real nix package for my script, hoping to be able to install it on my system (and possibly to share it with the community), in a file recent-files.nix:

with import <nixpkgs> {};
with pkgs.python3Packages;

buildPythonPackage rec {
  name = "recent-files";
  src = ./recent-files;
  dontUnpack = true;
  pyproject = false;
  installPhase = "install -Dm755 ${src} $out/bin/recent-files";
  propagatedBuildInputs = [ 
    pkgs.gobject-introspection
    pkgs.gtk3
    (pkgs.python3.withPackages (p: with p; [
      pygobject3
    ]))
  ];
}

Unfortunately:

$ nix-build recent-files.nix 
/nix/store/80sqgky1d3aqh0pw252h8a9gsgrxdxmc-python3.12-recent-files
$ ./result/bin/recent-files 
Traceback (most recent call last):
  File "/nix/store/80sqgky1d3aqh0pw252h8a9gsgrxdxmc-python3.12-recent-files/bin/.recent-files-wrapped", line 17, in <module>
    gi.require_version("Gtk", "3.0")
  File "/nix/store/7zb3n4nqxi3k8fpj9piz41d3pavis2mq-python3-3.12.11-env/lib/python3.12/site-packages/gi/__init__.py", line 122, in require_version
    raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gtk not available

and no better luck with nix-shell:

$ nix-shell recent-files.nix 
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook

[nix-shell:~/dev/recent-files-cli]$ ./recent-files 
Traceback (most recent call last):
  File "/home/moy/dev/recent-files-cli/./recent-files", line 16, in <module>
    gi.require_version("Gtk", "3.0")
  File "/nix/store/7zb3n4nqxi3k8fpj9piz41d3pavis2mq-python3-3.12.11-env/lib/python3.12/site-packages/gi/__init__.py", line 122, in require_version
    raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gtk not available

Apparently, I managed to get the pure Python dependency right, but there is some kind of magic with the native dependencies that I don’t understand.

What am I doing wrong? How can I get my package to work?

Also, I’m a nixos noob, so feel free to comment on whatever else I did wrong and suggest any kind of improvements.

Thanks in advance,

Another nix noob. I was also confused by this for a while and spent some time throwing my head against the wall because it looked like what you had should work. After enough of that I threw it into Gemini, which suggested

This is a very common and frustrating issue in Nix when dealing with Gtk. Even with buildInputs, the pygobject module often can’t find the Gtk typelibs it needs at runtime. The buildInputs list only guarantees that the C-level libraries are available during the build, but Gtk requires a bit more to be discoverable by Python’s introspection mechanism.

The fix is to use wrapGAppsHook. This hook modifies your application’s wrapper script to set the correct environment variables, like GI_TYPELIB_PATH, which tell GObject Introspection where to find the Gtk library’s typelib files.

The Correct Nix Derivation

To resolve the error, add wrapGAppsHook to your nativeBuildInputs list. This is the standard way to package Gtk applications correctly in Nix.

I then modified your recent-files.nix like so:

let
  pkgs = import <nixpkgs> {};
in
  pkgs.python3Packages.buildPythonPackage rec {
    name = "recent-files";
    src = ./recent-files;
    dontUnpack = true;
    pyproject = false;
    installPhase = "install -Dm755 ${src} $out/bin/recent-files";
    propagatedBuildInputs = with pkgs; [ 
      gobject-introspection
      gtk3
      (python3.withPackages (p: with p; [
        pygobject3
      ]))
      wrapGAppsHook
    ];
  }

and it seemed to work. I’m didn’t try putting the wrapGAppsHook into an actual nativeBuildInputs attribute like Gemini is telling me to. Maybe that is the better way, but I don’t really know and am sure someone else can comment better than I.

I also changed your initial with ... lines since you didn’t even seem to be using them for their namespacing and were still calling e.g. pkgs.gtk3 before.

With the benefit of hindsight, it turns out that search “ValueError: Namespace Gtk not available nixos” would be enough to get you to a reddit thread that also uultimately gets you to wrapGAppsHook (although now you need a reddit account to actually see the solution because of anti-AI scraping measures).

See also Nixpkgs Reference Manual section on these hooks.

I’m also guessing wrapGAppsHook is necessary in the package but not in the shell.nix because of pre-existing GIO_EXTRA_MODULES in the interactive shell used to run nix-shell, but if someone with more experience could chime in with a bit more authority, that would be cool too.

One of the dependencies you listed or their transitive dependencies provide the setup hook that is run when nix-shell is run (Code search results · GitHub).

1 Like

Thank you, it does fix the packaging.

If I understand correctly, I can’t make the script work without either a shell.nix/flake.nix or a proper package, but now that I do have a package, I can use it to install it (so I can run it without pain as a user of the script) and I can use nix-shell to execute the original script as a developer of my own script, so my use-case are covered.

I slightly modified the packaging to split it into a recent-files.nix:

{ pkgs, stdenv, python3, ... }:

pkgs.python3Packages.buildPythonPackage rec {
  name = "recent-files";
  src = ./recent-files;
  dontUnpack = true;
  pyproject = false;
  installPhase = "install -Dm755 ${src} $out/bin/recent-files";
  propagatedBuildInputs = with pkgs; [
    gobject-introspection
    gtk3
    (python3.withPackages (p: with p; [ pygobject3 ]))
    wrapGAppsHook
  ];
}

and a default.nix:

(import <nixpkgs> { }).callPackage ./recent-files.nix { }

this way I can:

$ nix-shell
nix-shell:~/dev/recent-files-cli]$ ./recent-files
# it works!

and since I run nixos, I added it to my configuration.nix:

  environment.systemPackages = with pkgs; [
    (callPackage /home/moy/dev/recent-files-cli/recent-files.nix {})
    # ...
   ];

I’m still a bit puzzled about what default.nix should contain (a set or a function), and perhaps I should move to flakes instead of traditional package, but what I have seems reasonable at least. Any suggestion for improvement welcome.