Skip to content

srthorat/pjsua2-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pjsua2-python

Build Wheels PyPI

Pre-built binary wheels for the PJSUA2 Python bindings, packaged from upstream pjproject.

pip install pjsua2-python

Install a specific bundled PJSIP line when your application needs one:

# Latest/default line, bundles pjproject 2.15.1
pip install "pjsua2-python==2.15.1.post1"

# Legacy line, bundles pjproject 2.10
pip install "pjsua2-python==2.10.post1"

Available for Linux (x86_64), macOS (Intel + Apple Silicon), and Windows (AMD64) on Python 3.8–3.12.


Reusable binary wheel packaging for PJSUA2 Python bindings built from upstream pjproject.

This repository is designed for the problem you called out directly:

  • no official wheel
  • brittle local builds
  • repeated manual SWIG compilation per project

The repo is wheel-first. It builds PJSIP/PJSUA2, stages the generated SWIG artifacts into a Python package, and emits installable wheels with cibuildwheel.

Packaging model

pjsua2-python is a native extension package, not a pure Python package.

The package version normally tracks the bundled pjproject version. For packaging-only fixes, the Python package may use a post-release suffix while the bundled upstream ref stays on the original pjproject tag.

Examples:

  • pjsua2-python==2.15.1 packages pjproject 2.15.1
  • pjsua2-python==2.15.1.post1 packages pjproject 2.15.1
  • pjsua2-python==2.10.post1 packages pjproject 2.10
  • future pjsua2-python==2.16.x should package pjproject 2.16.x

This repository treats pjsua2/_version.py as the Python package version and pjsua2/_pjproject_ref as the bundled upstream ref.

That means pip install pjsua2-python works by publishing multiple wheels, then letting pip choose the right one for the current machine:

  • Linux x86_64
  • macOS x86_64
  • macOS arm64
  • Windows AMD64
  • Python 3.8 through 3.12

For this scope, that means 20 wheels total.

There is no correct single binary wheel that works unchanged across Linux, macOS, and Windows. Serious projects like NumPy and PyTorch solve this exactly the same way: one wheel per platform and per Python ABI.

Target outputs

Examples:

  • pjsua2_python-2.15.1-cp38-cp38-manylinux2014_x86_64.whl
  • pjsua2_python-2.15.1-cp311-cp311-macosx_13_0_x86_64.whl
  • pjsua2_python-2.15.1-cp311-cp311-macosx_14_0_arm64.whl
  • pjsua2_python-2.15.1-cp311-cp311-win_amd64.whl

What this repo does

  1. Clones pjproject
  2. Checks out a configured release/tag/branch
  3. Builds upstream PJSIP and the SWIG Python binding
  4. Copies generated artifacts into the local pjsua2 package
  5. Builds wheels for each Python/platform target with cibuildwheel

Repository layout

pjsua2-python/
├── .github/workflows/build.yml
├── pjsua2/
│   ├── __init__.py
│   └── _version.py
├── scripts/
│   ├── build_pjsip_linux.sh
│   ├── build_pjsip_macos.sh
│   ├── build_pjsip_windows.ps1
│   ├── build_linux_wheel.sh
│   ├── build_macos_wheel.sh
│   ├── build_windows_wheel.ps1
│   ├── check_linux_build_tools.sh
│   ├── check_macos_build_tools.sh
│   ├── check_windows_build_tools.ps1
│   ├── config_site.h
│   ├── get_package_version.py
│   ├── install_linux_build_deps.sh
│   ├── install_macos_build_deps.sh
│   ├── install_windows_build_deps.ps1
│   ├── setup_pjsua2_windows.py   ← MSVC-explicit build script (replaces upstream setup.py)
│   ├── smoke_test.py             ← end-to-end wheel validation + SIP reg/unreg
│   ├── stage_bindings.py
│   ├── test_import.py
│   └── test_local_cp312.sh
├── MANIFEST.in
└── pyproject.toml

Supported build targets

  • Linux: manylinux2014_x86_64
  • macOS Intel: x86_64
  • macOS Apple Silicon: arm64
  • Windows: AMD64
  • Python: 3.8 through 3.12

