From a4945f419c8c3fda0e55a16376cf372dca44ac51 Mon Sep 17 00:00:00 2001 From: Berwn Date: Thu, 23 Jul 2026 16:08:15 +0700 Subject: [PATCH] NixOS works: document the full FHS recipe, drop the nix-ld dead end First successful Linux field test (NixOS 26.05, Plasma 6, KiCad 10). Getting there peeled three layers the error messages misattribute: the FHS env itself (libgthread), then libs the xcb-cursor hint hides (the xcb-util family, and libzstd for libQt6Core - in zstd.out, not zstd's default output), and finally Plasma's QT_PLUGIN_PATH feeding the system Qt's platform plugins to the wheel's bundled Qt, a private-ABI mismatch that fails exactly like a missing library. New docs/NIXOS.md carries the verified buildFHSEnv wrapper, the steam-run quick test (now with env -u QT_PLUGIN_PATH), and a guide for chasing future missing sonames. README upgrades Linux from untested to works and links the doc. The in-plugin error no longer suggests programs.nix-ld: the venv python is a Nix binary, so the nix-ld shim never engages for it. Co-Authored-By: Claude Fable 5 --- README.md | 44 +++++++------- docs/NIXOS.md | 128 ++++++++++++++++++++++++++++++++++++++++ fill_resistance/main.py | 6 +- 3 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 docs/NIXOS.md diff --git a/README.md b/README.md index 9f67374..c281759 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ SWIG API. Requires KiCad **10.0.1+**. ## Setup (one-time) The plugin is developed and tested on **Windows**; **macOS works** -(field-tested on KiCad 10 after a round of mac-specific fixes). -**Linux is expected to work but is untested so far** — the code and -the dependency stack have been audited (KiCad builds the plugin a -private Python venv from `requirements.txt` on every platform, from -pre-built wheels only, no compiler needed), but nobody has run the -plugin there yet. Reports welcome! Steps 1–4 are the same everywhere; -OS specifics are spelled out per step and in *Platform notes* below. +(field-tested on KiCad 10 after a round of mac-specific fixes), and +**Linux works** (field-tested on NixOS — the hardest Linux to run pip +wheels on; mainstream FHS distributions should be no harder, reports +welcome). KiCad builds the plugin a private Python venv from +`requirements.txt` on every platform, from pre-built wheels only, no +compiler needed. Steps 1–4 are the same everywhere; OS specifics are +spelled out per step and in *Platform notes* below. 1. **Enable the API server**: KiCad → Preferences → Plugins → check *Enable KiCad API*. @@ -87,27 +87,23 @@ OS specifics are spelled out per step and in *Platform notes* below. Plot and dialog windows may open **behind** the KiCad window (they are raised best-effort) — check the Dock if nothing seems to appear after a solve. -- **Linux** — **untested** (audited only, same caveat). The venv uses +- **Linux** — **works** (field-tested on NixOS, KiCad 10; mainstream + distributions are audited but not yet field-tested). The venv uses the system Python (3.9+), so the stack matches your distribution. 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. + **NixOS**: **works** (field-tested on NixOS 26.05, Plasma 6). 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 built with `buildFHSEnv`, and — on KDE Plasma — unset + `QT_PLUGIN_PATH`, which otherwise poisons the wheel's bundled Qt + with the system's Qt plugins. The tested wrapper (exact package + list incl. the non-obvious `zstd.out` and xcb-util family), a + `steam-run` quick test, and a debugging guide are in + [docs/NIXOS.md](docs/NIXOS.md). ## Usage diff --git a/docs/NIXOS.md b/docs/NIXOS.md new file mode 100644 index 0000000..a579dce --- /dev/null +++ b/docs/NIXOS.md @@ -0,0 +1,128 @@ +# Running Fill Resistance on NixOS + +KiCad builds the plugin a private venv from pip wheels +(`requirements.txt`). Those Linux wheels — PySide6 in particular — +`dlopen` system libraries at standard FHS paths (`/usr/lib`), which +NixOS does not provide. Nothing inside the venv can fix that; KiCad +must be **launched in an environment that supplies the libraries**. +This page gives a verified, declarative setup (field-tested on +NixOS 26.05, KiCad 10, Plasma 6 on Wayland) and maps each failure +mode to its cause, because the error messages are misleading. + +## The three failure layers + +You will hit these in order; each fix below removes one. + +1. **`libgthread-2.0.so.0: cannot open shared object file`** + (shown in the plugin's own error figure) — no FHS environment at + all. PySide6's bundled Qt cannot load glib & friends. +2. **`qt.qpa.plugin: From 6.5.0, xcb-cursor0 or libxcb-cursor0 is + needed…` / `no Qt platform plugin could be initialized`** — + FHS present but incomplete. Beyond the obvious `xcb-util-cursor`, + the bundled Qt needs the whole **xcb-util family** (`icccm`, + `image`, `keysyms`, `render-util`, `util`) and **`libzstd`** + (a hard dependency of `libQt6Core`; note that `pkgs.zstd`'s + default output ships only the CLI — the library lives in + `zstd.out`). Qt prints the xcb-cursor hint for *any* missing + dependency of the platform plugin, so don't trust it literally. +3. **`Could not load the Qt platform plugin "wayland"/"xcb" in "" + even though it was found`** with all libraries present — the + desktop session (KDE Plasma does this) exports **`QT_PLUGIN_PATH`** + pointing at the system's Qt plugin directories. That variable takes + precedence over the wheel's bundled plugins, so the venv's PySide6 + (its own Qt, e.g. 6.11.1) tries to load platform plugins built + against the system Qt (e.g. 6.11.0) — a private-ABI mismatch that + fails exactly like a missing library. The fix is to **unset + `QT_PLUGIN_PATH`** for KiCad and everything it spawns. KiCad + itself is wxWidgets/GTK and does not use the variable. + +## Recommended setup: an FHS wrapper + +Wrap KiCad in `buildFHSEnv` so a plain `kicad` launch carries +everything. Home-manager example (`home.nix`); for a system-wide +install put the same package in `environment.systemPackages` in +`configuration.nix` instead: + +```nix +{ pkgs, ... }: + +let + kicad-fhs = pkgs.buildFHSEnv { + name = "kicad"; + targetPkgs = p: with p; [ + kicad glib fontconfig freetype dbus libGL libxkbcommon + xcb-util-cursor wayland zlib zstd.out + xorg.libX11 xorg.libxcb xorg.libXext xorg.libXrender + xorg.libSM xorg.libICE xorg.libXrandr xorg.libXi + xorg.libXcursor xorg.libXfixes + # xcb-util family needed by PySide6's bundled xcb platform plugin + xorg.xcbutil xorg.xcbutilwm xorg.xcbutilimage + xorg.xcbutilkeysyms xorg.xcbutilrenderutil + ]; + # Plasma exports QT_PLUGIN_PATH (system Qt plugin dirs); the venv's + # bundled Qt chokes on those mismatched plugins. KiCad is wx/GTK, + # so dropping the variable is safe. + profile = "unset QT_PLUGIN_PATH"; + runScript = "kicad"; + }; +in +{ + home.packages = [ kicad-fhs /* replaces bare pkgs.kicad */ ]; + + # buildFHSEnv ships no .desktop file; restore the menu launcher. + xdg.desktopEntries.kicad = { + name = "KiCad"; + exec = "kicad %F"; + icon = "kicad"; + categories = [ "Development" "Electronics" ]; + mimeType = [ "application/x-kicad-project" ]; + }; +} +``` + +Rebuild, **fully quit any running KiCad** (a running instance keeps +its old environment), relaunch, click the Ω button. A one-line +`Could not load the Qt platform plugin "wayland"` warning may remain +if the wayland client stack is unhappy — it is harmless; Qt falls +back to xcb (XWayland) and the dialog appears. + +## Quick test without a rebuild + +`steam-run` provides a broad FHS that is one library short +(`libxcb-cursor`, required by Qt ≥ 6.5) and, on Plasma, still leaks +`QT_PLUGIN_PATH`: + +```sh +XCBCUR=$(nix build --no-link --print-out-paths nixpkgs#xcb-util-cursor) +env -u QT_PLUGIN_PATH QT_QPA_PLATFORM=xcb \ + LD_LIBRARY_PATH=$XCBCUR/lib steam-run kicad +``` + +(Fish: `set XCBCUR (nix build --no-link --print-out-paths +nixpkgs#xcb-util-cursor)`, then the same `env …` line.) + +## Debugging further failures + +If a new wheel version needs another library, reproduce the plugin's +Qt startup *outside* KiCad against the wrapper's library set — the +venv survives at +`~/.cache/kicad/10.0/python-environments/th.co.b4l.fill-resistance`: + +```sh +ROOTFS=$(nix-store -qR "$(readlink -f "$(command -v kicad)")" \ + | grep fhsenv-rootfs | head -1) +env -i HOME=$HOME DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY \ + LD_LIBRARY_PATH=$ROOTFS/usr/lib64 QT_DEBUG_PLUGINS=1 \ + ~/.cache/kicad/10.0/python-environments/th.co.b4l.fill-resistance/bin/python3 \ + -c 'from PySide6.QtWidgets import QApplication; \ +print(QApplication([]).platformName())' +``` + +A missing library shows up as a plain `ImportError: libfoo.so.N: +cannot open shared object file` — map the soname to its nixpkgs +attribute (`nix-locate libfoo.so.N`, from `nix-index`) and add it to +`targetPkgs`. If instead every library loads and only the platform +plugin fails, compare the environment of the *running* KiCad +(`tr '\0' '\n' < /proc/$(pgrep -x kicad)/environ`) for Qt variables +leaking in from the session — `QT_PLUGIN_PATH` above was found +exactly this way. diff --git a/fill_resistance/main.py b/fill_resistance/main.py index 30cb9db..2a631af 100644 --- a/fill_resistance/main.py +++ b/fill_resistance/main.py @@ -53,9 +53,9 @@ def main() -> None: 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." + f"On NixOS, run KiCad inside an FHS environment " + f"(buildFHSEnv wrapper, or steam-run for a quick " + f"test) - see docs/NIXOS.md in the plugin repo." ) try: kicad, board = board_io.connect()