Skip to content

Move to a PEP 517 pyproject.toml build, drop setup.py - #73

Open
IvanaGyro wants to merge 3 commits into
fonttools:masterfrom
IvanaGyro:claude/unicodedata2-build-modernize-1m43e8
Open

Move to a PEP 517 pyproject.toml build, drop setup.py#73
IvanaGyro wants to merge 3 commits into
fonttools:masterfrom
IvanaGyro:claude/unicodedata2-build-modernize-1m43e8

Conversation

@IvanaGyro

@IvanaGyro IvanaGyro commented Aug 28, 2026

Copy link
Copy Markdown

Closes #53.

The only thing setup.py still needed dynamic logic for was appending unicodedata2/pypy_ctype.c to the source list on PyPy. That file existed solely to supply _Py_ctype_toupper[256] for Py_TOUPPER(), which CPython exports from libpython but PyPy's cpyext headers do not define.

Replace it with capability detection in _unicodedata2_compat.h. That header is included after Python.h, which is what makes "#ifdef Py_TOUPPER" meaningful: CPython defines the macro via cpython/pyctype.h (pulled in by Python.h on every non-limited-API build, 3.8 through main), PyPy does not. When it is missing we define UNICODEDATA2_TOUPPER() over a private, static 256-byte table with the same mapping. Detecting the macro rather than _Py_ctype_toupper matters: the latter is a linker symbol the preprocessor cannot see. The table is deliberately not named _Py_ctype_toupper, so nothing is added to CPython's reserved namespace, and it stays static so it is not exported from the extension.

Both branches of the macro apply Py_CHARMASK() to their argument, as Py_TOUPPER() always did, so the call sites no longer mask it themselves. The mask is idempotent and the compiler already folded the duplicate: the .text section is byte identical either way, on both interpreters.

Uppercasing stays table-based on both implementations, so name lookup performance is unchanged; on PyPy the table is now a same-translation-unit static const rather than an extern from a separate object file.

With no implementation-specific source, the extension can be declared statically via setuptools' ext-modules table, and setup.py can go. The build now needs setuptools >= 77.0.1 (declarative ext-modules landed in 74.1, PEP 639 license metadata in 77; 77.0.0 was never published).

Also:

  • long_description keeps its rendered form: setup.py injected a "Changelog" setext heading between README.md and CHANGELOG.md, so CHANGELOG.md now carries that heading itself and the two files are concatenated via tool.setuptools.dynamic.
  • deploy job builds the sdist with "python -m build --sdist".
  • tox envlist points at PyPy versions that still exist.
  • CIBW_ENABLE=pypy restores PyPy wheels: cibuildwheel 3.0 moved PyPy into an opt-in enable group, so 17.0.0 and 17.0.1 shipped none, where 16.0.0 and earlier shipped 11-12.

IvanaGyro and others added 3 commits August 28, 2026 11:04
Closes fonttools#53.

The only thing setup.py still needed dynamic logic for was appending
unicodedata2/pypy_ctype.c to the source list on PyPy. That file existed
solely to supply _Py_ctype_toupper[256] for Py_TOUPPER(), which CPython
exports from libpython but PyPy's cpyext headers do not define.

Replace it with capability detection in _unicodedata2_compat.h. That
header is included after Python.h, which is what makes "#ifdef Py_TOUPPER"
meaningful: CPython defines the macro via cpython/pyctype.h (pulled in by
Python.h on every non-limited-API build, 3.8 through main), PyPy does not.
When it is missing we define UNICODEDATA2_TOUPPER() over a private, static
256-byte table with the same mapping. Detecting the macro rather than
_Py_ctype_toupper matters: the latter is a linker symbol the preprocessor
cannot see. The table is deliberately not named _Py_ctype_toupper, so
nothing is added to CPython's reserved namespace, and it stays static so
it is not exported from the extension.

Both branches of the macro apply Py_CHARMASK() to their argument, as
Py_TOUPPER() always did, so the call sites no longer mask it themselves.
The mask is idempotent and the compiler already folded the duplicate: the
.text section is byte identical either way, on both interpreters.

Uppercasing stays table-based on both implementations, so name lookup
performance is unchanged; on PyPy the table is now a same-translation-unit
static const rather than an extern from a separate object file.

With no implementation-specific source, the extension can be declared
statically via setuptools' ext-modules table, and setup.py can go. The
build now needs setuptools >= 77.0.1 (declarative ext-modules landed in
74.1, PEP 639 license metadata in 77; 77.0.0 was never published).

Also:
- long_description keeps its rendered form: setup.py injected a "Changelog"
  setext heading between README.md and CHANGELOG.md, so CHANGELOG.md now
  carries that heading itself and the two files are concatenated via
  tool.setuptools.dynamic.
- deploy job builds the sdist with "python -m build --sdist".
- tox envlist points at PyPy versions that still exist.
- CIBW_ENABLE=pypy restores PyPy wheels: cibuildwheel 3.0 moved PyPy into
  an opt-in enable group, so 17.0.0 and 17.0.1 shipped none, where 16.0.0
  and earlier shipped 11-12.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fyhi9MNffq3B9JLVSukhEa
CIBW_TEST_EXTRAS installs this extra into every cibuildwheel test
environment, then CIBW_TEST_COMMAND runs plain "pytest {project}/tests".
Nothing in the repo sets addopts, passes -n, or passes --cov: there is no
conftest.py, no pytest.ini, no setup.cfg, and neither pyproject.toml nor
tox.ini configures pytest. So pytest-xdist loaded as a plugin and sat
idle, and coverage was never reachable at all -- pytest integration would
need pytest-cov, and nothing invokes "coverage run" either.

Both were installed for every wheel in the matrix across three operating
systems, including the aarch64 jobs that run under QEMU emulation.

pytest-randomly stays: it is a plugin that shuffles test order simply by
being installed, so it does affect the run.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fyhi9MNffq3B9JLVSukhEa
tox is an orchestrator, not a test dependency: it builds the environment
that installs the package, so it does not belong in the testing extra,
which CIBW_TEST_EXTRAS installs into every wheel test environment and
which tox itself does not even use (tox.ini declares deps = pytest).

Dependency groups are the right home. They are local-only: nothing about
them reaches the published wheel or sdist metadata, so this adds no
install-time cost or risk for anyone depending on unicodedata2, while
giving contributors "pip install --group dev" (pip >= 25.1) alongside the
existing "pip install tox".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fyhi9MNffq3B9JLVSukhEa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PEP517

1 participant