| name | manual-jni |
| description | Regenerate the manual JNI surface report via `scripts/manual-jni-report.cs`. Use to answer which bindings remain hand-written (raw `JNIEnv.*` calls, suspend bridges, Java Callable Wrappers), what blocks moving a hand-written helper to the `[ComposeBridge]` generator, or after adding/removing manual JNI (e.g. a new `[ComposeBridge]` migration, a new suspend bridge, a new `[Register("net/compose/…")]` JCW). |
Manual JNI surface
Regenerate docs/manual-jni.md — the inventory of every place in
src/Microsoft.AndroidX.Compose/ that still uses hand-written JNI
(JNIEnv.* calls, [Register("net/compose/…")] JCWs) instead of the
source-generated [ComposeBridge] / [ComposeFacade] machinery. The
report is produced by a single .NET 10 file-based program at
scripts/manual-jni-report.cs. It scans every .cs file under
src/Microsoft.AndroidX.Compose/, anchors each JNI call to its enclosing
member, pulls a "Why not generated?" rationale from existing XML doc
<remarks> and adjacent // ... comments, and writes a markdown
report with summary metrics, per-file member tables, and a JCW
catalogue.
Workflow
1. Run the script
From the repo root (Windows / PowerShell):
dotnet run scripts/manual-jni-report.cs
That's it — no .csproj, no #:package, no MSBuild target. Pure
text/regex scan; runs in a couple of seconds.
Expected tail output:
Files scanned: 255
Files with manual JNI: 47
JNIEnv.* call sites: 196
JCW classes: 25 (4 with JNI, 21 pure-managed)
[ComposeBridge] partials: 202
[ComposeFacade] partials: 95
Report written: docs/manual-jni.md
2. Verify the diff
git diff --stat docs/manual-jni.md
The headline numbers live in the Summary section near the top.
Quick sanity check:
Select-String -Path docs/manual-jni.md -Pattern '^- \*\*' | Select-Object -First 7
Re-running the script with no source change should produce a byte-
identical file modulo the Generated by ... on YYYY-MM-DD HH:MM UTC
timestamp. If it doesn't, see Troubleshooting.
3. Commit the regenerated report alongside the change that prompted it
The report is checked in deliberately so reviewers can see the manual
surface shrink (or grow) in the PR diff. Don't commit the script run
on its own — pair it with the bridge migration, new suspend bridge,
or whatever change motivated the refresh.
git add docs/manual-jni.md
git commit --amend --no-edit # or a fresh commit, depending on context
What the report contains
- Summary — files scanned / flagged, raw
JNIEnv.* call count,
total lines inside flagged members, JCW totals (split: with-JNI vs
pure-managed delegate adapters), generator-emitted partials
([ComposeBridge] + [ComposeFacade]), manual : generated ratio.
- Per-file detail — one section per
.cs file containing manual
JNI or a JCW. Each section opens with the file's <summary> /
leading comment as Purpose, notes how many [ComposeBridge] /
[ComposeFacade] partials in the same file are excluded, then
enumerates manual entry points in a table: member name, kind,
line range, JNI call count, "Why not generated?", "Migration".
- JCW classes — every
[Register("net/compose/…")] class in the
project, with its Kotlin interface, source location, and whether
the JCW body itself contains raw JNI.
- Caveats — heuristic limitations (regex-based detection,
comment-driven rationale extraction).
Improving "Why not generated?" entries
The script extracts the rationale paragraph by walking upward from
the member declaration (up to 30 lines), matching in this order:
- An explicit Why raw JNI / Why manual marker — either form
wins regardless of which comment style hosts it:
// Why raw JNI: … / // Why manual: … line.
/// XML doc <remarks> paragraph containing Why raw JNI.
- Otherwise, the adjacent
///-doc or // comment block sitting
directly above the declaration is used verbatim.
- Falls back to TODO: document if there are no comments above
the member.
If a member shows TODO: document in the table, that's a docs gap
in the source itself. The fix is in the source file, not the script
— add a // Why raw JNI: ... line above the method declaration, or
flesh out the existing XML <remarks>. Re-run the script and the
entry refreshes.
Don't add a banner just to please the report — only document
members where the rationale is non-obvious from the surrounding
context.
Troubleshooting
Member detection misses a multi-line declaration — the script
expects member declarations to begin with the standard modifier
prefix (public|internal|private|protected|static|...). If a method
uses an unusual modifier order or layout ([MethodImpl(...)] first,
then modifiers on the next line), the regex may skip it and the
member's JNI calls land in a synthetic (file-level) row. Either
restructure the declaration to put modifiers on the same line as the
return type, or extend the modifier soup in rxMethod /
rxCtor near the top of the script.
JCW count looks wrong — only Microsoft.AndroidX.Compose's own [Register("net/compose/…")]
classes are counted. JCWs Mono.Android emits for upstream interfaces
are deliberately excluded. If a new file registers a JCW under a
different namespace prefix, update the rxRegister regex.
Generator partials counted as manual — the script excludes
members decorated with [ComposeBridge] / [ComposeFacade] /
[ComposeDefaults] by walking attribute lines immediately above the
declaration. If a comment line splits an attribute from its target,
the exclusion silently fails. Move the attribute adjacent to the
declaration.
Report regen produced spurious diffs — every run except for the
header timestamp should be byte-identical. Common causes if it
isn't:
- A non-ASCII char drift (smart quotes, em-dashes) in a source
comment got mangled by some other tool.
- Member detection picked up a new declaration that wasn't there
before. To find which entry shifted, note the line range in the
diffed row (e.g.
Lines 221–246) and use
git diff -U0 src/Microsoft.AndroidX.Compose/<file>.cs | rg -n '^@@' to
locate the corresponding source change.
If diffs persist with no source change, the script has a bug; look
at the order-sensitive sites (OrderBy(j.ClassName, StringComparer.Ordinal)
on the JCW table, OrderBy(StartLine) inside per-file entries).
Don't do these things
- Don't hand-edit
docs/manual-jni.md. It's regenerated every run
and your edit will be lost.
- Don't run the script from inside a subdirectory; the output path
(
docs/manual-jni.md) and source-root path
(src/Microsoft.AndroidX.Compose) are relative to the repo root.
- Don't add a
// Why raw JNI: banner to a member that already has
a clear <summary> / <remarks> block — the script's fallback
reads adjacent doc comments fine.
- Don't include managed helpers with zero
JNIEnv.* calls in the
manual tally just because they live in ComposeBridges.cs. The
report tracks raw JNI usage, not everything that isn't
generator-decorated; a one-line managed helper isn't relevant
surface.