|
| 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