Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/book-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: book-pages

# Publishes the book (book/, plan 9.7) to GitHub Pages on every push to main that changes what it is
# built from: its chapters, the examples and notebook it includes, the CHANGELOG, and the scripts.
# The site is public, at https://tap.github.io/PythonTap/. Pages must be enabled once, with
# Settings → Pages → Source: "GitHub Actions"; build.yml's book job builds the same thing on every
# push, so a broken book fails there first.

on:
push:
branches: [main]
paths:
- "book/**"
- "python/**"
- "CHANGELOG.md"
- "scripts/notebook-to-book.py"
- ".github/workflows/book-pages.yml"
workflow_dispatch:

# Pages deploys with an OIDC token; nothing else is written. Third-party actions are pinned by
# commit SHA (the tag each SHA was cut from is noted beside it).
permissions:
contents: read
pages: write
id-token: write

# One deployment at a time, and never one cancelled halfway.
concurrency:
group: pages
cancel-in-progress: false

env:
MDBOOK_URL: https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz
MDBOOK_SHA256: "9ef07fd288ba58ff3b99d1c94e6d414d431c9a61fdb20348e5beb74b823d546b"

jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Install mdBook (pinned)
run: |
curl -sfLo /tmp/mdbook.tar.gz "$MDBOOK_URL"
actual=$(sha256sum /tmp/mdbook.tar.gz | cut -d' ' -f1)
if [ "$actual" != "$MDBOOK_SHA256" ]; then
echo "::error::mdbook checksum mismatch"; exit 1
fi
tar -xzf /tmp/mdbook.tar.gz -C /tmp

- name: Render the notebook
run: python3 scripts/notebook-to-book.py

- name: Build the book (warnings are errors)
run: |
/tmp/mdbook build book 2>&1 | tee /tmp/book-build.log
if grep -qiE 'warning|error' /tmp/book-build.log; then
echo "::error::mdbook reported warnings/errors (an include naming no file?)"
exit 1
fi

- name: Configure Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0

- name: Upload the site
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: book/book

- name: Deploy
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ name: build
# CPython 3.13, plain and under sanitizers — no Max, min-api or runtime install.
# The external and its mock-kernel unit test build on Linux too (Max does not
# run there, but the glue does), embedding the same CPython 3.13.
#
# And the book (book/), built as book-pages.yml publishes it.

on:
push:
Expand Down Expand Up @@ -151,6 +153,44 @@ jobs:
--support "$(python -c 'import sys; print(sys.base_prefix)')" --output build/licenses-check
cat build/licenses-check/licenses/README.md

# The book (plan 9.7): rendered with the notebook as committed and built with mdBook, pinned by
# checksum, warnings as errors — an include that names no file fails here, not on the website.
# book-pages.yml publishes the same build from main; here it is an artifact to look at.
book:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MDBOOK_URL: https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz
MDBOOK_SHA256: "9ef07fd288ba58ff3b99d1c94e6d414d431c9a61fdb20348e5beb74b823d546b"
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Install mdBook (pinned)
run: |
curl -sfLo /tmp/mdbook.tar.gz "$MDBOOK_URL"
actual=$(sha256sum /tmp/mdbook.tar.gz | cut -d' ' -f1)
if [ "$actual" != "$MDBOOK_SHA256" ]; then
echo "::error::mdbook checksum mismatch"; exit 1
fi
tar -xzf /tmp/mdbook.tar.gz -C /tmp

- name: Render the notebook
run: python3 scripts/notebook-to-book.py

- name: Build the book (warnings are errors)
run: |
/tmp/mdbook build book 2>&1 | tee /tmp/book-build.log
if grep -qiE 'warning|error' /tmp/book-build.log; then
echo "::error::mdbook reported warnings/errors (an include naming no file?)"
exit 1
fi

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: book
path: book/book
retention-days: 7

macos:
runs-on: macos-latest
steps:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ support.previous/
# and Max's output
/python/maxtest_*.py
/runtime-tests/logs/

