Skip to content

Commit 8f64d5c

Browse files
committed
add CI steps, docs stub, pre-commit hook, and other automation
1 parent 26e5aa3 commit 8f64d5c

21 files changed

Lines changed: 471 additions & 139 deletions

.github/workflows/python_test.yml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: python (pybt)
2+
3+
on:
4+
push:
5+
branches: [master]
6+
paths:
7+
- 'python/**'
8+
- 'include/behaviortree_cpp/**'
9+
- 'src/**'
10+
- 'CMakeLists.txt'
11+
- '.github/workflows/python_test.yml'
12+
pull_request:
13+
types: [opened, synchronize, reopened]
14+
paths:
15+
- 'python/**'
16+
- 'include/behaviortree_cpp/**'
17+
- 'src/**'
18+
- 'CMakeLists.txt'
19+
- '.github/workflows/python_test.yml'
20+
workflow_dispatch:
21+
22+
concurrency:
23+
group: ${{ github.workflow }}-${{ github.ref }}
24+
cancel-in-progress: true
25+
26+
jobs:
27+
# ---------------------------------------------------------------------------
28+
# Lint, type-check, smoke tests, and benchmarks across the supported Python
29+
# versions. STABLE_ABI means one abi3 wheel covers 3.12+, so the matrix is
30+
# just (3.12, 3.13) — plus 3.13t (free-threaded) as opt-in / allowed-to-fail.
31+
# ---------------------------------------------------------------------------
32+
test:
33+
name: test (cp${{ matrix.python }})
34+
runs-on: ubuntu-22.04
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
python: ["3.12", "3.13"]
39+
include:
40+
- python: "3.13t"
41+
continue-on-error: true
42+
continue-on-error: ${{ matrix.continue-on-error || false }}
43+
steps:
44+
- uses: actions/checkout@v6
45+
with:
46+
fetch-depth: 0 # setuptools-scm needs full history
47+
48+
- uses: actions/setup-python@v6
49+
with:
50+
python-version: ${{ matrix.python }}
51+
52+
- name: Cache pip
53+
uses: actions/cache@v4
54+
with:
55+
path: ~/.cache/pip
56+
key: pip-${{ runner.os }}-py${{ matrix.python }}-${{ hashFiles('python/pyproject.toml') }}
57+
58+
- name: Set up ccache
59+
uses: hendrikmuhs/ccache-action@v1.2
60+
with:
61+
key: ccache-${{ runner.os }}-py${{ matrix.python }}
62+
63+
- name: Install pybt (editable, with dev extras)
64+
run: pip install -e python/[dev] -v
65+
66+
- name: ruff check
67+
run: ruff check python/
68+
69+
- name: mypy
70+
run: mypy python/src/pybt/
71+
continue-on-error: true # mypy on a fresh nanobind extension surfaces noise until stub gen lands
72+
73+
- name: pytest -m smoke
74+
run: pytest python/tests -n auto -m smoke
75+
76+
- name: pytest benchmarks (record only, no thresholds yet)
77+
run: pytest python/benchmarks/ --benchmark-only --benchmark-json=bench.json
78+
79+
- name: Upload benchmark results
80+
if: always()
81+
uses: actions/upload-artifact@v4
82+
with:
83+
name: bench-cp${{ matrix.python }}
84+
path: bench.json
85+
if-no-files-found: ignore
86+
87+
# ---------------------------------------------------------------------------
88+
# Symbol hygiene: assert that no Python/nanobind symbols appear in
89+
# libbehaviortree_cpp.
90+
# ---------------------------------------------------------------------------
91+
symbol-hygiene:
92+
name: symbol hygiene (nm scan)
93+
runs-on: ubuntu-22.04
94+
steps:
95+
- uses: actions/checkout@v6
96+
97+
- uses: actions/setup-python@v6
98+
with:
99+
python-version: "3.12"
100+
101+
- name: Install nanobind (for CMake config)
102+
run: pip install "nanobind>=2.5,<3"
103+
104+
- name: Configure + build (BTCPP_PYTHON=ON, no examples/tools/tests on core)
105+
run: |
106+
cmake -S . -B build \
107+
-DBTCPP_PYTHON=ON \
108+
-DBTCPP_BUILD_TOOLS=OFF \
109+
-DBTCPP_EXAMPLES=OFF \
110+
-DBUILD_TESTING=OFF \
111+
-DBTCPP_GROOT_INTERFACE=OFF \
112+
-DBTCPP_SQLITE_LOGGING=OFF \
113+
-Dnanobind_DIR=$(python -c "import nanobind, pathlib; print(pathlib.Path(nanobind.__file__).parent / 'cmake')")
114+
cmake --build build --parallel
115+
116+
- name: ctest -R pybt_no_python_symbols_in_core
117+
run: ctest --test-dir build --output-on-failure -R pybt_no_python_symbols_in_core
118+
119+
# ---------------------------------------------------------------------------
120+
# Hidden-visibility + LTO build. `import pybt` must still resolve cleanly here.
121+
# ---------------------------------------------------------------------------
122+
hidden-lto:
123+
name: hidden-visibility + LTO import
124+
runs-on: ubuntu-22.04
125+
steps:
126+
- uses: actions/checkout@v6
127+
128+
- uses: actions/setup-python@v6
129+
with:
130+
python-version: "3.12"
131+
132+
- name: Install nanobind (for CMake config)
133+
run: pip install "nanobind>=2.5,<3"
134+
135+
- name: Configure + build with -fvisibility=hidden -flto
136+
run: |
137+
cmake -S . -B build \
138+
-DBTCPP_PYTHON=ON \
139+
-DBTCPP_BUILD_TOOLS=OFF \
140+
-DBTCPP_EXAMPLES=OFF \
141+
-DBUILD_TESTING=OFF \
142+
-DBTCPP_GROOT_INTERFACE=OFF \
143+
-DBTCPP_SQLITE_LOGGING=OFF \
144+
-DCMAKE_C_FLAGS="-fvisibility=hidden -flto" \
145+
-DCMAKE_CXX_FLAGS="-fvisibility=hidden -flto" \
146+
-Dnanobind_DIR=$(python -c "import nanobind, pathlib; print(pathlib.Path(nanobind.__file__).parent / 'cmake')")
147+
cmake --build build --parallel
148+
149+
- name: ctest -R pybt_import_under_hidden_lto
150+
run: ctest --test-dir build --output-on-failure -R pybt_import_under_hidden_lto

