Pruning a spatial wheel matrix with download data

This page answers one question: your matrix builds platforms nobody installs and the release takes an hour, so how do you use real download statistics to decide which cells to drop — without removing a platform that only looks unused because it has been broken? It sits inside the CI Matrix Recipes for Spatial Wheels section of the Modern Python Build Tooling & Wheel Configuration reference, and gives you the query, the interpretation and the deprecation path.

Downloads per platform for a typical spatial package over one month A horizontal breakdown of downloads by platform tag. Linux x86_64 manylinux dominates at about seventy per cent. Linux aarch64 is about twelve per cent. macOS arm64 is about nine per cent. Windows amd64 is about six per cent. musllinux is about two per cent. Older Intel macOS is under one per cent. A note observes that the smallest bar is a candidate for removal only if it has been working. share of downloads, one month manylinux x86_64 70% manylinux aarch64 12% macosx arm64 9% win amd64 6% musllinux x86_64 2% macosx x86_64 <1% the smallest bars are candidates for removal — but only after confirming they have actually been working

Context & Root Cause

Every cell in a spatial matrix compiles GDAL, so the cost of a platform is measured in runner-minutes rather than in configuration lines. A matrix that has accumulated cells over several years — a 32-bit target, an old macOS, an architecture added speculatively — spends most of its wall-clock time producing artifacts almost nobody downloads.

The reason to use data rather than intuition is that both directions of error are expensive. Keeping an unused cell wastes an hour per release forever; removing a used one breaks installs for a population you cannot see and generates issues you will spend longer on. And there is a specific trap in the data itself: a platform that has never worked has no downloads, so the statistics recommend removing exactly the platform you should have fixed.

Solution / Fix

This targets a package published on a public index whose download statistics are queryable, and a matrix defined in the shared platform file described in CI matrix recipes for spatial wheels.

1. Get downloads per platform tag, not per version

-- Downloads by wheel platform tag over the last 90 days
SELECT
  REGEXP_EXTRACT(file.filename, r'-(manylinux[^.]*|musllinux[^.]*|macosx[^.]*|win[^.]*)\.whl$') AS platform,
  COUNT(*) AS downloads
FROM `bigquery-public-data.pypi.file_downloads`
WHERE file.project = 'geo-core'
  AND DATE(timestamp) > DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
  AND file.filename LIKE '%.whl'
GROUP BY platform
ORDER BY downloads DESC

2. Discount the noise before reading it

# Mirrors and CI runners inflate the leading platform; exclude known installers
#   AND details.installer.name = 'pip'
# and consider excluding your own CI's egress range if it is identifiable

3. Check whether a quiet platform is broken rather than unwanted

docker run --rm python:3.12-alpine sh -c \
  "pip install -q geo-core && python -c 'import geo_core; print(\"musllinux ok\")'"

4. Deprecate before removing

<!-- release notes, one release before removal -->
The `macosx_10_13_x86_64` wheel will not be published after version 3.0.
Users on Intel macOS below 11.0 should pin `geo-core<3.0` or build from source.

Verification

# 1. The platform file and the published artifacts agree
python - <<'PY'
import json, glob, re
want = {p["tag"] for p in json.load(open("ci/platforms.json"))["platforms"]}
have = {re.search(r'-([^-]+)\.whl$', f).group(1) for f in glob.glob("dist/*.whl")}
assert want == have, (want ^ have)
print("matrix and artifacts agree")
PY
# 2. Every platform still in the matrix installs and imports
for img in python:3.12-slim python:3.12-alpine; do
  docker run --rm -v "$PWD/dist:/d" $img sh -c \
    "pip install -q --no-index --find-links /d geo-core && python -c 'import geo_core'"
done
# 3. Runner time actually fell
grep -h 'duration' ci-timings-*.json | python -c "
import sys, json
total = sum(json.loads(l)['duration'] for l in sys.stdin)
print(f'total runner minutes: {total/60:.0f}')"

The first check is the one to keep permanently. A matrix and an artifact set that disagree is how a platform silently disappears from a release, and comparing them takes one line — far cheaper than the bug report that otherwise reveals it.

Reading the Statistics Honestly

Download numbers are noisy in specific, known ways, and correcting for them changes which cells look expendable.

Four distortions in raw download statistics and how to correct each Continuous integration systems inflate the dominant Linux platform because every pipeline run installs again; filtering by installer or excluding known ranges helps. Mirrors fetch every file regardless of demand, which inflates rarely-used platforms. A broken platform reports near-zero downloads because nobody can use it, which recommends removing exactly the wrong cell. And a recently added platform has low numbers simply because adoption takes months. CI inflation every pipeline run downloads again, overwhelmingly on Linux x86_64 correction: filter by installer, and read shares between the non-dominant platforms mirror fetches a mirror pulls every file whether or not anyone wants it correction: a small constant floor on every platform is mirrors, not users broken platforms a wheel that has never imported has no downstream users to count correction: install and import each platform before trusting its number recent additions adoption of a new platform lags its availability by months correction: give a new cell at least two release cycles before judging it

