How the packages are built
Both packages are C++ extensions built with CMake and scikit-build-core, from vendored upstream release sources, into one wheel per platform. Beyond that they use different binding stacks, on purpose. This page explains the difference and the reasons; Building and publishing covers the day-to-day workflow.
At a glance
| pyside6-scintilla | lexilla | |
|---|---|---|
| Wraps | Scintilla's Qt widget (ScintillaEditBase, a QAbstractScrollArea) |
Lexilla's C++ API (CreateLexer, ILexer5) |
| Binding generator | shiboken6 (PySide6's own) | nanobind |
| Build needs | a Qt 6.12 SDK (incl. Qt6Core5Compat), PySide6 + shiboken6 6.12, libclang |
a C++17 compiler |
| Runtime needs | PySide6-Essentials 6.12.x (pinned to the minor) | nothing (pyside6-scintilla only for the glue) |
| Python ABI | limited API, cp311-abi3 (shiboken6's FORCE_LIMITED_API) |
limited API, cp312-abi3 (nanobind's STABLE_ABI needs 3.12) |
| Built as | scintilla_qt shared library + the _pyside6_scintilla extension |
lexilla_core static library linked into _lexilla |
| Workspace build | without build isolation (must see .venv's PySide6) |
isolated, as usual |
| Generated code | the typed ScintillaEdit class (tools/generate_scintilla_edit.py, from Scintilla.iface), the .pyi stub (genpyi + tools/generate_pyi.py) |
the LanguageIdentifier/Language enums (tools/generate_language_enums.py), the .pyi stub (nanobind's stubgen) |
| Wheel repair | excludes the Qt/PySide6/shiboken6 libraries PySide6 provides, bundles Qt6Core5Compat |
default (nothing to exclude or bundle) |
| Details | Bindings architecture, build notes | Bindings architecture, build notes |
Why shiboken6 for pyside6-scintilla
The thing being bound is a Qt widget. Python code has to be able to put it in
a layout, parent it, connect its signals to slots, subclass it and pass
QColors and QStrings in and out -- i.e. it has to be a PySide6
QWidget, not merely look like one. Only shiboken6 generates wrappers that
plug into PySide6's own type system (the converters, the QObject ownership
model, the signal/slot machinery, PySide6's typesystems for QtCore/QtGui/
QtWidgets). A binding made with any other generator would be a separate type
universe that PySide6 doesn't recognize as a widget.
The cost is PySide6's toolchain:
- The Qt SDK, at the same minor version as the PySide6 the extension runs
against, because
scintilla_qtcompiles against Qt's headers. - One PySide6 minor per release. The extension calls into shiboken6's and
PySide6's C++ libraries, whose ABI changes between minors, so each release
pins
PySide6-Essentials>=6.X,<6.(X+1)(issue #15). - No build isolation in the workspace: CMake must find the PySide6/shiboken6 CMake packages of the environment the extension is imported from.
Why nanobind for lexilla
Lexilla's public surface is plain C++ -- a few free functions and one abstract interface -- with no Qt types anywhere. shiboken6 would drag in the whole Qt toolchain and the PySide6 version pin just to bind those, for no benefit. nanobind is small, has no Qt/PySide6 dependency, and fits a handful of functions plus one interface class. As a result lexilla builds with just a C++ compiler, has no runtime dependencies, and doesn't care which PySide6 version is installed.
Where they meet
- The lexer pointer.
create_lexer()returns aLexerwhoseILexer5*pointer is handed to the editor withSCI_SETILEXER, as a plain integer -- the two extensions never see each other's types.lexilla.pyside6_scintilla.set_lexer()wraps that for convenience; it's the only place lexilla imports pyside6-scintilla. - The Scintilla interface headers. For that pointer to be usable, both
sides must agree on
ILexer5's layout, declared in Scintilla'sILexer.h. pyside6-scintilla compiles against its vendored Scintilla; lexilla against a byte-identical copy of the same headers (lexilla's sdist must build on its own, so it can't reach into the other package), kept in sync bytools/sync_scintilla_headers.pyand checked in CI. See Verifying vendored Lexilla for what that does and doesn't guarantee across releases. - Tests. In the workspace and in CI, lexilla's tests (including its glue) run against the pyside6-scintilla built from the same commit, and its wheel tests install the pyside6-scintilla wheels built in the same run.