diff --git a/AGENTS.md b/AGENTS.md index 9d9cfd0..c5643e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ available; otherwise use the pure-Python `pygraphics` package from MIP. ## Layout -- Root: `micropython.mk`, `micropython.cmake`, `circuitpython.mk`, `setup.py`, +- Root: `micropython.mk` (MicroPython and CircuitPython), `micropython.cmake`, `setup.py`, patch scripts — build glue stays here for `USER_C_MODULES` discovery - `src/` — `.c` sources and shared headers (`gfx_*.h`, `font_8x*.h`, `pygraphics_qstrdefs.h`) diff --git a/README.md b/README.md index 70daebe..e31caff 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ MIT (framebuf algorithms derived from MicroPython `extmod/modframebuf.c`, Damien ``` pygraphics/ - micropython.mk / micropython.cmake / circuitpython.mk / setup.py + micropython.mk / micropython.cmake / setup.py src/ # C sources + headers (gfx_*.h, font_8x*.h, qstrs) lib/pygraphics/ # pure-Python package (import pygraphics) tests/ # native smoke / parity tests @@ -235,39 +235,31 @@ For several PyDevices modules at once, [micropython-pydevices](https://github.com/PyDevices/micropython-pydevices) keeps ready-made manifests, variants and boards. -### CircuitPython (unix) +### CircuitPython -Adafruit’s [Extending CircuitPython](https://learn.adafruit.com/extending-circuitpython) -guide (and the [design guide — native modules](https://docs.circuitpython.org/en/latest/docs/design_guide.html)) -describe adding `shared-bindings/` + `shared-module/` **inside** the CircuitPython -tree. This repo keeps those sources out-of-tree under `src/circuitpython_spike/` -and applies them with `./apply_cp_patches.sh` into a local (uncommitted) -CircuitPython clone — Adafruit has no separate out-of-tree C-module path. -The build glue itself lives in `circuitpython.mk` (analogous to -`micropython.mk` for MicroPython), included by the patched variant/board -Makefile for both the unix `coverage` variant used below and MCU boards. - -| Adafruit step | This repo | -|---------------|-----------| -| `shared-bindings//` | `src/circuitpython_spike/shared-bindings/pygraphics/` | -| `shared-module//` | `src/circuitpython_spike/shared-module/pygraphics/` | -| Enable `CIRCUITPY_*` | Patches set `CIRCUITPY_PYGRAPHICS` | -| List sources in port Makefile | Variant `.mk` + `SRC_PATTERNS` | -| Build | `make` after `--apply` | - -Clone as a sibling of `circuitpython/`: +CircuitPython builds user C modules the way MicroPython does, through +`USER_C_MODULES`, so `micropython.mk` is the build glue there too. Hand this +directory to a port's `make`: ```bash -# siblings: circuitpython/ and pygraphics/ -./apply_cp_patches.sh --apply -cd ../circuitpython/ports/unix && make -j VARIANT=coverage +# unix, from a CircuitPython 11 checkout +make -C ports/unix -j VARIANT=coverage USER_C_MODULES=/path/to/pygraphics +# a board +make -C ports/raspberrypi -j BOARD=adafruit_feather_rp2040 USER_C_MODULES=/path/to/pygraphics ``` -**Tested against** CircuitPython 10.2.1. The patch script edits a moving tree, -so this is the fragile recipe of the two: if `--apply` fails to find what it -expects, check the CircuitPython revision first. +[micropython-pydevices](https://github.com/PyDevices/micropython-pydevices)' +`build_mp.py --interpreter circuitpython --modules pygraphics` does the same +with the CircuitPython checkout, submodules and board list handled for you. + +**Tested against** CircuitPython 11.0.0-alpha.1, on unix, raspberrypi +(Feather RP2040) and atmel-samd (Feather M4 Express, built with +`CIRCUITPY_ULAB=0` to fit 512 KB). The atmel-samd and raspberrypi ports turn +some warnings into errors after a module's own flags; `micropython.mk` turns +them off again per object, for this module's files only. -To build this repo with other user C modules (MicroPython), include their manifests alongside this one; with other CircuitPython extensions, run each repository's `apply_cp_patches.sh` against the same checkout before the one `make`. +To build this repo with other user C modules, list them all in the one +`USER_C_MODULES`. ### pydevices-examples integration diff --git a/apply_cp_patches.sh b/apply_cp_patches.sh deleted file mode 100755 index 020c419..0000000 --- a/apply_cp_patches.sh +++ /dev/null @@ -1,461 +0,0 @@ -#!/usr/bin/env bash -# Apply CircuitPython pygraphics integration (unix only). -# -# Out-of-tree substitute for Adafruit Extending CircuitPython (no upstream PR): -# Learn / design-guide step This script -# shared-bindings// copy spike → CP shared-bindings/pygraphics/ -# shared-module// copy spike → CP shared-module/pygraphics/ -# enable CIRCUITPY_* / mpconfig CIRCUITPY_PYGRAPHICS in variant mk + mpconfig -# list sources in port Makefile variant .mk SRC lists + SRC_PATTERNS -# build CircuitPython caller runs make (or workspace build_cp.sh) -# Conceptual refs: -# https://learn.adafruit.com/extending-circuitpython -# https://docs.circuitpython.org/en/latest/docs/design_guide.html -# -# Usage: -# ./apply_cp_patches.sh --dry-run [--port PORT] [--variant VARIANT] -# ./apply_cp_patches.sh --apply [--port PORT] [--variant VARIANT] -# ./apply_cp_patches.sh --status [--port PORT] [--variant VARIANT] -# -# Environment: -# CP_DIR CircuitPython tree (default: sibling circuitpython/ next to this repo) -# WORKSPACE_DIR Parent of pygraphics (default: parent of this repo) -# PORT Must be unix (default: unix); other ports skip with exit 0 -# VARIANT Unix variant (default: coverage) -# -# Standalone: clone circuitpython + pygraphics as siblings, then: -# ./apply_cp_patches.sh --apply --port unix --variant coverage -# cd ../circuitpython/ports/unix && make -j VARIANT=coverage -# No other usermods required. -# -# Migrates legacy ``graphics`` / ``graphics-cmod`` / ``CIRCUITPY_GRAPHICS`` -# patches to ``pygraphics`` / ``CIRCUITPY_PYGRAPHICS``. - -set -euo pipefail - -PYGRAPHICS_MOD_DIR=$(cd "$(dirname "$0")" && pwd) -WORKSPACE_DIR="${WORKSPACE_DIR:-$(cd "$PYGRAPHICS_MOD_DIR/.." && pwd)}" - -PORT="${PORT:-unix}" -BOARD="${BOARD:-}" -VARIANT="${VARIANT:-coverage}" -MODE="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run|--apply|--status|--force-apply) MODE="$1"; shift ;; - --port) PORT="$2"; shift 2 ;; - --board) BOARD="$2"; shift 2 ;; - --variant) VARIANT="$2"; shift 2 ;; - -h|--help) - sed -n '2,20p' "$0" - exit 0 - ;; - *) - echo "Unknown argument: $1 (try --help)" >&2 - exit 1 - ;; - esac -done - -MODE="${MODE:---dry-run}" -case "$MODE" in - --force-apply) MODE=--apply ;; - --dry-run|--apply|--status) ;; - *) - echo "Unknown mode: $MODE (try --help)" >&2 - exit 1 - ;; -esac - -# Resolve CircuitPython: CP_DIR, else sibling circuitpython/ under WORKSPACE_DIR. -if [[ -n "${CP_DIR:-}" && -d "${CP_DIR}/ports" ]]; then - CP_DIR=$(cd "$CP_DIR" && pwd) -elif [[ -d "$WORKSPACE_DIR/circuitpython/ports" ]]; then - CP_DIR=$(cd "$WORKSPACE_DIR/circuitpython" && pwd) -else - CP_DIR="" -fi - -SPIKE_DIR="$PYGRAPHICS_MOD_DIR/src/circuitpython_spike" -SPIKE_MANIFEST="$SPIKE_DIR/copy_manifest.txt" - -MARKER_TAG="pygraphics-cmod begin" -MARKER_BEGIN="# >>> $MARKER_TAG" -MARKER_END="# >>> pygraphics-cmod end" - -DRY_RUN=0 -# Nonzero if any dry-run check finds something --apply could not do. -DRY_RC=0 -APPLY=0 -case "$MODE" in - --dry-run) DRY_RUN=1 ;; - --apply) APPLY=1 ;; -esac - -log() { echo "$*"; } - -die() { echo "error: $*" >&2; exit 1; } - -if [[ -z "${CP_DIR:-}" ]] || [[ ! -d "$CP_DIR/ports" ]]; then - die "CircuitPython not found (set CP_DIR, or place circuitpython/ next to this repo under $WORKSPACE_DIR)." -fi -CP_DIR=$(cd "$CP_DIR" && pwd) -[[ -f "$SPIKE_MANIFEST" ]] || die "Spike manifest missing: $SPIKE_MANIFEST" - -PORT_DIR="$CP_DIR/ports/$PORT" -# The "enable" makefile differs by port: unix builds select features per -# variant, MCU ports per board. Everything else this script touches -# (circuitpy_mpconfig.mk, circuitpy_defns.mk, the port Makefile) is generic. -if [[ "$PORT" == unix ]]; then - ENABLE_MK="$PORT_DIR/variants/$VARIANT/mpconfigvariant.mk" -else - [[ -n "$BOARD" ]] || die "non-unix ports need --board (got port=$PORT)" - ENABLE_MK="$PORT_DIR/boards/$BOARD/mpconfigboard.mk" -fi -PORT_MK="$PORT_DIR/Makefile" -MPCONFIG_MK="$CP_DIR/py/circuitpy_mpconfig.mk" -DEFNS_MK="$CP_DIR/py/circuitpy_defns.mk" -PYGRAPHICS_MOD_REL=$(python3 -c "import os; print(os.path.relpath('$PYGRAPHICS_MOD_DIR', '$PORT_DIR'))") - -for f in "$ENABLE_MK" "$PORT_MK" "$MPCONFIG_MK" "$DEFNS_MK"; do - [[ -f "$f" ]] || die "missing: $f" -done - -remove_marked_blocks() { - local file="$1" - shift - local tag - [ -f "$file" ] || return 0 - for tag in "$@"; do - if ! grep -qF "$tag" "$file" 2>/dev/null; then - continue - fi - if [ "$DRY_RUN" = 1 ]; then - log " [dry-run] remove marked block '$tag' from $file" - continue - fi - python3 - "$file" "$tag" <<'PY' -import re -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -tag = re.escape(sys.argv[2]) -text = path.read_text() -# Match begin…end with either "graphics-cmod" or "pygraphics-cmod" style ends. -end = r"(?:graphics-cmod|pygraphics-cmod|graphics_native-cmod) end" -pat = rf"\n?# >>> {tag}\n.*?\n# >>> {end}\n?" -text2, n = re.subn(pat, "\n", text, count=0, flags=re.DOTALL) -if n: - path.write_text(text2) -PY - log " removed marked block '$tag' from $file" - done -} - -replace_in_file() { - local file="$1" old="$2" new="$3" - [ -f "$file" ] || return 0 - if ! grep -qF "$old" "$file" 2>/dev/null; then - return 0 - fi - if [ "$DRY_RUN" = 1 ]; then - log " [dry-run] replace in $file: ${old//$'\t'/\\t} -> ${new//$'\t'/\\t}" - return 0 - fi - python3 - "$file" "$old" "$new" <<'PY' -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -old, new = sys.argv[2], sys.argv[3] -text = path.read_text() -if old not in text: - sys.exit(0) -# Do not rewrite ``pygraphics/...`` when replacing legacy ``graphics/...``. -out = [] -i = 0 -while True: - j = text.find(old, i) - if j < 0: - out.append(text[i:]) - break - # Skip if this ``graphics`` is the suffix of ``pygraphics``. - if old.lstrip("\t").startswith("graphics/") and j >= 2 and text[j - 2 : j] == "py": - # find() landed on the ``graphics`` inside ``pygraphics`` — advance past it. - out.append(text[i : j + len(old)]) - i = j + len(old) - continue - out.append(text[i:j]) - out.append(new) - i = j + len(old) -path.write_text("".join(out)) -PY - log " replaced in $file: graphics → pygraphics path" -} - -insert_raw_after_line() { - local file="$1" needle="$2" raw="$3" already="$4" - if grep -qF "$already" "$file" 2>/dev/null; then - return 0 - fi - if [ "$DRY_RUN" = 1 ]; then - if grep -qF "$needle" "$file" 2>/dev/null; then - log " [dry-run] insert after needle in $file: $already" - else - # The apply path fails here when the needle is gone. The dry run - # used to print the same line either way, so a moved upstream - # anchor - the one thing a pin move must stop on - was invisible - # until the apply. - log " [dry-run] ERROR: needle not found in $file: $needle" - DRY_RC=1 - fi - return 0 - fi - python3 - "$file" "$needle" "$raw" <<'PY' -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -needle = sys.argv[2] -raw = sys.argv[3] -text = path.read_text() -if raw in text: - sys.exit(0) -if needle not in text: - print(f"needle not found in {path}: {needle!r}", file=sys.stderr) - sys.exit(1) -path.write_text(text.replace(needle, needle + "\n" + raw, 1)) -PY -} - -ensure_block_append() { - local file="$1" marker_grep="$2" block="$3" - if grep -qF "$marker_grep" "$file" 2>/dev/null; then - return 0 - fi - if [ "$DRY_RUN" = 1 ]; then - log " [dry-run] append $marker_grep block to $file" - return 0 - fi - # Keep a blank line before the block; printf %s avoids $(…) eating newlines. - printf '\n%s\n' "$block" >> "$file" - log " appended $marker_grep block to $file" -} - -insert_block_before_needle() { - local file="$1" needle="$2" block="$3" already="$4" - if grep -qF "$already" "$file" 2>/dev/null; then - return 0 - fi - if [ "$DRY_RUN" = 1 ]; then - if grep -qF "$needle" "$file" 2>/dev/null; then - log " [dry-run] insert block before needle in $file" - else - # The apply path fails here when the needle is gone. The dry run - # used to print the same line either way, so a moved upstream - # anchor - the one thing a pin move must stop on - was invisible - # until the apply. - log " [dry-run] ERROR: needle not found in $file: $needle" - DRY_RC=1 - fi - return 0 - fi - python3 - "$file" "$needle" "$block" <<'PY' -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -needle = sys.argv[2] -block = sys.argv[3] -if not block.endswith("\n"): - block += "\n" -text = path.read_text() -if "CIRCUITPY_PYGRAPHICS),1)" in text or "SRC_PATTERNS += pygraphics/%" in text: - sys.exit(0) -if needle not in text: - raise SystemExit(f"needle not found in {path}") -path.write_text(text.replace(needle, block + needle, 1)) -PY - log " inserted SRC_PATTERNS block into $file" -} - -copy_spike() { - if [ "$DRY_RUN" = 1 ]; then - log " [dry-run] copy spike from $SPIKE_DIR" - return 0 - fi - while read -r dest src; do - [[ -z "${dest:-}" ]] && continue - mkdir -p "$CP_DIR/$dest" - cp "$SPIKE_DIR/$dest/$src" "$CP_DIR/$dest/$src" - done < "$SPIKE_MANIFEST" - log " copied pygraphics spike into $CP_DIR" -} - -# Sets STATUS_RC=1 if anything is not in the state --apply would leave behind: -# a pygraphics tree missing, or a legacy graphics tree still STALE. It used to -# report the defect and exit 0, so a caller that checked $? read a broken tree -# as a good one - found by planting a fault in the pin move's step-0 rehearsal, -# 2026-09-09. -STATUS_RC=0 -status_report() { - echo "CP_DIR=$CP_DIR" - echo "VARIANT=$VARIANT" - echo - [[ -f $CP_DIR/shared-bindings/pygraphics/__init__.c ]] || STATUS_RC=1 - [[ -f $CP_DIR/shared-module/pygraphics/__init__.c ]] || STATUS_RC=1 - [[ -f $CP_DIR/shared-bindings/graphics/__init__.c ]] && STATUS_RC=1 - [[ -f $CP_DIR/shared-module/graphics/__init__.c ]] && STATUS_RC=1 - printf ' %-40s %s\n' "shared-bindings/pygraphics" \ - "$( [[ -f $CP_DIR/shared-bindings/pygraphics/__init__.c ]] && echo present || echo MISSING )" - printf ' %-40s %s\n' "shared-module/pygraphics" \ - "$( [[ -f $CP_DIR/shared-module/pygraphics/__init__.c ]] && echo present || echo MISSING )" - printf ' %-40s %s\n' "legacy shared-bindings/graphics" \ - "$( [[ -f $CP_DIR/shared-bindings/graphics/__init__.c ]] && echo STALE || echo gone )" - printf ' %-40s %s\n' "legacy shared-module/graphics" \ - "$( [[ -f $CP_DIR/shared-module/graphics/__init__.c ]] && echo STALE || echo gone )" - echo - grep -nE 'CIRCUITPY_(PY)?GRAPHICS|pygraphics-cmod|graphics-cmod|shared-bindings/(py)?graphics|SRC_PATTERNS \+= (py)?graphics' \ - "$ENABLE_MK" "$PORT_MK" "$MPCONFIG_MK" "$DEFNS_MK" 2>/dev/null || true -} - -if [ "$MODE" = --status ]; then - status_report - exit $STATUS_RC -fi - -log "Applying pygraphics CP patches (mode=$MODE)…" - -# Drop obsolete / renamed spike stubs. -if [ "$DRY_RUN" = 1 ]; then - log " [dry-run] rm -rf shared-bindings/{graphics,pygraphics_native} shared-module/{graphics,pygraphics_native}" -else - rm -rf \ - "$CP_DIR/shared-bindings/graphics" \ - "$CP_DIR/shared-module/graphics" \ - "$CP_DIR/shared-bindings/pygraphics_native" \ - "$CP_DIR/shared-module/pygraphics_native" \ - "$CP_DIR/shared-bindings/graphics_native" \ - "$CP_DIR/shared-module/graphics_native" - log " removed legacy graphics / graphics_native spike dirs" -fi - -copy_spike - -# Strip legacy and current marked blocks so we can rewrite cleanly. -for f in "$ENABLE_MK" "$PORT_MK" "$MPCONFIG_MK" "$DEFNS_MK"; do - remove_marked_blocks "$f" \ - "graphics-cmod begin" \ - "graphics_native-cmod begin" \ - "pygraphics-cmod begin" -done - -# Rewrite any leftover path references outside marked blocks. -# Only match legacy ``graphics/`` paths (not the ``graphics`` suffix of ``pygraphics``). -if [[ "$PORT" == unix ]]; then - replace_in_file "$ENABLE_MK" $'shared-bindings/graphics/__init__.c \\' $'shared-bindings/pygraphics/__init__.c \\' - replace_in_file "$ENABLE_MK" $'shared-module/graphics/__init__.c \\' $'shared-module/pygraphics/__init__.c \\' -fi -replace_in_file "$DEFNS_MK" $'\tgraphics/__init__.c \\' $'\tpygraphics/__init__.c \\' - -# Variant enable + include circuitpython.mk (relpath works for any sibling layout) -ensure_block_append "$ENABLE_MK" "pygraphics-cmod begin" "$(cat <>> pygraphics-cmod begin -CIRCUITPY_PYGRAPHICS = 1 -CFLAGS += -DCIRCUITPY_PYGRAPHICS=1 -PYGRAPHICS_MOD_DIR := \$(abspath $PYGRAPHICS_MOD_REL) -include \$(PYGRAPHICS_MOD_DIR)/circuitpython.mk -# >>> pygraphics-cmod end -EOF -)" - -# Module source lists (unix only: MCU ports resolve these through -# SRC_PATTERNS += pygraphics/% in circuitpy_defns.mk). -if [[ "$PORT" == unix ]]; then - if grep -qF $'shared-bindings/lvgl/__init__.c \\' "$ENABLE_MK"; then - insert_raw_after_line "$ENABLE_MK" $'shared-bindings/lvgl/__init__.c \\' \ - $'\tshared-bindings/pygraphics/__init__.c \\' \ - "shared-bindings/pygraphics/__init__.c" - insert_raw_after_line "$ENABLE_MK" $'shared-module/lvgl/__init__.c \\' \ - $'\tshared-module/pygraphics/__init__.c \\' \ - "shared-module/pygraphics/__init__.c" - elif grep -qF $'shared-bindings/usdl2/__init__.c \\' "$ENABLE_MK"; then - insert_raw_after_line "$ENABLE_MK" $'shared-bindings/usdl2/__init__.c \\' \ - $'\tshared-bindings/pygraphics/__init__.c \\' \ - "shared-bindings/pygraphics/__init__.c" - insert_raw_after_line "$ENABLE_MK" $'shared-module/usdl2/__init__.c \\' \ - $'\tshared-module/pygraphics/__init__.c \\' \ - "shared-module/pygraphics/__init__.c" - else - insert_raw_after_line "$ENABLE_MK" $'shared-bindings/jpegio/JpegDecoder.c \\' \ - $'\tshared-bindings/pygraphics/__init__.c \\' \ - "shared-bindings/pygraphics/__init__.c" - insert_raw_after_line "$ENABLE_MK" $'shared-module/jpegio/JpegDecoder.c \\' \ - $'\tshared-module/pygraphics/__init__.c \\' \ - "shared-module/pygraphics/__init__.c" - fi -fi - -ensure_block_append "$PORT_MK" "pygraphics-cmod begin" "$(cat <>> pygraphics-cmod begin -ifneq (\$(wildcard \$(abspath $PYGRAPHICS_MOD_REL/circuitpython.mk)),) -PYGRAPHICS_MOD_DIR ?= \$(abspath $PYGRAPHICS_MOD_REL) -include \$(PYGRAPHICS_MOD_DIR)/circuitpython.mk -endif -# >>> pygraphics-cmod end -EOF -)" - -ensure_block_append "$MPCONFIG_MK" "CIRCUITPY_PYGRAPHICS ?=" "$(cat <<'EOF' -# >>> pygraphics-cmod begin -CIRCUITPY_PYGRAPHICS ?= 0 -CFLAGS += -DCIRCUITPY_PYGRAPHICS=$(CIRCUITPY_PYGRAPHICS) -# >>> pygraphics-cmod end -EOF -)" - -insert_block_before_needle "$DEFNS_MK" "ifeq (\$(CIRCUITPY_MATH),1)" "$(cat <<'EOF' -# >>> pygraphics-cmod begin -ifeq ($(CIRCUITPY_PYGRAPHICS),1) -SRC_PATTERNS += pygraphics/% -endif -# >>> pygraphics-cmod end -EOF -)" "SRC_PATTERNS += pygraphics/%" - -if grep -qF $'\tlvgl/__init__.c \\' "$DEFNS_MK"; then - insert_raw_after_line "$DEFNS_MK" $'\tlvgl/__init__.c \\' \ - $'\tpygraphics/__init__.c \\' \ - $'pygraphics/__init__.c' -elif grep -qF $'\tusdl2/__init__.c \\' "$DEFNS_MK"; then - # Insert before usdl2 so alpha-ish order stays reasonable when both present. - insert_raw_after_line "$DEFNS_MK" $'\tjpegio/JpegDecoder.c \\' \ - $'\tpygraphics/__init__.c \\' \ - $'pygraphics/__init__.c' -else - insert_raw_after_line "$DEFNS_MK" $'\tjpegio/JpegDecoder.c \\' \ - $'\tpygraphics/__init__.c \\' \ - $'pygraphics/__init__.c' -fi - -if [ "$APPLY" = 1 ] || [ "$DRY_RUN" = 1 ]; then - log "pygraphics CP patches applied" -fi - -if [ "$DRY_RUN" = 1 ] && [ "$DRY_RC" != 0 ]; then - log "Dry run reported at least one ERROR above." - exit "$DRY_RC" -fi - -if [ "$DRY_RUN" = 0 ]; then - echo - status_report - if [ "$APPLY" = 1 ]; then - echo - log "Next:" - log " cd $PORT_DIR && make -j VARIANT=$VARIANT" - log "To build with other extensions, run their apply_cp_patches.sh against the same checkout before make." - fi -fi diff --git a/circuitpython.mk b/circuitpython.mk deleted file mode 100644 index a4dddc1..0000000 --- a/circuitpython.mk +++ /dev/null @@ -1,55 +0,0 @@ -# CircuitPython build glue for pygraphics (unix coverage and MCU ports). -# -# Prefer caller-set GRAPHICS_MOD_DIR / PYGRAPHICS_MOD_DIR. Do not use a lazy -# ``MAKEFILE_LIST`` default: QSTR_DEFS prerequisites expand late, after other -# makefiles have been parsed, so ``lastword $(MAKEFILE_LIST)`` becomes wrong -# (e.g. ``../../py/src/pygraphics_qstrdefs.h``). -# Include guard: this file is included from both the enable makefile -# (variant on unix, board on MCU ports) and the port Makefile. Without the -# guard SRC_C gets every source twice and the link fails with "multiple -# definition of ..." naming the *same* object file on both sides. -ifndef PYGRAPHICS_MK_INCLUDED -PYGRAPHICS_MK_INCLUDED := 1 - -ifndef PYGRAPHICS_MOD_DIR - ifdef GRAPHICS_MOD_DIR - PYGRAPHICS_MOD_DIR := $(GRAPHICS_MOD_DIR) - else - PYGRAPHICS_MOD_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) - endif -endif - -CFLAGS += -I$(PYGRAPHICS_MOD_DIR)/src -DCIRCUITPY_PYGRAPHICS=1 -QSTR_DEFS += $(PYGRAPHICS_MOD_DIR)/src/pygraphics_qstrdefs.h - -# __version__ / __revision__, the same two strings micropython.mk passes; -# gfx_build.c defines the objects the module table points at, so a build -# that leaves it out fails at link, not at runtime. -PYGRAPHICS_VERSION := $(shell cat $(PYGRAPHICS_MOD_DIR)/VERSION 2>/dev/null || echo 0.0.0+unknown) -PYGRAPHICS_REVISION := $(shell git -C $(PYGRAPHICS_MOD_DIR) describe --always --dirty --abbrev=7 2>/dev/null || echo unknown) -CFLAGS += -DPYGRAPHICS_VERSION='"$(PYGRAPHICS_VERSION)"' -DPYGRAPHICS_REVISION='"$(PYGRAPHICS_REVISION)"' - -GRAPHICS_SOURCES := \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_build.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_module_mp.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_bindings_mp.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_canvas_mp.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_framebuffer.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_shapes.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_draw.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_font.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_bmp565.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_files.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_png.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_capabilities.c \ - $(PYGRAPHICS_MOD_DIR)/src/gfx_area_mp.c - -# -Wno-float-equal: CircuitPython builds with -Werror=float-equal, and the -# shape code compares a caller-supplied angle against exact 0.0f to skip -# rotation. That is a deliberate fast path, not accumulated-float equality. -GRAPHICS_SUPPRESS_CFLAGS := -Wno-unused-function -Wno-sign-compare -Wno-float-equal -$(foreach _gfx,$(GRAPHICS_SOURCES),$(eval $(BUILD)/$(_gfx:.c=.o): CFLAGS += $(GRAPHICS_SUPPRESS_CFLAGS))) - -SRC_C += $(GRAPHICS_SOURCES) - -endif # PYGRAPHICS_MK_INCLUDED diff --git a/docs/installation.md b/docs/installation.md index d8527ef..b32bcd1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -40,22 +40,18 @@ under the new name — the cutover publish already went out. Copy the `pygraphics/` package folder onto `sys.path` (from `lib/pygraphics/` in this repo, or from mip). -To link the **native** cmod into a CircuitPython unix build (out-of-tree -[Extending CircuitPython](https://learn.adafruit.com/extending-circuitpython) -layout via spike + `apply_cp_patches.sh` — see the -[repository README](https://github.com/PyDevices/pygraphics#circuitpython-unix)): +To build the **native** module into CircuitPython, hand this repository to the +port's `make` as a user C module: ```bash -# siblings: circuitpython/ and pygraphics/ -./apply_cp_patches.sh --apply -cd ../circuitpython/ports/unix && make -j VARIANT=coverage +make -C ports/unix -j VARIANT=coverage USER_C_MODULES=/path/to/pygraphics ``` -Tested against CircuitPython 10.2.1; the MicroPython recipe is tested against +That works on CircuitPython 11 and on boards as well as unix; the +[repository README](https://github.com/PyDevices/pygraphics#circuitpython) has +the board command and what was tested. The MicroPython recipe is tested against v1.28.0 and current master. -See the org's [optional aggregator workspace](https://github.com/PyDevices/cmods) for an easier way to build this repo with other user C modules (MicroPython) or extensions (CircuitPython). - ## CPython — native/C extension (preferred when available) ```bash diff --git a/docs/newcomers.md b/docs/newcomers.md index 4b376bd..38addea 100644 --- a/docs/newcomers.md +++ b/docs/newcomers.md @@ -52,7 +52,7 @@ polygons, rounded rectangles, arcs, gradients, text, and transparent blits. |---|---| | `src/` | Native C framebuffer, shape, font, file, and CPython-extension implementation. | | `lib/pygraphics/` | Pure-Python implementation and the source of truth for generated Python API pages. | -| `micropython.mk`, `micropython.cmake`, `circuitpython.mk` | Build glue for native firmware targets. | +| `micropython.mk`, `micropython.cmake` | Build glue for native firmware targets (`micropython.mk` serves CircuitPython too). | | `setup.py`, `pyproject.toml` | CPython native wheel build metadata. | | `docs/` | Installation, usage, image-file, benchmark, and API documentation. | | `tests/` | Native smoke tests and native/pure-Python parity coverage. | diff --git a/micropython.mk b/micropython.mk index 62deb23..384678c 100644 --- a/micropython.mk +++ b/micropython.mk @@ -5,9 +5,9 @@ PYGRAPHICS_MOD_DIR := $(USERMOD_DIR) -# PYGRAPHICS_USER_C_MODULE tells the C that it was built through USER_C_MODULES, -# which CircuitPython builds also honour. Its unix port sets CIRCUITPY=1, and -# without this the module would skip MP_REGISTER_MODULE and never be importable. +# PYGRAPHICS_USER_C_MODULE tells the C it was built through USER_C_MODULES +# (MicroPython's make ports, or CircuitPython): gfx_png.c allocates from the GC +# heap then, because some CircuitPython ports have no C heap. CFLAGS_USERMOD += -DPYGRAPHICS_USER_C_MODULE=1 -I$(PYGRAPHICS_MOD_DIR)/src -Wno-unused-function -Wno-sign-compare -Wno-unused-const-variable # Arc/polygon use Q15 LUT in gfx_trig.h — no libm required. diff --git a/src/circuitpython_spike/copy_manifest.txt b/src/circuitpython_spike/copy_manifest.txt deleted file mode 100644 index d30548f..0000000 --- a/src/circuitpython_spike/copy_manifest.txt +++ /dev/null @@ -1,4 +0,0 @@ -shared-bindings/pygraphics __init__.c -shared-bindings/pygraphics __init__.h -shared-module/pygraphics __init__.c -shared-module/pygraphics __init__.h diff --git a/src/circuitpython_spike/shared-bindings/pygraphics/__init__.c b/src/circuitpython_spike/shared-bindings/pygraphics/__init__.c deleted file mode 100644 index b082425..0000000 --- a/src/circuitpython_spike/shared-bindings/pygraphics/__init__.c +++ /dev/null @@ -1,8 +0,0 @@ -// CircuitPython shared-bindings registration for pygraphics. -#include "shared-bindings/pygraphics/__init__.h" -#include "py/obj.h" -#include "py/runtime.h" - -extern const mp_obj_module_t mp_module_pygraphics; - -MP_REGISTER_MODULE(MP_QSTR_pygraphics, mp_module_pygraphics); diff --git a/src/circuitpython_spike/shared-bindings/pygraphics/__init__.h b/src/circuitpython_spike/shared-bindings/pygraphics/__init__.h deleted file mode 100644 index 5db325d..0000000 --- a/src/circuitpython_spike/shared-bindings/pygraphics/__init__.h +++ /dev/null @@ -1,9 +0,0 @@ -// CircuitPython shared-bindings header for pygraphics. -#ifndef SHARED_BINDINGS_PYGRAPHICS___INIT___H -#define SHARED_BINDINGS_PYGRAPHICS___INIT___H - -#include "py/obj.h" - -extern const mp_obj_module_t mp_module_pygraphics; - -#endif diff --git a/src/circuitpython_spike/shared-module/pygraphics/__init__.c b/src/circuitpython_spike/shared-module/pygraphics/__init__.c deleted file mode 100644 index 32b237a..0000000 --- a/src/circuitpython_spike/shared-module/pygraphics/__init__.c +++ /dev/null @@ -1,3 +0,0 @@ -// CircuitPython shared-module stub for pygraphics. -// Module object is defined in gfx_bindings_mp.c (pygraphics cmod). -#include "shared-module/pygraphics/__init__.h" diff --git a/src/circuitpython_spike/shared-module/pygraphics/__init__.h b/src/circuitpython_spike/shared-module/pygraphics/__init__.h deleted file mode 100644 index 249c93d..0000000 --- a/src/circuitpython_spike/shared-module/pygraphics/__init__.h +++ /dev/null @@ -1,5 +0,0 @@ -// CircuitPython shared-module header for pygraphics. -#ifndef SHARED_MODULE_PYGRAPHICS___INIT___H -#define SHARED_MODULE_PYGRAPHICS___INIT___H - -#endif diff --git a/src/gfx_bindings_mp.c b/src/gfx_bindings_mp.c index b48643a..ae6d0ce 100644 --- a/src/gfx_bindings_mp.c +++ b/src/gfx_bindings_mp.c @@ -2027,15 +2027,4 @@ const mp_obj_module_t mp_module_pygraphics = { .globals = (mp_obj_dict_t *)&graphics_module_globals, }; -/* CIRCUITPY alone means the shared-bindings build (apply_cp_patches.sh), which - * registers the module itself. A CircuitPython build through USER_C_MODULES - * registers it here, as MicroPython does. */ -#if CIRCUITPY && !defined(PYGRAPHICS_USER_C_MODULE) -/* Alias for any leftover shared-bindings that still reference mp_module_graphics. */ -const mp_obj_module_t mp_module_graphics = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&graphics_module_globals, -}; -#else MP_REGISTER_MODULE(MP_QSTR_pygraphics, mp_module_pygraphics); -#endif