Skip to content

[BUG] Wheel tagging uses the build interpreter rather than the target when cross-compiling #5360

Closed

Description

@jaraco

Description

bdist_wheel.get_tag() derives the wheel's interpreter and ABI from the running interpreter, so a cross-build can compute a correct platform tag and still be rejected — or tagged for the build machine rather than the target.

This came out of @jameshilliard's review of #5193. It is deliberately out of scope for #5352 (which fixes the extension filename via target sysconfig) and for #5193 (which fixes the wheel tag for abi3t), so filing it separately.

The host-derived inputs

In setuptools/command/bdist_wheel.py:

  • tags.interpreter_name() and tags.interpreter_version() (lines 357–358) read sys.implementation and the host-derived py_version_nodot.

  • tags.sys_tags() (line 369) is defined entirely in terms of the running interpreter. The current code substitutes plat_name for each tag's platform:

    supported_tags = [(t.interpreter, t.abi, plat_name) for t in tags.sys_tags()]
    assert tag in supported_tags, f"would build wheel with unsupported tag {tag}"

    Replacing only the platform leaves the interpreter-version and ABI assumptions intact, so the assertion still describes the build machine.

Note that sysconfig.get_config_var(...) is not affected in the same way: under _PYTHON_SYSCONFIGDATA_NAME it reads the target's data. That asymmetry is what makes the bug easy to miss — Py_GIL_DISABLED describes the target while sys_tags() describes the host, and the two can disagree.

Suggested direction

@jameshilliard suggests the documented tags.cpython_tags(python_version=target_version, abis=target_abis, platforms=[plat_name]) for the CPython path, with all three inputs derived from the configured target, preserving separate handling for other implementations. For CPython POSIX builds, sysconfig.get_config_var("VERSION") comes from the selected target build data.

He also warns against sysconfig.get_python_version(), py_version_short or py_version_nodot as substitutes — CPython derives those from the running interpreter too. With a real AArch64 3.14 sysconfig loaded into a 3.15 build interpreter he measured VERSION="3.14" but py_version_short="3.15" and py_version_nodot="315".

Related

Also worth auditing

_is_32bit_interpreter() uses the build interpreter's struct.calcsize("P") where the target's SIZEOF_VOID_P is the relevant value.

Activity

  1. added a commit that references this issue on Oct 19, 2026
  2. jameshilliard commented on Oct 20, 2026

    @jameshilliard
    Contributor

    I think I found a few similar issues in distutils.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions