Skip to content

chore: use CPython macros to construct PYBIND11_VERSION_HEX #5683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 23, 2025
Merged
10 changes: 5 additions & 5 deletions docs/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ For example:

#define PYBIND11_VERSION_MAJOR X
#define PYBIND11_VERSION_MINOR Y
#define PYBIND11_VERSION_MICRO Z
#define PYBIND11_VERSION_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA
#define PYBIND11_VERSION_RELEASE_SERIAL 0
#define PYBIND11_VERSION_PATCH Za0

For beta, ``PYBIND11_VERSION_PATCH`` should be ``Zb1``. RC's can be ``Zrc1``.
For a final release, this must be a simple integer. There is also
``PYBIND11_VERSION_HEX`` just below that needs to be updated.
For a final release, this must be a simple integer.


To release a new version of pybind11:
Expand All @@ -26,9 +28,7 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use
- Update the version number

- Update ``PYBIND11_VERSION_MAJOR`` etc. in
``include/pybind11/detail/common.h``. PATCH should be a simple integer.

- Update ``PYBIND11_VERSION_HEX`` just below as well.
``include/pybind11/detail/common.h``. MICRO should be a simple integer.

- Run ``nox -s tests_packaging`` to ensure this was done correctly.

Expand Down
28 changes: 25 additions & 3 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,35 @@
# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7."
#endif

// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// See also: https://github.com/python/cpython/blob/HEAD/Include/patchlevel.h
/* -- start version constants -- */
#define PYBIND11_VERSION_MAJOR 3
#define PYBIND11_VERSION_MINOR 0
#define PYBIND11_VERSION_MICRO 0
// ALPHA = 0xA, BETA = 0xB, GAMMA = 0xC (release candidate), FINAL = 0xF (stable release)
// - The release level is set to "alpha" for development versions.
// Use 0xA0 (LEVEL=0xA, SERIAL=0) for development versions.
// - For stable releases, set the serial to 0.
#define PYBIND11_VERSION_RELEASE_LEVEL PY_RELEASE_LEVEL_GAMMA
#define PYBIND11_VERSION_RELEASE_SERIAL 1
// String version of (micro, release level, release serial), e.g.: 0a0, 0b1, 0rc1, 0
#define PYBIND11_VERSION_PATCH 0rc1
/* -- end version constants -- */

// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Use 0xA0 for dev
#define PYBIND11_VERSION_HEX 0x030000C1
#if !defined(Py_PACK_FULL_VERSION)
// Stable API since Python 3.14.0a4
# define Py_PACK_FULL_VERSION(X, Y, Z, LEVEL, SERIAL) \
((((X) & 0xff) << 24) | (((Y) & 0xff) << 16) | (((Z) & 0xff) << 8) \
| (((LEVEL) & 0xf) << 4) | (((SERIAL) & 0xf) << 0))
#endif
// Version as a single 4-byte hex number, e.g. 0x030C04B5 == 3.12.4b5.
#define PYBIND11_VERSION_HEX \
Py_PACK_FULL_VERSION(PYBIND11_VERSION_MAJOR, \
PYBIND11_VERSION_MINOR, \
PYBIND11_VERSION_MICRO, \
PYBIND11_VERSION_RELEASE_LEVEL, \
PYBIND11_VERSION_RELEASE_SERIAL)

#include "pybind11_namespace_macros.h"

Expand Down
33 changes: 33 additions & 0 deletions tests/extra_python_package/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,36 @@ def tests_build_global_wheel(monkeypatch, tmpdir):

pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version)
assert pkgconfig_expected == pkgconfig


def test_version_matches():
header = MAIN_DIR / "include/pybind11/detail/common.h"
text = header.read_text()

# Extract the relevant macro values
regex_prefix = r"#\s*define\s+PYBIND11_VERSION_"
micro = re.search(rf"{regex_prefix}MICRO\s+(\d+)\b", text).group(1)
release_level = re.search(rf"{regex_prefix}RELEASE_LEVEL\s+(\w+)\b", text).group(1)
release_serial = re.search(
rf"{regex_prefix}RELEASE_SERIAL\s+(\d+)\b",
text,
).group(1)
patch = re.search(rf"{regex_prefix}PATCH\s+([\w.-]+)\b", text).group(1)

# Map release level macro to string
level_map = {
"PY_RELEASE_LEVEL_ALPHA": "a",
"PY_RELEASE_LEVEL_BETA": "b",
"PY_RELEASE_LEVEL_GAMMA": "rc",
"PY_RELEASE_LEVEL_FINAL": "",
}
level_str = level_map[release_level]

if release_level == "PY_RELEASE_LEVEL_FINAL":
assert level_str == ""
assert release_serial == "0"
expected_patch = micro
else:
expected_patch = f"{micro}{level_str}{release_serial}"

assert patch == expected_patch
Loading