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
109 changes: 97 additions & 12 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ jobs:
contents: write

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.1
- name: Check out project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
Expand Down Expand Up @@ -72,26 +73,31 @@ jobs:
outputs:
include: ${{ steps.set-matrix.outputs.include }}
steps:
- uses: actions/checkout@v6.0.1
- name: Check out project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install cibuildwheel
# Nb. keep cibuildwheel version pin consistent with job below
run: pipx install cibuildwheel==3.3.0
run: pipx install cibuildwheel==3.4.1
- id: set-matrix
run: |
MATRIX=$(
{
cibuildwheel --print-build-identifiers --platform linux \
cibuildwheel --print-build-identifiers --platform linux --archs x86_64 \
| jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
| sed -e '/aarch64\|armv7l/s|ubuntu-latest|ubuntu-24.04-arm|' \
&& cibuildwheel --print-build-identifiers --platform macos \
| jq -nRc '{"only": inputs, "os": "macos-latest"}' \
&& cibuildwheel --print-build-identifiers --platform windows \
&& cibuildwheel --print-build-identifiers --platform windows --archs x86,AMD64 \
| jq -nRc '{"only": inputs, "os": "windows-2022"}' \
&& cibuildwheel --print-build-identifiers --platform windows --archs ARM64 \
| jq -nRc '{"only": inputs, "os": "windows-11-arm"}'
} | jq -sc
)
echo "include=$MATRIX"
echo "include=$MATRIX" >> $GITHUB_OUTPUT
env:
# Skip abi3 targets here:
CIBW_SKIP: "cp3{9,1?}-win_arm64 cp3{9,1?}-win32 cp3{9,1?}-macosx_x86_64 pp* *musllinux*"

build_wheels:
name: Build ${{ matrix.only }}
Expand All @@ -104,8 +110,8 @@ jobs:
include: ${{ fromJson(needs.generate-wheels-matrix.outputs.include) }}

steps:
- name: Check out the repo
uses: actions/checkout@v6.0.1
- name: Check out project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up QEMU
if: runner.os == 'Linux'
Expand All @@ -114,14 +120,14 @@ jobs:
platforms: all

- name: Build wheels
uses: pypa/cibuildwheel@v3.3.0
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
with:
only: ${{ matrix.only }}

