Check build hints at compile time instead of shipping them inert - #5586
Check build hints at compile time instead of shipping them inert#5586shai-almog wants to merge 43 commits into
Conversation
A build hint is a `codename1.arg.<name>=<value>` line that reaches a builder as
`request.getArg(name, default)`. Nothing checked the name, so a misspelling was
accepted, copied into the build request, never read, and silently discarded: a
green build with the setting simply not applied.
Our own agent reference had been shipping `android.xPermissions`,
`android.minSdkVersion` and `android.sdkVersion` for exactly that reason. The
builders read `android.xpermissions`, `android.min_sdk_version`, and nothing at
all.
Most hints can now be written as annotations on the application's main class,
where javac does the checking: a misspelled name is an unknown symbol, a wrong
value type is a type error, and a value outside a hint's supported set is an
unknown enum constant.
@iOS(newStorageLocation = true, themeMode = IosThemeMode.MODERN)
@android(minSdkVersion = 24, useAndroidX = true)
@desktop(titleBar = DesktopTitleBar.NATIVE)
public class MyApplication extends Lifecycle {
}
The builders are untouched: `BuildHintAnnotationProcessor` converts the
annotations back into the same key/value pairs and `CN1BuildMojo` merges them
before the command-line overlay, the CN1Lib merges and both preflights, so a
library still appends onto an annotation-supplied value and `-D` still wins.
`Simulator` publishes them as system properties at startup so `cn1:run` sees
hints that no longer live in the properties file.
The properties file is untouched too. It stays the way to set the long tail and
the open-ended families such as `android.permission.<NAME>` that an annotation
cannot express, with no new warnings or errors. Declaring one hint both ways is
a build error.
One catalog, five generated views
---------------------------------
The hint set had been described in five places that had drifted apart: a prose
table in the developer guide, a runtime scraper of that table in the Settings
tool that guessed each type by string-matching the description, a fifteen-entry
schema in the simulator, a fourteen-entry separator map in the plugin, and a
hand-written agent reference. Only 147 of ~520 names appeared in more than one.
`maven/build-hint-catalog` is now the single source of truth (529 hints: 457
mined from the builders, 56 documented-but-unread, 16 dynamic families; 82
exposed as annotation attributes). The annotations, the binding table the
processor reads back, the guide's table, the simulator's editor schema and the
agent reference are all generated from it. The guide's table goes from 208 rows
to 529 with no prose lost.
Enums are emitted only where the accepted set is demonstrable from the code that
reads the hint -- `HardeningPreflight` rejects an unknown `harden.level`,
`IOSDependencyManager` throws on an unknown `ios.dependencyManager`, and
`GenerateDesktopAppWrapperMojo` silently falls back to `native` on an unknown
`desktop.titleBar`, which is the failure this removes.
Generated projects
------------------
The archetype and all four initializr templates now carry the annotations, and
`cn1:migrate-build-hints` moves an existing project over. Eleven in-repo
projects are migrated. `java.version` deliberately stays in the properties file:
it picks the toolchain that compiles the class the annotations live on.
Gates
-----
`scripts/check-build-hint-catalog.sh` fails when code reads a hint the catalog
does not describe, and when our own docs or templates name one that no builder
reads. Its baseline is empty, so it is a hard gate rather than a ratchet.
`scripts/gen-build-hint-annotations.sh --check` fails on generated-file drift.
Both run in the Java 8 leg of PR CI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d2cfcfde3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
`.gitignore` carries a repo-wide `**/build/*`. The catalog's package is `com.codename1.build.shared`, so all 13 of its sources sat under a path segment named `build` and `git add` silently skipped them. Only `pom.xml` was committed: the module built locally from the working tree and produced an empty jar in CI, which is why `codenameone-maven-plugin` then failed with `cannot find symbol` on `BuildHints` and nearly every job went red. The sibling `platform-feature-catalog` lives in the same package and is fine, because it was added before that rule existed -- tracked files stay tracked, so nothing ever pointed at the hazard. Un-ignore `build` when it is a Java package rather than a build output directory, with the rationale beside the rule so the next file added there is not lost the same way. `maven/core/build/*` and `CodenameOne/build/*` stay ignored. Also from review: - Every bare `open()` in the four Python scripts now uses a context manager, so the handle closes even if parsing or `json.dump` raises, and the writes state their encoding. - The generator no longer emits an IP literal as an annotation default. PMD reads `default "127.0.0.1"` as hardcoded configuration, and the default clause is documentation only -- the processor emits a hint solely for members the developer actually wrote -- so the value moves to the javadoc where it belongs. - Files the migration touched that never carried a copyright header now have the complete one. The archetype's `__mainName__.java` is excluded instead: it is a template for the user's own application class, and stamping a Codename One GPL header onto it would put our licence on their code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
The updated Initializr starter archives now import and use com.codename1.annotations.buildhints, but GeneratorModel.java:44 still generates projects pinned to CN1 7.0.267, whose core artifact predates this package. Consequently every newly generated barebones, Kotlin, Grub, or Tweet project fails compilation on the unresolved annotations unless the user manually changes the CN1 version; either defer these template changes or update the generated runtime/plugin version to the first release containing them.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The archived bootstrap ran generation at module scope, so gen_external.py's `import gen_catalog` -- which only wants three helper functions -- rewrote every catalog source as a side effect. Generation and its diagnostics now live in `main()` behind a `__main__` guard, and the module-level file reads became `load_license()` / `load_mined()` / `load_docs()`, so importing does no I/O and cannot fail on inputs the archived copy deliberately does not carry. Verified both directions: importing leaves the catalog untouched, and running the two scripts end to end still reproduces the committed catalog byte for byte. Also drops `json` and `subprocess` from check-build-hint-catalog.py. Both were left from an earlier version that shelled out to the miner instead of importing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Compared 12 screenshots: 12 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d727c7d976
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 149 screenshots: 149 matched. Benchmark ResultsDetailed Performance Metrics
|
Cloudflare Preview
|
|
Compared 149 screenshots: 149 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 149 screenshots: 149 matched. |
|
Compared 149 screenshots: 149 matched. |
|
Compared 149 screenshots: 149 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 181 screenshots: 181 matched. |
Three separate gates rejected generated output. Each is fixed in the generator so the class of problem cannot come back through a catalog edit. Unmappable characters. The prose is imported from the developer guide, which uses typographic punctuation, and `CodenameOne/src` is also compiled by an Ant javac step with ASCII encoding where a single em dash is `error: unmappable character for encoding ASCII` -- a build failure, not a warning. A Unicode escape would not have helped: javac expands `\uXXXX` before it strips comments, so the character reappears. `toAscii` now folds the punctuation that actually occurs, and *refuses* anything it has no mapping for rather than dropping it, because silently deleting a character from a hint's documentation is the worse outcome. Broken table. `ios.spm.packages` is documented as `identity|url|requirement`, and a bare `|` starts a new AsciiDoc cell, so asciidoctor reported "dropping cells from incomplete row" for the whole 529-row table. Cells are escaped now. Vale. The guide enforces the Microsoft style as errors, and the generated table feeds it, so the catalog's prose has to satisfy it too: contractions, no "and so on", no stray adverbs. A default value is not prose, though -- the one remaining hit was `android.file_paths`, whose default is an XML fragment -- so a quoted default now carries the `// vale-skip:` comment .vale.ini documents for individual false positives. Also fixes a data bug the guide exposed. The miner preserved Java escape sequences instead of decoding them, so `android.file_paths` and `android.facebook_permissions` recorded defaults containing literal backslashes that the build never sees, and those reached the rendered table. The miner decodes escapes and re-quotes safely, and the two catalog entries are corrected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96bff9038a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`killedThreadReportsItselfFinished` failed the Java 21 leg with "FormTest timed out after 5000ms; edt=initialized pendingSerialCalls=0". The waits in this class used a 5000ms deadline, which is exactly the `@FormTest` timeout in EDTTestInterceptor -- so on a loaded runner the poll loop consumed the entire harness budget and the interceptor fired first. The report then said only that the method timed out, with nothing about which condition never became true. The waits now use 2000ms, well inside the harness budget and still roughly two thousand times the ~1ms these threads actually take to stop. A genuine regression now fails on the test's own assertion, which names what went wrong. Pre-existing (the test arrived with #5526) and unrelated to the build hint work: core-unittests has no dependency on the JavaSE port, so none of the simulator registration in this branch runs there, this branch changes nothing under com.codename1.db or EasyThread, and the Java 8 leg passed the same commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a343fe3335
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A mojo's defaultPhase does not bind it to a project -- the project's POM has to -- and nothing turns a build hint annotation back into a codename1.arg.* pair except the process-annotations goal. So migrating a project without that binding deleted working properties and replaced them with annotations no goal ever reads: the hints vanished from the build with no diagnostic anywhere. Five projects in this branch were already in that state. gamebuilder, docs/demos, video-builder and cn1playground bind the plugin but not that goal, so the binding is added. input-validation-app's common module has no build section at all, so its migration is reverted rather than inventing a lifecycle for a demo app. The goal now checks the reactor for the binding and refuses with the execution block to paste, so this cannot happen to anyone else. Three more from the same review: - The deletion pass recognized only `key=value`. `Properties.load` also accepts `key:value`, `key value`, escaped separators inside the key, and logical continuation lines; a declaration it failed to match was left behind while the annotation was added, so the next build failed with the duplicate-hint error this goal exists to prevent. Keys are parsed the way Properties.load defines them now, with a unit test per form. - The settings file was read as ISO-8859-1 and written back as UTF-8, turning any unrelated non-ASCII byte -- an accented displayName, say -- into mojibake. It is written back as ISO-8859-1. - cn1.androidTheme and cn1.nativeTheme are deprecated aliases of and.themeMode and nativeTheme, which the builders honour as fallbacks. Neither declared aliasOf, so conflict detection missed them and one value silently won. Also: the generation script rebuilt the generator only when its class was absent, so editing a catalog source and rerunning regenerated every view from the previous build's bytecode -- reporting success while ignoring the edit, and passing --check on a tree that was genuinely stale. It always rebuilds now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0edef42ca4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…plicating a hint docs/demos is the developer guide's snippet project: deliberately incomplete code fragments that illustrate @entity, @route, @AppIntent and @mapped. Binding process-annotations there put those snippets in front of the other processors, which correctly rejected six of them, so the migration is reverted and its two hints are back in the properties file. That the project omitted the goal was the point, not an oversight. The other three newly bound projects were checked rather than assumed: gamebuilder, video-builder and cn1playground each run process-annotations cleanly and emit 6, 3 and 5 hints respectively. Settings could still create the duplicate the migration is careful to avoid. In a generated project ios.themeMode and its neighbours are annotations, but the Build Hints UI decides a hint is inactive from the properties file alone and its Add button writes a property -- producing a second declaration that fails the next build. The tool now reads META-INF/codenameone/build-hints.properties, the file the processor writes on every build and deletes when the last annotation goes, and renders those hints read-only with the attribute that owns them: "Set by @iOS(themeMode) on the main class." An unbuilt project has no such file and behaves as before. Also fixes the SpotBugs finding this branch introduced: `backslashes % 2 == 1` in the continuation scan is false for negative odd numbers, so it is `!= 0`. The count cannot go negative, but the idiom is wrong regardless of that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Every Initializr source archive now imports com.codename1.annotations.buildhints and uses the new annotations, while GeneratorModel.CN1_PLUGIN_VERSION still rewrites generated projects to 7.0.267, whose codenameone-core predates that package; the generated common POM also omits process-annotations. Consequently all newly downloaded Initializr projects fail compilation instead of receiving the defaults removed from common.zip's settings file. Leave these templates property-backed until Initializr targets the release containing this feature, or bump the generated version and bind the processor.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
Every project the archetype and the initializr produce is pinned to a released Codename One version -- the initializr hardcodes 7.0.267 in GeneratorModel.CN1_PLUGIN_VERSION -- and no released core carries com.codename1.annotations.buildhints. So a generated project would import annotations that do not resolve and fail to compile before the user has written a line, and the settings those templates stopped declaring would simply be gone. The templates are reverted to exactly their previous state: the archetype's __mainName__.java and codenameone_settings.properties, and the initializr's common.zip and four source archives. They can move to annotations in a follow-up once a release containing the package is out. The generated build hint table is dropped from the agent skill reference for the same reason -- it documented a form those projects cannot use yet -- so the generator no longer rewrites markdown at all. What stays from that area is unrelated to annotations: the skill reference described build hints that no builder reads, so a reader copying them got a green build and no effect. android.xPermissions is spelled android.xpermissions, android.minSdkVersion is android.min_sdk_version, and android.sdkVersion, android.googlePlayVersion, build.compile, build.timeout, javascript.html5, javascript.bundleResources and ios.orientation do not exist at all. Those corrections are right for the published version too, and the catalog gate now holds our own documentation to them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three from the same review, each a follow-up to the previous round's fix. Trying the conventional target/classes before the classpath looked harmless and was not. A project that moves to a configured output directory without running clean leaves the old target/classes in place, manifest and class together, so the staleness check compares two obsolete files against each other, finds them consistent, and publishes last week's hints while the real ones sit on the classpath. The classpath is searched first now -- it is the output the build is actually using -- with the conventional path kept only for a launch that never passed the module's output directory at all. The computed-site file was keyed by path, so a builder already listed absorbed a SECOND computed hint for free: adding `platform + ".maps.apiKey"` beside the provider expression kept the gate green with the new hint uncatalogued. Keyed by file AND expression now, whitespace-normalised so a reformat is not a change, and split from both ends so an expression containing a pipe still parses. The line number is deliberately not part of the key, so moving code does not churn the file. Verified by adding exactly that second expression: the gate names it. Alias discovery still used a raw indexOf, so a commented-out `// import ...Ios as Old` above the live `import ...Ios as BuildIos` won, and the live @BuildIos was never looked for -- reinstating the bug the alias support was added for. It uses the same comment-aware walk as the marker search, and the occurrence must be the target of an `import` on that line, so a mention of the package in code is not read as one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fbc47f587
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review. and.captureRecord is not an abbreviation of android.captureRecord: the builder reads the long name and then lets the short one override it, so the two name one setting. Uncatalogued as an alias, @android(captureRecord) and a properties line spelling it the short way were both accepted -- and the properties line wins in the builder, so the compile-checked annotation was silently ineffective, which is exactly the failure this feature exists to remove. Marked as an alias, with a processor test asserting the pair now conflicts. and.facebook_permissions has the same override relationship and is marked too: nothing can conflict with it today since the long name is not annotated, but recording it means annotating that name later cannot reintroduce this. Settings looked for an annotation's argument list with indexOf('('), and parentheses are optional -- a bare @iOS is legal Java and Kotlin. It therefore adopted whatever call came next, so `@Ios` above a `configure(teamId = "...")` read as owning ios.teamId and the tool withheld Add and the editor for a hint the processor never emits. The next LIVE character after the name must now be the paren, comments skipped, so an annotation's own list is still found across one and a bare annotation adopts nothing. The simulator never checked the manifest's main-class stamp. Change codename1.mainName without a clean build and the old class and its manifest stay together in the output directory, perfectly consistent with each other, so the timestamp check passes them and the previous application's hints get published. The native merge has refused this since it was written; the simulator does now too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9162f41247
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two from the same review. Publishing an annotation hint the properties file also declares buries the error rather than reporting it: buildHint() reads the system property before the settings file, so the line the developer just added is silently ignored in the simulator while the device build refuses to run at all. Reachable because editing the properties file does not touch the class, so the timestamp check still finds the manifest current. The pair is detected before publishing now, and the annotation value is withheld with a message naming the hint. Aliases have to collapse for that check, and the catalog that knows about them is a build-time artifact this port cannot reach. Rather than give the JavaSE port a dependency on it, the processor writes the other spellings of each hint into the manifest -- cn1.buildHints.alias.<name> -- alongside the origin it already records, and only where a hint has more than one. The conflict check and the manifest now derive that set from one method instead of two that can disagree. This is the third time the simulator has lagged a check the native merge already performed. Recorded in the reply as such: if a fourth appears the two paths want a shared decision rather than parallel implementations. skipNonCode had no triple-quote branch, so a Kotlin raw string or Java text block read as an empty literal followed by a new one -- and an embedded quote then opened a literal that swallowed the annotation after it, leaving the hint unowned and Add free to write the duplicate. Both directions are tested: a raw string containing a quote no longer hides the annotation after it, and an annotation written inside one is still not ownership. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 392e2a5f9a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A reload re-enters main() in the same JVM rather than starting a process, so everything published on the previous launch is still set. The rule that protects a -D value -- an existing value always wins -- then protected the PREVIOUS build's annotation value too: editing @desktop(titleBar = ...) and reloading kept showing the old setting, and deleting the annotation altogether kept it forever, since the missing manifest takes an early return that touched nothing. What this method installed is now withdrawn at the top, before any early return, so a removed annotation and an unreadable manifest clear the value as surely as a changed one replaces it. Only what it installed. A -D was never a candidate, because a key that is already set is skipped rather than published, so it can never enter the withdraw set -- the command line keeps winning without a special case for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01e471ff4c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two from the same review.
The triple-quote branch closed at the next """, which is wrong in BOTH languages
and in opposite ways -- and getting it wrong over-consumes past a live
annotation, so the hint reads as unowned and Settings writes the duplicate.
Java escapes DO apply, so \""" is an escaped quote and two more, not a
delimiter. Reading it as one made the REAL delimiter open a second
text block that ran past whatever followed.
Kotlin escapes do NOT apply, and a run of four or more quotes closes at its
LAST three: """a"""" holds a" .
So the scanner needs to know which language it is reading, and it can: the
caller already picks the file by extension. The flag is threaded from there
down to skipNonCode, with the two-argument entry point keeping Java rules for
callers that have no file to name.
Separately, the manifest was merged over the source result as a union. It cannot
ADD ownership the source does not show: an attribute deleted from the main class
and not yet rebuilt is precisely that, and the union kept Add and the editor
hidden for a hint nothing owns any more, until the user happened to rebuild with
nothing to suggest that was the fix. The source now decides WHICH hints are
owned and the manifest only supplies their origins -- except when no source file
could be read at all, which the scan now reports as null rather than as an empty
result, because there the manifest is all there is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd00b3a4c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review. Ten real hints had no catalog row while the gate reported success. IPhoneBuilder's WALLET_INJECTION_HINTS holds them in a String[][] and reaches getArg as hintAndMarker[0], so no literal anywhere in the tree sits at a getArg call -- invisible to every literal search, this gate included, which is exactly the hole the computed-site accounting was added to close. A subscript now counts as a computed name alongside a built one; that adds precisely one site, and its ten expansions are catalogued and accounted for. An output directory keeps class files whose source is gone. Rename the main class, update codename1.mainName, skip the clean, and the old annotated .class still sits there -- so every incremental build failed with a placement error naming a class the developer had already deleted, and the orphan's hints were merged in besides. A class that is not the main one and has no source under the project is now ignored. Two limits on that, and the second only because the tests caught it: the main class is never dropped, because failing to find ITS source would silently apply none of a project's hints, which is worse than any placement message; and a project with no source tree at all is left alone entirely. Absence of a source tree is not evidence a class is orphaned, it means this is a layout the lookup does not know. The migration read byName(), which returns the ALIAS entry for a legacy spelling -- and an alias's own isAnnotated() is false even though the setting it names has an annotation. So cn1.androidTheme, cn1.nativeTheme and and.captureRecord, the spellings an existing project is most likely to be carrying, were reported as having no annotation and left behind. Resolved through the alias now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b465921269
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
3,345 generated lines leave git. The table is rendered from maven/build-hint-catalog every time the developer guide is built, so it cannot drift from the catalog and a hand edit has nothing to survive in -- which is what a generated file living in the repository always eventually invites, and what the drift gate and the pr.yml path re-inclusion existed to compensate for. Both of those go with it. Two renderers, so the step is a script rather than a workflow line: developer-guide-docs.yml runs it before the Asciidoctor lint, which is ahead of everything else that reads the guide -- the HTML and PDF build, Vale -- and scripts/website/build.sh runs it before its own asciidoctor call. The generator grows a --table-only mode for them. A documentation build has no business rewriting CodenameOne/src and Ports/JavaSE on its way past, and the mode's output is byte-identical to what the full run wrote, so this is not a second implementation of the table. Forgetting the step cannot be quiet: asciidoctor reports "include file not found" and the lint fails on it. Verified in both directions -- the render fails without the table and succeeds after generating it. scripts/gen-build-hint-annotations.sh still writes the file for a local asciidoctor run, and it is gitignored, so that copy is never reviewed and never committed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1fd2035a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Four from the same review, two of them consequences of the alias fix I made last round. Verification searched the manifest for the key the FILE used. A legacy spelling is deleted under its own name and comes back under the canonical one, so migrating cn1.nativeTheme, cn1.androidTheme or and.captureRecord reported the hint missing and rolled a correct migration back. The two lists are separate now: migratedKeys is what gets deleted, verifiedKeys is what must come back. Declaring one setting under two spellings with different values was resolved by whichever Properties.stringPropertyNames happened to enumerate last -- and then both lines were deleted, so a migration could change what the app builds with and still report success, since the check asks whether the hint came back and not what it holds. There is no rule to apply: and.captureRecord is read after android.captureRecord and overrides it, while the theme aliases are each handed to Display.setProperty and resolved in the framework. So the ambiguous case is refused, naming both spellings, and the developer decides. Equal values are not ambiguous and migrate. Settings accepted a bare @build or @android whatever it meant. Those names are ordinary enough that another library's annotation with a matching attribute read as ownership, and the editor was withheld for a hint the processor never emits. The simple name now counts only when an import brings it in from com.codename1.annotations.buildhints -- by name or on demand -- while the fully qualified spelling still needs none. Nine existing tests failed on this, all because their snippets used a bare annotation with no import, which no real source does; they carry the import now and are better tests for it. Two identical computed expressions in one file collapsed to one key, so a second getArg(hintAndMarker[0], ...) over a different table would have been validated against the first table's expansions and its own hints never checked. The accounting line carries a call count -- `#2` -- and a mismatch is reported with the number to record, which forces the second call to be looked at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffb67152e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Generating the build hint table instead of committing it took away a trigger nobody had to think about: while the table was checked in, editing the catalog produced a diff under docs/ and the documentation workflow ran on it for free. Now a change to maven/build-hint-catalog or to the renderer changes what the guide contains without touching a file under docs/ at all, so the Asciidoctor lint, Vale, and the HTML and PDF build never saw it -- and a malformed table would have merged and surfaced in the release documentation build. Both paths are added to the workflow trigger AND to the paths-filter the steps are gated on. Triggering alone is not enough: the HTML and PDF build and the steps beside it are conditional on that filter, so they would have started and skipped. The render step also moves ahead of everything that reads the guide rather than sitting just before the lint -- the image and snippet checks read it too. It is unconditional, because every one of those steps is not, and asciidoctor reports a missing include as an error, so a future step inserted above it fails loudly rather than rendering a guide with the table quietly absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e8e7e23b2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review. A closed domain rejected spellings the runtime honours. IOSImplementation.installNativeTheme compares against flat, liquid and iphone alongside the catalogued values, AndroidImplementation against material and holo, so Settings told a developer that a working configuration was invalid and then refused to save the edit -- and the migration refused those values as outside the domain, which is exactly what a project old enough to be carrying a legacy spelling would have hit. They are recorded as valueAliases, deliberately NOT as domain values: an alias must not become an enum constant, because two constants for one behaviour is an API that asks a question with no right answer. Validation and migration both go through canonicalValue, so `flat` saves and migrates to IosThemeMode.IOS7, while the picklist and the annotation still offer one spelling per concept. `import ...Ios as BuildIos` puts BuildIos in scope and NOT Ios, so counting it as a simple-name import attributed another library's @iOS to us -- the same misattribution the import check had just been added to prevent. An import with an `as` clause now contributes only its alias marker. The orphan check hard-coded src/main/java. A module may add generated-sources or replace the conventional root, and Kotlin does not require a file to be named after the class it declares, so a live class could be read as orphaned -- and then silently dropped, taking its hints with it and suppressing the placement error that would have explained it. It now asks Maven for the compile source roots, which is where generated and Kotlin roots already are, and looks for the file name the COMPILER recorded in the SourceFile attribute rather than one derived from the class name. That is the one reliable link back from a class to its source, and it is what makes the Kotlin case answerable at all. ClassScanner records it; AnnotatedClass exposes it. Unknown roots, or a class compiled without debug information, still answer "has a source", because the only thing this decides is whether to IGNORE a class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 186a75cfbb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review. Three more real hints had no catalog row. MacNativeBuilder reads them through parseEntitlementBool(request, hint, def), so every caller passes a literal, none of the calls is an accessor, and a literal search of accessor calls walks past all of them. The miner now recognises a helper that forwards one of its own parameters to getArg/arg/booleanArg and mines that helper's CALLERS instead -- which found exactly the three named, by looking rather than by being told. Same-file only, deliberately: a private helper's name is not unique across the tree, and mining calls to a same-named method of an unrelated class would invent hints rather than find them. The orphan check matched a SourceFile name anywhere under any source root, so moving a class to another package without a clean left an orphan that the NEW file answered for -- App.java satisfying the lookup for the old package's App.class -- and the stale class stayed, failing the placement check on every incremental build. The package is part of the match now, read from the file rather than inferred from its directory, since Kotlin does not require the two to agree. Settings had the same conventional-roots assumption on the other side, and falling through to null there let the caller trust a stale manifest again -- the bug the source scan exists to prevent, reappearing for anyone whose layout is merely unusual. It searches the project for a file that DECLARES the class now: package statement plus a class or object declaration, so a configured root or a Kotlin file named after something else is found anyway. Bounded in depth, in queue length and in how many files it will open, and target/ and build/ are skipped so a compiled copy of the same source cannot answer for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a83dbff176
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two from the same review. IPhoneBuilder builds a hint name into a local -- `"ios." + privacyKey` -- and passes the local to getArg, so the accessor's argument is a bare variable and read as forwarding while the name is assembled two lines up. That is the fourth route by which a literal reaches an accessor without sitting at one. The miner recognises a local assembled from a literal now and reports it, which surfaced this site and no other. The finding underneath it is sharper than "uncatalogued", and worth stating exactly: ios.NSBluetoothAlwaysUsageDescription, its Peripheral twin and ios.NSSpeechRecognitionUsageDescription were matched by the dynamic family ios.NS*UsageDescription, so the gate was right that they were "described" and they still had no annotation, no documentation row and no editor entry. That family exists so an app can set an ARBITRARY Apple key. A key the platform feature catalog injects is a known one, and known keys get described individually -- they are now, as @IosPrivacy attributes like the other fourteen. So the new cross-check requires a CONCRETE row for every injected plist key and deliberately does not accept the dynamic pattern. My first version did accept it and was therefore inert; the negative test passing is what showed that, after I went looking for why an injected unknown key did not fail the gate. Second finding: matching a source file by name and package alone kept a stale class when a Kotlin type is renamed in place without renaming its file, or when one type is deleted from a file holding several -- the survivor answered for it, the orphan stayed, and the placement check failed every incremental build. The file must declare the type now: class, interface, enum, object or record. Unreadable still answers yes, as everywhere else in this guard, because what it decides is whether to IGNORE an annotated class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c6d94999b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review, all fallout from the orphan filter. A nested type's binary name is Main$Wrong and no source declares a type spelled that way, so the search found nothing, the class was read as an orphan and dropped, and the placement check that would have said "annotations belong on the main class" never ran. The build then succeeded with the requested hints silently absent -- the exact failure this feature exists to remove, reintroduced by the guard meant to protect it. The lookup uses the outermost simple name now, which is the type a source file actually declares. There is a test putting an annotation on a nested class and asserting the placement error. CN1BuildMojo's guard rescanned every class file without that filter, so a stale annotated .class left by a rename without a clean failed every build with "no manifest was produced", naming a class already deleted. Rather than duplicate the orphan test there, the guard now asks about the MAIN class alone: the processor honours no other class, so no other class is evidence that annotations went unprocessed. That is both narrower and immune to orphans by construction. It still scans everything when the project names no main class, since then there is nothing more specific to ask. The Settings search decided as it walked, so its budget could be spent on unrelated files before reaching the one Kotlin source whose name differs from its class -- the only layout that search exists for, and so exactly the case it dropped. Files are collected first and examined in two passes: every file named after the class, then Kotlin files that are not. Java is excluded from the second pass because a public Java type must be named after its file, so a differently named .java cannot declare an application's main class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d29969e4c6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three from the same review.
A scalar with surrounding whitespace is no longer migrated. It looks like it
means what it says and it does not: AndroidGradleBuilder compares
android.hideStatusBar with .equals("true"), so `=true ` is false today, while
other builders trim or ignore case. Trimming it into an annotation `true` would
change what the app builds with and still report success, since the verification
asks whether the hint came back and not what it holds. Which reading is right
differs per builder, so this refuses and says why rather than picking one. A
string keeps its whitespace and migrates as before -- there the space is the
value.
Reducing Main$Wrong to Main so its file could be found let the LIVE outer class
vouch for a nested type that had been deleted, so the orphan stayed and failed
the placement check on every incremental build: one silent failure swapped for a
loud permanent one. The nested name has to be declared too. An unnamed segment
-- Main$1, an anonymous class -- asks nothing, since no source declares one and
none can carry an annotation.
Both declaration checks looked for `class X` with indexOf, so a commented-out
`// class Wrong` left by the edit that deleted the type vouched for its own
orphan, and on the Settings side an unrelated file mentioning the main class in
a comment or a string answered for it -- ownership then reads as empty and
Settings offers Add for a hint the real main class annotates. The processor
blanks comments and string literals before looking; Settings reuses the
comment-aware walk it already had, for the package statement as well as the
declaration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3235077b25
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two from the same review, and the last two places still asking the wrong question. Settings returned from the conventional path on the file merely EXISTING. Move a Kotlin main class into a differently named file and leave the old Main.kt holding something else, and it returned that unrelated file and never reached the declaration search added for exactly this -- reporting the annotated hints as unowned, which is the state that lets Add write the duplicate. The conventional file now has to declare the class before it is accepted. cn1:migrate-build-hints had the same hard-coded three roots and filename, and aborted with "Could not find the source" on a project Maven compiles perfectly well: a module may add src/app/java, and Kotlin does not require a file to be named after its class. It asks the owning MavenProject for its compile source roots and looks for the file that DECLARES the class. The declaration test is the annotation processor's, made public and called, rather than a third copy. Two copies have already drifted apart in this change and a third would drift again; what counts as a declaration should have one answer, and it now has tests of its own -- comment, block comment, string, a commented-out declaration followed by the real one, a longer name that must not match, and that blanking preserves offsets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80e6c2a9b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case BOOLEAN: | ||
| if ("true".equalsIgnoreCase(v.trim())) return "true"; | ||
| if ("false".equalsIgnoreCase(v.trim())) return "false"; |
There was a problem hiding this comment.
Preserve case-sensitive boolean semantics during migration
Fresh evidence after the whitespace fix is that mixed-case values are still normalized: when a project has codename1.arg.android.hideStatusBar=TRUE, AndroidGradleBuilder's exact .equals("true") comparison currently treats it as false, but this case-insensitive branch migrates it to @Android(hideStatusBar = true) and deletes the property. Because verification checks only that the key was emitted, the migration succeeds while changing build behavior; reject noncanonical casing unless the specific consumer is known to accept it.
Useful? React with 👍 / 👎.
| while (i < text.length() | ||
| && (text.charAt(i) == ' ' || text.charAt(i) == '\t')) { | ||
| i++; |
There was a problem hiding this comment.
Recognize legal whitespace before the main class name
When a valid Java or Kotlin declaration puts a newline or comment between the keyword and name, such as class\nMain, this loop skips only spaces and tabs, so both the conventional lookup and fallback search fail to recognize the configured main class. If an annotation was added since the last build, annotationOwnedHintsFromSource() then falls back to a missing or stale manifest, and Settings can offer Add and write a duplicate property that the next build rejects; consume all legal whitespace and skipped comments before reading the identifier.
Useful? React with 👍 / 👎.
| // The nested type has to be there too. Reducing Main$Wrong to Main so the | ||
| // file can be found let the LIVE outer class vouch for a nested type that | ||
| // had been deleted, and the orphan then failed the placement check on | ||
| // every incremental build -- swapping one silent failure for a loud | ||
| // permanent one. | ||
| return nested == null || declaresType(text, nested); |
There was a problem hiding this comment.
Match nested owners by their full declaration path
Fresh evidence after the nested-orphan fix is that this verifies only the innermost simple name, not its enclosing declaration chain. If stale Main$A$Wrong.class remains after A.Wrong is deleted while the same source still declares an unrelated Main.B.Wrong, declaresType(text, "Wrong") returns true, the orphan is retained, and every incremental process-annotations run fails placement until a clean build; match the full Main.A.Wrong nesting identity.
Useful? React with 👍 / 👎.
The problem
A build hint is a
codename1.arg.<name>=<value>line that reaches a builder asrequest.getArg(name, default). Nothing checked the name, so a misspelling was accepted, copied into the build request, never read, and silently discarded — a green build with the setting simply not applied.Our own agent reference had been shipping keys in exactly that state:
skill/references/build-hints.mdandroid.xPermissionsandroid.xpermissions(AndroidGradleBuilder.java:1206)android.minSdkVersionandroid.min_sdk_versionandroid.sdkVersionandroid.targetSDKVersionis the real one)The change
Most hints can now be written as annotations on the application's main class, where javac does the checking: a misspelled name is an unknown symbol, a wrong value type is a type error, and a value outside a hint's supported set is an unknown enum constant.
The builders are untouched.
BuildHintAnnotationProcessorconverts the annotations back into the same key/value pairs, andCN1BuildMojomerges them before the command-line overlay, the CN1Lib merges and both preflights — so a library still appends onto an annotation-supplied value and-Dstill wins.Simulatorpublishes them as system properties at startup socn1:runsees hints that no longer live in the properties file.The properties file is untouched too. It stays the way to set the long tail and the open-ended families such as
android.permission.<NAME>that an annotation cannot express, with no new warnings or errors on that path. Declaring one hint both ways is a build error.Scope: generated projects are deliberately not migrated here
Every project the archetype and the initializr produce is pinned to a released Codename One version — the initializr hardcodes
7.0.267inGeneratorModel.CN1_PLUGIN_VERSION— and no released core carriescom.codename1.annotations.buildhints. A generated project would import annotations that do not resolve and fail to compile before the user has written a line.So the archetype's
__mainName__.javaandcodenameone_settings.properties, and the initializr'scommon.zipand four source archives, are unchanged. They move to annotations in a follow-up once a release containing the package is out.scripts/skindesigner(7.0.255) stays on properties for the same reason, andcn1:migrate-build-hintsrefuses any project whose core lacks the package.The in-repo tool projects that build against 8.0-SNAPSHOT from source are migrated:
gamebuilder,video-builder,cn1playground,certificatewizard,guibuilder,fidelity-app,purchase-test-app,settings,hellocodenameone,protocol-e2e.docs/demosis deliberately excluded: it is the developer guide's snippet project, full of intentionally incomplete fragments, and running the annotation processors over it fails by design.One catalog, generated views
The hint set was described in five places that had drifted apart: a prose table in the developer guide, a runtime scraper of that table in the Settings tool that guessed each type by string-matching the description, a fifteen-entry schema in the simulator, a fourteen-entry separator map in the plugin, and a hand-written agent reference. Only 147 of ~520 names appeared in more than one.
maven/build-hint-catalogis now the single source of truth — 529 hints: 457 mined from the builders, 56 documented-but-unread (external), 16 dynamic families, 82 exposed as annotation attributes across 7 annotations and 10 enums. The annotations, the binding table the processor reads back, the developer guide's table and the simulator's editor schema are all generated from it.The guide's table goes from 208 rows to 529 with no prose lost, and gains Type, Default and Annotation columns it never had.
Enums are emitted only where the accepted set is demonstrable from the code that reads the hint —
HardeningPreflightrejects an unknownharden.level,IOSDependencyManagerthrows on an unknownios.dependencyManager, andGenerateDesktopAppWrapperMojosilently falls back tonativeon an unknowndesktop.titleBar, which is precisely the failure this removes.Settings tool
It no longer scrapes the guide's AsciiDoc and guesses types; it reads the catalog. It also validates closed value domains, and refuses to edit a hint an annotation already owns — reading
META-INF/codenameone/build-hints.propertiesand showing "Set by@Ios(themeMode)on the main class" — because writing a property for such a hint would create the duplicate declaration that fails the next build.Gates
scripts/check-build-hint-catalog.shfails when code reads a hint the catalog does not describe, and when our own docs or project templates name one that no builder reads. Its baseline is empty, so it is a hard gate rather than a ratchet.scripts/gen-build-hint-annotations.sh --checkfails on generated-file drift.Object/Annotationmethod-name rule, enum domains, separators matching whatLibraryHintMergerused to define).Both scripts run in the Java 8 leg of PR CI.
Verification
maven/integration-tests/build-hint-annotations-test.shgenerates a project from the archetype, adds the annotations by hand (the template no longer ships them), and checks the emitted wire values — a list joined with the hint's own separator, an enum written as the catalog's value rather than the constant name, and an attribute nobody set written not at all — that they reach the real build request, and that declaring one hint twice fails with a message naming both the hint and the annotation attribute.Also confirmed empirically, since the whole "unset means absent" design rests on it: javac records a member the developer wrote even when its value equals the annotation's default, and omits one left unwritten.
Notes for review
com.codename1.annotations.buildhints, not...annotations.build:.gitignorecarries a repo-wide**/build/*rule that silently swallowed every source under thecom.codename1.build.sharedpackage path. The rule now un-ignoresbuildwhen it is a Java package rather than a build output directory.android.permission.,harden., …) and are modelled as dynamic families rather than settable keys.Pre-existing problems surfaced but not fixed here
cn1:testbuild hints are broken today.RunTestsMojo.java:253sets the forked runner's CWD totarget/cn1-reports, where neither ofloadCodenameOneSettings()'s fallbacks looks, sobuildHint()returns null there for properties-file hints too.<skipTests>true</skipTests>), so one had rotted against aGridLayout(3, 2)that became(5, 2)in d15cfa5. The skip is now a property (default unchanged) and the assertion tracks the column count.GeneratedAccess_*files are stale against the current core; building it regenerates ~3,300 lines. Left alone to keep this change scoped.Follow-up
Once a release carrying
com.codename1.annotations.buildhintsis published:__mainName__.javaand settings template to annotations.common.zipand the four source archives, bumpGeneratorModel.CN1_PLUGIN_VERSION, and bindprocess-annotationsin the generated common POM.scripts/initializrandscripts/skindesignerthemselves.🤖 Generated with Claude Code