Bundled upstream version

The Python package version is declared in pjsua2/_version.py. The bundled pjproject checkout is declared separately in pjsua2/_pjproject_ref.

For normal releases these usually match. For packaging-only fixes, the package version may be a post-release while the bundled upstream ref stays on the original pjproject tag.

Examples:

  • package 2.15.1 with pjproject ref 2.15.1
  • package 2.15.1.post1 with pjproject ref 2.15.1
  • package 2.10.post1 with pjproject ref 2.10

Release tags should follow the Python package version, for example v2.15.1.post1.

Override it when needed:

PJSIP_REF=master python -m cibuildwheel --output-dir dist

If you want strict reproducibility, keep using a tag. If you want the newest upstream commit, set PJSIP_REF=master or a commit SHA.

Older PJSIP releases

Older upstream releases can be built by setting PJSIP_REF to the matching tag, for example:

PJSIP_REF=2.10 python -m cibuildwheel --output-dir dist

For published wheels, prefer a release branch per upstream line. For example, keep main on the latest PJSIP line and create release/2.10 where pjsua2/_pjproject_ref contains 2.10. If you need packaging-only fixes for an already published upstream version, use a post-release package version such as 2.10.post1 while keeping pjsua2/_pjproject_ref set to 2.10.

PJSIP 2.10 exposes the legacy digest authentication API. It does not provide AuthCredInfo.algoType or constants such as PJSIP_AUTH_ALGORITHM_MD5, so application and test code should set those only when present.

Design choices

1. Wheel-first, not source-first

This repo is optimized for prebuilt wheels. That is the real operational win. Source installs are intentionally not the primary path.

2. Minimal upstream feature set

The supplied config_site.h disables video and sound-device support to reduce build friction and external library sprawl. That keeps wheel creation practical.

If your application needs additional PJMEDIA features, expand scripts/config_site.h and the dependency install scripts deliberately.

3. Static-first upstream linking

The build scripts prefer static upstream linkage to reduce runtime sidecar libraries and keep pip install closer to a self-contained experience.

Local build examples

Linux

Full build (all Python versions)

./scripts/install_linux_build_deps.sh
python -m pip install --upgrade pip build cibuildwheel auditwheel
python -m cibuildwheel --platform linux --output-dir dist

This pulls the manylinux2014 Docker image and builds wheels for cp38–cp312 inside the same container CI uses. Resulting wheels land in dist/.

Quick single-version validation (recommended for iterating on build script changes)

Use the focused helper to test cp312 only (the most likely to break due to distutils removal):

# Prerequisites — one-time setup
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo pip3 install --break-system-packages cibuildwheel

# Run the cp312-only test (~5 min, uses the exact manylinux2014 Docker image CI uses)
bash scripts/test_local_cp312.sh

The script at scripts/test_local_cp312.sh sets CIBW_BUILD=cp312-* and runs cibuildwheel inside the manylinux2014 container, emitting the wheel into dist/. Failures are identical to what CI would show, without the 15-minute wait per push.

Why not just run bash scripts/build_pjsip_linux.sh directly? The host build uses your local Python and a cached build/pjproject tree, so it passes even when the CI container would fail (missing setuptools, wrong Python version, stale build artifacts). Always use the Docker-based test to validate changes to the build scripts.

macOS

./scripts/install_macos_build_deps.sh
python -m pip install --upgrade pip build cibuildwheel delocate
python -m cibuildwheel --platform macos --output-dir dist

If you run scripts/build_pjsip_macos.sh directly, it now checks for required tools before starting the build.

Windows

./scripts/install_windows_build_deps.ps1
py -m pip install --upgrade pip build cibuildwheel
py -m cibuildwheel --platform windows --output-dir dist

For a host-built Windows wheel after staging the native artifacts:

./scripts/build_windows_wheel.ps1

The Windows build path now checks for git, python, swig, and either msbuild or cl before starting.

macOS host wheel helper

./scripts/build_macos_wheel.sh

That helper builds the package on the current macOS host and runs delocate into wheelhouse/.

