Uh oh!
There was an error while loading. Please reload this page.
Check build hints at compile time instead of shipping them inert - #5586
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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
✅ 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>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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".
Uh oh!
There was an error while loading. Please reload this page.
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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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 163 screenshots: 163 matched. Benchmark ResultsDetailed Performance Metrics
|
Cloudflare Preview
|
Compared 163 screenshots: 163 matched. Benchmark ResultsDetailed Performance Metrics
|
Compared 163 screenshots: 163 matched. |
Compared 163 screenshots: 163 matched. |
Compared 163 screenshots: 163 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>
Uh oh!
There was an error while loading. Please reload this page.
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".
Uh oh!
There was an error while loading. Please reload this page.
`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".
Uh oh!
There was an error while loading. Please reload this page.
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".
Uh oh!
There was an error while loading. Please reload this page.
…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>
compilesJava() already withheld the conventional src/main/java from the search when the POM chain switches default-compile off with <phase>none</phase> and binds nothing in its place. <sourceDirectory> is the same root, declared instead of assumed, and it was not gated -- so a module that compiles nothing with javac still offered it, and a stale copy of the main class there answered ahead of the compiled source. Per element rather than over the whole list, because a Kotlin-only module is exactly the case where javac does not run and its <sourceDirs> must still be searched. Both directions are asserted. 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:705783d9b5
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
…writes The .java branch in write() guarded against a non-ASCII character reaching a generated Java source; the generator writes a JSON data file and an asciidoc table and nothing else. toAscii survives because the reader test still needs it -- the catalog's prose came from the developer guide and the annotations are ASCII Java sources, so the two cannot be compared verbatim -- and its documentation now says that rather than describing a build step it no longer feeds. Also the HintGroup local in Bindings that nothing read, and the no-op continue at the end of the loop that was its only use. toHint already resolves the group and throws when an annotation type names none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Central recovery poll queried the two build hint artifacts with a single curl carrying two URLs. curl needs an -o per URL and emits --write-out after each transfer, so the second POM body arrived on stdout with both status codes appended: the captured value could never equal 200, and the poll -- which exists to rescue a release Central has already accepted -- would exhaust all 90 attempts and fail it. Reported by codex. Both this poll and the R2 confirmation walk a list of artifacts, and each carried its own copy, which had already drifted: Central's omitted codenameone-core. The list is now named once in the job environment and both read it, with a guard that refuses an empty list rather than reporting success for having checked nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aliasOf, deprecated, external, enterpriseOnly and link exist so an annotated hint can say what the catalog entries already say, and none of the 87 shipped attributes needs any of them -- so the reader's handling of all five was carried by no test at all. Exercised now against a copy of the real annotation package with one probe attribute added, which is what the reader compiles anyway. Kept rather than deleted: the first annotated hint that has to be marked deprecated needs somewhere to say so, and would otherwise be the first thing ever to run that path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
validateScreenshotContent threw before the write, so a failing scenario left nothing behind but a pixel count -- which is exactly what happened to the component-inspector run that reported textPixels=738 with no image to look at. The capture is now written as <name>.png.rejected.png first, into the directory the workflow already uploads with if: always(), so the next occurrence can be diagnosed instead of guessed at. Not a fix for that failure: the check reads a fixed screen rectangle and the run before and after it were green on the same commit, so what it saw is still unknown. This is what makes finding out possible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e</phase> compilesJava() answered from the first level in the chain that switched default-compile off, so an ancestor that disables it decided for a child that binds its own compile execution. A module that plainly does compile Java read as one that does not, and its Java roots were then withheld from the search -- where a Kotlin or stale copy of the main class answers for it instead. An enabled compile binding anywhere in the chain now settles it, and it is looked for before any disabling level is allowed to decide. Extracted to a static form taking the chain directly, because the walk needed a project on disk and was the untested half of a method whose two halves are separately covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
attributeOf was written to replace a substring search for combine.self, and then looked for the attribute name anywhere inside the start tag -- so xcombine.self would have answered for it. The same rule, reproduced one level down in the fix for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…were masking Two failures on the JDK 8 leg, one the cause of the other. The module's POM binds cn1:css at process-classes and the CSS compiler opens a JFrame, which in this container is java.awt.HeadlessException before a single test runs. Run under xvfb-run, which this job already uses for the Ant build a few steps later. That failure then skipped every step after it, including "Run SpotBugs for ByteCodeTranslator" -- so the quality report failed for a missing ByteCodeTranslator report, which said nothing about what had actually broken. The step now runs last among this leg's gates, which also keeps its reinstall of the maven plugin away from the SpotBugs reports the earlier steps produce. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three groups, none of which any reviewer asked for and all of which I added off my own red-team pass: The Settings POM-parser rewrites -- depth-matched </plugin>, combine.self and combine.children read off their own element, comment stripping in parentPomPath, the compilesJava chain walk, and the <sourceDirectory> gate. These change how the Settings tool parses arbitrary user POMs, on the strength of cases nobody reported. The parser is back to what it was. The simulator verifier's rejected-capture diagnostics, which have nothing to do with build hints -- I added them while chasing a screenshot failure that predates this branch. The release workflow's shared RELEASE_ARTIFACTS list. Only the defect codex reported is kept: the two build hint POMs are queried with one curl each, because two URLs share the single -o and the captured value could never be 200. The plugin-side cleanups that rode along in the same commit as the parser work -- the unused ProcessorContext constructors and LibraryHintMerger's duplicate prefix -- are kept, as is the comment de-duplication. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A generated file in git conflicts on every merge and there is nothing for a hand edit to survive in, so it should never have been checked in. It cannot simply move to one module's target/classes: BuildHints.entries() loads it, so every consumer of the catalog needs it on its own classpath, and the generator lives in build-hint-tools which depends on the catalog -- the catalog cannot render its own resource without a dependency cycle. So each of the three modules that actually needs it renders it into its own target/classes at process-classes: maven/javase, maven/codenameone-maven-plugin and scripts/settings/common. exec:java with classpathScope=compile, because a plugin-level dependency resolves from the repository rather than the reactor and would be a chicken and egg on a clean checkout. build-hint-tools is provided scope in the two application modules so ASM cannot reach a built app. Verified: all three render byte-identically to the file that was committed, the catalog jar no longer carries it, and the javase jar does. Consequences elsewhere. gen-build-hint-annotations.sh renders to a scratch directory, and --check no longer diffs against a committed copy -- there is none -- but asserts the rendering still succeeds and produces the full set, which is the failure that would otherwise reach a user as an editor with no hints in it. check-build-hint-catalog.py read the committed path and returned an empty set when it was absent, which failed the gate OPEN; it now reads whichever module has rendered it and says to build one when none has. The Ant JavaSE build takes it from maven/javase/target/classes when that build has run, and without it the simulator keeps the hints BuildHintSchemaDefaults compiles in and says the annotated ones are missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removing the committed data file broke two catalog tests, and I did not see it because I verified that module with -DskipTests -- on the one module whose structure I had just changed. BuildHints.entries() is complete only on a classpath carrying the rendered data file. The catalog module cannot render one for its own tests: the generator lives in build-hint-tools, which depends on the catalog. So the two assertions that need the complete set -- every alias resolves to a real hint, and the catalog agrees with LibraryHintMerger on every separator it defines -- move to build-hint-tools, which renders the data into its own test classes first. Both fail without that render, which is checked. What stays beside the catalog is what its own sources declare, and the class now says so rather than looking like it covers everything. 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:9b07db8e93
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
…lows Making the table generated instead of committed took away the trigger it used to get for free: while it was checked in, editing it showed up as a docs diff. The replacement path list named the catalog and the render script but not the two inputs that matter most -- the annotations, whose javadoc is the Description column verbatim, and build-hint-tools, which is the renderer itself. A PR renaming an attribute or rewording its documentation could therefore change the guide without ever running the AsciiDoc and Vale checks over the result. Reported by codex. Fixed in both copies of that list in developer-guide-docs.yml, since triggering the workflow is not enough on its own -- the HTML and PDF build is gated on the paths-filter as well, and the two had already drifted. And in website-docs.yml, which is the other workflow that renders the same table and had the same gap for the same reason. Its pull_request and push lists both already covered CodenameOne/src/**, so only the catalog, the renderer and the script were missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two files overlapped and both auto-merged: website-docs.yml keeps master's website telemetry step alongside the build hint table paths added here, and CLAUDE.md keeps master's restructure -- 163 lines cut and the GC notes moved to vm/CLAUDE.md -- alongside this branch's build hints section. That section is rewritten rather than merged as-is: it still described writing cn1-build-hints.json into the tree and told the reader to run the generator to "rewrite the data file". Nothing is written into the tree any more, so it now says where the file is rendered instead, and the command list loses the step that no longer does anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
windows-tooling builds the Settings tool with -Dcodename1.platform=javase, which activates a profile declaring exec-maven-plugin with its own plugin level <arguments> containing a <classpath/> element. Maven merges plugin level configuration into every execution of that plugin, so that Object landed in the generator execution's String array: Cannot store value into array: ... can not cast one of the elements of java.lang.Object[] to the type of the destination array, java.lang.String A plain local build never activates that profile, which is why four green local runs said nothing. Reproduced with the property set, fixed with combine.self="override" on the arguments, and re-verified against the exact command the workflow runs. Applied to all four generator executions rather than only the one that failed: the collision needs a second declaration of the same plugin anywhere in the effective build, which is not a property of this module. Also restores the simulator verifier's rejected capture. It was reverted as out of scope, and then the component inspector scenario failed a second time with a byte identical textPixels=738 -- twice the same number is a state, not a race -- with no image kept to say what was on screen. Two failures in 22 runs on this branch and none in 38 on any other is too specific to guess 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:2d2708a0c6
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
BytecodeComplianceMojo rewrites class files in place -- capping a class to the supported version, and redirecting a call the runtime does not have. The build hint manifest records the main class's own bytes, because the simulator has no bytecode reader and can only compare the class file itself. If the main class is one of the rewritten ones, a manifest written before that goal describes a class that no longer exists on disk, and the simulator reads a manifest generated moments earlier as stale and publishes none of the annotated hints. Reported by codex. Every pom in this repository happens to run process-annotations after this goal, where the stamp is taken from the rewritten bytes anyway, so nothing is broken today -- I checked all twelve. Nothing enforces that order though, and the failure mode is silent: hints disappear, no error. Re-stamping here as well makes the order stop mattering, since whichever of the two runs last leaves a manifest describing the class that is actually there. It is a no-op when there is no manifest, which is every project that declares its hints in the properties file. The test asserts the hazard and the repair together: capping a version changes the class, which invalidates a stamp taken before it, and re-stamping restores the match without touching the hints. Removing the re-stamp fails it. What the test does not cover is the one-line call from executeImpl, which needs a MavenProject to reach. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wants Both predate this branch without one. check-copyright-headers is scoped to the PR's diff, so editing them is what brought them into scope -- the gate working as designed, not a new defect. They take the Codename One GPLv2 + Classpath Exception header their siblings in this package carry. I ran that gate locally before the last push and it reported success while checking nothing: with no --base it has no diff to scope to, and "0 file(s) passed" was the tell I read straight past. Run with --base origin/master it checks the same 128 files CI does, and passes. check-cast-semantics.sh takes --baseline, a ratchet file rather than a git ref, so it is whole-repo and running it bare is valid; copyright was the only gate here with that shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guide said the compiler-checked form existed and the generated table named the annotation for each hint, but nothing anywhere showed what one looks like. A reader had the Annotation column and no way to turn `@Ios(pods)` into code. Adds a worked example and the exact properties lines it replaces, then explains the three things about the syntax that are not guessable from the table: a list hint takes a Java array and the build joins it with that hint's own separator, a boolean hint takes Toggle rather than boolean so that leaving it out means the build decides, and a hint with a closed value set takes an enum. Also says plainly that the long tail and the open-ended families stay in the properties file and that the two forms mix freely. Every mapping in the example is checked against the generated data rather than written from memory: each attribute resolves to the hint name claimed, each enum constant to the wire value claimed, and ios.pods really does join with a comma. The Java snippet compiles against the annotations, the guide's snippet validator passes (inline blocks are refused there, so both snippets live in docs/demos), Vale is clean, and the chapter renders without warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LanguageTool fails the developer guide build on a single match, and I had hyphenated it in one sentence and not the other. Caught by running the gate locally on the rendered chapter rather than by a CI cycle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A new .java file, so the diff-scoped gate checks it. I ran that gate with a base earlier and it passed, then added this file and pushed without re-running it -- which is exactly what my own note about running diff-scoped gates AFTER the commit is for. 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:6fd1406078
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
A project that adds @build or @DesktopBuild to its main class but never binds cn1:process-annotations -- an upgraded or hand-written POM -- compiles and launches fine, emits no manifest, and the simulator returned quietly and applied none of the annotated hints. CN1BuildMojo refuses a device build for exactly this case, so the simulator was the one place where the hints vanished with nothing said and local behaviour diverged from device behaviour for the same project. Reported by codex. It now says so. A warning rather than a refusal: the simulator's job is to start, and what it is missing are build settings, not something it cannot run without. Detected by scanning the main class file's bytes for the annotations package, because the simulator has no bytecode reader and an annotation's type is in the constant pool as a descriptor. That is wider than reading the annotation table -- a main class that merely mentions the package matches too -- which is why this warns and does not refuse. Three cases are asserted: annotated with no manifest warns and names both the class and the goal, an unannotated main class is silent, and a main class absent from the classpath is silent. The first fails if the marker check is removed. 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:b0f5213ef4
ℹ️ 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".
| import com.codename1.annotations.buildhints.Toggle; | ||
| // tag::buildHintAnnotations[] | ||
| @Android(themeMode = ThemeMode.MODERN, minSdkVersion = AndroidMinSdk.API_24) |
There was a problem hiding this comment.
Keep the documentation snippet out of application bytecode
When docs/demos is built for a device through its reactor, this source is compiled into the common module even though codenameone_settings.properties names com.codenameone.developerguide.DemoCode as the main class. CN1BuildMojo.failOnMisplacedAnnotations() rejects live build-hint annotations on every non-main class, so BuildHintAnnotationSnippet makes that build fail before submission; binding process-annotations would instead fail during process-classes. Store the include outside the compiled source root or render it from a noncompiled snippet.
Useful? React with 👍 / 👎.
The developer guide's quality gate counts Vale alerts at suggestion level and fails on any of them. I checked the chapter locally with --minAlertLevel=error, which hides warnings, so two Microsoft.Adverbs warnings -- "deliberately" and "freely" -- went through and reddened the build. Both sentences say the same thing without the adverb. Re-checked at the level CI uses: Vale 0/0/0, LanguageTool 0 matches, snippet validator 706 blocks, paragraph capitalization clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Two conflicts. .gitignore had a new entry on each side and keeps both. Advanced-Topics-Under-The-Hood.asciidoc is the one that mattered: master's #5586 replaced the hand-written build hint table with an include generated from maven/build-hint-catalog, while this branch had added 28 macos.* rows to that table. Resolving to master's include alone would have compiled cleanly and silently deleted the documentation for every macOS build hint the port added -- the table is generated now, so a hint absent from the catalog has no documentation anywhere. So the 28 hints move into BuildHintsApple with the descriptions they had in the table, and the generated table carries all 28 again. Confirmed by running scripts/gen-build-hint-table.sh and counting them in the output, not by reading the diff. macNative.iosMinDeploymentTarget was already in the catalog and is NOT duplicated; the first pass nearly added it back because a name pattern stopped at the underscore in macos.add_libs and mis-parsed that row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
master's #5586 made annotations the source for build hints that have one, and the merge landed this port's 28 macos.* hints as hand-written catalog entries -- in HintGroup.MAC_NATIVE, whose key prefix is macNative. and which carries no annotation at all. They belonged in the feature, not beside it. HintGroup gains MAC_OS("Mac", "macos."), and @Mac declares all 28 as compile-checked attributes: Toggle for the nine booleans, an appendable String[] for addLibs following the ios.add_libs shape, and an explicit name for the fifteen whose tail has dots and cannot be derived from a method name. The hand-written entries are gone, because two sources for one hint is the drift this feature exists to remove. macNative.* stays hand written: it is the spelling the legacy Catalyst target reads and no annotation offers it. Verified by regenerating rather than by reading the diff -- 28 macos rows in the guide table, macos.entitlements.appSandbox typed boolean, and the catalog and tools tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
87 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.
Seven annotations —
@Ios,@Android,@DesktopBuild,@Build,@Hardening,@IosPrivacy,@OnDeviceDebug— and ten enums.No attribute has a default that means anything
Every default is a marker for "nothing was said":
Toggle.DEFAULT,"",{},0, or an enum's@HintUnsetconstant. An attribute left out is absent from the class file, so the processor emits nothing for it and the build decides, exactly as it does for a hint nobody wrote.This is the reason there are no
booleanattributes left.boolean appBundle() default falsereads as "off unless you turn it on" whileAndroidGradleBuilderdefaultsandroid.appBundletotrue— and a copy of the server's answer compiled into every app already built cannot follow the server when it changes.Toggleis the three-state replacement, and a test refuses any hint attribute that declares a value-bearing default.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.Where a hint is declared
Exactly one of two places:
CodenameOne/src/com/codename1/annotations/buildhintsif it has an annotation. These are hand-written and are the source of truth for the hints they expose;BuildHintAnnotationReaderreads them back with ASM rather than any file restating them.maven/build-hint-catalogotherwise — dynamic families, build-service-only hints, the long tail.The hint set used to be 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.
Nothing is generated into the tree
No generated file is committed.
BuildHintCodeGeneratorrenders two views, both during a build:cn1-build-hints.jsonfor the two editors that are Codename One apps and so have no bytecode reader — the Settings tool and the simulator's hint editor. Each module that needs it renders it into its owntarget/classes(maven/javase,maven/codenameone-maven-plugin,scripts/settings/common). The catalog cannot render its own, because the generator lives inbuild-hint-tools, which depends on the catalog.Anything that can read bytecode reads the annotations directly and never touches the data file.
The guide's table goes from 208 rows to 570 with no prose lost, and gains Type, Default and Annotation columns it never had. An annotated hint's Default reads "set by the build" rather than a value, which is the honest statement of the paragraph above.
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.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 the snapshot from source are migrated:
gamebuilder,video-builder,cn1playground,certificatewizard,guibuilder,fidelity-app,purchase-test-app,settings,hellocodenameone.protocol-e2eis not in that list: its only hint iscodename1.arg.java.version, which has to stay in the properties file because the compiler needs it in order to compile the class that would otherwise declare it.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.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.Its own tests had never run anywhere: both workflows that touched the module passed
-Dmaven.test.skip=true, so every test written for the POM reader and the hint editor was dead weight. They now run in PR CI, 161 of them.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. 497 hints read by the builders, all described.scripts/gen-build-hint-annotations.sh --checkasserts the render still succeeds and produces the full set. It is not a drift check — there is no committed copy to drift from — but a render that silently produced nothing would otherwise reach a user as an editor with no hints in it.Object/Annotationmethod-name rule, enum domains, separators matching whatLibraryHintMergerused to define), andbuild-hint-toolsasserts the invariants that need the complete hint set, since that only exists on a classpath carrying the rendered data.BytecodeComplianceMojore-stamps the manifest after its in-place class rewrites, so the order ofprocess-annotationsagainst it stops mattering.