Skip to content

Commit ba688b9

Browse files
authored
ci: gate publish on cross-platform tests; fix GPU + resource scripts (#232)
- Add tests.yml — 9-cell matrix (Ubuntu/Windows/macOS x py3.11/3.12/3.13) - Gate python-publish-cpu/gpu and release on the matrix via needs: test - Bump dep floors; add huggingface_hub floor - Bump action versions across all workflows - Add CLAUDE.md (architecture + pre-publish playbook); align .cursorrules - Fix scripts/compile_languages.py and generate_languages.py for PyQt6 (pyrcc dropped in Qt6; switch to pyside6-rcc + pyside6-lrelease) - Fix GPU publish path: sed pyproject.toml so the wheel is anylabeling-gpu (PEP 621 silently rejects setup.py's name override) - tests.yml fires on both main and master pushes/PRs Closes #227.
1 parent b69ea6b commit ba688b9

9 files changed

Lines changed: 544 additions & 51 deletions

File tree

.cursorrules

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,44 @@
1-
- Use PyQt6 for GUI.
2-
- Split code into files when possible.
3-
- Make code clean and understandable.
4-
- Optimize code for performance and memory usage.
1+
# anylabeling — guidance for AI assistants
2+
3+
This is a desktop image-annotation app on PyQt6, with an auto-labeling
4+
backend that runs ONNX models (YOLOv5/v8, SAM1/MobileSAM, SAM2, SAM3)
5+
and a CoreML path for SAM2 on macOS. PyPI ships two parallel packages
6+
from the same source tree: `anylabeling` (CPU, default) and
7+
`anylabeling-gpu` (Linux/Windows, swaps in `onnxruntime-gpu`).
8+
9+
## Conventions
10+
- Use **PyQt6** (not PyQt5). The migration happened in commit 9735fe8.
11+
- The macOS install path **excludes PyQt6** from `pyproject.toml`
12+
(`platform_system != 'Darwin'`); macOS users get it via conda.
13+
Don't add a Darwin-side floor without changing the install story.
14+
- Keep code split into focused files; avoid growing `label_widget.py`
15+
(already ~3.2k LOC) further when a new widget would do.
16+
17+
## Architecture cheat sheet
18+
- Entry point: `anylabeling/app.py` → `MainWindow` → `LabelingWrapper` →
19+
`LabelingWidget` (the "god widget" — owns canvas, file list, toolbars).
20+
- Auto-labeling: `anylabeling/services/auto_labeling/`
21+
- `registry.py` → `@ModelRegistry.register("type-name")` decorator
22+
- `model_manager.py` → loads `models.yaml`, downloads weights to
23+
`~/anylabeling_data/models/`, dispatches `predict_shapes_threading()`
24+
- `models.yaml` (`anylabeling/configs/auto_labeling/`) is the model
25+
catalog the UI reads. New model = new entry **and** registered class.
26+
- `segment_anything.py` auto-detects SAM1/SAM2/SAM3 from ONNX inputs.
27+
- CPU/GPU duality: `setup.py` reads `__preferred_device__` from
28+
`anylabeling/app_info.py`; publish workflows `sed` it before `python -m build`.
29+
30+
## Pre-publish gate
31+
- `.github/workflows/tests.yml` runs a 9-cell matrix (Ubuntu/Windows/macOS
32+
× py3.11/3.12/3.13). All publish/release workflows declare `needs: test`.
33+
- Why it exists: `anylabeling-gpu==0.4.30` shipped to PyPI broken because
34+
no automated step ran `pip install .` against current dep floors before
35+
publish (issue #227, `imgviz>=2.0` returned a read-only colormap).
36+
- Always run `python -m unittest discover -s tests` in a fresh venv before
37+
tagging a release. See `CLAUDE.md` for the full pre-publish playbook.
38+
39+
## Resource regeneration
40+
- PyQt6 dropped `pyrcc`. To rebuild `anylabeling/resources/resources.py`
41+
use `python scripts/compile_languages.py` — it shells out to
42+
`pyside6-rcc` and `pyside6-lrelease` and rewrites imports back to PyQt6.
43+
- `PySide6-Essentials` is a `[dev]` extra for this reason only; runtime
44+
has no PySide6 dependency.

.github/workflows/python-publish-cpu.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ on:
55
- 'v*'
66

77
jobs:
8+
test:
9+
name: Test before publish
10+
uses: ./.github/workflows/tests.yml
11+
812
build-n-publish:
13+
needs: test
914
if: startsWith(github.ref, 'refs/tags/')
1015
name: Build and publish CPU 🐍📦 to PyPI
1116
runs-on: ubuntu-latest
@@ -15,9 +20,9 @@ jobs:
1520
permissions:
1621
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
1722
steps:
18-
- uses: actions/checkout@v3
23+
- uses: actions/checkout@v4
1924
- name: Set up Python
20-
uses: actions/setup-python@v4
25+
uses: actions/setup-python@v5
2126
with:
2227
python-version: "3.x"
2328
- name: Install pypa/build

.github/workflows/python-publish-gpu.yml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ on:
77

88
jobs:
99

10+
test:
11+
name: Test before publish
12+
uses: ./.github/workflows/tests.yml
13+
1014
build-n-publish-gpu:
15+
needs: test
1116
if: startsWith(github.ref, 'refs/tags/')
1217
name: Build and publish GPU 🐍📦 to PyPI
1318
runs-on: ubuntu-latest
@@ -17,9 +22,9 @@ jobs:
1722
permissions:
1823
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
1924
steps:
20-
- uses: actions/checkout@v3
25+
- uses: actions/checkout@v4
2126
- name: Set up Python
22-
uses: actions/setup-python@v4
27+
uses: actions/setup-python@v5
2328
with:
2429
python-version: "3.x"
2530
- name: Install pypa/build
@@ -29,9 +34,26 @@ jobs:
2934
run: >-
3035
sed -i'' -e 's/\_\_preferred_device\_\_[ ]*=[ ]*\"[A-Za-z0-9]*\"/__preferred_device__ = "GPU"/g' anylabeling/app_info.py
3136
37+
# PEP 621 makes pyproject.toml `[project]` metadata authoritative — setup.py
38+
# cannot override `name` or replace `dependencies`. Rewrite pyproject.toml
39+
# in place so the GPU wheel ships as `anylabeling-gpu` with onnxruntime-gpu.
40+
- name: Rewrite pyproject.toml for the GPU package
41+
run: |
42+
sed -i 's/^name = "anylabeling"$/name = "anylabeling-gpu"/' pyproject.toml
43+
sed -i 's/"onnxruntime>=1.20.0"/"onnxruntime-gpu>=1.20.0"/' pyproject.toml
44+
echo "--- after rewrite ---"
45+
grep -E '^name|onnxruntime' pyproject.toml
46+
3247
- name: Build a binary wheel and a source tarball
3348
run: >-
3449
python -m build --wheel --outdir dist/ .
50+
51+
- name: Verify built wheel is anylabeling-gpu
52+
run: |
53+
ls dist/
54+
whl=$(ls dist/anylabeling_gpu-*.whl)
55+
python -m zipfile -e "$whl" /tmp/whl_extract/
56+
grep -E '^Name:|^Requires-Dist: onnxruntime' /tmp/whl_extract/anylabeling_gpu-*.dist-info/METADATA
3557
- name: Publish distribution 📦 to PyPI
3658
if: startsWith(github.ref, 'refs/tags')
3759
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/release.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ permissions:
99
contents: write
1010

1111
jobs:
12+
test:
13+
name: Test before release
14+
uses: ./.github/workflows/tests.yml
15+
1216
release:
17+
needs: test
1318
if: startsWith(github.ref, 'refs/tags/')
1419
runs-on: ubuntu-latest
1520

@@ -62,11 +67,11 @@ jobs:
6267
contents: write
6368

6469
steps:
65-
- uses: actions/checkout@v2
70+
- uses: actions/checkout@v4
6671
with:
6772
submodules: true
6873

69-
- uses: conda-incubator/setup-miniconda@v2
74+
- uses: conda-incubator/setup-miniconda@v3
7075
with:
7176
python-version: "3.12"
7277
miniconda-version: "latest"
@@ -123,11 +128,11 @@ jobs:
123128
device: [CPU, GPU]
124129

125130
steps:
126-
- uses: actions/checkout@v2
131+
- uses: actions/checkout@v4
127132
with:
128133
submodules: true
129134

130-
- uses: conda-incubator/setup-miniconda@v2
135+
- uses: conda-incubator/setup-miniconda@v3
131136
with:
132137
python-version: "3.12"
133138
miniconda-version: "latest"

.github/workflows/tests.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
tags: ['v*']
7+
pull_request:
8+
branches: [main, master]
9+
workflow_call:
10+
11+
jobs:
12+
test:
13+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest, windows-latest, macos-latest]
19+
python-version: ["3.11", "3.12", "3.13"]
20+
env:
21+
# Headless Qt — otherwise PyQt6 tries to talk to a display
22+
QT_QPA_PLATFORM: offscreen
23+
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Set up Python ${{ matrix.python-version }}
28+
uses: actions/setup-python@v5
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
cache: pip
32+
33+
- name: Install Qt system libraries (Linux)
34+
if: runner.os == 'Linux'
35+
run: |
36+
sudo apt-get update
37+
sudo apt-get install -y --no-install-recommends \
38+
libegl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-cursor0 \
39+
libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 \
40+
libxcb-render-util0 libxcb-shape0 libxcb-xinerama0 libxcb-xkb1
41+
42+
- name: Install PyQt6 (macOS — pyproject.toml excludes it on Darwin)
43+
if: runner.os == 'macOS'
44+
run: python -m pip install "PyQt6>=6.7.0"
45+
46+
- name: Install package
47+
run: |
48+
python -m pip install --upgrade pip
49+
pip install .
50+
51+
- name: Run unit tests
52+
run: python -m unittest discover -s tests -v

0 commit comments

Comments
 (0)