.pre-commit-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,30 @@ repos:
6262
- tomli
6363
args:
6464
[--toml=./pyproject.toml]
65+
66+
# Python lint + format (pybt)
67+
- repo: https://github.com/astral-sh/ruff-pre-commit
68+
rev: v0.7.0
69+
hooks:
70+
- id: ruff
71+
files: ^python/
72+
- id: ruff-format
73+
files: ^python/
74+
75+
# Python type-check (pybt)
76+
- repo: https://github.com/pre-commit/mirrors-mypy
77+
rev: v1.13.0
78+
hooks:
79+
- id: mypy
80+
files: ^python/src/pybt/
81+
additional_dependencies: []
82+
83+
# No C++ references in pybt user-facing docs/examples (allows READMEs).
84+
- repo: local
85+
hooks:
86+
- id: pybt-no-cpp-refs
87+
name: pybt no-C++-refs
88+
entry: python python/docs/check_no_cpp_refs.py
89+
language: system
90+
files: ^python/(src|examples|docs)/
91+
pass_filenames: false

python/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ src/pybt/_pybt*.dylib
2323
# Editable-install redirect files written by scikit-build-core
2424
src/pybt/*.pth
2525

26-
.venv/
26+
.venv/

python/.readthedocs.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# RTD project must be configured to read this file at `python/.readthedocs.yaml`
2+
# (Project Settings → Advanced → "Path for `.readthedocs.yaml`").
3+
4+
version: 2
5+
6+
build:
7+
os: ubuntu-22.04
8+
tools:
9+
python: "3.12"
10+
11+
python:
12+
install:
13+
- method: pip
14+
path: .
15+
extra_requirements:
16+
- dev
17+
18+
sphinx:
19+
configuration: docs/conf.py
20+
21+
formats:
22+
- htmlzip

python/CMakeLists.txt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.18)
22

33
# pybt — Python bindings for BehaviorTree.CPP.
44
#
5-
# Every pybind/nanobind/Python symbol must live in _pybt only.
6-
# The behaviortree_cpp target must remain Python-unaware.
5+
# Every pybind/nanobind/Python symbol must live in _pybt only.
6+
# The behaviortree_cpp target must remain Python-unaware.
77
# Linkage is one-way: _pybt -> behaviortree_cpp.
88

99
find_package(Python 3.9
@@ -43,3 +43,21 @@ endif()
4343
# Install the compiled extension into the pybt package directory so the
4444
# scikit-build-core wheel ships pybt/_pybt*.so alongside pybt/__init__.py.
4545
install(TARGETS _pybt LIBRARY DESTINATION pybt)
46+
47+
if(UNIX AND NOT APPLE)
48+
# No Python/nanobind symbols may appear in libbehaviortree_cpp.
49+
# This must NEVER FAIL. Otherwise the architecture rule is fundamentally broken.
50+
add_test(NAME pybt_no_python_symbols_in_core
51+
COMMAND sh -c "! nm -D $<TARGET_FILE:${BTCPP_LIBRARY}> | grep -qE 'PyObject|pybind|nanobind|nb::'"
52+
)
53+
54+
# Smoke import under the manylinux-equivalent compile flags.
55+
# The workflow re-runs the whole build with those flags and invokes
56+
# this test to verify `import pybt` still resolves cleanly.
57+
add_test(NAME pybt_import_under_hidden_lto
58+
COMMAND ${Python_EXECUTABLE} -c "import pybt; pybt.BehaviorTreeFactory()"
59+
)
60+
set_tests_properties(pybt_import_under_hidden_lto PROPERTIES
61+
ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/src:$<TARGET_FILE_DIR:_pybt>"
62+
)
63+
endif()

python/CONTRIBUTING.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Contributing to pybt
2+
3+
pybt is the Python binding for [BehaviorTree.CPP](https://github.com/BehaviorTree/BehaviorTree.CPP). This page covers local development; user-facing install lives in [README.md](README.md).
4+
5+
## Setup (once per clone)
6+
7+
```bash
8+
cd python
9+
python3 -m venv .venv
10+
source .venv/bin/activate
11+
pip install -e .[dev] -v
12+
```
13+
14+
Requires Python 3.12+ and a C++17 toolchain (the build compiles the bundled BehaviorTree.CPP source).
15+
16+
## Run tests
17+
18+
```bash
19+
pytest # everything in tests/
20+
pytest -m smoke # only the smoke gate (fast)
21+
pytest tests/test_smoke.py::test_sync_action_node -v
22+
python tests/test_smoke.py # standalone runner, no pytest
23+
```
24+
25+
## Run benchmarks
26+
27+
```bash
28+
pytest benchmarks/ --benchmark-only
29+
```
30+
31+
Results written in `benchmarks/.benchmarks/` (git-ignored). See [`benchmarks/README.md`](benchmarks/README.md).
32+
33+
## Pre-commit hooks
34+
35+
Install once:
36+
37+
```bash
38+
pip install pre-commit
39+
pre-commit install # in the repo root
40+
```
41+
42+
This runs `ruff`, `mypy`, the no-C++-refs check, and the project's standard hooks before each commit.
43+
44+
## CI
45+
46+
`.github/workflows/python_test.yml` mirrors the local pytest invocation across Python 3.12, 3.13, and 3.13t (free-threaded, allowed to fail). Two extra jobs run a standalone CMake build under default flags and under `-fvisibility=hidden -flto` (the manylinux configuration) to catch symbol-hygiene regressions.
47+
48+
## Where things live
49+
50+
| Path | What |
51+
|---|---|
52+
| `src/pybt/` | Python package (the user-facing surface) |
53+
| `src/_pybt/` | C++ binding code (nanobind) |
54+
| `tests/` | pytest suite — smoke + lifecycle |
55+
| `benchmarks/` | pytest-benchmark microbenchmarks |
56+
| `docs/` | Sphinx site (stub in Phase 1) |
57+
| `pyproject.toml` | Build config (scikit-build-core, nanobind, pytest) |
58+
| `CMakeLists.txt` | nanobind extension + CTest regression guards |
59+
60+
## Style
61+
62+
- Python: `ruff` for lint and format, `mypy` for types.
63+
- C++: project root `.clang-format` (Google C++ with 2-space indent, 90-char line limit).
64+
- Docs: per the Documentation Standards in the project plan — standalone, brief, no C++ references outside this file and `README.md`.

python/benchmarks/bench_pybt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def one_tick():
4040

4141
benchmark(one_tick)
4242

43+
4344
# ---------------------------------------------------------------------------
4445
# 2. Port get/set latency
4546
# ---------------------------------------------------------------------------

python/docs/check_no_cpp_refs.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""This script will scan `python/src/pybt/**.py`,
2+
`python/examples/**.py`, and the built Sphinx HTML for forbidden C++
3+
references (`BT::`, `.cpp`, `.hpp`, `include/behaviortree_cpp`, etc.) per
4+
the Documentation Standards in the plan. README files are allowlisted.
5+
"""
6+
7+
import sys
8+
9+
10+
def main() -> int:
11+
return 0
12+
13+
14+
if __name__ == "__main__":
15+
sys.exit(main())

python/docs/conf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Sphinx config."""
2+
3+
project = "pybt"
4+
author = "BehaviorTree.CPP contributors"
5+
extensions = ["myst_parser"]
6+
source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
7+
exclude_patterns = ["_build"]
8+
html_theme = "alabaster" # default; furo theme arrives in Phase 7

python/docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# pybt
2+
3+
Python bindings for the BehaviorTree.CPP library.
4+
5+
Documentation is under construction. See the [project README](../README.md) for install instructions in the meantime.

0 commit comments

Comments
 (0)