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
105 changes: 105 additions & 0 deletions .github/workflows/build-libsass.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on: https://github.com/sass/libsass-python/blob/0.23.0/.github/workflows/main.yml
name: Build libsass wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'libsass version to build (git tag, e.g. 0.23.0)'
required: true
default: '0.23.0'
pull_request:
paths:
- '.github/workflows/build-libsass.yml'
- 'patches/libsass/**'

concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '0.23.0' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

env:
LIBSASS_VERSION: ${{ inputs.version || '0.23.0' }}
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64

jobs:
setup:
uses: $/.github/workflows/_setup.yml

build_wheels:
needs: [setup]
name: Build libsass ${{ inputs.version || '0.23.0' }} cp312-abi3-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 60

steps:
- name: Checkout libsass-python v${{ env.LIBSASS_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: sass/libsass-python
ref: ${{ env.LIBSASS_VERSION }}
submodules: true
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false

# setup.cfg's `license_files = LICENSE` is an explicit list, which turns off
# setuptools' default glob (gotcha 57); the vendored libsass submodule (compiled
# straight into the extension) carries its own separate MIT licence (gotcha 32).
- name: Stage the libsass licence
run: |
cp libsass/LICENSE LICENSE.libsass
sed -i '/^license_files = LICENSE$/a\ LICENSE.libsass' setup.cfg

- name: Patch libsass-python source
run: git apply python-wheels/patches/libsass/${{ env.LIBSASS_VERSION }}/*.patch

- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
output-dir: wheelhouse/
env:
CIBW_ARCHS: riscv64
# setup.py's bdist_wheel override sets py_limited_api to the building
# interpreter's own version, so cp312 (our floor) becomes the abi3 tag;
# the later entries build nothing, they just re-test the one wheel.
# cp314t is skipped: setup.py forces py_limited_api unconditionally for
# CPython with no Py_GIL_DISABLED guard, same shape upstream's own zopfli
# sibling hits (build-zopfli.yml) - free-threaded builds don't support abi3.
CIBW_BUILD: >-
cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# sassutils/wsgi.py imports pkg_resources, which setuptools 82 deleted
# outright (gotcha 29); pin below that floor for the test env only.
CIBW_TEST_REQUIRES: pytest werkzeug>=0.9 setuptools<82
# sasstests.py opens fixtures ("test/a.scss") relative to the process cwd,
# not __file__; cibuildwheel runs test-command from an empty temp dir, so
# stage the file plus its fixture dirs there and run against them, leaving
# sass.py/sassutils unstaged so the import resolves to the installed wheel
# (gotcha 36).
CIBW_TEST_SOURCES: sasstests.py test testpkg
CIBW_TEST_COMMAND: pytest sasstests.py -v

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: libsass-${{ env.LIBSASS_VERSION }}-cp312-abi3-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish libsass ${{ inputs.version || '0.23.0' }}
needs: [setup, build_wheels]
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
with:
artifact-pattern: libsass-${{ inputs.version || '0.23.0' }}-*-manylinux_riscv64
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 6 Sep 2026 14:09:19 +0200
Subject: Give the internal enum-dict/init-module helpers internal linkage

Upstream-Status: To upstream [not yet submitted; upstream's own CI does not run abi3audit so this leak is invisible to them]

PySass_make_enum_dict() and PySass_init_module() are declared with
default (extern) visibility but are only ever called from within
_sass.c itself. Because the extension is built with Py_LIMITED_API
(setup.py sets py_limited_api=True), abi3audit --strict flags both as
non-abi3 symbols leaking from the .abi3.so: a wheel claiming forward
ABI compatibility must not export symbols outside the stable-ABI
allowlist, since a symbol like this could collide with another
extension or a future CPython internal of the same name. Only
PyInit__sass needs to be visible; mark both helpers static so the
compiler gives them internal linkage instead.

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
_sass.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/_sass.c b/_sass.c
index a3bec29..6a57ba8 100644
--- a/_sass.c
+++ b/_sass.c
@@ -652,7 +652,7 @@ static PyMethodDef PySass_methods[] = {

static char PySass_doc[] = "The thin binding of libsass for Python.";

-PyObject* PySass_make_enum_dict() {
+static PyObject* PySass_make_enum_dict() {
PyObject* dct = PyDict_New();
PyDict_SetItemString(dct, "nested", PyLong_FromLong(SASS_STYLE_NESTED));
PyDict_SetItemString(dct, "expanded", PyLong_FromLong(SASS_STYLE_EXPANDED));
@@ -661,7 +661,7 @@ PyObject* PySass_make_enum_dict() {
return dct;
}

-void PySass_init_module(PyObject *module) {
+static void PySass_init_module(PyObject *module) {
PyModule_AddObject(module, "OUTPUT_STYLES", PySass_make_enum_dict());
PyModule_AddObject(module, "libsass_version", PyUnicode_FromString(libsass_version()));
}
--
2.50.1 (Apple Git-155)