Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/publish-micropython-lib.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Publish pure-Python usdl2 to micropython-lib, MIP index, and TestPyPI (usdl2-py).
# Native usdl2 wheels are published by publish-testpypi.yml on the same tag.
#
# Release trigger: push tag vX.Y.Z (see scripts/publish_release_tag.sh).
#
# Secrets:
# MICROPYTHON_LIB_DEPLOY_TOKEN — PAT with contents:write on PyDevices/micropython-lib
# TESTPYPI_API_TOKEN — TestPyPI API token

name: Publish micropython-lib

on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: Semver X.Y.Z
type: string
required: false
sync_sources:
description: Sync usdl2 into micropython-lib
type: boolean
default: true
upload_testpypi:
description: Upload usdl2-py wheels to TestPyPI
type: boolean
default: false
publish_mip_index:
description: Rebuild mip/PyDevices and push gh-pages
type: boolean
default: true
commit_message:
description: micropython-lib commit message
type: string
required: false

permissions:
contents: read

# Sibling repos also push to micropython-lib; scripts rebase-retry on conflict.
concurrency:
group: publish-micropython-lib-${{ github.repository }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'PyDevices/usdl2'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve release version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
normalize() {
local v="${1#v}"
v="$(echo "$v" | tr -d '[:space:]')"
if [[ ! "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "Invalid semver: $1" >&2
exit 1
fi
echo "$v"
}
if [[ "${{ github.event_name }}" == "push" ]]; then
VERSION="$(normalize "${GITHUB_REF_NAME}")"
elif [[ -n "${INPUT_VERSION:-}" ]]; then
VERSION="$(normalize "$INPUT_VERSION")"
else
TAG="$(git describe --tags --exact-match 2>/dev/null || true)"
if [[ -z "$TAG" ]]; then
echo "Set version input or run from a vX.Y.Z tag." >&2
exit 1
fi
VERSION="$(normalize "$TAG")"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing usdl2-py / MIP release $VERSION"

- name: Verify publish secrets
env:
DEPLOY_TOKEN: ${{ secrets.MICROPYTHON_LIB_DEPLOY_TOKEN }}
TESTPYPI_TOKEN: ${{ secrets.TESTPYPI_API_TOKEN }}
run: |
set -euo pipefail
missing=()
if [[ -z "${DEPLOY_TOKEN}" ]]; then
missing+=("MICROPYTHON_LIB_DEPLOY_TOKEN")
fi
if [[ "${{ github.event_name }}" == "push" || "${{ inputs.upload_testpypi }}" == "true" ]]; then
if [[ -z "${TESTPYPI_TOKEN}" ]]; then
missing+=("TESTPYPI_API_TOKEN")
fi
fi
if [[ ${#missing[@]} -gt 0 ]]; then
echo "::error::GitHub did not supply secrets to PyDevices/usdl2: ${missing[*]}"
echo "Org Settings → Secrets → Actions → each secret → Repository access → include usdl2."
exit 1
fi

- uses: actions/checkout@v4
with:
repository: PyDevices/micropython-lib
ref: PyDevices
path: micropython-lib
token: ${{ secrets.MICROPYTHON_LIB_DEPLOY_TOKEN }}

- uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install publish tools
if: github.event_name == 'push' || inputs.upload_testpypi == true
run: pip install hatch twine

- name: Configure micropython-lib git
run: |
git -C micropython-lib config user.name 'github-actions[bot]'
git -C micropython-lib config user.email 'github-actions[bot]@users.noreply.github.com'
git -C micropython-lib remote set-url origin \
"https://x-access-token:${{ secrets.MICROPYTHON_LIB_DEPLOY_TOKEN }}@github.com/PyDevices/micropython-lib.git"

- name: Sync usdl2 into micropython-lib
if: github.event_name == 'push' || inputs.sync_sources == true
env:
MICROPYTHON_LIB_DIR: ${{ github.workspace }}/micropython-lib
USDL2_VERSION: ${{ steps.version.outputs.version }}
TESTPYPI_API_TOKEN: ${{ secrets.TESTPYPI_API_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
MSG="${{ inputs.commit_message }}"
if [[ -z "$MSG" ]]; then
MSG="usdl2: Release v${VERSION} (${GITHUB_SHA::7})."
fi
EXTRA=(--commit-message "$MSG" --push)
if [[ "${{ github.event_name }}" != "push" && "${{ inputs.upload_testpypi }}" != "true" ]]; then
EXTRA=(--skip-pypi --commit-message "$MSG" --push)
fi
chmod +x scripts/publish_micropython_lib.sh
./scripts/publish_micropython_lib.sh "${EXTRA[@]}"

- name: Publish MIP index to gh-pages
if: github.event_name == 'push' || inputs.publish_mip_index == true
env:
MICROPYTHON_LIB_DIR: ${{ github.workspace }}/micropython-lib
USDL2_DIR: ${{ github.workspace }}
MICROPYTHON_DIR: /tmp/micropython
MIP_INDEX_OUTPUT: /tmp/mip-index
run: |
chmod +x scripts/publish_mip_ghpages.sh
./scripts/publish_mip_ghpages.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ default.profraw
# Cursor IDE throwaways (do not ignore the whole .cursor/ tree)
.cursor/debug*
.cursor/plans/
wheels/
26 changes: 19 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# AGENTS.md — usdl2

Native **SDL2 subset for Python** (`import usdl2`). Pure C for MicroPython,
CircuitPython, and CPython. Android APK packaging is in
`pydisplay_android` (TestPyPI wheel + p4a recipes), not this repo. When this
module is not linked, pydisplay falls back to `src/add_ons/usdl2.py` (same
public contract).
Native and pure-Python **SDL2 subset for Python** (`import usdl2`). C for
MicroPython, CircuitPython, and CPython; ctypes/ffi fallback at `lib/usdl2.py`.
Android APK packaging is in `pydisplay_android` (TestPyPI wheel + p4a recipes),
not this repo.

| Product | Role |
|---------|------|
| **usdl2** (native) | C extension / usermod — TestPyPI `usdl2` |
| **usdl2-py** | Pure Python `lib/usdl2.py` — TestPyPI `usdl2-py`, MIP `usdl2` |

One tag `vX.Y.Z` publishes both (see `PUBLISHING.md`).

## Hard rule: SDL2 symbols only

Expand All @@ -27,15 +33,21 @@ or put the helper in the consumer package — never grow usdl2’s public surfac

When adding a binding, check the SDL2 docs/headers first. If the name is not
`SDL_*` (or an established SDL macro like `SDL_DEFINE_PIXELFORMAT`), it does
not belong here.
not belong here. Keep `lib/usdl2.py` in lockstep with the C public surface.

## Layout

- Root: `micropython.mk`, `circuitpython.mk`, `setup.py`, patch scripts
- `src/usdl2_mp.c` — MicroPython + CircuitPython
- `src/usdl2_cpy.c` — CPython extension
- `include/` — shared headers / `usdl2_module_globals.inc` / qstrs
- No `.c` / `.h` / `.inc` at repo root; no ctypes `python/` package
- `lib/usdl2.py` — pure-Python module (`src/` is C only)
- No `.c` / `.h` / `.inc` at repo root

## Publish

1. **Native wheels** — `publish-testpypi.yml` (cibuildwheel)
2. **usdl2-py** + MIP — `publish-micropython-lib.yml` (micropython-lib + TestPyPI + gh-pages)

## Smoke

Expand Down
77 changes: 49 additions & 28 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
# Publishing and releases

How changes in this repo become versioned **`usdl2`** wheels on [TestPyPI](https://test.pypi.org/project/usdl2/), and how to install them.
One annotated tag `vX.Y.Z` publishes **both** products at that version:

The CPython package ships a native extension (`src/usdl2_cpy.c`) linked against libSDL2. CI builds platform wheels with [cibuildwheel](https://cibuildwheel.pypa.io/):
| Product | Channel | Workflow |
|---------|---------|----------|
| **usdl2** (native) | TestPyPI (platform wheels) | `publish-testpypi.yml` |
| **usdl2-py** | TestPyPI (pure Python) + micropython-lib / MIP | `publish-micropython-lib.yml` |

## Pipeline

```text
usdl2 (commit on main)
./scripts/publish_release_tag.sh --push # next patch after highest v*
├─► publish-testpypi.yml
│ cibuildwheel → Linux + Windows + Android → usdl2
└─► publish-micropython-lib.yml
sync → micropython/usdl2/
hatch + twine → usdl2-py
rebuild mip/PyDevices → gh-pages
```

The CPython native package ships an extension (`src/usdl2_cpy.c`) linked against
libSDL2. CI builds platform wheels with [cibuildwheel](https://cibuildwheel.pypa.io/):

| Platform | Wheel tag |
|----------|-----------|
Expand All @@ -13,43 +34,49 @@ The CPython package ships a native extension (`src/usdl2_cpy.c`) linked against

Android wheels link against SDL2 prepared in CI (`scripts/ci_prepare_sdl2_android.sh`) but do **not** vendor `libSDL2.so` — the p4a SDL2 bootstrap provides it inside the APK.

MicroPython / CircuitPython consume `src/usdl2_mp.c` via `micropython.mk` / `circuitpython.mk` (not the PyPI wheel).

APK packaging and p4a recipes live in [pydisplay_android](https://github.com/PyDevices/pydisplay_android).

When the native module is unavailable, pydisplay uses `src/add_ons/usdl2.py` as a pure-Python fallback — that file lives in pydisplay, not this repo.

## Pipeline overview

```text
usdl2 (your machine)
commit → push main
./scripts/publish_release_tag.sh --push (or manual git tag vX.Y.Z)
usdl2: Publish TestPyPI
cibuildwheel → Linux + Windows + Android wheels → twine upload
```
MicroPython / CircuitPython consume `src/usdl2_mp.c` via `micropython.mk` / `circuitpython.mk` (not the PyPI wheel). APK packaging lives in [pydisplay_android](https://github.com/PyDevices/pydisplay_android).

## Version numbers

Format: **`X.Y.Z`** (semver). Later releases use the highest existing tag + 1 patch (`v0.0.7` → `0.0.8`, …).
Continue the native **usdl2** line (`v0.0.11` → `v0.0.12`, …). Preview:

```bash
./scripts/next_release_version.sh --verbose
./scripts/publish_release_tag.sh --dry-run
```

TestPyPI rejects re-uploading the same version — each release needs a new tag.

## Secrets

| Secret | Purpose |
|--------|---------|
| `TESTPYPI_API_TOKEN` | TestPyPI upload (native + usdl2-py) |
| `MICROPYTHON_LIB_DEPLOY_TOKEN` | PAT with `contents:write` on PyDevices/micropython-lib |

Grant both secrets to the **usdl2** repository (org secret repository access).

## Release (local clone)

```bash
git push origin main
./scripts/publish_release_tag.sh --push
```

## Install

```bash
# native
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ usdl2

# pure Python
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ usdl2-py
```

```python
mip.install("usdl2", index="https://PyDevices.github.io/micropython-lib/mip/PyDevices")
```

## Local wheel builds (cibuildwheel)

```bash
Expand All @@ -69,9 +96,3 @@ echo "0.0.0.dev" > VERSION
pipx run cibuildwheel --platform android
ls wheelhouse/*android*.whl
```

## Install from TestPyPI

```bash
pip install -i https://test.pypi.org/simple/ usdl2
```
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# usdl2

Native **SDL2 subset** for Python (`import usdl2`) — CPython wheels (Linux, Windows, Android) plus MicroPython / CircuitPython user C modules. Public names are **SDL2 symbols only**.
Native and pure-Python **SDL2 subset** for Python (`import usdl2`) — CPython
wheels (Linux, Windows, Android), MicroPython / CircuitPython user C modules,
plus a ctypes/ffi fallback. Public names are **SDL2 symbols only**.

When this module is not linked or installed, [pydisplay](https://github.com/PyDevices/pydisplay) falls back to [`add_ons/usdl2.py`](https://github.com/PyDevices/pydisplay/blob/main/src/add_ons/usdl2.py).
| Product | Pip / MIP | Role |
|---------|-----------|------|
| **usdl2** | TestPyPI `usdl2` | Native C extension (prefer on desktop/Android when available) |
| **usdl2-py** | TestPyPI `usdl2-py`, MIP `usdl2` | Pure-Python package (same public API) |

One release tag `vX.Y.Z` publishes both products at that version. See [PUBLISHING.md](PUBLISHING.md).

## Install

### CPython (TestPyPI)
### Native (TestPyPI)

```bash
pip install \
Expand All @@ -17,6 +24,22 @@ pip install \

Requires a system or bundled **SDL2** shared library at runtime (`libSDL2.so` / `SDL2.dll`). Android APKs use wheels tagged `android_21_*` with the APK’s p4a SDL2 bootstrap — see [pydisplay_android](https://github.com/PyDevices/pydisplay_android).

### Pure Python (TestPyPI)

```bash
pip install \
-i https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
usdl2-py
```

### MicroPython (MIP)

```python
import mip
mip.install("usdl2", index="https://PyDevices.github.io/micropython-lib/mip/PyDevices")
```

### Quick check

```bash
Expand Down Expand Up @@ -53,6 +76,7 @@ usdl2/
src/usdl2_mp.c # MicroPython + CircuitPython
src/usdl2_cpy.c # CPython Extension
include/usdl2.h, usdl2_module_globals.inc, …
lib/usdl2.py # pure-Python fallback (usdl2-py)
test_usdl2.py
```

Expand Down
Loading