OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 089

Dependency boundaries in Python, uv, and Matplotlib/Qt

Trace Python packages, system libraries, and plugin paths through Qt5Agg and PyPI wheel troubleshooting examples.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSNix development environments and binary compatibility ↗
About 3 min readStart reading

Technology stack: nix-shell + uv + direnv

Main issues: Python compilation problems and plotting problems with matplotlib.

Example shell.nix configuration#

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell{
  buildInputs = with pkgs; [
    (python312.withPackages (ps: with ps; [
      matplotlib
      pyqt5  # Qt5Agg 所需 Python binding
    ]))
    qt5.qtwayland
    qt5.qtbase
    uv
  ];

  shellHook = ''
    export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [
      pkgs.stdenv.cc.cc.lib
      pkgs.zlib
      pkgs.libGL
      pkgs.qt5.qtbase
      pkgs.glib
    ]}
    export MPLBACKEND=Qt5Agg
    export QT_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.qtPluginPrefix}
    export QT_QPA_PLATFORM_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.qtPluginPrefix}/platforms
  '';
}

If you need to override the backend in code, do so before importing pyplot; this is unnecessary if MPLBACKEND is already set:

import matplotlib
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt

How to find missing dependencies#

1. nix-locate (look up a package by its .so filename)

nix-locate --whole-name libgthread-2.0.so.0

Install nix-index and build the index first: nix-index (the first run takes a few minutes).

2. Search the nixpkgs website

Package search primarily searches package metadata, not a complete file index. Once you know a candidate package name, confirm its attribute here: search.nixos.org

3. Diagnose PyPI package dependencies with ldd

Use ldd only on trusted binaries; for untrusted files, first inspect dependencies statically with readelf -d or objdump -p. Paths vary with the Python and PyQt versions.

ldd .venv/lib/python3.12/site-packages/PyQt5/QtWidgets.so | grep "not found"

List all missing .so files at once, then look them up in bulk with nix-locate.

Recommended workflow: find all missing libraries with ldd → look up package names with nix-locate → add them to shell.nix in one pass

Interim summary (2026-04-08)#

The central issue in this record is inconsistent search paths and ABIs when mixing PyPI wheels, Qt plugins, and system libraries supplied by Nix; uv and Nix do not inherently conflict.

  • uv’s design philosophy: it typically resolves Python dependencies and installs them into .venv; wheels with native extensions may still depend on system libraries supplied by the environment.
  • Nix’s design philosophy: all dependencies are in the Nix store (/nix/store/…), rather than standard system paths.

Pure Python packages pose no conflict, but the matplotlib+Qt chain requires system libraries:

matplotlib (PyPI)
  └── PyQt5
        └── libQt5XcbQpa.so (Qt 运行时)
              └── libqxcb.so (platform plugin)
                    └── libxcb, libX11, libxkbcommon…(全在 nix store)

Pitfalls encountered:

  1. PyQt5 from PyPI bundles its platform plugin but lacks almost all X11 libraries
  2. Adding libraries to LD_LIBRARY_PATH caused version mismatches and undefined symbol errors
  3. PyQt5 was moved under Nix management, but uv’s .venv overrode PATH
  4. --python $(which python3) selected Nix’s Python, but matplotlib was still in the venv

The solution this troubleshooting record settled on: manage matplotlib, PyQt, and the Qt5 system libraries through the same Nixpkgs to reduce ABI mixing. uv can also be used for projects with native dependencies, but it requires the correct dynamic-library environment, compatible wheels, and a Python path strategy; globally piling additions onto LD_LIBRARY_PATH is not enough.

Versions and the two dependency paths#

This article retains the Python 3.12 / Qt5 examples from the 2026-04-08 record; backend names, plugin paths, and package attributes depend on the version in use. The pure withPackages approach uses that Python directly. If you also create a uv virtual environment, be explicit about which packages come from PyPI and which system libraries come from Nix, and plan for --system-site-packages if needed, rather than mixing two Qt/PyQt installations in the search paths. Before querying nix-locate, build or obtain a file index appropriate for the system.

References & further reading

  1. Nix Python development environments ↗
  2. Matplotlib backends ↗
  3. Python limitations of nix-ld ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Nix development environments and binary compatibility

  1. 01Progressing from nix-shell to devShell and direnv
  2. 02Nix development environments: languages, databases, and full-stack examples
  3. 03Dependency boundaries in Python, uv, and Matplotlib/Qt
  4. 04nix-ld: precompiled ELF binaries, loaders, and library paths
  5. 05Overlays, override, and overrideAttrs
CONTINUE READING / NEXT ARTICLENix commands explained: builds, environments, profiles, and systems

Retain new and legacy CLI commands, store operations, system installation, and Home Manager commands while clarifying easily confused flags.