Fail usefully on NixOS: probe QtCore, explain the missing-.so error
Build PCM package / build (push) Successful in 6s
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>
This commit is contained in:
@@ -92,6 +92,14 @@ 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 (e.g. `steam-run kicad`) or
|
||||
enable `programs.nix-ld` with Qt's runtime libraries (glib,
|
||||
fontconfig, freetype, dbus, libGL, libxkbcommon and the X11/xcb
|
||||
set).
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -38,10 +38,25 @@ def _fail(message: str, outdir) -> None:
|
||||
|
||||
def main() -> None:
|
||||
outdir = None
|
||||
try:
|
||||
try:
|
||||
from kipy.errors import ApiError
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user