- name: Build faster Linux wheels
# also build wheels with the most recent manylinux images and gcc
if: runner.os == 'Linux' && !contains(matrix.only, 'i686')
uses: pypa/cibuildwheel@v3.3.0
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
env:
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_34 # manylinux_2_39 ?
Expand All @@ -143,9 +149,88 @@ jobs:
path: ./wheelhouse/*.whl
name: wheels-${{ matrix.only }}

build_limited_api_wheels:
name: Build ${{ matrix.target }} Stable ABI ${{ matrix.lapiversion }} wheels
if: >-
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'schedule' && github.repository == 'cython/cython') ||
github.event_name == 'workflow_dispatch'

strategy:
fail-fast: false
matrix:
target:
# Smaller set of platforms that we only provide Stable ABI wheels for:
- 'musllinux_x86_64'
- 'musllinux_aarch64'
- 'manylinux_i686'
- 'musllinux_i686'
- 'manylinux_ppc64le'
- 'musllinux_ppc64le'
- 'manylinux_riscv64'
- 'musllinux_riscv64'
- 'manylinux_armv7l'
- 'musllinux_armv7l'
- 'macosx_x86_64'
- 'win32'
- 'win_arm64'
lapiversion:
- "3.9"
- "3.12"

runs-on: ${{
contains(matrix.target, 'aarch64') && 'ubuntu-24.04-arm' ||
contains(matrix.target, 'armv7l') && 'ubuntu-24.04-arm' ||
contains(matrix.target, 'win_arm64') && 'windows-11-arm' ||
contains(matrix.target, 'win32') && 'windows-latest' ||
contains(matrix.target, 'macosx') && 'macos-latest' ||
'ubuntu-latest'
}}

steps:
- name: Check out project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.x'

- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
with:
platforms: all

- name: Setup Visual Studio on Windows
if: startsWith(matrix.target, 'win')
uses: TheMrMilchmann/setup-msvc-dev@79dac248aac9d0059f86eae9d8b5bfab4e95e97c # v4.0.0
with:
arch: ${{ matrix.target == 'win32' && 'x86' || matrix.target == 'win_arm64' && 'arm64' || matrix.target == 'win_amd64' && 'amd64' || '' }}

- name: Build wheels
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
env:
CIBW_BUILD: "*${{ matrix.target }}"
CIBW_SKIP: "cp31*t-* pp3*"
CIBW_PROJECT_REQUIRES_PYTHON: ">=${{ matrix.lapiversion }}"
QUICKTIONS_LIMITED_API: "${{ matrix.lapiversion }}"

- name: Check wheel
run: |
python -m pip install twine
python -m twine check ./wheelhouse/*.whl

- name: Upload wheels
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: Stable-ABI-${{ matrix.target }}-${{ matrix.lapiversion }}
path: ./wheelhouse/*.whl

merge_wheels:
name: Merge wheel archives
needs: build_wheels
needs: [build_wheels, build_limited_api_wheels]
runs-on: ubuntu-latest

steps:
Expand All @@ -167,8 +252,8 @@ jobs:
contents: write

steps:
- name: Check out the repo
uses: actions/checkout@v6.0.1
- name: Check out project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Download files
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include MANIFEST.in LICENSE *.rst
include setup.py *.yml tox.ini *.cmd *.txt .coveragerc
recursive-include src *.py *.pyx *.pxd *.c *.txt *.html
recursive-include src *.py *.pyx *.pxd *.c *.h *.txt *.html
recursive-include benchmark *.py telco-bench.b
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ clean:
realclean: clean
rm -fr src/*.c src/*.html

src/todecimal.h: gen_todecimal.py
$(PYTHON) $< > $@

qemu-user-static:
docker run --rm --privileged hypriot/qemu-register

Expand Down
57 changes: 57 additions & 0 deletions gen_todecimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Generate and print a C snippet that maps Unicode digit code points to ASCII digits.
"""

import sys
import unicodedata
from collections import defaultdict


def list_digits():
category_of = unicodedata.category
return [
ch for ch in range(128, 1114111+1)
if category_of(chr(ch)) == 'Nd'
]


def map_to_ascii_digit(digits):
adigit_to_udigit = defaultdict(list)
for ch in digits:
adigit = int(chr(ch))
if ch & 15 == adigit:
adigit_to_udigit['{digit} & 15'].append(ch)
elif (ch - 6) & 15 == adigit:
adigit_to_udigit['({digit} - 6) & 15'].append(ch)
elif (ch - 0x116da) & 15 == adigit:
adigit_to_udigit['({digit} - 0x116da) & 15'].append(ch)
elif (ch - 0x1e5f1) & 15 == adigit:
adigit_to_udigit['({digit} - 0x1e5f1) & 15'].append(ch)
elif ch >= 0x1d7ce and (ch - 0x1d7ce) % 10 == adigit:
adigit_to_udigit['({digit} - 0x1d7ce) % 10'].append(ch)
else:
adigit_to_udigit[str(adigit)].append(ch)
return adigit_to_udigit


def gen_switch_cases(digits_by_adigit):
pyver = sys.version_info
print(f"/* Switch cases generated from Python {pyver[0]}.{pyver[1]}.{pyver[2]} {pyver[3]} {pyver[4]} */")
print("/* Deliberately excluding ASCII digit characters. */")
print()
print(f"/* Lowest digit: {min(ch for characters in digits_by_adigit.values() for ch in characters)} */")
print("switch (digit) {")

for adigit_format, udigits in digits_by_adigit.items():
for ch in udigits:
print(f' case 0x{ch:x}:')

print(f" return {adigit_format.format(digit='digit')};")

print(" default:")
print(" return -1;")
print("}")


if __name__ == '__main__':
gen_switch_cases(map_to_ascii_digit(list_digits()))
33 changes: 22 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@ requires = ["Cython>=3.2.4", "setuptools"]
[tool.cibuildwheel]
build-verbosity = 2
skip = ["pp*", "*-musllinux_i686", "cp35*", "cp36*", "cp37*"]
enable =["cpython-freethreading", "cpython-prerelease"]
enable =[
"cpython-prerelease",
]
environment-pass = "QUICKTIONS_LIMITED_API"
# test-command = "python -m unittest {package}/test_fractions.py -p -v"

