Skip to content
Draft
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
145 changes: 145 additions & 0 deletions .github/workflows/build-matplotlib.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
name: Build matplotlib wheels (riscv64)

on:
workflow_dispatch:
push:
branches:
- main
pull_request:

permissions: {}

env:
# Query PyPI + gitlab riscv64 mirror; pick best compatible wheel
# (`unsafe-best-match` = pip-like). `first-index` stops at PyPI for sdist
# packages like cffi, never consulting gitlab for the riscv64 wheel.
UV_EXTRA_INDEX_URL: https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
UV_INDEX_STRATEGY: unsafe-best-match
# Refuse sdist for dependencies — wheels only. Forces uv to pick the gitlab
# riscv64 wheel instead of falling back to building cffi/cryptography/etc.
UV_ONLY_BINARY: ':all:'

jobs:
build_sdist:
name: Build sdist
runs-on: ubuntu-24.04-riscv
permissions:
contents: read
outputs:
SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }}

steps:
- name: Checkout python-wheels
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Checkout matplotlib
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: matplotlib/matplotlib
path: upstream
fetch-depth: 0
persist-credentials: false

- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
name: Install Python
with:
python-version: '3.12'
activate-environment: true
enable-cache: false

# Something changed somewhere that prevents the downloaded-at-build-time
# licenses from being included in built wheels, so pre-download them so
# that they exist before the build and are included.
- name: Pre-download bundled licenses
working-directory: upstream
run: >
curl -Lo LICENSE/LICENSE_QHULL
https://github.com/qhull/qhull/raw/2020.2/COPYING.txt

- name: Install dependencies
run: uv pip install build twine

- name: Build sdist
id: sdist
working-directory: upstream
run: |
python -m build --sdist
python ci/export_sdist_name.py

