Skip to content

Building and publishing

Prerequisites

  • Python 3.11+ and uv
  • Qt 6.10+ matching the PySide6/shiboken6 version resolved by uv sync (currently 6.11.x), 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.
  • CMake 3.25+ and Ninja -- available as dev dependencies via uv, or from <Qt>/Tools/CMake_64 / <Qt>/Tools/Ninja if you installed them through the Qt installer.

Local development build

The build is driven by uv sync (scikit-build-core + CMake + shiboken_generator_create_binding()). CMake needs CMAKE_PREFIX_PATH pointing at your Qt 6 installation:

# Windows (PowerShell)
$env:CMAKE_PREFIX_PATH = "C:\Qt\6.11.1\msvc2022_64"
uv sync
# Linux / macOS
export CMAKE_PREFIX_PATH=/path/to/Qt/6.11.1/gcc_64   # or macos/clang_64, etc.
uv sync

Tip

Windows: to avoid setting CMAKE_PREFIX_PATH every session, persist it to the user environment instead:

$existing = [System.Environment]::GetEnvironmentVariable("CMAKE_PREFIX_PATH", "User")
$new = if ($existing) { "$existing;C:\Qt\6.11.1\msvc2022_64" } else { "C:\Qt\6.11.1\msvc2022_64" }
[System.Environment]::SetEnvironmentVariable("CMAKE_PREFIX_PATH", $new, "User")

Restart your terminal (or VS Code) afterward -- the new value only applies to terminals opened after it's set.

Forcing a rebuild

uv sync doesn't always notice changes to CMakeLists.txt or the typesystem files -- it may print "Checked N packages" without rebuilding. Force a rebuild + reinstall with:

uv sync --reinstall-package pyside6-scintilla

Faster C++/binding-only iteration

For changes to CMakeLists.txt, bindings.xml, or the binding glue, uv sync --reinstall-package does a clean, isolated rebuild every time. The configure/build/install Make targets are faster for repeated iteration: they reuse an incremental CMake build tree (build/venv/) and, via the venv preset's CMAKE_INSTALL_PREFIX, drop the rebuilt extension and shared libs directly into src/pyside6_scintilla/, where import pyside6_scintilla picks them up immediately.

make configure   # once, to set up build/venv/
make install      # rebuild + drop the extension into src/pyside6_scintilla/

Windows: the venv preset uses the Ninja generator, which needs cl.exe on PATH -- run these from an x64 Native Tools Command Prompt for VS 2022 (or call vcvarsall.bat x64 first). uv sync doesn't need this, since scikit-build-core uses the Visual Studio generator, which locates MSVC itself.

Verifying the build

uv run python -c "from pyside6_scintilla import Scintilla, ScintillaEditBase; print(Scintilla.Message.SetText)"
uv run pytest
uv run ruff check .
uv run pyright

or, via the Makefile:

make setup
make test
make lint

Continuous integration

.github/workflows/ci.yml runs on every push and on pull requests targeting master, across ubuntu-latest, windows-latest, and ubuntu-24.04-arm (Linux x86_64/aarch64 + Windows x86_64). Each job:

  1. Installs Qt 6.11.1 plus the Qt6Core5Compat module via jurplel/install-qt-action -- matching the PySide6 6.11.1 pinned in uv.lock, since CI and local builds must use the same Qt minor version to avoid ABI mismatches (see Prerequisites above).
  2. Points CMAKE_PREFIX_PATH at that Qt install, and on Linux also sets LD_LIBRARY_PATH so scintilla_qt.so can find libQt6Core5Compat.so.6 at runtime (see bindings.md issue 3).
  3. Runs uv sync, uv run pytest (with QT_QPA_PLATFORM=offscreen for a headless pytest-qt), and uv run ruff check . / ruff format --check . / uv run pyright -- the same commands as Verifying the build above.

CI doesn't run the example app, and only builds the dev install (not wheels) -- see Building wheels below for the wheel-building workflow.

Building wheels

uv build

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, covering cp31{1,2,3,4} for Linux x86_64 (manylinux_2_34), Windows x86_64, and macOS arm64/x86_64 (the latter cross-built via Rosetta 2 on the macos-latest runner). It's workflow_dispatch-only for manual/ad-hoc builds, and also exposed as a reusable workflow_call job consumed by publish.yml (see Publishing to PyPI).

Platform-specific bundling caveats

Qt6Core5Compat is required by scintilla_qt but isn't bundled in the PySide6 wheel on any platform. bindings/CMakeLists.txt installs it alongside the extension for all three platforms (a flat .dll/.so on Windows/Linux, a framework bundle on macOS), and each platform's repair-wheel-command in pyproject.toml (delvewheel/auditwheel/delocate) excludes the Qt/PySide6/shiboken6 libraries provided at runtime by the installed PySide6 package. See bindings.md issue 3 for the details and history.

Publishing to PyPI

  1. Bump __version__ in src/pyside6_scintilla/__init__.py -- this is the single source of truth for the package version ([tool.scikit-build.metadata.version] in pyproject.toml reads it via regex). Follow Scintilla's version: X.Y.Z.N, where X.Y.Z is the vendored Scintilla release and N increments for binding-only changes against that release. See bindings.md for the Scintilla update process.
  2. Push the version bump to master.
  3. Create a GitHub Release with a tag matching the version (e.g. v5.6.3.0). This triggers .github/workflows/publish.yml, which builds the sdist and all wheels (via wheels.yml), publishes them to TestPyPI, and then -- for real releases only -- to PyPI.

No PyPI API tokens are involved: both the TestPyPI and PyPI publish steps use PyPI trusted publishing (GitHub OIDC), configured per-project on test.pypi.org/pypi.org against this repo's publish.yml workflow and the testpypi/pypi GitHub environments.

The pyside6-scintilla PyPI name was claimed early with a placeholder 0.0.0 release (pure-Python, hatchling backend, published manually via uv publish) to prevent squatting -- the first real release is the first one published via publish.yml.