# The book (plan 9.7): mdBook's output, and the notebook page scripts/notebook-to-book.py renders
/book/book/
/book/src/notebook.md
/book/src/notebook/
33 changes: 23 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**PythonTap** — the `tap.python~` Max package: a Max external that embeds CPython 3.13 and runs a
user's Python class as an audio object. `[tap.python~ name]` loads `python/name.py`, instantiates
`class name`, turns its annotated public fields into Max attributes and its public methods into Max
messages, calls its `process()` on the signal, and hot-reloads on save. `ReadMe.md` is the user-facing
contract; **`docs/PRODUCTION-PLAN.md` is the authoritative roadmap** — its settled decisions (D1–D6),
its phases, and the audit findings behind them. Tick its items (with the PR) as they land. Its
Phase 9 is a second object, **`tap.python`**, a Python class as a Max object without audio (what a
method returns is what it outputs): designed in `docs/TAP-PYTHON-PLAN.md` (decisions D7–D11), built
in the same binary by 9.3; its documentation (9.4) and runtime tests (9.5) are still to come.
messages, calls its `process()` on the signal, and hot-reloads on save. Since 2.0.0 the package has a
second object, **`tap.python`**, a Python class as a Max object without audio (what a method returns
is what it outputs), in the same binary: designed in `docs/TAP-PYTHON-PLAN.md` (decisions D7–D11).
**The book (`book/`, published at https://tap.github.io/PythonTap/) is the user-facing contract**;
`ReadMe.md` is only the front door — what it is, install, a quick start, links — so each fact has
one home (plan 9.7). **`docs/PRODUCTION-PLAN.md` is the authoritative roadmap** — its settled
decisions (D1–D6), its phases, and the audit findings behind them. Tick its items (with the PR) as
they land.

## Layout (D6: a host-independent core plus a thin Max wrapper)

Expand All @@ -37,7 +39,7 @@ in the same binary by 9.3; its documentation (9.4) and runtime tests (9.5) are s
(plan 6.3); `tap_python_reload_bench` times the audio buffers while the class reloads, with the
audio thread scheduled as Core Audio's are (plan 2.6: why the runtime sets a 0.5 ms switch
interval). `scripts/update-perf-docs.py` builds it (Release), runs it, and
rewrites the ReadMe's generated performance tables — with `--max`, Max's CPU meter too, through
rewrites the book's generated performance tables (`book/src/performance.md`) — with `--max`, Max's CPU meter too, through
`runtime-tests/run.py --session perf`. Never hand-edit those tables; measure on an idle machine.
- **`source/projects/tap.python_tilde/`** — one binary, two Max classes (D11), its `ext_main`
registering both: the Min external `tap.python~` (`tap.python_tilde.h`) and, after it, `tap.python`,
Expand All @@ -61,6 +63,14 @@ in the same binary by 9.3; its documentation (9.4) and runtime tests (9.5) are s
lists `tap.python` as a reference page, not an object — it has no file of its own, as Max's
`init`-mapped objects (`mc.abs~`) have not. `scripts/check-docs.py` (CI) checks them all: the XML
well-formed, every link resolving, every patch cord whole.
- **`book/`** — the book (plan 9.7), mdBook 0.4.40: `book/src/SUMMARY.md` and a chapter per
topic. The examples and the CHANGELOG are included as they are (`{{#include}}`), never copied; the
notebook page is rendered from `python/allpass-doc.ipynb` by `scripts/notebook-to-book.py` before
`mdbook build book` (generated, gitignored); the performance tables are written by
`scripts/update-perf-docs.py`. `scripts/check-docs.py` checks every chapter is listed and every
include and relative link (the ReadMe's too) resolves. CI's `book` job builds it, warnings as
errors; `book-pages.yml` publishes it from `main`. The favicons in `book/theme/` are TapHouse
copies (`sync.sh --icon PythonTap`), drift-checked like the rest.
- **`init/tap.python.txt`** — read by Max at launch: `tap.python` and `mc.tap.python~` are mapped to
the `tap.python~` binary (the second through Max's MC wrapper, as Max's own `init/` maps
`mc.cycle~`); `assemble-package.py` fails a package without both lines.
Expand Down Expand Up @@ -122,7 +132,7 @@ python3 runtime-tests/run.py
```

CI (`build.yml`): `linux-core` and `linux-max-glue` (each release, asan-ubsan, tsan — the glue
test's `TAP_PYTHON_SANITIZE`, as the core's; the release row also runs `scripts/check-docs.py`), `macos` (universal + `lipo`/`otool` checks, including
test's `TAP_PYTHON_SANITIZE`, as the core's; the release row also runs `scripts/check-docs.py`), `book`, `macos` (universal + `lipo`/`otool` checks, including
no absolute rpath, and the package assembled, which fails without `init/tap.python.txt`), `windows`. `style.yml`: TapHouse drift check,
clang-format, clang-tidy (a clang-tidy failure or crash fails the gate, not just a finding). Workflows
run with `contents: read`, pin third-party actions by commit SHA (tag noted beside it), and cancel
Expand All @@ -131,6 +141,8 @@ on runners of that architecture — the runtime is per arch, only libpython is u
the secrets exist, merges the three into one package for every platform (`assemble-package.py
--merge`: each runtime in `support/<platform>`, where the external looks before `support/`), and
attaches all the zips + SHA256s to a release — a pre-release for 0.x, a draft from 1.0.
`book-pages.yml`: on a push to `main` that changes the book or what it includes, builds it and
deploys it to GitHub Pages (`pages: write`, `id-token: write` — the one workflow that writes).

## Threads and the GIL (load-bearing)

Expand Down Expand Up @@ -208,7 +220,7 @@ attaches all the zips + SHA256s to a release — a pre-release for 0.x, a draft
- **Honest limits are pinned, not hidden.** A known bug or limit gets a test that states it (named
for the promise, with the plan item that will change it); fixes land against a test that reproduces
the bug first. The core battery is where that happens. Limits no test can pin are written in the
ReadMe's errors paragraph and stay there: the guards catch every Python *exception*, not a
book's Errors and limits chapter and stay there: the guards catch every Python *exception*, not a
process exit below Python (`os._exit()`), a crash in a C extension, or code that never returns;
helper modules load once per session; a second embedded CPython in the same Max is unsupported.
Never let a document claim more than that.
Expand All @@ -219,7 +231,8 @@ attaches all the zips + SHA256s to a release — a pre-release for 0.x, a draft
tests through its compile database.
- **C++20** everywhere; the root `CMakeLists.txt` forces it on every object and `_test` target (Min pins
C++17), as TapTools-Max does.
- **Keep in sync when behavior changes:** `ReadMe.md`, the object's min metadata
- **Keep in sync when behavior changes:** the book's chapters (and `ReadMe.md`, if its quick start
or install steps change), the object's min metadata
(`MIN_DESCRIPTION`, argument and message descriptions — min regenerates
`docs/tap.python~.maxref.xml` from them when Max loads an external newer than the page, so never
hand-edit the page: rebuild, run Max once — `runtime-tests/run.py` says when the page was
Expand Down
Loading
Loading