Behaviour Tree for memory constrained devices. icpmoles.tngl.io/liborchard
README.md

Scripts #

Tools for testing orchard, drawing trees and publishing the documentation. Run them from the repository root. Each script also describes itself in the comment at its top.

script what it does needs
run-tests.sh runs the GoogleTest and Ztest suites CMake, Ninja, GoogleTest; a Zephyr tree for Ztest
orchard_tree.py draws a tree file, checks it like the loader Python 3, Graphviz for images
doxygen_orchard.py Doxygen input filter for the docs pages Python 3
sphinx_orchard.py Sphinx extension with an orchard-tree directive Sphinx
publish-docs.sh commits the built docs to the docs branch bash, rsync; gophermap for the sitemap

The containers in docker/ have everything the tests and the documentation need.

run-tests.sh #

scripts/run-tests.sh            # both suites
scripts/run-tests.sh gtest      # GoogleTest suite and the C++ example
scripts/run-tests.sh ztest      # Ztest suite on native_sim
  • gtest configures CMake with -DORCHARD_BUILD_TESTS=ON in build/tests and runs ctest. The suite in tests/gtest calls the C API from C++: every node type tick by tick (including the scenarios drawn in the docs), every loader error with its line and column, the builder, reporting, the POSIX port and orchard.hpp. ctest also compiles the trees in tests/compile_fail, which must fail with a given error. The suite runs twice, once per clock width: with the host's default in build/tests, then 32-bit in build/tests-time32.
  • sanitize, clang and m32 run the same GoogleTest suite in other builds, only when named: GCC with AddressSanitizer and UBSan (build/tests-sanitize), Clang with warnings as errors (build/tests-clang), and 32-bit x86 with both clock widths (build/tests-m32, build/tests-m32-time64; GoogleTest is built from /usr/src/googletest, or GTEST_SOURCE_DIR). CI runs all three.
  • fuzz builds tests/fuzz with Clang, libFuzzer, ASan and UBSan, and runs it on the JSON loader for FUZZ_SECONDS (600). It starts from every tree file in the repository and tests/fuzz/json.dict; inputs that crash land in build/fuzz/crashes. Valid trees are also ticked, halted and printed.
  • boards builds, without running them, examples/zephyr (both clock widths) and tests/ztest for rpi_pico, and both for stm32_min_dev@blue, an STM32F103C8 with 64 KB of flash and 20 KB of RAM, where their boards/*.conf select -Oz. Warnings are errors; the builds are in build/boards. It needs the Zephyr SDK's ARM toolchain and ZEPHYR_MODULES naming cmsis_6, hal_rpi_pico and hal_stm32: the boards stage of docker/tests has them.

fuzz and boards take minutes, so CI runs them, with ztest, once a week.

  • ztest runs tests/ztest with Zephyr's twister on native_sim/native/64, results in build/twister. It covers the Zephyr port, the runtime on the Zephyr kernel, and orchard.hpp with Zephyr's minimal C++ library, in two scenarios: orchard.ztest with the default (64-bit) clock and orchard.ztest.time32 with CONFIG_ORCHARD_TIME_32BIT. Set ZEPHYR_BASE to a Zephyr tree with its Python requirements installed. It builds with the host compiler, so no Zephyr SDK is needed.

The script stops at the first suite that fails, with a non-zero exit status. In the container:

docker build -t orchard-tests docker/tests
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/src" orchard-tests

orchard_tree.py #

scripts/orchard_tree.py tree.json -o tree.svg     # or .png, .pdf, .dot
scripts/orchard_tree.py tree.json > tree.dot      # DOT on stdout
scripts/orchard_tree.py tree.json --no-comments   # leave out "comment" keys

Draws composites, actions, conditions and waits in different shapes and colours, with children in tick order. It runs the loader's checks, so it also works as a lint: an invalid file prints the error with the path of the node and exits with status 1. Nodes with a "status" per tick are drawn once per tick, side by side, as in the figures of Node types. See Drawing a tree.

In SVG output every node also has a CSS class for its kind or status (orchard-composite, orchard-success, ...), for HTML themes that replace Graphviz's colours: the m.css docs style them in docs/mcss-figures.css.

doxygen_orchard.py #

Set as FILTER_PATTERNS in the Doxyfile, so every Markdown page goes through it before Doxygen reads it. It replaces \orchardtree path "caption" lines with a figure of the tree, and rewrites links that work in the repository but not in Doxygen's output (to a section of another page, an example folder or an example file). For m.css, which writes the HTML, it also turns links in headings into their text and remote images (the build badge) into raw HTML. Lines in code blocks are left alone, and the line count is kept so Doxygen's warnings point at the right line.

sphinx_orchard.py #

For projects documented with Sphinx. In conf.py:

sys.path.insert(0, "path/to/orchard/scripts")
extensions = ["sphinx_orchard"]

Then .. orchard-tree:: path/to/tree.json in a page, with :caption:, :align:, :alt:, :name:, :class: and :no-comments:. The figure is drawn through sphinx.ext.graphviz, so it works in HTML and LaTeX.

publish-docs.sh #

scripts/publish-docs.sh          # commit public/ to the docs branch
scripts/publish-docs.sh --push   # and push it

Build the documentation first (docs/99_index.md). The script copies public/ to latest/ on the docs branch, and also to vX.Y.Z/ when the current commit is exactly at that tag with no local changes. If gophermap is installed, it also writes one sitemap.xml for the whole branch; without it, the sitemap is left as it was.

variable default meaning
ORCHARD_DOCS_DIR ../orchard-docs where the docs branch is checked out
ORCHARD_DOCS_REMOTE bt_home remote to push to
ORCHARD_DOCS_URL https://icpmoles.tngl.io/liborchard address the branch is served at, for the sitemap