Verify a wheel

pip install pjsua2-python
python scripts/smoke_test.py --expected-version 2.15.1.post1

Expected output:

============================================================
  import pjsua2        ... PASS
  version check        ... PASS [2.15.1.post1]
  module attributes    ... PASS [96 attrs]
  endpoint lifecycle   ... PASS
  SIP register         ... PASS [200 OK (expires=3600s)]
  SIP unregister       ... PASS [200 OK (expires=0)]
============================================================
Results: 6 passed, 0 failed

GitHub Actions

The included workflow builds wheels on:

  • ubuntu-22.04 (manylinux2014_x86_64)
  • macos-15-intel (x86_64)
  • macos-14 (arm64)
  • windows-2022 (AMD64)

For every push to main the pipeline runs these jobs in order:

  1. Buildcibuildwheel builds 5 wheels (cp38–cp312) per platform = 20 wheels total
  2. Build source packagepython -m build --sdist + twine check
  3. Verify — 20 smoke-test jobs (one per wheel): installs the wheel, checks version, validates 96+ pjsua2 class/struct attributes, runs endpoint lifecycle, and tests SIP REGISTER + UNREGISTER against opensips.org (timeouts on network-filtered runners are treated as warnings, not failures)

For tagged releases (v*) two additional jobs run after verify passes:

  1. Create GitHub Release — attaches all 20 wheels, the sdist, and SHA256SUMS.txt
  2. Publish to PyPI — uploads via OIDC trusted publishing

SIP smoke test credentials

The SIP register/unregister tests use repository variables (not secrets):

Variable Example
SIP_SERVER opensips.org
SIP_USER sthorat1
SIP_PASSWORD your password

Set these under Settings → Secrets and variables → Actions → Variables. If absent, the SIP tests are skipped (non-fatal).

GitHub Actions is the release authority for distributable wheels. Local host wheel helpers are mainly for validation and debugging.

Keep the release order strict:

  1. fix GitHub Actions until all build and release jobs are green
  2. verify the generated artifacts on GitHub
  3. only then configure or use PyPI publishing

How pip install works for users

After the wheels are published to PyPI, users run:

pip install pjsua2-python

That installs the newest published package version, currently the 2.15.1 line once 2.15.1.post1 is released. To install a specific bundled PJSIP line, pin the package version:

pip install "pjsua2-python==2.15.1.post1"
pip install "pjsua2-python==2.10.post1"

PyPI keeps both releases. Publishing 2.10.post1 does not replace 2.15.1.post1; users who need the older PJSIP line must request it explicitly.

pip inspects the current machine:

  • OS
  • CPU architecture
  • Python version

Then it downloads the matching wheel automatically. Users do not choose among the 20 wheels manually.

Important caveats

Linux compatibility

Linux wheels are built against manylinux2014 to keep glibc compatibility broad.

Important distinction:

  • Local wheels built on a normal host reflect that host libc and OpenSSL baseline.
  • CI wheels built by cibuildwheel in the configured manylinux container are the portable artifacts you want to publish.

On this Ubuntu 24.04 host, the repaired local wheel validated successfully but landed at manylinux_2_39_x86_64, not manylinux2014_x86_64. That is expected for a host build and is exactly why the repo uses cibuildwheel for release artifacts.

macOS compatibility

Apple Silicon and Intel are built separately in the workflow. If you prefer universal2 later, change the workflow and CIBW_ARCHS strategy.

Windows build path

Windows is the most fragile target in the PJSIP ecosystem. The script here is structured for CI and upstream Python SWIG packaging, but you should expect occasional upstream breakage across Visual Studio and Python releases.

That is exactly why centralizing this in one repo is the right move.

Local vs release builds

  • Local helper scripts validate that the packaging flow works on a given machine.
  • Release wheels should come from cibuildwheel in CI.
  • On Linux specifically, the local repaired wheel policy reflects the host baseline, while CI should emit the intended manylinux2014 wheels.

About

Prebuilt cross-platform Python wheels for PJSUA2, version-matched to upstream PJSIP

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors