From 04a0f2111b0cfbd78024200228cb45c11f5526cf Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Mon, 5 May 2025 17:56:38 +0800 Subject: [PATCH 01/11] feat(PyTreeKind): use `pybind11::native_enum` to create enum class `PyTreeKind` --- include/optree/pymacros.h | 10 +++++++--- src/optree.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/include/optree/pymacros.h b/include/optree/pymacros.h index 92011e1a..77d5bf74 100644 --- a/include/optree/pymacros.h +++ b/include/optree/pymacros.h @@ -21,12 +21,16 @@ limitations under the License. #include -namespace py = pybind11; - -#if PY_VERSION_HEX < 0x03090000 // Python 3.9 +#if !(defined(PY_VERSION_HEX) && PY_VERSION_HEX >= 0x03090000) // Python 3.9 #error "Python 3.9 or newer is required." #endif +#if !(defined(PYBIND11_VERSION_HEX) && PYBIND11_VERSION_HEX >= 0x020C00F0) // pybind11 2.12.0 +#error "pybind11 2.12.0 or newer is required." +#endif + +namespace py = pybind11; + #ifndef Py_ALWAYS_INLINE #define Py_ALWAYS_INLINE #endif diff --git a/src/optree.cpp b/src/optree.cpp index 91497933..d2445e4d 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -26,6 +26,16 @@ limitations under the License. #include #include +#if defined(PYBIND11_HAS_NATIVE_ENUM) || \ + (defined(PYBIND11_INTERNALS_VERSION) && PYBIND11_INTERNALS_VERSION >= 8) +#ifndef PYBIND11_HAS_NATIVE_ENUM +#define PYBIND11_HAS_NATIVE_ENUM +#endif +#include +#else +#undef PYBIND11_HAS_NATIVE_ENUM +#endif + namespace optree { py::module_ GetCxxModule(const std::optional& module) { @@ -205,6 +215,22 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] #define def_method_pos_only(...) def(__VA_ARGS__) #endif +#ifdef PYBIND11_HAS_NATIVE_ENUM + py::native_enum(mod, "PyTreeKind", "enum.IntEnum", "The kind of a pytree node.") + .value("CUSTOM", PyTreeKind::Custom, "A custom type.") + .value("LEAF", PyTreeKind::Leaf, "A opaque leaf node.") + .value("NONE", PyTreeKind::None, "None.") + .value("TUPLE", PyTreeKind::Tuple, "A tuple.") + .value("LIST", PyTreeKind::List, "A list.") + .value("DICT", PyTreeKind::Dict, "A dict.") + .value("NAMEDTUPLE", PyTreeKind::NamedTuple, "A collections.namedtuple.") + .value("ORDEREDDICT", PyTreeKind::OrderedDict, "A collections.OrderedDict.") + .value("DEFAULTDICT", PyTreeKind::DefaultDict, "A collections.defaultdict.") + .value("DEQUE", PyTreeKind::Deque, "A collections.deque.") + .value("STRUCTSEQUENCE", PyTreeKind::StructSequence, "A PyStructSequence.") + .finalize(); + auto PyTreeKindTypeObject = py::getattr(mod, "PyTreeKind"); +#else auto PyTreeKindTypeObject = py::enum_(mod, "PyTreeKind", "The kind of a pytree node.", py::module_local()) .value("CUSTOM", PyTreeKind::Custom, "A custom type.") @@ -218,6 +244,7 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] .value("DEFAULTDICT", PyTreeKind::DefaultDict, "A collections.defaultdict.") .value("DEQUE", PyTreeKind::Deque, "A collections.deque.") .value("STRUCTSEQUENCE", PyTreeKind::StructSequence, "A PyStructSequence."); +#endif auto* const PyTreeKind_Type = reinterpret_cast(PyTreeKindTypeObject.ptr()); PyTreeKind_Type->tp_name = "optree.PyTreeKind"; py::setattr(PyTreeKindTypeObject.ptr(), Py_Get_ID(__module__), Py_Get_ID(optree)); @@ -442,15 +469,23 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] #undef def_method_pos_only +// Make the types immutable to avoid attribute assignment, modification, and deletion. #ifdef Py_TPFLAGS_IMMUTABLETYPE PyTreeKind_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeSpec_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeIter_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; + +#ifndef PYBIND11_HAS_NATIVE_ENUM + // Only run Python C API `PyType_Ready(type)` on C++ types. + // Re-running `PyType_Ready` for native Python enums can cause unexpected behavior, + // such as infinite recursion for `repr(e)` and `hash(e)`. PyTreeKind_Type->tp_flags &= ~Py_TPFLAGS_READY; +#endif PyTreeSpec_Type->tp_flags &= ~Py_TPFLAGS_READY; PyTreeIter_Type->tp_flags &= ~Py_TPFLAGS_READY; #endif + // Re-ready types or do consistency checks for the types. if (PyType_Ready(PyTreeKind_Type) < 0) [[unlikely]] { INTERNAL_ERROR("`PyType_Ready(&PyTreeKind_Type)` failed."); } From 27f840d863aeefd91a7de28c09b7c53abb74316b Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Mon, 5 May 2025 20:34:01 +0800 Subject: [PATCH 02/11] test(workflows): test with latest `pybind11` --- .github/workflows/lint.yml | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index db2be506..a2f6b56b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,6 +42,7 @@ jobs: - name: Install dependencies run: | python -m pip install wheel pybind11 -r docs/requirements.txt + python -m pip install --force-reinstall pybind11@git+https://github.com/pybind/pybind11.git#egg=pybind11 - name: Install OpTree run: | diff --git a/pyproject.toml b/pyproject.toml index 557c180d..74165c87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # Package ###################################################################### [build-system] -requires = ["setuptools", "pybind11 >= 2.12"] +requires = ["setuptools", "pybind11@git+https://github.com/pybind/pybind11.git#egg=pybind11"] build-backend = "setuptools.build_meta" [project] From 3eb50787f9ce17a280f45a694b8b94a8419e0673 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Wed, 7 May 2025 14:24:18 +0800 Subject: [PATCH 03/11] fix: never call `PyType_Ready()` twice --- src/optree.cpp | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/optree.cpp b/src/optree.cpp index d2445e4d..f25e3f6e 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -49,6 +49,8 @@ py::module_ GetCxxModule(const std::optional& module) { } void BuildModule(py::module_& mod) { // NOLINT[runtime/references] + const scoped_critical_section lock{mod}; + GetCxxModule(mod); mod.doc() = "Optimized PyTree Utilities."; @@ -471,30 +473,14 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] // Make the types immutable to avoid attribute assignment, modification, and deletion. #ifdef Py_TPFLAGS_IMMUTABLETYPE + // Locked by scoped_critical_section{mod} PyTreeKind_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeSpec_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeIter_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; - -#ifndef PYBIND11_HAS_NATIVE_ENUM - // Only run Python C API `PyType_Ready(type)` on C++ types. - // Re-running `PyType_Ready` for native Python enums can cause unexpected behavior, - // such as infinite recursion for `repr(e)` and `hash(e)`. - PyTreeKind_Type->tp_flags &= ~Py_TPFLAGS_READY; #endif - PyTreeSpec_Type->tp_flags &= ~Py_TPFLAGS_READY; - PyTreeIter_Type->tp_flags &= ~Py_TPFLAGS_READY; -#endif - - // Re-ready types or do consistency checks for the types. - if (PyType_Ready(PyTreeKind_Type) < 0) [[unlikely]] { - INTERNAL_ERROR("`PyType_Ready(&PyTreeKind_Type)` failed."); - } - if (PyType_Ready(PyTreeSpec_Type) < 0) [[unlikely]] { - INTERNAL_ERROR("`PyType_Ready(&PyTreeSpec_Type)` failed."); - } - if (PyType_Ready(PyTreeIter_Type) < 0) [[unlikely]] { - INTERNAL_ERROR("`PyType_Ready(&PyTreeIter_Type)` failed."); - } + PyType_Modified(PyTreeKind_Type); + PyType_Modified(PyTreeSpec_Type); + PyType_Modified(PyTreeIter_Type); py::getattr(py::module_::import("atexit"), "register")(py::cpp_function(&PyTreeTypeRegistry::Clear)); From 39897df3bcc2523a866e4df8f0c4743e02b1d2ac Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Wed, 7 May 2025 21:35:36 +0800 Subject: [PATCH 04/11] docs(CHANGELOG): update CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93400f01..8c839784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Explicitly set recursion limit for recursion tests by [@XuehaiPan](https://github.com/XuehaiPan) in [#207](https://github.com/metaopt/optree/pull/207). -- Dump build-time meta-information to C extension [@XuehaiPan](https://github.com/XuehaiPan) in [#215](https://github.com/metaopt/optree/pull/215). +- Dump build-time meta-information to C extension by [@XuehaiPan](https://github.com/XuehaiPan) in [#215](https://github.com/metaopt/optree/pull/215). +- Use `pybind11::native_enum` to create enum class `PyTreeKind` if available by [@XuehaiPan](https://github.com/XuehaiPan) in [#214](https://github.com/metaopt/optree/pull/214). ### Changed @@ -23,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- +- Never call `PyType_Ready` twice and use `PyType_Modified` instead by [@XuehaiPan](https://github.com/XuehaiPan) in [#214](https://github.com/metaopt/optree/pull/214). ### Removed From 2e00749adaf23c9ade8c83b68981ce8e3ce3a02b Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Wed, 7 May 2025 22:09:21 +0800 Subject: [PATCH 05/11] feat(workflows): use nightly pybind11 with PR label --- .github/workflows/build.yml | 15 +++++++++++---- .github/workflows/lint.yml | 13 ++++++++++++- .github/workflows/set_setup_requires.py | 24 ++++++++++++++++++++++++ .github/workflows/tests-with-pydebug.yml | 13 ++++++++++++- .github/workflows/tests.yml | 13 ++++++++++++- pyproject.toml | 2 +- 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100755 .github/workflows/set_setup_requires.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ab363ac..62ae68f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,9 @@ env: jobs: build-sdist: name: Build sdist - if: github.repository == 'metaopt/optree' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) + if: | + github.repository_owner == 'metaopt' && + (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -86,7 +88,9 @@ jobs: build-wheels: name: Build wheels for Python ${{ matrix.python-version }} on ${{ matrix.runner }} (${{ matrix.archs }}) - if: github.repository == 'metaopt/optree' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) + if: | + github.repository_owner == 'metaopt' && + (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) runs-on: ${{ matrix.runner }} strategy: matrix: @@ -241,7 +245,9 @@ jobs: list-artifacts: name: List artifacts - if: github.repository == 'metaopt/optree' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) + if: | + github.repository_owner == 'metaopt' && + (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) runs-on: ubuntu-latest needs: [build-sdist, build-wheels] timeout-minutes: 15 @@ -275,7 +281,8 @@ jobs: runs-on: ubuntu-latest needs: [list-artifacts] if: | - github.repository == 'metaopt/optree' && github.event_name != 'pull_request' && + github.repository_owner == 'metaopt' && + github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || github.event.inputs.task == 'build-and-publish') && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) timeout-minutes: 15 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a2f6b56b..f1248284 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,7 +42,18 @@ jobs: - name: Install dependencies run: | python -m pip install wheel pybind11 -r docs/requirements.txt - python -m pip install --force-reinstall pybind11@git+https://github.com/pybind/pybind11.git#egg=pybind11 + + - name: Install nightly pybind11 + shell: bash + if: | + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') + run: | + python .github/workflows/set_setup_requires.py + echo "::group::pyproject.toml" + cat pyproject.toml + echo "::endgroup::" + python -m pip install --force-reinstall 'pybind11 @ git+https://github.com/pybind/pybind11.git#egg=pybind11' - name: Install OpTree run: | diff --git a/.github/workflows/set_setup_requires.py b/.github/workflows/set_setup_requires.py new file mode 100755 index 00000000..5bbe74c0 --- /dev/null +++ b/.github/workflows/set_setup_requires.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +# pylint: disable=missing-module-docstring + +import re +from pathlib import Path + + +ROOT = Path(__file__).absolute().parents[2] + +PYPROJECT_FILE = ROOT / 'pyproject.toml' + + +if __name__ == '__main__': + PYPROJECT_CONTENT = PYPROJECT_FILE.read_text(encoding='utf-8') + + PYPROJECT_FILE.write_text( + data=re.sub( + r'(requires\s*=\s*\[.*"\s*)\bpybind11\b[^"]*(\s*".*\])', + r'\g<1>pybind11 @ git+https://github.com/pybind/pybind11.git#egg=pybind11\g<2>', + string=PYPROJECT_CONTENT, + ), + encoding='utf-8', + ) diff --git a/.github/workflows/tests-with-pydebug.yml b/.github/workflows/tests-with-pydebug.yml index 753085dd..3e55db60 100644 --- a/.github/workflows/tests-with-pydebug.yml +++ b/.github/workflows/tests-with-pydebug.yml @@ -284,6 +284,17 @@ jobs: cdb -version + - name: Use nightly pybind11 + shell: bash + if: | + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') + run: | + python .github/workflows/set_setup_requires.py + echo "::group::pyproject.toml" + cat pyproject.toml + echo "::endgroup::" + - name: Install OpTree run: | ${{ env.PYTHON }} -m pip install -v --editable '.[test]' @@ -311,7 +322,7 @@ jobs: find . -type f -name '*.py[cod]' -delete find . -depth -type d -name "__pycache__" -exec rm -r "{}" + if git status --ignored --porcelain | grep -qvE '/$'; then - ls -alh $(git status --ignored --porcelain | grep -vE '/$' | cut -d ' ' -f2) + ls -alh $(git status --ignored --porcelain | grep -vE '/$' | grep -oE '\S+$') fi - name: Collect backtraces from coredumps (if any) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b069cce..b5ba06db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -142,6 +142,17 @@ jobs: cdb -version + - name: Use nightly pybind11 + shell: bash + if: | + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') + run: | + python .github/workflows/set_setup_requires.py + echo "::group::pyproject.toml" + cat pyproject.toml + echo "::endgroup::" + - name: Test installable with C++17 shell: bash if: runner.os != 'Windows' @@ -236,7 +247,7 @@ jobs: find . -type f -name '*.py[cod]' -delete find . -depth -type d -name "__pycache__" -exec rm -r "{}" + if git status --ignored --porcelain | grep -qvE '/$'; then - ls -alh $(git status --ignored --porcelain | grep -vE '/$' | cut -d ' ' -f2) + ls -alh $(git status --ignored --porcelain | grep -vE '/$' | grep -oE '\S+$') fi - name: Collect backtraces from coredumps (if any) diff --git a/pyproject.toml b/pyproject.toml index 74165c87..557c180d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # Package ###################################################################### [build-system] -requires = ["setuptools", "pybind11@git+https://github.com/pybind/pybind11.git#egg=pybind11"] +requires = ["setuptools", "pybind11 >= 2.12"] build-backend = "setuptools.build_meta" [project] From 2d56739d92a96570be6f1e90728d3080f836a762 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 11:20:56 +0800 Subject: [PATCH 06/11] chore: simplify `py::native_enum` detection --- optree/_C.pyi | 1 + src/optree.cpp | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/optree/_C.pyi b/optree/_C.pyi index 31a5f01d..7edf2637 100644 --- a/optree/_C.pyi +++ b/optree/_C.pyi @@ -45,6 +45,7 @@ if sys.implementation.name == 'pypy': # noqa: PYI002 PYPY_VERSION_HEX: Final[int] PYBIND11_VERSION_HEX: Final[int] PYBIND11_INTERNALS_VERSION: Final[int] +PYBIND11_HAS_NATIVE_ENUM: Final[bool] GLIBCXX_USE_CXX11_ABI: Final[bool] @final diff --git a/src/optree.cpp b/src/optree.cpp index f25e3f6e..1189a1e7 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -26,14 +26,8 @@ limitations under the License. #include #include -#if defined(PYBIND11_HAS_NATIVE_ENUM) || \ - (defined(PYBIND11_INTERNALS_VERSION) && PYBIND11_INTERNALS_VERSION >= 8) -#ifndef PYBIND11_HAS_NATIVE_ENUM -#define PYBIND11_HAS_NATIVE_ENUM -#endif +#ifdef PYBIND11_HAS_NATIVE_ENUM #include -#else -#undef PYBIND11_HAS_NATIVE_ENUM #endif namespace optree { @@ -66,6 +60,11 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] #endif mod.attr("PYBIND11_VERSION_HEX") = py::int_(PYBIND11_VERSION_HEX); mod.attr("PYBIND11_INTERNALS_VERSION") = py::int_(PYBIND11_INTERNALS_VERSION); +#ifdef PYBIND11_HAS_NATIVE_ENUM + mod.attr("PYBIND11_HAS_NATIVE_ENUM") = py::bool_(true); +#else + mod.attr("PYBIND11_HAS_NATIVE_ENUM") = py::bool_(false); +#endif #ifdef _GLIBCXX_USE_CXX11_ABI // NOLINTNEXTLINE[modernize-use-bool-literals] mod.attr("GLIBCXX_USE_CXX11_ABI") = py::bool_(static_cast(_GLIBCXX_USE_CXX11_ABI)); From 9dd49a959868e3ea487e06944fa8ba9f84a1e15d Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 12:02:35 +0800 Subject: [PATCH 07/11] test: add test for enum class `PyTreeKind` --- include/optree/registry.h | 2 ++ optree/_C.pyi | 6 ++++-- src/optree.cpp | 1 + tests/test_typing.py | 40 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/optree/registry.h b/include/optree/registry.h index 2aaf935e..fecdc61b 100644 --- a/include/optree/registry.h +++ b/include/optree/registry.h @@ -64,6 +64,8 @@ constexpr PyTreeKind kDefaultDict = PyTreeKind::DefaultDict; constexpr PyTreeKind kDeque = PyTreeKind::Deque; constexpr PyTreeKind kStructSequence = PyTreeKind::StructSequence; +constexpr std::uint8_t kNumPyTreeKinds = std::uint8_t(kStructSequence) + 1; // last enum value + 1 + // Registry of custom node types. class PyTreeTypeRegistry { public: diff --git a/optree/_C.pyi b/optree/_C.pyi index 7edf2637..56f07f01 100644 --- a/optree/_C.pyi +++ b/optree/_C.pyi @@ -19,8 +19,8 @@ import builtins import enum import sys from collections.abc import Callable, Collection, Iterable, Iterator -from typing import Any, Final, final -from typing_extensions import Self +from typing import Any, ClassVar, Final, final +from typing_extensions import Self # Python 3.11+ from optree.typing import ( FlattenFunc, @@ -65,6 +65,8 @@ class PyTreeKind(enum.IntEnum): DEQUE = enum.auto() # a collections.deque STRUCTSEQUENCE = enum.auto() # a PyStructSequence + NUM_KINDS: ClassVar[int] + MAX_RECURSION_DEPTH: Final[int] @final diff --git a/src/optree.cpp b/src/optree.cpp index 1189a1e7..9209462c 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -249,6 +249,7 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] auto* const PyTreeKind_Type = reinterpret_cast(PyTreeKindTypeObject.ptr()); PyTreeKind_Type->tp_name = "optree.PyTreeKind"; py::setattr(PyTreeKindTypeObject.ptr(), Py_Get_ID(__module__), Py_Get_ID(optree)); + py::setattr(PyTreeKindTypeObject.ptr(), "NUM_KINDS", py::int_(optree::kNumPyTreeKinds)); auto PyTreeSpecTypeObject = py::class_( mod, diff --git a/tests/test_typing.py b/tests/test_typing.py index 97b27e8d..ea5ffa07 100644 --- a/tests/test_typing.py +++ b/tests/test_typing.py @@ -15,6 +15,7 @@ # pylint: disable=missing-function-docstring +import enum import re import sys import time @@ -33,6 +34,7 @@ getrefcount, skipif_pypy, ) +from optree._C import PYBIND11_HAS_NATIVE_ENUM class FakeNamedTuple(tuple): @@ -67,6 +69,44 @@ class FakeStructSequence(tuple): n_unnamed_fields = 0 +def test_pytreekind_enum(): + if PYBIND11_HAS_NATIVE_ENUM: + all_kinds = list(optree.PyTreeKind) + assert len(all_kinds) == optree.PyTreeKind.NUM_KINDS + assert issubclass(optree.PyTreeKind, enum.IntEnum) + assert issubclass(optree.PyTreeKind, int) + + assert optree.PyTreeKind.CUSTOM == 0 + assert optree.PyTreeKind.LEAF == 1 + assert optree.PyTreeKind.NONE == 2 + assert optree.PyTreeKind.CUSTOM.name == 'CUSTOM' + assert optree.PyTreeKind.LEAF.name == 'LEAF' + assert optree.PyTreeKind.NONE.name == 'NONE' + for i, kind in enumerate(all_kinds): + assert isinstance(kind, int) + assert kind == i + assert kind is optree.PyTreeKind(i) + assert kind is getattr(optree.PyTreeKind, kind.name) + + with pytest.raises(ValueError, match=r'.* is not a valid .*\bPyTreeKind\b.*'): + optree.PyTreeKind(optree.PyTreeKind.NUM_KINDS) + else: + all_kinds = [optree.PyTreeKind(i) for i in range(optree.PyTreeKind.NUM_KINDS)] + + assert optree.PyTreeKind.CUSTOM.value == 0 + assert optree.PyTreeKind.LEAF.value == 1 + assert optree.PyTreeKind.NONE.value == 2 + assert optree.PyTreeKind.CUSTOM.name == 'CUSTOM' + assert optree.PyTreeKind.LEAF.name == 'LEAF' + assert optree.PyTreeKind.NONE.name == 'NONE' + for i, kind in enumerate(all_kinds): + assert isinstance(kind, optree.PyTreeKind) + assert int(kind) == i + assert kind.value == i + assert kind == optree.PyTreeKind(i) + assert kind == getattr(optree.PyTreeKind, kind.name) + + def test_is_namedtuple(): def is_namedtuple_(obj): nonlocal is_namedtuple, is_namedtuple_class, is_namedtuple_instance From 94b5f0bdc6623fd19193289538dea9f0eba50159 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 14:19:33 +0800 Subject: [PATCH 08/11] feat: add `_C.BUILDTIME_METADATA` --- include/optree/registry.h | 4 +-- optree/_C.pyi | 2 ++ src/optree.cpp | 47 ++++++++++++++++++++------------- src/treespec/constructors.cpp | 1 + src/treespec/flatten.cpp | 3 +++ src/treespec/hashing.cpp | 1 + src/treespec/richcomparison.cpp | 1 + src/treespec/serialization.cpp | 3 +++ src/treespec/traversal.cpp | 2 ++ src/treespec/treespec.cpp | 8 ++++++ src/treespec/unflatten.cpp | 1 + 11 files changed, 53 insertions(+), 20 deletions(-) diff --git a/include/optree/registry.h b/include/optree/registry.h index fecdc61b..d39c792e 100644 --- a/include/optree/registry.h +++ b/include/optree/registry.h @@ -50,6 +50,7 @@ enum class PyTreeKind : std::uint8_t { DefaultDict, // A collections.defaultdict Deque, // A collections.deque StructSequence, // A PyStructSequence + NumKinds, // Number of kinds (placed at the end) }; constexpr PyTreeKind kCustom = PyTreeKind::Custom; @@ -63,8 +64,7 @@ constexpr PyTreeKind kOrderedDict = PyTreeKind::OrderedDict; constexpr PyTreeKind kDefaultDict = PyTreeKind::DefaultDict; constexpr PyTreeKind kDeque = PyTreeKind::Deque; constexpr PyTreeKind kStructSequence = PyTreeKind::StructSequence; - -constexpr std::uint8_t kNumPyTreeKinds = std::uint8_t(kStructSequence) + 1; // last enum value + 1 +constexpr PyTreeKind kNumPyTreeKinds = PyTreeKind::NumKinds; // Registry of custom node types. class PyTreeTypeRegistry { diff --git a/optree/_C.pyi b/optree/_C.pyi index 56f07f01..043aaa34 100644 --- a/optree/_C.pyi +++ b/optree/_C.pyi @@ -19,6 +19,7 @@ import builtins import enum import sys from collections.abc import Callable, Collection, Iterable, Iterator +from types import MappingProxyType from typing import Any, ClassVar, Final, final from typing_extensions import Self # Python 3.11+ @@ -37,6 +38,7 @@ from optree.typing import ( Py_TPFLAGS_BASETYPE: Final[int] # (1UL << 10) # Meta-information during build-time +BUILDTIME_METADATA: Final[MappingProxyType[str, Any]] PY_VERSION: Final[str] PY_VERSION_HEX: Final[int] if sys.implementation.name == 'pypy': # noqa: PYI002 diff --git a/src/optree.cpp b/src/optree.cpp index 9209462c..60fed733 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -21,6 +21,7 @@ limitations under the License. #include // std::unique_ptr #include // std::optional, std::nullopt #include // std::string +#include // std::move #include #include @@ -51,41 +52,50 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] mod.attr("Py_TPFLAGS_BASETYPE") = py::int_(Py_TPFLAGS_BASETYPE); // Meta information during build - mod.attr("PY_VERSION") = py::str(PY_VERSION); - mod.attr("PY_VERSION_HEX") = py::int_(PY_VERSION_HEX); + py::dict BUILDTIME_METADATA{}; + BUILDTIME_METADATA["PY_VERSION"] = py::str(PY_VERSION); + BUILDTIME_METADATA["PY_VERSION_HEX"] = py::int_(PY_VERSION_HEX); #ifdef PYPY_VERSION - mod.attr("PYPY_VERSION") = py::str(PYPY_VERSION); - mod.attr("PYPY_VERSION_NUM") = py::int_(PYPY_VERSION_NUM); - mod.attr("PYPY_VERSION_HEX") = py::int_(PYPY_VERSION_NUM); + BUILDTIME_METADATA["PYPY_VERSION"] = py::str(PYPY_VERSION); + BUILDTIME_METADATA["PYPY_VERSION_NUM"] = py::int_(PYPY_VERSION_NUM); + BUILDTIME_METADATA["PYPY_VERSION_HEX"] = py::int_(PYPY_VERSION_NUM); #endif - mod.attr("PYBIND11_VERSION_HEX") = py::int_(PYBIND11_VERSION_HEX); - mod.attr("PYBIND11_INTERNALS_VERSION") = py::int_(PYBIND11_INTERNALS_VERSION); + BUILDTIME_METADATA["PYBIND11_VERSION_HEX"] = py::int_(PYBIND11_VERSION_HEX); + BUILDTIME_METADATA["PYBIND11_INTERNALS_VERSION"] = py::int_(PYBIND11_INTERNALS_VERSION); #ifdef PYBIND11_HAS_NATIVE_ENUM - mod.attr("PYBIND11_HAS_NATIVE_ENUM") = py::bool_(true); + BUILDTIME_METADATA["PYBIND11_HAS_NATIVE_ENUM"] = py::bool_(true); #else - mod.attr("PYBIND11_HAS_NATIVE_ENUM") = py::bool_(false); + BUILDTIME_METADATA["PYBIND11_HAS_NATIVE_ENUM"] = py::bool_(false); #endif #ifdef _GLIBCXX_USE_CXX11_ABI - // NOLINTNEXTLINE[modernize-use-bool-literals] - mod.attr("GLIBCXX_USE_CXX11_ABI") = py::bool_(static_cast(_GLIBCXX_USE_CXX11_ABI)); + BUILDTIME_METADATA["GLIBCXX_USE_CXX11_ABI"] = + // NOLINTNEXTLINE[modernize-use-bool-literals] + py::bool_(static_cast(_GLIBCXX_USE_CXX11_ABI)); #else - mod.attr("GLIBCXX_USE_CXX11_ABI") = py::bool_(false); + BUILDTIME_METADATA["GLIBCXX_USE_CXX11_ABI"] = py::bool_(false); #endif + mod.attr("BUILDTIME_METADATA") = std::move(BUILDTIME_METADATA); py::exec( R"py( + import types + class HexInt(int): def __repr__(self) -> str: return f'0x{self:08X}' - globals().update( + BUILDTIME_METADATA.update( **{ name: HexInt(value) - for name, value in globals().items() + for name, value in BUILDTIME_METADATA.items() if name.endswith('_HEX') and isinstance(value, int) }, ) - del HexInt + BUILDTIME_METADATA = types.MappingProxyType(BUILDTIME_METADATA) + + globals().update(BUILDTIME_METADATA) + + del types, HexInt )py", py::getattr(mod, "__dict__")); @@ -249,7 +259,9 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] auto* const PyTreeKind_Type = reinterpret_cast(PyTreeKindTypeObject.ptr()); PyTreeKind_Type->tp_name = "optree.PyTreeKind"; py::setattr(PyTreeKindTypeObject.ptr(), Py_Get_ID(__module__), Py_Get_ID(optree)); - py::setattr(PyTreeKindTypeObject.ptr(), "NUM_KINDS", py::int_(optree::kNumPyTreeKinds)); + py::setattr(PyTreeKindTypeObject.ptr(), + "NUM_KINDS", + py::int_(py::ssize_t(PyTreeKind::NumKinds))); auto PyTreeSpecTypeObject = py::class_( mod, @@ -471,9 +483,8 @@ void BuildModule(py::module_& mod) { // NOLINT[runtime/references] #undef def_method_pos_only -// Make the types immutable to avoid attribute assignment, modification, and deletion. #ifdef Py_TPFLAGS_IMMUTABLETYPE - // Locked by scoped_critical_section{mod} + // Make the types immutable to avoid attribute assignment, modification, and deletion. PyTreeKind_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeSpec_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; PyTreeIter_Type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; diff --git a/src/treespec/constructors.cpp b/src/treespec/constructors.cpp index ce97f44b..1e9e6a02 100644 --- a/src/treespec/constructors.cpp +++ b/src/treespec/constructors.cpp @@ -254,6 +254,7 @@ template break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/flatten.cpp b/src/treespec/flatten.cpp index 369e6396..aace1a92 100644 --- a/src/treespec/flatten.cpp +++ b/src/treespec/flatten.cpp @@ -188,6 +188,7 @@ bool PyTreeSpec::FlattenIntoImpl(const py::handle& handle, break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -463,6 +464,7 @@ bool PyTreeSpec::FlattenIntoWithPathImpl(const py::handle& handle, break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -789,6 +791,7 @@ py::list PyTreeSpec::FlattenUpTo(const py::object& tree) const { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/hashing.cpp b/src/treespec/hashing.cpp index 91b4f10d..dc748d7a 100644 --- a/src/treespec/hashing.cpp +++ b/src/treespec/hashing.cpp @@ -82,6 +82,7 @@ ssize_t PyTreeSpec::HashValueImpl() const { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/richcomparison.cpp b/src/treespec/richcomparison.cpp index 76399cf2..a553f6e4 100644 --- a/src/treespec/richcomparison.cpp +++ b/src/treespec/richcomparison.cpp @@ -151,6 +151,7 @@ bool PyTreeSpec::IsPrefix(const PyTreeSpec &other, const bool &strict) const { } case PyTreeKind::Leaf: + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/serialization.cpp b/src/treespec/serialization.cpp index 90552d2d..bf61df44 100644 --- a/src/treespec/serialization.cpp +++ b/src/treespec/serialization.cpp @@ -51,6 +51,7 @@ namespace optree { case PyTreeKind::Custom: EXPECT_NE(node.custom, nullptr, "The custom registration is null."); return PyRepr(node.custom->type); + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -233,6 +234,7 @@ std::string PyTreeSpec::ToStringImpl() const { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -378,6 +380,7 @@ py::object PyTreeSpec::ToPickleable() const { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/traversal.cpp b/src/treespec/traversal.cpp index 709821ef..0657f9a0 100644 --- a/src/treespec/traversal.cpp +++ b/src/treespec/traversal.cpp @@ -152,6 +152,7 @@ py::object PyTreeIter::NextImpl() { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -238,6 +239,7 @@ py::object PyTreeSpec::WalkImpl(const py::iterable &leaves, break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/treespec.cpp b/src/treespec/treespec.cpp index f12e44fe..922f1ef6 100644 --- a/src/treespec/treespec.cpp +++ b/src/treespec/treespec.cpp @@ -120,6 +120,7 @@ namespace optree { node.custom->unflatten_func); } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -175,6 +176,7 @@ namespace optree { return node.custom->path_entry_type; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -376,6 +378,7 @@ namespace optree { case PyTreeKind::Leaf: case PyTreeKind::None: + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -684,6 +687,7 @@ ssize_t PyTreeSpec::PathsImpl(Span& paths, // NOLINT[misc-no-recursion] break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -793,6 +797,7 @@ ssize_t PyTreeSpec::AccessorsImpl(Span& accessors, // NOLINT[misc-no-recursion] break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -857,6 +862,7 @@ py::list PyTreeSpec::Entries() const { return py::getattr(TupleGetItem(root.node_data, 1), Py_Get_ID(copy))(); } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -898,6 +904,7 @@ py::object PyTreeSpec::Entry(ssize_t index) const { case PyTreeKind::None: case PyTreeKind::Leaf: + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } @@ -987,6 +994,7 @@ py::object PyTreeSpec::GetType(const std::optional& node) const { return PyDefaultDictTypeObject; case PyTreeKind::Deque: return PyDequeTypeObject; + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } diff --git a/src/treespec/unflatten.cpp b/src/treespec/unflatten.cpp index bc460c24..37ed3b36 100644 --- a/src/treespec/unflatten.cpp +++ b/src/treespec/unflatten.cpp @@ -65,6 +65,7 @@ py::object PyTreeSpec::UnflattenImpl(const Span& leaves) const { break; } + case PyTreeKind::NumKinds: default: INTERNAL_ERROR(); } From c82c49820c54e94c075cc998f25168b1c2f0a477 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 16:53:01 +0800 Subject: [PATCH 09/11] test: add test for cmake build --- .github/workflows/tests-with-pydebug.yml | 9 ++++++++- .github/workflows/tests.yml | 19 ++++++++++++++++++- Makefile | 19 ++++++++++--------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests-with-pydebug.yml b/.github/workflows/tests-with-pydebug.yml index 3e55db60..40328799 100644 --- a/.github/workflows/tests-with-pydebug.yml +++ b/.github/workflows/tests-with-pydebug.yml @@ -34,6 +34,7 @@ env: PYTHON: "python" # to be updated PYTHON_TAG: "py3" # to be updated PYTHON_VERSION: "3" # to be updated + pybind11_VERSION: "stable" # to be updated PYENV_ROOT: "~/.pyenv" # to be updated COLUMNS: "128" @@ -290,10 +291,16 @@ jobs: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') run: | - python .github/workflows/set_setup_requires.py + ${{ env.PYTHON }} .github/workflows/set_setup_requires.py echo "::group::pyproject.toml" cat pyproject.toml echo "::endgroup::" + echo "pybind11_VERSION=HEAD" | tee -a "${GITHUB_ENV}" + + - name: Test buildable without Python frontend + shell: bash + run: | + make cmake-build PYTHON="${{ env.PYTHON }}" && make clean - name: Install OpTree run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5ba06db..17b9f8f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,6 +34,7 @@ env: PYTHONUNBUFFERED: "1" PYTHON: "python" # to be updated PYTHON_TAG: "py3" # to be updated + pybind11_VERSION: "stable" # to be updated COLUMNS: "128" jobs: @@ -148,10 +149,11 @@ jobs: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') run: | - python .github/workflows/set_setup_requires.py + ${{ env.PYTHON }} .github/workflows/set_setup_requires.py echo "::group::pyproject.toml" cat pyproject.toml echo "::endgroup::" + echo "pybind11_VERSION=HEAD" | tee -a "${GITHUB_ENV}" - name: Test installable with C++17 shell: bash @@ -169,6 +171,21 @@ jobs: rm -rf venv ) + - name: Test buildable without Python frontend + shell: bash + run: | + ACTIVATION_SCRIPT="venv/bin/activate" + if [[ "${{ runner.os }}" == 'Windows' ]]; then + ACTIVATION_SCRIPT="venv/Scripts/activate" + fi + ( + set -x + ${{ env.PYTHON }} -m venv venv && + source "${ACTIVATION_SCRIPT}" && + make cmake-build && make clean && + rm -rf venv + ) + - name: Install test dependencies shell: bash run: | diff --git a/Makefile b/Makefile index 801cf760..bc0a04fd 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ PATH := $(PATH):$(GOBIN) PYTHON ?= $(shell command -v python3 || command -v python) PYTEST ?= $(PYTHON) -X dev -m pytest -Walways PYTESTOPTS ?= +CMAKE_BUILD_TYPE ?= Debug CMAKE_CXX_STANDARD ?= 20 OPTREE_CXX_WERROR ?= ON _GLIBCXX_USE_CXX11_ABI ?= 1 @@ -196,9 +197,9 @@ xdoctest doctest: xdoctest-install .PHONY: cmake-configure cmake-configure: cmake-install cmake --version - cmake -S . -B cmake-build-debug \ + cmake -S . -B cmake-build \ --fresh \ - -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_BUILD_TYPE="$(CMAKE_BUILD_TYPE)" \ -DCMAKE_CXX_STANDARD="$(CMAKE_CXX_STANDARD)" \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DPython_EXECUTABLE="$(PYTHON)" \ @@ -207,7 +208,7 @@ cmake-configure: cmake-install .PHONY: cmake cmake-build cmake cmake-build: cmake-configure - cmake --build cmake-build-debug --parallel + cmake --build cmake-build --parallel .PHONY: clang-format clang-format: clang-format-install @@ -217,7 +218,7 @@ clang-format: clang-format-install .PHONY: clang-tidy clang-tidy: clang-tidy-install cmake-configure clang-tidy --version - clang-tidy --extra-arg="-v" --fix -p=cmake-build-debug $(CXX_FILES) + clang-tidy --extra-arg="-v" --fix -p=cmake-build $(CXX_FILES) .PHONY: cpplint cpplint: cpplint-install @@ -233,8 +234,8 @@ addlicense: addlicense-install .PHONY: docstyle docstyle: docs-install - make -C docs clean || true - $(PYTHON) -m doc8 docs && make -C docs html SPHINXOPTS="-W" + $(MAKE) -C docs clean || true + $(PYTHON) -m doc8 docs && $(MAKE) -C docs html SPHINXOPTS="-W" .PHONY: docs docs: docs-install @@ -242,12 +243,12 @@ docs: docs-install .PHONY: spelling spelling: docs-install - make -C docs clean || true - make -C docs spelling SPHINXOPTS="-W" + $(MAKE) -C docs clean || true + $(MAKE) -C docs spelling SPHINXOPTS="-W" .PHONY: clean-docs clean-docs: - make -C docs clean || true + $(MAKE) -C docs clean || true # Utility Functions From 4939d5233d395182b96fc3c64130bf4c6181f75b Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 17:45:13 +0800 Subject: [PATCH 10/11] chore(pre-commit): update pre-commit hooks --- .editorconfig | 22 ++++++++++++++-------- .pre-commit-config.yaml | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.editorconfig b/.editorconfig index d98c19e6..7c4a9d9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,14 +12,26 @@ insert_final_newline = true [*.py] indent_size = 4 -src_paths=optree,tests -[*.{yaml,yml}] +[*.{cpp,hpp,cxx,cc,c,h,cu,cuh}] +indent_size = 4 + +[*.{yaml,yml,json}] indent_size = 2 [.clang-{format,tidy}] indent_size = 2 +[Makefile] +indent_style = tab + +[*.sh] +indent_size = 4 + +[*.bat] +indent_size = 4 +end_of_line = crlf + [*.md] indent_size = 2 x-soft-wrap-text = true @@ -27,9 +39,3 @@ x-soft-wrap-text = true [*.rst] indent_size = 4 x-soft-wrap-text = true - -[Makefile] -indent_style = tab - -[*.{cpp,h,cu,cuh}] -indent_size = 4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ad4063f..79eb638d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: fail_fast: true - id: debug-statements - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v20.1.3 + rev: v20.1.4 hooks: - id: clang-format - repo: https://github.com/cpplint/cpplint From bbcdd5e933ef8aaececc0a14df89571da007c494 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Thu, 8 May 2025 18:10:50 +0800 Subject: [PATCH 11/11] test: do not do build test on Windows --- .github/workflows/tests-with-pydebug.yml | 4 ++-- .github/workflows/tests.yml | 14 +++++--------- CMakeLists.txt | 5 +++++ Makefile | 7 ++++--- pyproject.toml | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests-with-pydebug.yml b/.github/workflows/tests-with-pydebug.yml index 40328799..0ad8949d 100644 --- a/.github/workflows/tests-with-pydebug.yml +++ b/.github/workflows/tests-with-pydebug.yml @@ -36,7 +36,7 @@ env: PYTHON_VERSION: "3" # to be updated pybind11_VERSION: "stable" # to be updated PYENV_ROOT: "~/.pyenv" # to be updated - COLUMNS: "128" + COLUMNS: "100" jobs: test: @@ -298,7 +298,7 @@ jobs: echo "pybind11_VERSION=HEAD" | tee -a "${GITHUB_ENV}" - name: Test buildable without Python frontend - shell: bash + if: runner.os != 'Windows' run: | make cmake-build PYTHON="${{ env.PYTHON }}" && make clean diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 17b9f8f2..ef819d2b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,7 @@ env: PYTHON: "python" # to be updated PYTHON_TAG: "py3" # to be updated pybind11_VERSION: "stable" # to be updated - COLUMNS: "128" + COLUMNS: "100" jobs: test: @@ -163,8 +163,8 @@ jobs: set -x ${{ env.PYTHON }} -m venv venv && source venv/bin/activate && - export OPTREE_CXX_WERROR=OFF CMAKE_CXX_STANDARD=17 && - ${{ env.PYTHON }} -m pip install -v . && + OPTREE_CXX_WERROR=OFF CMAKE_CXX_STANDARD=17 \ + ${{ env.PYTHON }} -m pip install -v . && pushd venv && ${{ env.PYTHON }} -X dev -Walways -Werror -c 'import optree' && popd && @@ -172,16 +172,12 @@ jobs: ) - name: Test buildable without Python frontend - shell: bash + if: runner.os != 'Windows' run: | - ACTIVATION_SCRIPT="venv/bin/activate" - if [[ "${{ runner.os }}" == 'Windows' ]]; then - ACTIVATION_SCRIPT="venv/Scripts/activate" - fi ( set -x ${{ env.PYTHON }} -m venv venv && - source "${ACTIVATION_SCRIPT}" && + source venv/bin/activate && make cmake-build && make clean && rm -rf venv ) diff --git a/CMakeLists.txt b/CMakeLists.txt index b22caf13..98ebcc8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,11 @@ string(STRIP "${CMAKE_CXX_FLAGS_RELEASE}" CMAKE_CXX_FLAGS_RELEASE) message(STATUS "CXX flags: \"${CMAKE_CXX_FLAGS}\"") message(STATUS "CXX flags (Debug): \"${CMAKE_CXX_FLAGS_DEBUG}\"") message(STATUS "CXX flags (Release): \"${CMAKE_CXX_FLAGS_RELEASE}\"") +if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release") + string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) + string(STRIP "${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}") + message(STATUS "CXX flags (${CMAKE_BUILD_TYPE}): \"${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}\"") +endif() if(MSVC AND NOT "$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "") message(STATUS "Use VSCMD_ARG_TGT_ARCH: \"$ENV{VSCMD_ARG_TGT_ARCH}\"") diff --git a/Makefile b/Makefile index bc0a04fd..209ee68d 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ PYTHON ?= $(shell command -v python3 || command -v python) PYTEST ?= $(PYTHON) -X dev -m pytest -Walways PYTESTOPTS ?= CMAKE_BUILD_TYPE ?= Debug +CMAKE_BUILD_TYPE_LOWER = $(shell $(PYTHON) -c 'print("$(CMAKE_BUILD_TYPE)".lower())') CMAKE_CXX_STANDARD ?= 20 OPTREE_CXX_WERROR ?= ON _GLIBCXX_USE_CXX11_ABI ?= 1 @@ -197,7 +198,7 @@ xdoctest doctest: xdoctest-install .PHONY: cmake-configure cmake-configure: cmake-install cmake --version - cmake -S . -B cmake-build \ + cmake -S . -B cmake-build-$(CMAKE_BUILD_TYPE_LOWER) \ --fresh \ -DCMAKE_BUILD_TYPE="$(CMAKE_BUILD_TYPE)" \ -DCMAKE_CXX_STANDARD="$(CMAKE_CXX_STANDARD)" \ @@ -208,7 +209,7 @@ cmake-configure: cmake-install .PHONY: cmake cmake-build cmake cmake-build: cmake-configure - cmake --build cmake-build --parallel + cmake --build cmake-build-$(CMAKE_BUILD_TYPE_LOWER) --parallel .PHONY: clang-format clang-format: clang-format-install @@ -218,7 +219,7 @@ clang-format: clang-format-install .PHONY: clang-tidy clang-tidy: clang-tidy-install cmake-configure clang-tidy --version - clang-tidy --extra-arg="-v" --fix -p=cmake-build $(CXX_FILES) + clang-tidy --extra-arg="-v" --fix -p=cmake-build-$(CMAKE_BUILD_TYPE_LOWER) $(CXX_FILES) .PHONY: cpplint cpplint: cpplint-install diff --git a/pyproject.toml b/pyproject.toml index 557c180d..6a942304 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ archs = ["native"] enable = ["pypy", "cpython-freethreading"] skip = "*musllinux*" build-frontend = "build" -environment = { PYTHONUNBUFFERED = "1", COLUMNS = "128" } +environment = { PYTHONUNBUFFERED = "1", COLUMNS = "100" } environment-pass = [ "CMAKE_CXX_STANDARD", "OPTREE_CXX_WERROR",