From 93d1b244dd893f69c7f0d2c056814d44738a2736 Mon Sep 17 00:00:00 2001 From: John Twigg Date: Fri, 24 Jul 2026 21:47:20 -0700 Subject: [PATCH] Add musllinux wheel packaging for Alpine. Build and publish musllinux wheels from this fork; disable unused upstream Actions; document in MUSLLINUX.md. Co-authored-by: Cursor --- .github/workflows/build-musllinux-wheel.yml | 45 +++++++++++ .github/workflows/build.yml | 35 +++++---- .github/workflows/closing_ticket.yml | 7 +- .github/workflows/labeling_ticket_done.yml | 7 +- .github/workflows/labeling_ticket_todo.yml | 7 +- .github/workflows/memory-benchmark.yml | 15 ++-- .github/workflows/publish-docs.yml | 8 +- .github/workflows/reopening_ticket.yml | 7 +- MUSLLINUX.md | 85 +++++++++++++++++++++ README.md | 2 + scripts/build_musllinux_wheel.sh | 64 ++++++++++++++++ 11 files changed, 249 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/build-musllinux-wheel.yml create mode 100644 MUSLLINUX.md create mode 100755 scripts/build_musllinux_wheel.sh diff --git a/.github/workflows/build-musllinux-wheel.yml b/.github/workflows/build-musllinux-wheel.yml new file mode 100644 index 0000000..907d761 --- /dev/null +++ b/.github/workflows/build-musllinux-wheel.yml @@ -0,0 +1,45 @@ +# Build musllinux wheels for Alpine. Upstream only publishes manylinux (glibc). +name: Build musllinux wheel + +on: + workflow_dispatch: + push: + tags: + - "v*-musl*" + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build musllinux_1_2_x86_64 wheel + run: | + chmod +x scripts/build_musllinux_wheel.sh + OUT_DIR="${{ github.workspace }}/dist" ./scripts/build_musllinux_wheel.sh + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: musllinux-x86_64-wheel + path: dist/*.whl + if-no-files-found: error + + - name: Create GitHub Release (tag pushes) + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: dist/*.whl + generate_release_notes: true + body: | + Musllinux (Alpine/musl) wheel. + + Pin with uv find-links (example): + ```toml + [tool.uv] + find-links = ["https://github.com/${{ github.repository }}/releases/expanded_assets/${{ github.ref_name }}"] + ``` + Keep `c2pa-python==0.37.1` (or matching) on PyPI for macOS/glibc; uv selects this wheel on Alpine via the `musllinux` tag. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e970c93..656cbf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,23 +1,26 @@ name: Build +# DISABLED on this fork — see MUSLLINUX.md. Restore the commented `on:` to match upstream. on: - pull_request: - types: - - opened - - reopened - - synchronize - - labeled - push: - branches: - - main - tags: - - "*" workflow_dispatch: - inputs: - publish: - description: 'Publish' - required: true - default: 'false' +# on: +# pull_request: +# types: +# - opened +# - reopened +# - synchronize +# - labeled +# push: +# branches: +# - main +# tags: +# - "*" +# workflow_dispatch: +# inputs: +# publish: +# description: 'Publish' +# required: true +# default: 'false' permissions: contents: read diff --git a/.github/workflows/closing_ticket.yml b/.github/workflows/closing_ticket.yml index f1fb6bd..11e2bef 100644 --- a/.github/workflows/closing_ticket.yml +++ b/.github/workflows/closing_ticket.yml @@ -2,9 +2,12 @@ # it will update the workflow labels appropriately. name: Closing ticket +# DISABLED on this fork — see MUSLLINUX.md. on: - issues: - types: [closed] + workflow_dispatch: +# on: +# issues: +# types: [closed] jobs: label_issues: runs-on: ubuntu-latest diff --git a/.github/workflows/labeling_ticket_done.yml b/.github/workflows/labeling_ticket_done.yml index 76278c5..08d4c45 100644 --- a/.github/workflows/labeling_ticket_done.yml +++ b/.github/workflows/labeling_ticket_done.yml @@ -1,9 +1,12 @@ # This ensures that when a ticket is labeled as Done in Jira, it will close the ticket on GitHub. name: Labeling ticket "Done" +# DISABLED on this fork — see MUSLLINUX.md. on: - issues: - types: [labeled] + workflow_dispatch: +# on: +# issues: +# types: [labeled] jobs: label_issues: runs-on: ubuntu-latest diff --git a/.github/workflows/labeling_ticket_todo.yml b/.github/workflows/labeling_ticket_todo.yml index 06e552c..2c4ee7a 100644 --- a/.github/workflows/labeling_ticket_todo.yml +++ b/.github/workflows/labeling_ticket_todo.yml @@ -1,9 +1,12 @@ # This ensures that when a ticket is not labeled as Done in Jira, it will re-open the ticket on GitHub. name: Labeling ticket "To Do" +# DISABLED on this fork — see MUSLLINUX.md. on: - issues: - types: [labeled] + workflow_dispatch: +# on: +# issues: +# types: [labeled] jobs: label_issues: runs-on: ubuntu-latest diff --git a/.github/workflows/memory-benchmark.yml b/.github/workflows/memory-benchmark.yml index 992587b..ba5d59e 100644 --- a/.github/workflows/memory-benchmark.yml +++ b/.github/workflows/memory-benchmark.yml @@ -1,12 +1,15 @@ name: Python SDK memray memory benchmark +# DISABLED on this fork — see MUSLLINUX.md. Restore the commented `on:` to match upstream. on: - pull_request: - types: - - opened - - reopened - - synchronize - - labeled + workflow_dispatch: +# on: +# pull_request: +# types: +# - opened +# - reopened +# - synchronize +# - labeled permissions: contents: read diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index d3c3b42..3612966 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -1,10 +1,12 @@ name: Build and publish docs +# DISABLED on this fork — see MUSLLINUX.md. Restore the commented `on:` to match upstream. on: - release: - types: [published] - workflow_dispatch: +# on: +# release: +# types: [published] +# workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/reopening_ticket.yml b/.github/workflows/reopening_ticket.yml index 8f984cc..4a99dde 100644 --- a/.github/workflows/reopening_ticket.yml +++ b/.github/workflows/reopening_ticket.yml @@ -2,9 +2,12 @@ # workflow labels in GitHub appropriately. name: Reopening ticket +# DISABLED on this fork — see MUSLLINUX.md. on: - issues: - types: [reopened] + workflow_dispatch: +# on: +# issues: +# types: [reopened] jobs: label_issues: runs-on: ubuntu-latest diff --git a/MUSLLINUX.md b/MUSLLINUX.md new file mode 100644 index 0000000..48c683f --- /dev/null +++ b/MUSLLINUX.md @@ -0,0 +1,85 @@ +# Musllinux wheels (this fork) + +## Why this exists + +Upstream [contentauth/c2pa-python](https://github.com/contentauth/c2pa-python) publishes **manylinux** (glibc) wheels only. + +Alpine (`python:*-alpine`, **musl**) cannot load those wheels; the sdist pulls gnu natives that fail at `dlopen`; compiling Rust inside every image build is too slow. + +This fork builds and publishes **musllinux** wheels so Alpine can `import c2pa` without a per-image Rust compile. The Python API is unchanged. macOS and glibc Linux keep using PyPI. + +Active surface area (keep this small): + +| Path | Purpose | +| --- | --- | +| `scripts/build_musllinux_wheel.sh` | Build musllinux wheel in Docker | +| `.github/workflows/build-musllinux-wheel.yml` | CI + GitHub Release on `v*-musl*` tags | +| `MUSLLINUX.md` | This file | + +Upstream Adobe Actions (Build/PyPI, Pages, Jira ticket labels, memray) are **disabled** here so musl tags do not fan out into their matrix or publish to PyPI. + +## How to upgrade + +When Adobe ships a new `c2pa-python` / `c2pa-rs` pair: + +1. **Fetch upstream** into this fork (rebase or merge `contentauth/c2pa-python` `main`). +2. **Re-apply fork bits** if the upgrade wiped them: + - `scripts/build_musllinux_wheel.sh` + - `.github/workflows/build-musllinux-wheel.yml` + - disabled triggers on unused workflows (see below) + - this `MUSLLINUX.md` +3. **Confirm pins** match the intended Adobe release: + - `pyproject.toml` → `[project].version` (e.g. `0.37.1`) + - `c2pa-native-version.txt` → `c2pa-rs` tag (e.g. `c2pa-v0.90.1`) +4. **Build & publish** a musllinux wheel: + ```bash + ./scripts/build_musllinux_wheel.sh + # or push a tag and let CI do it: + git tag v-musl.1 # e.g. v0.37.1-musl.1 + git push origin v-musl.1 + ``` + Tag shape: `v-musl.` + Bump `` for a rebuild of the same Adobe version (link flags, script fixes). +5. **Consume** from the release (example with uv): + + ```toml + dependencies = [ + "c2pa-python==0.37.1", + ] + + [tool.uv] + # Prefer releases/expanded_assets/ if /releases/download// 404s as a directory. + find-links = [ + "https://github.com///releases/expanded_assets/v0.37.1-musl.1", + ] + ``` + + Do **not** set `c2pa-python = { url = "...musllinux....whl" }` — that forces one platform everywhere. Use find-links + wheel tags so macOS/glibc still take PyPI. + +### Local build + +Requires Docker (linux/amd64): + +```bash +./scripts/build_musllinux_wheel.sh +OUT_DIR=/tmp/wheels ./scripts/build_musllinux_wheel.sh +``` + +Runtime on Alpine needs `libgcc` / `libstdc++` (apk). + +### Disabled upstream Actions + +These workflow files are kept for easier upstream merges, but their triggers are stubbed so they do not run on this fork: + +- `build.yml` (+ callee `build-wheel.yml`) — Adobe multi-platform Build / PyPI publish +- `publish-docs.yml` — GitHub Pages +- `memory-benchmark.yml` +- `closing_ticket.yml` / `labeling_ticket_*.yml` / `reopening_ticket.yml` — Adobe Jira label sync + +Only `build-musllinux-wheel.yml` should run automatically (on `v*-musl*` tags or `workflow_dispatch`). + +## Current pin + +- Package: `0.37.1` +- Native: `c2pa-v0.90.1` +- Release tag: `v0.37.1-musl.1` diff --git a/README.md b/README.md index 0328ee9..f25c9d1 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ To use the module in Python code, import the module like this: import c2pa ``` +**This fork:** Alpine/musl wheels are not on PyPI. See [MUSLLINUX.md](MUSLLINUX.md). + ## Building from local c2pa-rs sources ### Using a virtual environment with local builds diff --git a/scripts/build_musllinux_wheel.sh b/scripts/build_musllinux_wheel.sh new file mode 100755 index 0000000..70d396f --- /dev/null +++ b/scripts/build_musllinux_wheel.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env sh +# Build a musllinux_1_2_x86_64 wheel of c2pa-python for Alpine (musl). +# +# Upstream only publishes manylinux (glibc) wheels. This script clones the +# c2pa-rs tag from c2pa-native-version.txt, builds c2pa-c-ffi inside +# python:3.14-alpine, and packs a musllinux wheel into dist/. +# +# Usage (from repo root): +# ./scripts/build_musllinux_wheel.sh +# OUT_DIR=/tmp/wheels ./scripts/build_musllinux_wheel.sh +# +# Requires: docker. + +set -eu + +ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)" +C2PA_RS_TAG="$(tr -d '[:space:]' <"$ROOT/c2pa-native-version.txt")" +OUT_DIR="${OUT_DIR:-$ROOT/dist}" +PLATFORM_TAG="musllinux_1_2_x86_64" +# setup.py only knows gnu artifact folder names; the wheel tag is set via --plat-name. +ARTIFACT_PLATFORM="x86_64-unknown-linux-gnu" +PYTHON_IMAGE="${PYTHON_IMAGE:-python:3.14-alpine}" + +echo "c2pa-rs tag: $C2PA_RS_TAG" +echo "python image: $PYTHON_IMAGE" +echo "wheel tag: $PLATFORM_TAG" + +mkdir -p "$OUT_DIR" +# Clone + cargo target stay inside the container so host cleanup never hits +# root-owned files from a bind-mounted target/ directory. +docker run --rm --platform linux/amd64 \ + -e C2PA_RS_TAG="$C2PA_RS_TAG" \ + -e ARTIFACT_PLATFORM="$ARTIFACT_PLATFORM" \ + -e PLATFORM_TAG="$PLATFORM_TAG" \ + -v "$ROOT:/c2pa-python:ro" \ + -v "$OUT_DIR:/out" \ + -w /tmp/build \ + "$PYTHON_IMAGE" \ + sh -c ' +set -eux +# perl: required to configure vendored openssl-src during cargo build +apk add --no-cache build-base rust cargo openssl-dev pkgconfig git perl +cp -a /c2pa-python/. /tmp/build/ +rm -rf artifacts build dist src/c2pa/libs +mkdir -p "artifacts/$ARTIFACT_PLATFORM" src/c2pa/libs + +git clone --depth 1 --branch "$C2PA_RS_TAG" https://github.com/contentauth/c2pa-rs.git /tmp/c2pa-rs +cd /tmp/c2pa-rs +cargo build --release -p c2pa-c-ffi --features file_io +cp target/release/libc2pa_c.so "/tmp/build/artifacts/$ARTIFACT_PLATFORM/" +cp target/release/libc2pa_c.so /tmp/build/src/c2pa/libs/ +ldd target/release/libc2pa_c.so + +cd /tmp/build +pip install -q -r requirements.txt -r requirements-dev.txt build wheel "setuptools>=68" toml +python setup.py bdist_wheel --plat-name "$PLATFORM_TAG" +ls -la dist/ +cp dist/*.whl /out/ +pip install -q dist/*.whl +python -c "import c2pa; c=c2pa.Context(); c.__enter__(); c.__exit__(None,None,None); print(\"import_ok\", c2pa.__file__)" +' + +echo "Wheels written to $OUT_DIR:" +ls -la "$OUT_DIR"/*.whl