Skip to content

Commit 899a734

Browse files
committed
docs: add AGENTS.md for AI-agent collaboration on the binding
Guidance doc for AI coding agents (Claude Code, Cursor, etc.) working on this repo. Covers the evergreen stuff that's easy to get wrong without orientation: - The codegen-driven structure (src/main/java annotated, src/generated/java expanded by buildSrc Spoon pass, both committed) and the "never edit generated code" rule. - Project layout, build/test commands, and a fast standalone javadoc recipe for iterating on doclint errors without waiting for Gradle + CI. - The end-to-end flow for bumping Dear ImGui or an extension: orient via upstream changelog + header diff first (breaking changes / obsoleted APIs / new features worth exposing / AST implications), then the mechanical regen steps. - Gotchas: doclint rejecting C++ operator patterns in doc comments (with before/after examples), vendor patches under patches/, AST regeneration drift from differing system headers, and the regen-instead-of-hand-merge pattern when rebasing branches with codegen output. - Codegen conventions for buildSrc (bound-appropriate Spoon type args after the Kotlin 2 K2 migration, configuration-cache patterns). - PR workflow: separate commits and PRs for bumps/deps/codegen, suggested merge order when multiple are open. Intended to reduce the onboarding cost for agents bumping submodules in the future; most of the advice is drawn from the recent 1.92.7 + ImPlot v1.0 + Gradle 9 bump wave.
1 parent 7c06865 commit 899a734

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# AGENTS.md
2+
3+
Guidance for AI coding agents working on `imgui-java`.
4+
5+
## What this repo is
6+
7+
JNI-based Java binding for [Dear ImGui](https://github.com/ocornut/imgui) + extensions (ImPlot, ImNodes, ImGuizmo, imgui-node-editor, imgui-knobs, imgui-file-dialog, imgui-text-edit, imgui-club). Multi-module Gradle build, published as `io.github.spair:imgui-java-{binding,lwjgl3,app}`. Java is **codegen-driven**: annotated stubs in `imgui-binding/src/main/java/`, expanded by the Spoon-based generator in `buildSrc/` into `imgui-binding/src/generated/java/` — both trees committed.
8+
9+
## Golden rule: never edit generated code directly
10+
11+
`imgui-binding/src/generated/java/` is codegen output. `imgui-binding/src/main/java/` is the **single source of truth**. Workflow is always: edit source → `./gradlew :imgui-binding:generateApi` → commit both trees in one commit.
12+
13+
If the generator produces something you "just need to fix in the generated file" — that's a generator bug. Fix it in `buildSrc/`. Hand-edits to `src/generated/` are silently reverted on the next regen.
14+
15+
## Project layout
16+
17+
| Path | What's there |
18+
|---|---|
19+
| `imgui-binding/` | Core JNI binding + codegen |
20+
| `imgui-binding/src/main/java/` | Hand-written annotated sources (`@BindingSource`, `@BindingField`, `@BindingMethod`, `@BindingAstEnum`) |
21+
| `imgui-binding/src/generated/java/` | Codegen output; committed. See Golden rule |
22+
| `imgui-binding/src/main/native/` | Hand-written JNI `.cpp` / `.h` (struct marshaling etc.) |
23+
| `imgui-lwjgl3/` | LWJGL backend (GLFW + OpenGL) |
24+
| `imgui-app/` | Convenience wrapper bundling natives + an `Application` entry-point |
25+
| `include/` | Git submodules: imgui, implot, imnodes, imguizmo, etc. |
26+
| `bin/` | Native libs (`.so/.dylib/.dll`), committed by CI |
27+
| `buildSrc/` | Gradle plugin: codegen tasks, JNI build, AST parser |
28+
| `buildSrc/src/main/resources/generator/api/ast/*.json` | Clang-dumped AST data, consumed by `@BindingAstEnum` |
29+
| `example/` | Example apps |
30+
| `patches/` | Local patches against vendored submodules |
31+
32+
## Build & test
33+
34+
Gradle's toolchain pins JDK 17 regardless of the system JDK.
35+
36+
```bash
37+
./gradlew :imgui-binding:compileJava # fast Java-side check
38+
./gradlew :imgui-binding:javadoc # catch doc errors (CI also runs this; mandatory pass)
39+
./gradlew :imgui-binding:generateApi # regen src/generated/java from annotated sources
40+
./gradlew generateAst # regen clang AST JSONs (needs clang++ installed)
41+
./gradlew :imgui-binding:generateLibs # build native JNI libs (per-OS; CI handles cross-platform)
42+
./gradlew build # full build + tests
43+
```
44+
45+
**Fast javadoc iteration without Gradle:**
46+
47+
```bash
48+
find imgui-binding/src/generated/java imgui-binding/src/main/java -name '*.java' > /tmp/files.txt
49+
javadoc -d /tmp/jdoc -Xdoclint:all \
50+
-sourcepath imgui-binding/src/generated/java:imgui-binding/src/main/java \
51+
@/tmp/files.txt 2>&1 | grep 'error:'
52+
```
53+
54+
Zero `error:` lines is the bar. Warnings (mostly missing `@param`/`@return` on regenerated methods) are the baseline and non-blocking.
55+
56+
## Upgrading Dear ImGui or an extension
57+
58+
### 0. Orient yourself
59+
60+
Read the upstream changelog and diff before touching anything.
61+
62+
```bash
63+
cd include/imgui # or implot, imnodes, etc.
64+
git fetch --tags origin
65+
less CHANGELOG.txt # imgui has this; others vary (docs/CHANGELOG.md, release notes)
66+
git log --oneline <current>..<target> # commit-level overview
67+
git diff <current>..<target> -- '*.h' # header-level diff — what actually changes the C API
68+
```
69+
70+
Identify:
71+
72+
- **Breaking changes** — removed/renamed functions, changed signatures, enum renames, struct field changes. Grep hand-written sources for any `@BindingMethod`/`@BindingField` that hits these.
73+
- **Obsoleted APIs** — usually guarded by `IMGUI_DISABLE_OBSOLETE_FUNCTIONS`. Decide per-case whether the binding keeps the legacy surface (flagged as obsolete in docs) or drops it; mirror upstream's stance.
74+
- **New features worth exposing** — new functions, flags, struct fields useful from Java. Prefer landing the bump first, then adding new surface in follow-up commits.
75+
- **AST implications** — new types/renames surface in regenerated `ast-*.json`. Unexpected AST changes outside your target submodule = drift (revert per Gotchas).
76+
77+
Use this reading to write a meaningful commit message and PR description. A one-line "bump imgui" is never enough.
78+
79+
### 1. Mechanical flow
80+
81+
1. Bump the submodule pointer.
82+
```bash
83+
git -C include/imgui checkout v1.92.7
84+
```
85+
2. Regenerate the clang AST.
86+
```bash
87+
./gradlew generateAst
88+
```
89+
Commit the resulting `buildSrc/src/main/resources/generator/api/ast/ast-<name>.json`. If *other* AST JSONs change (e.g., `ast-ImGuiFileDialog.json`, `ast-TextEditor.json`), that's unrelated local clang/system-header drift — **revert those**, keep the diff scoped.
90+
3. Update annotated sources under `imgui-binding/src/main/java/` to match upstream's new/renamed/removed fields and methods exactly. Doc comments copy through verbatim — pre-sanitize anything that'd trip doclint (see Gotchas).
91+
4. Regenerate Java.
92+
```bash
93+
./gradlew :imgui-binding:generateApi
94+
```
95+
Commit `src/generated/java/` changes alongside the source changes.
96+
5. **Run javadoc locally** (see above). Any errors → fix in source, regen, recheck.
97+
6. `./gradlew generateLibs` rebuilds the native lib for the current OS. CI builds the cross-platform set on merge and commits to `bin/`.
98+
99+
## Gotchas (what bites on a submodule bump)
100+
101+
### Javadoc doclint rejects C++ comment patterns
102+
103+
Dear ImGui's C++ headers use `<`, `>`, `&`, `->` as operators in doc comments; strict doclint (JDK 17+) treats them as malformed HTML and fails. Wrap offending text in `{@code ...}` in source, then regen. Examples:
104+
105+
| Bad | Good |
106+
|---|---|
107+
| `(flags & X)` | `{@code (flags & X)}` |
108+
| `if threshold < 0.0f` | `{@code if threshold < 0.0f}` |
109+
| `"Demo->Child->X"` | `{@code Demo->Child->X}` |
110+
| `{@link ImGui#pushFont(ImFont)}` after rename | `{@link ImGui#pushFont(ImFont, float)}` |
111+
112+
Only enum-constant docs are auto-sanitized (via `ast_content.kt`'s `sanitizeDocComment`); class- and method-level Javadoc is copied verbatim. Extending the sanitizer to cover those too would eliminate this step — open opportunity. Run javadoc locally before pushing.
113+
114+
### Vendor patches
115+
116+
Some submodules need local patches to compile against current imgui (e.g., `imgui-node-editor`'s `operator*` overload colliding with imgui's). Patches live in `patches/`, applied idempotently by `buildSrc/scripts/apply_vendor_patches.sh` (wired into `generateLibs`). When bumping a patched submodule, re-check the patch still applies; drop if upstream fixed the issue.
117+
118+
### AST regeneration drift
119+
120+
`./gradlew generateAst` may update JSONs unrelated to your bump (e.g., `ast-ImGuiFileDialog.json`, `ast-TextEditor.json`) because clang picks up different system headers per machine. **Revert those** — keep the diff scoped.
121+
122+
### Rebasing: regenerate, don't hand-merge
123+
124+
Conflict markers inside generated files or AST JSONs are a false fight. Take your side, then re-run `generateAst` / `generateApi` on the rebased tree. Hand-merging codegen output just produces drift.
125+
126+
### Don't commit build artifacts
127+
128+
Native libs under repo-root `bin/` are committed by CI after merge; never commit your local `generateLibs` output there.
129+
130+
## Codegen conventions (when editing `buildSrc/`)
131+
132+
### Don't use `<Nothing>` as a generic type arg in Spoon calls
133+
134+
Kotlin 2.x K2 emits a runtime `CHECKCAST java/lang/Void` for `<Nothing>` generic returns, which fails since Spoon returns concrete types. Use bound-appropriate types:
135+
136+
| Setter | Bound / use |
137+
|---|---|
138+
| `setType` | `<CtTypedElement<Any>>` |
139+
| `setSimpleName` on CtNamedElement | `<CtNamedElement>` |
140+
| `setSimpleName` on CtReference | `<CtReference>` |
141+
| `setParent`, `setAnnotations`, `addAnnotation`, `setDocComment` | `<CtElement>` |
142+
| `addModifier`, `setModifiers` | `<CtModifiable>` |
143+
| `addParameter`, `addParameterAt`, `setParameters` | `<CtMethod<Any>>` |
144+
| `setBody` | `<CtBodyHolder>` |
145+
| `addStatement` | `<CtStatementList>` |
146+
| `setTags`, `removeTag` | `<CtJavaDoc>` |
147+
| `setValue` | `<CtCodeSnippet>` |
148+
| `addValue` | `<CtAnnotation<Annotation>>` |
149+
| `createMethod` / `createField` / `createParameter` | `<Any>` |
150+
151+
For `CtMethod<*>` receivers of setters bounded on `CtExecutable<captured *>`, Kotlin can't reconcile — cast:
152+
153+
```kotlin
154+
@Suppress("UNCHECKED_CAST")
155+
val newMethod = method.clone() as CtMethod<Any>
156+
newMethod.setParameters<CtMethod<Any>>(newParams)
157+
```
158+
159+
### Gradle 9 configuration cache
160+
161+
Use `providers.exec { ... }` for shell-outs, never `.execute()`. Capture resolved strings at configuration time; don't capture the `Project` model in closures that execute later.
162+
163+
## PR workflow
164+
165+
Keep submodule bumps, Gradle/deps bumps, and codegen changes as separate commits and separate PRs.
166+
167+
When multiple PRs are open, suggest a merge order:
168+
169+
1. Infrastructure (Gradle, build tooling) — unblocks everything else.
170+
2. Independent changes (submodule refresh) — low risk, fast review.
171+
3. Larger API bumps, in dependency order.
172+
173+
Offer to rebase after each preceding merge.
174+
175+
For CI failures, use `gh run view <run-id> --log-failed` and reproduce locally — don't iterate on CI.

0 commit comments

Comments
 (0)