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 pltHow to find missing dependencies#
1. nix-locate (look up a package by its .so filename)
nix-locate --whole-name libgthread-2.0.so.0Install 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:
- PyQt5 from PyPI bundles its platform plugin but lacks almost all X11 libraries
- Adding libraries to
LD_LIBRARY_PATHcaused version mismatches andundefined symbolerrors - PyQt5 was moved under Nix management, but uv’s
.venvoverrode PATH --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
Adapted from technical notes. Examples use generic names.