From c3a7dcceb9984bdd47c321d21175a7cbc8aed5cc Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:05:05 -0500 Subject: [PATCH] Docs: add newcomer codebase guide --- README.md | 1 + docs/README.md | 2 + docs/newcomers.md | 158 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 docs/newcomers.md diff --git a/README.md b/README.md index 30a64e1..677a09a 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Struct-backed APIs, including displays, use methods consistently on all three ru ## Links - [Source](https://github.com/PyDevices/lvgl-python) +- [Newcomer's guide](docs/newcomers.md) — repository architecture and contribution boundaries - [Issues](https://github.com/PyDevices/lvgl-python/issues) - [building.md](docs/building.md) — build from source - Related: [lvgl-bindings](https://github.com/PyDevices/lvgl-bindings), [pydevices-examples](https://github.com/PyDevices/pydevices-examples) diff --git a/docs/README.md b/docs/README.md index 45bd022..a5d298c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # lvgl-python documentation +- [newcomers.md](newcomers.md) — architecture, code ownership, and safe first changes. + - [building.md](building.md) — local build/dev workflow, syncing generated bindings from lvgl-bindings. - [publishing.md](publishing.md) — how a bindings change becomes a versioned diff --git a/docs/newcomers.md b/docs/newcomers.md new file mode 100644 index 0000000..f741fd2 --- /dev/null +++ b/docs/newcomers.md @@ -0,0 +1,158 @@ +# Newcomer's guide to the lvgl-python codebase + +`lvgl-python` packages LVGL for CPython as the native `lvgl` extension +distributed under the `pydevices-lvgl` project name. It is one endpoint of a +shared binding family: it exposes the same intentional Python-shaped API as +the MicroPython and CircuitPython targets, but it does **not** embed or run a +MicroPython interpreter. + +The most important boundary is ownership. This repository owns the CPython +runtime layer, packaging, tests, and release policy. The sibling +`lvgl-bindings` repository owns the generator and canonical API decisions. +Generated binding files and the `display_driver.py`/`fs_driver.py` helpers are +vendored here at one exact upstream commit; they are inputs to this project, +not ordinary hand-edited source files. + +## A mental model + +```text +lvgl-bindings (generator and API policy) + | + | explicit sync at a recorded 40-character commit + v +lvgl-python repository + |-- generated/lvgl_python.c and lvgl.pyi generated binding surface + |-- src/lvpy_runtime.c CPython ownership, callbacks, locking + |-- lvgl/ pinned upstream C submodule + `-- setup.py compiles one CPython extension + | + v +import lvgl as lv + |-- generated wrappers convert Python arguments and results + |-- runtime wrappers manage objects, structs, GIL, and callback lifetime + `-- LVGL C core owns widgets, displays, rendering, and event dispatch +``` + +Most users install a prebuilt `pydevices-lvgl` wheel from TestPyPI, then use +`import lvgl as lv`; they do not clone this repository, initialize its +submodule, or compile LVGL. The build tree exists for binding/runtime +contributors and release automation, not as the normal application setup. + +```bash +pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pydevices-lvgl +``` + +An application may provide its own display backend. In the PyDevices +convenience path it imports `display_driver`, which is a synced adapter around +`appdev.App` and `board_config`; it is deliberately outside the core extension +and is not required for a standalone backend. + +## Repository map + +| Path | Purpose | +|---|---| +| `generated/lvgl_python.c` | Vendored generated Python-to-LVGL wrappers, types, constants, and callback entry points. | +| `generated/lvgl.pyi` | Type stubs installed beside the extension wheel. | +| `src/lvpy_runtime.c`, `src/lvpy_runtime.h` | Handwritten CPython-specific implementation: object/struct wrappers, conversion helpers, locking, GIL handling, and callback ownership. | +| `lvgl/` | Pinned upstream LVGL C git submodule compiled into the extension. | +| `lv_conf.h` | LVGL build configuration for the CPython target. | +| `setup.py` | Defines the `lvgl` extension and compiles runtime, generated bindings, and all LVGL C sources. | +| `display_driver.py`, `fs_driver.py` | Synced PyDevices helpers; change their canonical copies in `lvgl-bindings`. | +| `LVGL_BINDINGS_COMMIT` | Exact 40-character source commit used for the vendored binding inputs. | +| `tests/` | Native-extension lifecycle and wheel-installation contracts. | +| `docs/building.md` | Authoritative local build, sync, Android, and Pyodide instructions. | +| `docs/publishing.md` | Release safety model and platform-wheel policy. | +| `scripts/sync_from_lvgl_bindings.sh` | Explicit upstream sync tool; it records the resolved source and updates the submodule pin. | + +## Follow a normal PyDevices application + +The usual PyDevices entry point imports `display_driver` before `lvgl`. That +adapter selects the active board configuration and wires display flush, input, +and LVGL event-loop coordination; the application then creates widgets. + +```python +import display_driver # wires LVGL display, input, and event loop +import lvgl as lv + +scr = lv.screen_active() +label = lv.label(scr) +label.set_text("Hello from PyDevices LVGL!") +label.center() +``` + +1. Installing the supported wheel provides the binary module named `lvgl` and + the bundled `display_driver` helper. +2. Importing `display_driver` first establishes the PyDevices display/input + integration. It owns LVGL ticking and refresh coordination for this path. +3. Importing `lvgl` exposes the generated module surface and runtime support + types: LVGL-style enum namespaces, functions, object constructors, and + struct methods rather than a separate Python UI model. +4. Widget constructors and methods enter generated wrappers. They validate and + convert Python values, take the runtime's LVGL lock where needed, and call + the corresponding LVGL C API. + +For a custom backend without PyDevices board configuration, use the raw LVGL +setup in the root README instead of adapting this convenience-path example. + +The public API is generated, but the lifecycle, ownership, and concurrency +behavior in that path are CPython runtime work. Those concerns belong in +`src/lvpy_runtime.*`, not in an ad hoc edit to the generated file. + +## Boundaries and invariants + +- Do not manually edit `generated/lvgl_python.c`, `generated/lvgl.pyi`, + `display_driver.py`, `fs_driver.py`, or `lv_conf.h`. Change the generator or + canonical helper in `lvgl-bindings`, regenerate there, then use the explicit + sync workflow here. +- `LVGL_BINDINGS_COMMIT` is a reproducible-source record, not a loose version + hint. A sync accepts an immutable full commit SHA or release tag and records + the resolved 40-character commit. +- Keep a Python reference to any widget that owns callbacks for as long as + LVGL may call them. With `user_data=None`, callback state is retained on the + wrapper, but an application that drops the widget can still lose the Python + object it needs. +- Pair `lv.init()` and `lv.deinit()` in standalone programs. Tests cover this + lifecycle, including callback cleanup during interpreter shutdown. +- The binding intentionally omits unsafe C signatures such as unbounded + pointer-to-pointer or variadic APIs unless conversion is reviewed. Do not + restore one merely to make the C surface look complete. +- The project shares naming and selected API policy with other runtimes, but + it is a CPython extension. A feature's availability may be explicitly + different on another target. +- The extension does not choose a display, event loop, or board. Treat the + PyDevices `display_driver` path as one integration, not as a requirement of + `import lvgl`. + +## When you need the source tree + +Only work on a source checkout when you are changing the generated binding, +the CPython runtime, packaging, or the release process. In that case, +[building.md](building.md) is the authoritative platform-specific build and +test guide; it covers the LVGL submodule and editable native build. It is not +needed to use a published wheel. + +The focused test suite verifies the recorded bindings source, the installed +`lvgl.pyi` stub, initialization/deinitialization, widget creation, and +teardown callbacks at interpreter exit. Read [publishing.md](publishing.md) +before changing sync, version, wheel-matrix, or TestPyPI release behavior: +normal pushes do not publish a wheel. + +## Safe first contributions + +Good first changes are a focused regression test for a CPython lifecycle or +conversion bug, a clarification in the build documentation, or a fix to the +handwritten runtime layer when the generated API already expresses the desired +surface. Confirm which repository owns the change before editing: + +| If the change concerns… | Start in… | +|---|---| +| API availability, generated wrappers, or synced Python helpers | `lvgl-bindings` | +| CPython object ownership, GIL/lock behavior, wheel packaging, or CPython tests | `lvgl-python` | +| A PyDevices application's screens, board configuration, or user interaction | that consuming application/repository | + +After changing any native code, rebuild the editable extension and run the +focused tests. If a proposed change moves generated files or the `lvgl` +submodule, use the documented sync path rather than staging a hand-picked +mixture of outputs. + +## Where to learn next