[tool.cibuildwheel.windows]
archs = ["AMD64", "x86", "ARM64"]

[tool.cibuildwheel.macos]
# https://cibuildwheel.readthedocs.io/en/stable/faq/#what-to-provide suggests to provide
# x86_64 and one of universal2 or arm64 wheels. x86_64 is still required by older pips,
# so additional arm64 wheels suffice.
#archs = ["x86_64", "universal2"]
archs = ["x86_64", "arm64"]

[tool.cibuildwheel.linux]
archs = ["x86_64", "aarch64", "i686", "ppc64le", "armv7l", "riscv64"]
repair-wheel-command = "auditwheel repair --strip -w {dest_dir} {wheel}"

[tool.cibuildwheel.linux.environment]
CFLAGS = "-O3 -g1 -pipe -fPIC"
CFLAGS = "-O3 -g1 -pipe -fPIC -std=c99"
AR = "gcc-ar"
NM = "gcc-nm"
RANLIB = "gcc-ranlib"
Expand All @@ -32,12 +45,10 @@ select = "*aarch64"
inherit.environment = "append"
environment.CFLAGS = "-O3 -g1 -pipe -fPIC -march=armv8-a -mtune=cortex-a72"

[tool.cibuildwheel.windows]
archs = ["AMD64", "x86"]

[tool.cibuildwheel.macos]
# https://cibuildwheel.readthedocs.io/en/stable/faq/#what-to-provide suggests to provide
# x86_64 and one of universal2 or arm64 wheels. x86_64 is still required by older pips,
# so additional arm64 wheels suffice.
#archs = ["x86_64", "universal2"]
archs = ["x86_64", "arm64"]
[[tool.cibuildwheel.overrides]]
#select = "*i686 *musllinux* *win32 *win_arm64 *macosx_x86_64 *armv7l"
select = "cp3{9,1?}-*i686 cp3{9,1?}-*musllinux* cp3{9,1?}-*macosx_x86_64 cp3{9,1?}-*armv7l cp3{9,1?}-*ppc64le cp3{9,1?}-*riscv64"
inherit.test-requires = "append"
test-requires = ["abi3audit>=0.0.25"]
inherit.test-command = "append"
test-command = ["abi3audit --strict --report {wheel}"]
43 changes: 39 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
from setuptools import setup, Extension


ext_modules = [
Extension("quicktions", ["src/quicktions.pyx"]),
]

try:
sys.argv.remove("--with-profile")
except ValueError:
Expand All @@ -20,6 +16,44 @@
enable_coverage = os.environ.get("WITH_COVERAGE") == "1"
force_rebuild = os.environ.get("FORCE_REBUILD") == "1"

def check_limited_api_option(value):
if not value:
return None
value = value.lower()
if value == "true":
# The default Limited API version is 3.9, unless we're on a lower Python version
# (which is mainly for the sake of testing 3.8 on the CI)
if sys.version_info >= (3, 9):
return (3, 9)
else:
return sys.version_info[:2]
if value == 'false':
return None
major, minor = value.split('.', 1)
return (int(major), int(minor))

extra_setup_args = {}
c_defines = []

option_limited_api = check_limited_api_option(os.environ.get("QUICKTIONS_LIMITED_API"))
if option_limited_api:
c_defines.append(('Py_LIMITED_API', f'0x{option_limited_api[0]:02x}{option_limited_api[1]:02x}0000'))

setup_options = extra_setup_args.setdefault('options', {})
bdist_wheel_options = setup_options.setdefault('bdist_wheel', {})
bdist_wheel_options['py_limited_api'] = f'cp{option_limited_api[0]}{option_limited_api[1]}'


ext_modules = [
Extension(
"quicktions",
["src/quicktions.pyx"],
py_limited_api=True if option_limited_api else False,
define_macros=c_defines,
),
]


try:
sys.argv.remove("--with-cython")
except ValueError:
Expand Down Expand Up @@ -101,4 +135,5 @@
"Topic :: Scientific/Engineering :: Mathematics",
"Topic :: Office/Business :: Financial",
],
**extra_setup_args,
)
Loading
Loading