2 Commits

Author SHA1 Message Date
janik 64676624e6 NixOS: steam-run alone is one library short of a working Qt
Build PCM package / build (push) Successful in 6s
Field-tested: under steam-run the PySide6 wheel loads, but Qt aborts
at platform-plugin init - the Steam runtime predates Qt 6.5, which
hard-requires libxcb-cursor0 for xcb (and its wayland stack is too
old for the wayland plugin). Document the working invocation: point
LD_LIBRARY_PATH at xcb-util-cursor on top of steam-run, or build a
proper buildFHSEnv wrapper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:32:46 +07:00
janik 7604e18587 Fail usefully on NixOS: probe QtCore, explain the missing-.so error
Build PCM package / build (push) Successful in 6s
First NixOS attempt: pip wheel PySide6 cannot load libgthread-2.0.so.0
because NixOS has no FHS library paths. Nothing inside the venv can
fix that (KiCad installs wheels only) - but the plugin made it worse
twice over:

- The backend probe imported bare PySide6, whose pure-Python __init__
  succeeds even when QtCore's .so cannot load - so matplotlib was
  promised QtAgg and the error figure died at switch_backend, taking
  the failure report with it. The probe now imports <binding>.QtCore
  and falls through to Tk/Agg on a broken Qt.
- The user got a raw ImportError traceback. A cannot-open-shared-object
  failure during the kipy/dialog imports now raises a UserFacingError
  that names the actual fixes: run KiCad in an FHS environment
  (steam-run) or enable nix-ld with Qt runtime libraries. The README
  Linux notes carry the same guidance.

141 passed on the dev stack and the Python 3.9 mac-equivalent stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:22:22 +07:00
4 changed files with 60 additions and 4 deletions
+16
View File
@@ -92,6 +92,22 @@ OS specifics are spelled out per step and in *Platform notes* below.
On **ARM64 (aarch64)** there are no pyamg wheels —
`requirements.txt` skips pyamg there and the solver falls back to
Jacobi-CG: same results, noticeably slower on large grids.
**NixOS**: pip's Linux wheels link against standard FHS library
paths, which NixOS does not provide — PySide6 fails with
`libgthread-2.0.so.0: cannot open shared object file`. The plugin
cannot fix this from inside its venv (KiCad installs wheels only);
run KiCad inside an FHS environment. `steam-run kicad` alone is
**not** enough: its runtime predates Qt 6.5's hard requirement for
`libxcb-cursor0`, so Qt aborts with *"no Qt platform plugin could
be initialized"*. Supply that one library on top:
```sh
XCBCUR=$(nix build --no-link --print-out-paths nixpkgs#xcb-util-cursor)
QT_QPA_PLATFORM=xcb LD_LIBRARY_PATH=$XCBCUR/lib steam-run kicad
```
or build a dedicated FHS wrapper with `buildFHSEnv` whose
`targetPkgs` include `kicad`, `xcb-util-cursor`, `glib`,
`fontconfig`, `freetype`, `dbus`, `libGL`, `libxkbcommon`,
`wayland` and the `xorg` X11/xcb libraries.
## Usage
+17 -2
View File
@@ -39,9 +39,24 @@ def _fail(message: str, outdir) -> None:
def main() -> None:
outdir = None
try:
from kipy.errors import ApiError
try:
from kipy.errors import ApiError
from . import board_io, dialog
from . import board_io, dialog
except ImportError as e:
if "cannot open shared object file" not in str(e):
raise
# pip's Linux wheels link against FHS system libraries;
# on NixOS those paths don't exist and PySide6/pynng die
# exactly like this. Nothing inside the venv can fix it.
raise UserFacingError(
f"A compiled dependency cannot load its system "
f"libraries: {e}\nThe plugin venv is built from pip "
f"wheels, which expect standard (FHS) library paths. "
f"On NixOS, run KiCad inside an FHS environment (e.g. "
f"steam-run) or enable programs.nix-ld with Qt's "
f"runtime libraries - see README, Platform notes."
)
try:
kicad, board = board_io.connect()
stackup = board_io.get_stackup_info(board)
+8 -2
View File
@@ -24,10 +24,16 @@ def _pick_backend():
dependency and the selection dialog / progress window put a Qt event
loop in this process, after which matplotlib refuses TkAgg
("Cannot load backend 'TkAgg' ... as 'qt' is currently running") -
exactly what happened on macOS, whose bundled Python ships tkinter."""
exactly what happened on macOS, whose bundled Python ships tkinter.
The probe must import the binding's native core, not just the
package: on NixOS `import PySide6` succeeds (pure __init__) while
QtCore's .so cannot find the system libraries pip wheels expect
("libgthread-2.0.so.0: cannot open shared object file") - promising
QtAgg then kills even the error figure at switch_backend time."""
for qt in ("PySide6", "PyQt6", "PyQt5", "PySide2"):
try:
__import__(qt)
__import__(qt + ".QtCore")
return "QtAgg" if qt in ("PySide6", "PyQt6") else "Qt5Agg"
except Exception:
continue
+19
View File
@@ -20,6 +20,25 @@ def test_backend_prefers_qt_over_tk():
assert plots._pick_backend() in ("QtAgg", "Qt5Agg")
def test_backend_probe_requires_working_qtcore(monkeypatch):
# NixOS: `import PySide6` succeeds (a pure-Python __init__) while
# QtCore's .so cannot load the FHS system libraries pip wheels
# expect. The probe must import the native core and fall through -
# promising QtAgg kills even the error figure at switch_backend
# time, and the failure report with it.
import builtins
real_import = builtins.__import__
def broken_qt(name, *args, **kwargs):
if name.split(".")[0] in ("PySide6", "PyQt6", "PyQt5", "PySide2"):
raise ImportError("libgthread-2.0.so.0: cannot open shared "
"object file: No such file or directory")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", broken_qt)
assert plots._pick_backend() in ("TkAgg", None)
def test_output_dir_falls_back_when_board_dir_unwritable(
tmp_path, monkeypatch, capsys):
board_dir = tmp_path / "board"