The third row is the important one and the reason step three of the fix exists. Before concluding that musllinux has no users, install the published musllinux wheel in an Alpine container and import it. If it fails, the low number measures brokenness rather than demand, and the correct action is a fix followed by another six months of data.

A fourth consideration sits outside the statistics entirely: some platforms matter out of proportion to their volume. A wheel used by two downstream distributions that themselves serve thousands of users shows up as a handful of downloads. Where you know of such consumers, ask them rather than inferring.

Cutting Cost Without Cutting Platforms

Pruning is the last lever, not the first, and two others usually recover more time with no user-facing cost at all.

Three ways to reduce matrix cost, ordered by whether users notice Collapsing the interpreter axis with an abi3 build removes four fifths of the cells and no capability. Caching or prebuilding the native stack removes most of the remaining time and no capability. Only after both is pruning platforms worth considering, and it is the only one of the three that removes something a user might depend on. 1 · collapse the interpreter axis one abi3 wheel per platform instead of five — removes 80% of the cells, costs nothing 2 · stop recompiling the native stack a prebuilt image or a warm object cache — removes most of the remaining minutes, costs nothing 3 · prune platforms the only lever that removes capability — use data, deprecate first, and announce it

Working in that order frequently makes step three unnecessary. A matrix that was sixty cells and forty minutes per cell becomes six cells and two minutes each once the interpreter axis is collapsed and the native stack is cached — at which point the musllinux cell costs two minutes a release and there is no reason to remove it.

Pitfalls & Alternatives

Removing a platform in a patch release. Nothing in a version number signals which wheels exist, so a user’s install simply starts falling back to a source build. Removals belong in a minor or major release with a note.

Reading absolute numbers rather than shares. Absolute downloads are dominated by CI traffic and vary with your project’s popularity. The share between the non-dominant platforms is the more stable signal.

Assuming Windows is negligible because its share is small. Windows users are disproportionately unable to build from source, so removing that wheel does not push them to a fallback — it removes the package for them entirely.

Dropping the sdist as part of the pruning. It is the fallback that makes a removed platform survivable, and it is cheap to produce. Removing platforms and the source distribution together is what turns a narrowing of support into a hard break.

Frequently Asked Questions

How much data is enough to decide?

At least ninety days, and at least two releases, so that a platform’s numbers are not dominated by a single unusual week. For a platform added recently, wait longer — adoption of a new wheel tag lags its availability by months as downstream projects update their own matrices.

Should the deprecation notice go anywhere besides the release notes?

A warning at import time is worth considering for a platform you are about to drop, because release notes are read by a minority of users. A single message naming the platform and the version it will disappear in reaches the people who actually need it.

Is it worth keeping a platform for one large user?

Frequently yes, and the way to find out is to ask. A cell that costs two minutes a release and serves a downstream distribution is a good trade; the same cell costing forty minutes because the native stack is uncached is a different calculation, and the fix there is the cache rather than the removal.

What about interpreters rather than platforms?

An abi3 build makes that axis a non-question: one wheel serves every interpreter from the floor upward. If you are still publishing version-specific wheels, collapsing that axis is a much larger saving than any platform pruning and removes nothing.

Do download statistics tell me which drivers to keep?

No — they are per file, not per feature. Driver usage has to come from your issue tracker, your test suite and from asking, which is why binary size and startup performance tuning treats driver pruning as a decision requiring evidence rather than a metric.

Can I bring a platform back after removing it?

Yes, and the cost is mostly credibility: users who moved to an alternative do not necessarily move back. Treat a removal as difficult to reverse, which is another argument for exhausting the no-cost levers first.

Does an sdist count as a platform for this purpose?

No, and it should never be pruned. It is the fallback that makes every removal survivable and the input that several distributions build from, and it costs seconds to produce. A release that drops platforms and the source distribution together turns a narrowing of support into a hard break.

How should the decision be recorded?

In the repository, next to the platform list: which platforms are published, when the data was last reviewed, and any commitment made to a specific downstream consumer. Without that, the same discussion recurs annually with no memory of what was decided or why.

What if downloads are dominated by a single mirror?

Then the numbers measure the mirror rather than users, and the shares between platforms become the only readable signal. Where a provider exposes an installer field, filtering on it removes most of that distortion; where it does not, treat small absolute numbers with corresponding caution.