Skip to content

Commit d0aa8eb

Browse files
feat: add GHA workflow to build opencv wheels without ffmpeg (#4335)
## Summary - Adds a `workflow_dispatch` GHA workflow that builds `opencv-python-headless` from source with `WITH_FFMPEG=OFF` on amd64 and arm64 - Wheels are uploaded as a GitHub release so Dockerfiles can download them at build time - Eliminates the 14 bundled ffmpeg CVEs present in the stock PyPI wheels ## Test plan - [ ] Trigger the workflow manually with the default inputs (`opencv-python-headless==4.12.0.88`) - [ ] Verify wheels are produced for both architectures - [ ] Verify the GitHub release is created with both wheels attached - [ ] Confirm the built wheels have no `.libs` directory (no bundled ffmpeg) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: changes are confined to GitHub Actions workflows, with the main impact being additional CI/release automation and a simple tag-based guard on PyPI publishing. > > **Overview** > Adds a new `workflow_dispatch` GitHub Actions workflow to build `opencv-contrib-python-headless` wheels from source for `amd64` and `arm64` with `WITH_FFMPEG=OFF`, validate the resulting `cv2` install has *no* bundled `.libs` and includes key contrib modules, and publish the wheels via a GitHub Release. > > Updates the existing release workflow to **skip** tags prefixed with `opencv-`, preventing these wheel-only releases from triggering the normal PyPI publish job. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f03df11. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 029f491 commit d0aa8eb

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
name: Build OpenCV Wheels (No FFmpeg)
2+
3+
# Produces opencv-contrib-python-headless wheels compiled from source with
4+
# WITH_FFMPEG=OFF to eliminate bundled ffmpeg CVEs from PyPI wheels.
5+
# The contrib-headless variant is a strict superset of opencv-python,
6+
# opencv-python-headless, and opencv-contrib-python: same cv2 module with
7+
# core + contrib modules and no GUI / no X11. One wheel can therefore
8+
# satisfy all four opencv-* package names in downstream Dockerfiles.
9+
# Wheels are uploaded as a GitHub release so both unstructured and
10+
# unstructured-api Dockerfiles can download them at build time.
11+
12+
on:
13+
workflow_dispatch:
14+
inputs:
15+
opencv_version:
16+
description: "opencv-contrib-python-headless version to build (must have sdist on PyPI)"
17+
required: true
18+
default: "4.12.0.88"
19+
release_tag:
20+
description: "GitHub release tag for the wheels (e.g. opencv-4.12.0.88)"
21+
required: true
22+
default: "opencv-4.12.0.88"
23+
24+
jobs:
25+
build-wheel:
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
include:
30+
- arch: amd64
31+
runs-on: ubuntu-latest-8-cores
32+
docker-platform: linux/amd64
33+
- arch: arm64
34+
runs-on: ubuntu-latest-arm-8-cores
35+
docker-platform: linux/arm64
36+
runs-on: ${{ matrix.runs-on }}
37+
env:
38+
OPENCV_VERSION: ${{ inputs.opencv_version }}
39+
DOCKER_PLATFORM: ${{ matrix.docker-platform }}
40+
steps:
41+
# Docker is preinstalled on amd64 ubuntu-latest runners but not on
42+
# the arm64 ubuntu-latest-arm-X-cores image. Install it on demand
43+
# and make the socket accessible to the runner user without needing
44+
# to re-login for the docker group to take effect.
45+
- name: Install Docker (arm64 runner)
46+
if: matrix.arch == 'arm64'
47+
run: |
48+
set -euo pipefail
49+
curl -fsSL https://get.docker.com | sudo sh
50+
sudo systemctl start docker
51+
sudo chmod 666 /var/run/docker.sock
52+
docker version --format '{{.Server.Version}}'
53+
54+
- name: Build opencv-contrib-python-headless from source
55+
run: |
56+
mkdir -p wheels
57+
docker run --rm \
58+
--platform="$DOCKER_PLATFORM" \
59+
-e "OPENCV_VERSION=$OPENCV_VERSION" \
60+
-v "$PWD/wheels:/out" \
61+
cgr.dev/chainguard/wolfi-base:latest sh -c '
62+
set -euo pipefail
63+
# Retry apk install: the chainguard mirror occasionally returns
64+
# transient errors mid-install (matches the pattern in unstructured/Dockerfile).
65+
apk_ok=false
66+
for attempt in 1 2 3; do
67+
apk update && \
68+
apk add python-3.12 python-3.12-dev python-3.12-base-dev \
69+
opencv-dev cmake gcc glibc-dev libstdc++-dev make pkgconf \
70+
py3.12-pip py3.12-numpy && \
71+
apk_ok=true && break
72+
echo "apk install failed (attempt $attempt/3), retrying in 5s..."
73+
sleep 5
74+
done
75+
$apk_ok || { echo "apk install failed after 3 attempts"; exit 1; }
76+
77+
CMAKE_ARGS="-DWITH_FFMPEG=OFF" \
78+
ENABLE_CONTRIB=1 \
79+
ENABLE_HEADLESS=1 \
80+
python3.12 -m pip wheel \
81+
--no-binary opencv-contrib-python-headless \
82+
--no-deps \
83+
"opencv-contrib-python-headless==${OPENCV_VERSION}" \
84+
-w /out
85+
86+
echo "=== Validate no bundled ffmpeg and contrib modules present ==="
87+
python3.12 -m pip install /out/opencv_contrib_python_headless-*.whl
88+
python3.12 -c "
89+
import cv2, pathlib
90+
d = pathlib.Path(cv2.__file__).parent
91+
libs = d / \".libs\"
92+
assert not libs.exists(), f\"Unexpected .libs dir: {list(libs.iterdir())}\"
93+
# Sanity check that contrib modules made it into the build
94+
assert hasattr(cv2, \"ximgproc\"), \"contrib module cv2.ximgproc missing\"
95+
assert hasattr(cv2, \"aruco\"), \"contrib module cv2.aruco missing\"
96+
print(f\"OK: cv2 {cv2.__version__}, contrib modules present, no bundled ffmpeg\")
97+
"
98+
'
99+
ls -lh wheels/
100+
101+
- name: Upload wheel artifact
102+
uses: actions/upload-artifact@v4
103+
with:
104+
name: opencv-wheel-${{ matrix.arch }}
105+
path: wheels/opencv_contrib_python_headless-*.whl
106+
retention-days: 90
107+
108+
create-release:
109+
needs: build-wheel
110+
runs-on: ubuntu-latest
111+
permissions:
112+
contents: write
113+
env:
114+
OPENCV_VERSION: ${{ inputs.opencv_version }}
115+
RELEASE_TAG: ${{ inputs.release_tag }}
116+
steps:
117+
- name: Download all wheel artifacts
118+
uses: actions/download-artifact@v4
119+
with:
120+
path: wheels
121+
merge-multiple: true
122+
123+
- name: List wheels
124+
run: ls -lh wheels/
125+
126+
- name: Create GitHub Release
127+
env:
128+
GH_TOKEN: ${{ github.token }}
129+
run: |
130+
# --latest=false keeps this auxiliary wheel release from displacing
131+
# the actual package release on the repo's Releases page.
132+
gh release create "$RELEASE_TAG" \
133+
--repo "$GITHUB_REPOSITORY" \
134+
--title "OpenCV Wheels ${OPENCV_VERSION} (no ffmpeg)" \
135+
--latest=false \
136+
--notes "$(cat <<NOTES
137+
OpenCV Python contrib-headless wheels built from source with WITH_FFMPEG=OFF.
138+
139+
These wheels eliminate bundled ffmpeg CVEs present in the stock PyPI wheels.
140+
Built against cgr.dev/chainguard/wolfi-base:latest with Python 3.12.
141+
142+
The contrib-headless variant provides the full cv2 API (core + contrib
143+
modules, no GUI), so a single wheel can satisfy opencv-python,
144+
opencv-python-headless, opencv-contrib-python, and
145+
opencv-contrib-python-headless in downstream Dockerfiles.
146+
147+
**Source version:** opencv-contrib-python-headless==${OPENCV_VERSION}
148+
**Build flags:** CMAKE_ARGS='-DWITH_FFMPEG=OFF' ENABLE_CONTRIB=1 ENABLE_HEADLESS=1
149+
NOTES
150+
)" \
151+
wheels/*.whl

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ env:
1818

1919
jobs:
2020
release:
21+
# Skip opencv wheel releases (tagged 'opencv-*') produced by the
22+
# build-opencv-wheels workflow - those aren't package releases and would
23+
# otherwise trigger a spurious PyPI publish failure.
24+
if: ${{ !startsWith(github.event.release.tag_name, 'opencv-') }}
2125
runs-on: ubuntu-latest
2226
steps:
2327
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

0 commit comments

Comments
 (0)