Building and publishing
This repo is a uv workspace with two packages, each released to PyPI on its own:
| Package | Directory | Binding | Needs a Qt SDK to build |
|---|---|---|---|
pyside6-scintilla |
packages/pyside6-scintilla/ |
shiboken6 | yes |
lexilla |
packages/lexilla/ |
nanobind | no |
The dev environment (.venv at the repo root) builds both from source,
so the prerequisites below are pyside6-scintilla's. lexilla-specific notes:
lexilla build notes, and How the packages are built for why the two differ.
Prerequisites
- Python 3.12+ (the workspace's floor -- lexilla needs 3.12; pyside6-scintilla itself supports 3.11) and uv
- Qt 6.12.x, the PySide6/shiboken6 minor pinned in
packages/pyside6-scintilla/pyproject.toml(the SDK must match the minor the extension links against; patch releases are interchangeable), including the Qt 5 Compatibility module (Qt6Core5Compat) -- not installed by default, add it via the Qt Maintenance Tool/online installer for your kit. - A C++17 compiler:
- Windows: MSVC (Visual Studio 2022). Use the same MSVC toolchain PySide6's wheels are built with, to avoid ABI mismatches against the Qt/shiboken libraries bundled in the PySide6 wheel.
- macOS: Xcode command line tools.
- Linux: GCC or Clang. Not Ubuntu 26.04 or newer (or another
distro with libxml2 2.14+):
shiboken6-generator's prebuiltshiboken6binary (6.11 and 6.12 alike) links the systemlibxml2.so.2, which libxml2 2.14 replaced withlibxml2.so.16, so the build fails with "error while loading shared libraries: libxml2.so.2". Build on Ubuntu 24.04 (as CI does) or in a container/manylinux image. Reported upstream as PYSIDE-3504. Only building is affected: the extension doesn't use libxml2, and the published wheels run fine on 26.04 (CI'sruntimejob checks this). - CMake 3.25+ and Ninja -- available as dev dependencies via
uv, or from<Qt>/Tools/CMake_64/<Qt>/Tools/Ninjaif you installed them through the Qt installer.
Local development build
From the repo root, uv sync builds both packages (editable) plus the dev
tools into .venv:
uv sync
pyside6-scintilla's CMake finds the Qt SDK matching the installed PySide6
by itself (packages/pyside6-scintilla/cmake/MatchingQt.cmake, issue #25):
it reads PySide6's version and looks for that Qt release where the Qt
Maintenance Tool installs it -- C:/Qt/<version>/msvc2022_64 on Windows,
~/Qt/<version>/macos on macOS, ~/Qt/<version>/gcc_64 (or gcc_arm64) on
Linux, also /opt/Qt -- preferring PySide6's exact version, then the newest
patch release of the same minor. So after bumping the PySide6 minor, install
the matching Qt and rebuild; nothing to reconfigure.
For an SDK elsewhere, set CMAKE_PREFIX_PATH; a matching Qt there is used
as is (CI sets it explicitly). One that doesn't match -- say, still the
previous minor's -- is skipped in favour of a matching standard install, and
without one the build stops with a message naming the Qt version it needs:
export CMAKE_PREFIX_PATH=/path/to/Qt/6.12.0/gcc_64 # PowerShell: $env:CMAKE_PREFIX_PATH = "..."
uv sync
The packages depend on each other only for development -- lexilla's glue
module and tests use pyside6-scintilla, pyside6-scintilla's lexilla example
uses lexilla -- and always as workspace sources ([tool.uv.sources] in the
root pyproject.toml), never the PyPI copies.
Forcing a rebuild
uv sync doesn't always notice changes to CMakeLists.txt, the typesystem
files or the binding sources -- it may print "Checked N packages" without
rebuilding. Force a rebuild + reinstall of one package with:
uv sync --reinstall-package pyside6-scintilla # or lexilla
For faster C++/binding-only iteration on pyside6-scintilla, see its build notes.
Verifying the build
make test # both packages' tests (uv run pytest)
make lint # ruff, docs/header/generator sync checks, pyright
make lists the per-package targets (scintilla-*, lexilla-*); see the
top-level Makefile.
Continuous integration
.github/workflows/ci.yml runs on every push and on pull requests targeting
master, across Ubuntu 24.04 x86_64/aarch64, Windows and macOS. Each job:
- Sets up the toolchain with
.github/actions/setup: Qt 6.12.0 plusQt6Core5Compatviajurplel/install-qt-action(the one place CI names the Qt version),CMAKE_PREFIX_PATH(and, on Linux,LD_LIBRARY_PATHsoscintilla_qt.socan findlibQt6Core5Compat.so.6at runtime -- see Bindings architecture issue 3), clang on Linux, thenuv sync(both packages). - Runs both packages' tests (with
QT_QPA_PLATFORM=offscreenfor a headlesspytest-qt) and the same checks asmake lint.
The Ubuntu 24.04 job also builds both wheels; the runtime jobs install them
on Ubuntu 26.04 and run the test suites against them, once per end-point of
the supported Python range: 3.11 (pyside6-scintilla's floor; its tests only,
since lexilla needs 3.12), 3.12 (lexilla's floor) and the newest, 3.15. The
wheels are stable-ABI, so the versions in between run the same build.
Building wheels
uv build --package pyside6-scintilla # or lexilla
produces a wheel + sdist in dist/, for the current platform only.
Multi-platform wheels are built in CI by .github/workflows/wheels.yml via
cibuildwheel, one package at a time
(package-dir: packages/<name>), each with its own [tool.cibuildwheel]
config in its pyproject.toml:
- pyside6-scintilla: a single
cp311-abi3wheel per platform -- Linux x86_64 (manylinux_2_34), Linux aarch64, Windows x86_64, and macOS arm64/x86_64 (the latter cross-built via Rosetta 2 on themacos-latestrunner). One wheel per platform, not one per Python version, because the extension is compiled against Python's limited API (FORCE_LIMITED_APIinbindings/CMakeLists.txt,wheel.py-api = "cp311"), same as PySide6's own wheels. - lexilla: a single
cp312-abi3wheel per platform, for the same reason (nanobind's stable ABI needs CPython 3.12). Its wheel tests need pyside6-scintilla, sowheels.ymlalways builds pyside6-scintilla's wheels first and lexilla's tests install those (packages/lexilla/tools/install_pyside6_scintilla.py), not PyPI's.
wheels.yml is workflow_dispatch-able for ad-hoc builds and is the
reusable job publish.yml calls.
Bundling caveats for pyside6-scintilla's wheels, and the checklist for moving it to a new PySide6 minor, are in its build notes.
Publishing to PyPI
One tag, one package: pushing a tag named <package>-<version> publishes
that package at that version.
- Bump
__version__in the package'ssrc/<module>/__init__.py-- the single source of truth for its version ([tool.scikit-build.metadata.version]reads it via regex). Versions follow the vendored upstream release:X.Y.Z.N, whereX.Y.Zis the vendored Scintilla (pyside6-scintilla) or Lexilla (lexilla) release andNincrements for binding-only changes against it. Date the package'sCHANGELOG.mdsection for that version (## [X.Y.Z.N] - YYYY-MM-DD). - Push the version bump to
master, and let CI pass. - Tag and push:
git tag pyside6-scintilla-5.6.3.10 && git push origin pyside6-scintilla-5.6.3.10(orlexilla-...). This triggers.github/workflows/publish.yml, which checks the tag against__version__, builds the sdist and all wheels (viawheels.yml), publishes them to TestPyPI, then to PyPI, and creates the GitHub Release with the changelog section as its notes.
Running publish.yml by hand (workflow_dispatch, choosing the package) is
a dry run: it stops at TestPyPI.
No PyPI API tokens are involved: both publish steps use
PyPI trusted publishing (GitHub
OIDC), configured per package on test.pypi.org/pypi.org against this repo,
the publish.yml workflow and the testpypi/pypi GitHub environments
(see TestPyPI).
Releases before the monorepo were tagged v5.6.3.N (pyside6-scintilla, in
this repo) and lexilla-5.5.0.N (lexilla, imported from lexilla-py, where
they were v5.5.0.N).