- name: Check README rendering for PyPI
working-directory: upstream
run: twine check dist/*

- name: Upload sdist result
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cibw-sdist
path: upstream/dist/*.tar.gz
if-no-files-found: error

build_wheels:
needs: build_sdist
name: Build wheels on ubuntu-24.04-riscv for riscv64
permissions:
contents: read
runs-on: ubuntu-24.04-riscv

steps:
- name: Checkout python-wheels
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Download sdist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cibw-sdist
path: dist/

- name: Build wheels for CPython 3.14
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "cp314-* cp314t-*"
CIBW_ARCHS: "riscv64"
CIBW_BUILD_FRONTEND: "build[uv]"
CIBW_ENVIRONMENT: >-
UV_EXTRA_INDEX_URL=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
UV_INDEX_STRATEGY=unsafe-best-match
UV_ONLY_BINARY=:all:

- name: Build wheels for CPython 3.13
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "cp313-*"
CIBW_ARCHS: "riscv64"
CIBW_BUILD_FRONTEND: "build[uv]"
CIBW_ENVIRONMENT: >-
UV_EXTRA_INDEX_URL=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
UV_INDEX_STRATEGY=unsafe-best-match
UV_ONLY_BINARY=:all:

- name: Build wheels for CPython 3.12
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "cp312-*"
CIBW_ARCHS: "riscv64"
CIBW_BUILD_FRONTEND: "build[uv]"
CIBW_ENVIRONMENT: >-
UV_EXTRA_INDEX_URL=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
UV_INDEX_STRATEGY=unsafe-best-match
UV_ONLY_BINARY=:all:

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cibw-wheels-${{ runner.os }}-riscv64
path: ./wheelhouse/*.whl
if-no-files-found: error
250 changes: 250 additions & 0 deletions .github/workflows/test-matplotlib.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
---
name: Test matplotlib (riscv64)
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }}
cancel-in-progress: true

on:
workflow_dispatch:
push:
branches:
- main
pull_request:

permissions: {}

env:
NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test.
OPENBLAS_NUM_THREADS: 1
PYTHONFAULTHANDLER: 1
# Query PyPI + gitlab riscv64 mirror; pick best compatible wheel.
# `first-index` is wrong here — PyPI has cffi sdist so uv would never check
# gitlab for the riscv64 wheel. `unsafe-best-match` is pip-like and selects
# the riscv64 wheel from gitlab when PyPI lacks one.
UV_EXTRA_INDEX_URL: https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
UV_INDEX_STRATEGY: unsafe-best-match
# Refuse sdist for dependencies — wheels only. Forces uv to pick the gitlab
# riscv64 wheel instead of falling back to building cffi/cryptography/etc.
UV_ONLY_BINARY: ':all:'

jobs:
test:
permissions:
contents: read
name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: upstream

strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04-riscv
python-version: '3.12'

steps:
- name: Checkout python-wheels
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Checkout matplotlib
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: matplotlib/matplotlib
path: upstream
fetch-depth: 0
persist-credentials: false

- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
enable-cache: false

- name: Install OS dependencies
run: |
echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries
sudo apt-get update -yy
sudo apt-get install -yy --no-install-recommends \
ccache \
cm-super \
dvipng \
fonts-freefont-otf \
fonts-noto-cjk \
fonts-wqy-zenhei \
gdb \
gir1.2-gtk-3.0 \
gir1.2-gtk-4.0 \
graphviz \
inkscape \
language-pack-de \
lcov \
libcairo2 \
libcairo2-dev \
libffi-dev \
libgeos-dev \
libnotify4 \
libsdl2-2.0-0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xinerama0 \
lmodern \
ninja-build \
pkg-config \
qtbase5-dev \
texlive-fonts-recommended \
texlive-latex-base \
texlive-latex-extra \
texlive-latex-recommended \
texlive-luatex \
texlive-pictures \
texlive-xetex
sudo apt-get install -yy --no-install-recommends ffmpeg poppler-utils
sudo apt-get install -yy --no-install-recommends libgirepository-2.0-dev

- name: Cache uv
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/uv
key: |
${{ matrix.os }}-py${{ matrix.python-version }}-uv-${{
hashFiles('upstream/pyproject.toml', 'upstream/ci/minver-requirements.txt') }}
restore-keys: |
${{ matrix.os }}-py${{ matrix.python-version }}-uv-
- name: Cache ccache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.ccache
key: ${{ matrix.os }}-py${{ matrix.python-version }}-ccache-${{ hashFiles('upstream/src/*') }}
restore-keys: |
${{ matrix.os }}-py${{ matrix.python-version }}-ccache-
- name: Cache Matplotlib
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cache/matplotlib
!~/.cache/matplotlib/tex.cache
!~/.cache/matplotlib/test_cache
key: 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }}
restore-keys: |
6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-
6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-

- name: Install Python dependencies
run: |
# Upgrade pip and setuptools and wheel to get as clean an install as
# possible.
uv pip install --upgrade pip setuptools wheel

# Install dependencies from PyPI.
# Preinstall build requirements to enable no-build-isolation builds.
uv pip install --upgrade --prefer-binary \
--group build --group test

# Install optional dependencies from PyPI.
# Sphinx is needed to run sphinxext tests
uv pip install --upgrade sphinx!=6.1.2

# GUI toolkits are pip-installable only for some versions of Python
# so don't fail if we can't install them. Make it easier to check
# whether the install was successful by trying to import the toolkit
# (sometimes, the install appears to be successful but shared
# libraries cannot be loaded at runtime, so an actual import is a
# better check).
uv pip install --upgrade pycairo 'cairocffi>=0.8' 'PyGObject' &&
(
python -c 'import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk' &&
echo 'PyGObject 4 is available' || echo 'PyGObject 4 is not available'
) && (
python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' &&
echo 'PyGObject 3 is available' || echo 'PyGObject 3 is not available'
)

# PyQt5 has no riscv64 wheels; skip.
uv pip install --upgrade --only-binary :all: pyqt6 &&
python -c 'import PyQt6.QtCore' &&
echo 'PyQt6 is available' ||
echo 'PyQt6 is not available'
uv pip install --upgrade --only-binary :all: pyside6 &&
python -c 'import PySide6.QtCore' &&
echo 'PySide6 is available' ||
echo 'PySide6 is not available'

- name: Install Matplotlib
run: |
ccache -s
git describe

export CPPFLAGS='--coverage -fprofile-abs-path'

uv pip install --no-deps --no-build-isolation --verbose \
--config-settings=setup-args="-DrcParams-backend=Agg" \
--editable .[dev]

unset CPPFLAGS

- name: Run pytest
run: |
pytest -rfEsXR -n auto \
--maxfail=50 --timeout=300 --durations=25 \
--cov-report=xml --cov=lib --log-level=DEBUG --color=yes

- name: Cleanup non-failed image files
if: failure()
run: |
find ./result_images -name "*-expected*.png" | while read file; do
if [[ $file == *-expected_???.png ]]; then
extension=${file: -7:3}
base=${file%*-expected_$extension.png}_$extension
else
extension="png"
base=${file%-expected.png}
fi
if [[ ! -e ${base}-failed-diff.png ]]; then
indent=""
list=($file $base.png)
if [[ $extension != "png" ]]; then
list+=(${base%_$extension}-expected.$extension ${base%_$extension}.$extension)
fi
for to_remove in "${list[@]}"; do
if [[ -e $to_remove ]]; then
rm $to_remove
echo "${indent}Removed $to_remove"
fi
indent+=" "
done
fi
done

if [ "$(find ./result_images -mindepth 1 -type d)" ]; then
find ./result_images/* -type d -empty -delete
fi

- name: Filter C coverage
if: ${{ !cancelled() }}
run: |
LCOV_IGNORE_ERRORS='mismatch'
lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \
--capture --directory . --exclude $PWD/subprojects --exclude $PWD/build \
--output-file coverage.info
lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \
--output-file coverage.info --extract coverage.info $PWD/src/'*'
lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \
--list coverage.info
find . -name '*.gc*' -delete

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: "${{ matrix.python-version }} ${{ matrix.os }} result images"
path: upstream/result_images
Loading