Skip to content

Python tools

Auto-generated reference for the Python tools under tools/. Module docstrings are the authoritative spec for CLI behaviour and output contracts; the prose pages link here for symbol-level detail.

select_assignments

The matrix-selection driver. Reads workspace/assignment.txt, diffs the push, and emits the GitHub Actions matrix.

Compute the autograder matrix for a student push.

Reads workspace/assignment.txt and a list of paths changed in the push, emits a JSON array [{id, folder, source}, ...] of assignments that need re-grading. source is one of: "changed" (folder touched), "manifest" (assignment.txt itself changed), "force" (force-all flag), "first-push" (no before SHA available).

Manifest format — one entry per line:

HW1 HW1mj
HW2 HW2mj  HW2_main.c   # optional 3rd column = explicit main file

Blank lines and # comments are ignored.

CLI::

select_assignments.py --manifest <path> --workspace <dir>
                      --before <sha> --head <sha>
                      [--force-all] [--changed-from-stdin]
                      [--output github|json]

The --changed-from-stdin flag reads newline-separated paths from stdin instead of running git diff (used by tests and by the workflow when it has already collected the diff).

The --output github form prints matrix=<json> for $GITHUB_OUTPUT consumption.

Run with --self-test to execute the in-file unit tests (stdlib only).

ManifestEntry dataclass

parse_manifest

parse_manifest(text)

assign_slots

assign_slots(entries)

Return one slot per entry. Slot == id when id is unique in the manifest; otherwise slot == f"{id}__{slug(folder)}" so duplicate-id rows produce distinct artifact names, JSON filenames, and history keys.

select

select(entries, changed, workspace, manifest_path, force_all, first_push)

git_diff

git_diff(before, head, repo)

main

main(argv=None)

render_report

The post-grade renderer. Aggregates per-slot results into per-folder detail pages, ID-level aggregate pages, and a top-level index with clickable folder + commit links.

Render grader JSON results into student-facing markdown + JSON.

Inputs (CLI): --results-dir

directory containing .json (one per graded run) and optional .log files. --manifest workspace/assignment.txt — used to render the cumulative index even for assignments not graded this run. --report-dir output directory inside the student repo (e.g. autograder/). Per-assignment files are updated in place; entries not in this run are preserved verbatim. --commit commit SHA of the run (recorded into every emitted JSON snapshot and history line so a reader can trace a given result back to the exact source). --run-url link to the GitHub Actions run (optional). --repo-url base URL of the student repo, e.g. https://github.com/owner/repo. When set, the rendered report turns each folder name into a link to that folder at the run's commit (<repo-url>/tree/<sha>/<workspace>/<folder>) and each commit SHA into a link to the commit (<repo-url>/commit/<sha>). Optional. --workspace workspace prefix used when building folder URLs (defaults to workspace). --step-summary if set, also write a Markdown summary suitable for $GITHUB_STEP_SUMMARY.

The manifest may contain duplicate IDs (e.g. two HW1 rows pointing at different folders). Each row produces an independent grader run keyed by its slot (unique per row, see select_assignments.py). For an ID with multiple rows we emit:

  • one detail file per row: /.md / .json
  • an aggregate .md (per-folder rows + total pass/fail)
  • an aggregate .json (machine-readable summary of all rows)
  • history/.jsonl (one JSONL per row, recording commit + folder)

When an ID has only one manifest row, slot == id, so the layout collapses to the historical single-file form.

Each .json read here is the format emitted by AutomaticGrader --report-json: { "assignment": "HW1", "passed": 6, "failed": 1, "checks": [{"name": "...", "status": "pass|fail", "messages": ["..."]}, ...] }

The grader does not know its own slot/folder/commit; the workflow is expected to drop a sidecar <slot>.meta.json next to the result with at least {"id": ..., "folder": ..., "slot": ...}. Without the sidecar we fall back to filename-stem heuristics (treating "" as a slot where everything after the "" is a folder slug, and a bare "" as a single-row entry).

load_manifest

load_manifest(path)

Return list of (id, folder) — preserves duplicates and order.

render_assignment_md

render_assignment_md(report, log_text, repo_url='', workspace='')

Detail page for a single graded run (one slot).

render_aggregate_md

render_aggregate_md(aid, runs, commit, repo_url='', workspace='')

Aggregate page for an ID that has multiple manifest rows.

The folder cell links to that folder at the commit it was graded against (each row may have its own commit, since unchanged siblings keep their original commit across partial regrades). The header commit links to the most recent run's commit.

render_index_md

render_index_md(rows, commit, run_url, repo_url='', workspace='')

render_step_summary

render_step_summary(reports)

append_history

append_history(history_path, entry)

main

main(argv=None)

patch_student_source

Build-time student-source rewriter. Injects the init-guard macro and rewrites while(1) to the cooperative-driver loop. See Cooperative driver for the contract.

Patch a student firmware source file for the cooperative main-loop driver.

Two transformations are applied:

  1. Inject GRADER_MAIN_INIT_GUARD immediately after the opening { of main(). The macro (defined in include/ti_stubs.h under AUTO_GRADER) uses a static flag plus a goto to skip the init code on every re-entry into temp_main() after the first one. This is what makes grader::step_main_loop() cheap (no re-init, no re-running of any busy-waits the student's setup helpers may contain).

  2. Replace while(1) with _grader_loop_start: ; GRADER_MAIN_LOOP. The label is the goto target from (1); GRADER_MAIN_LOOP expands to a for-loop bounded by grader_main_loop_iterations().

Usage: patch_student_source.py

patch

patch(src)

main

main()

Codegen tools

These three tools regenerate the auto-generated src/checks/generated.cpp, src/checks/compare_generated.cpp, and the body of HardwareStateValidator::populate_all_zero() from TI peripheral register headers. Run them when a TI header changes or a new peripheral is added.

validation

generate_zero_checks

generate_zero_checks(header_text)

Parses a C/C++ header file containing register structure definitions and generates C++ templates to verify all registers are zero.

compare_validation

generate_zero_checks

generate_zero_checks(header_text)

Parses a C/C++ header file containing register structure definitions and generates C++ templates to verify all registers are zero.

hash_map_setter

generate_hashmap_initializer

generate_hashmap_initializer(header_text)

Parses a C++ header file containing 'extern volatile struct' declarations and generates C++ code to populate a std::unordered_map for zero-checking.