From 5893991726359ba84315ccd18f421c7c7d709e87 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 26 May 2026 14:32:04 +0300 Subject: [PATCH 1/3] Add three build guardrails for framework hygiene Introduce checks that catch documentation/build hygiene issues that have slipped in repeatedly: - Error Prone BanClassForName: forbids Class.forName(...) anywhere under CodenameOne/src because ParparVM (iOS) cannot resolve classes by string name at runtime. Lives in a new maven/errorprone-checks module wired into core via an opt-in `errorprone` profile (Error Prone needs JDK 11+; the primary build runs on JDK 8). CI workflow runs `mvn -Perrorprone` on JDK 17. - scripts/check-since-tags.sh: fails the build when any @since javadoc tag references a version with no matching git tag. Wired up via a dedicated GitHub Actions workflow that fetches tags and runs the check. - Vale rule CodenameOneRules.NonexistentVersions: blocks prose like "Codename One 8", "Codename One 9", "Codename One 7.1" in the developer guide, while still allowing the live release line (Codename One 7 / 7.0.x) and historical releases. The rule needs `vocab: false` because the Codename entry in accept.txt would otherwise swallow every match. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/check-since-tags.yml | 41 +++++++ .github/workflows/errorprone.yml | 50 +++++++++ docs/developer-guide/.gitignore | 6 +- docs/developer-guide/.vale.ini | 2 +- .../CodenameOneRules/NonexistentVersions.yml | 23 ++++ maven/core/pom.xml | 75 +++++++++++++ maven/errorprone-checks/pom.xml | 89 +++++++++++++++ .../errorprone/BanClassForName.java | 55 ++++++++++ maven/pom.xml | 14 +++ scripts/check-since-tags.sh | 101 ++++++++++++++++++ 10 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/check-since-tags.yml create mode 100644 .github/workflows/errorprone.yml create mode 100644 docs/developer-guide/styles/CodenameOneRules/NonexistentVersions.yml create mode 100644 maven/errorprone-checks/pom.xml create mode 100644 maven/errorprone-checks/src/main/java/com/codenameone/errorprone/BanClassForName.java create mode 100755 scripts/check-since-tags.sh diff --git a/.github/workflows/check-since-tags.yml b/.github/workflows/check-since-tags.yml new file mode 100644 index 0000000000..8b9dd3bb3b --- /dev/null +++ b/.github/workflows/check-since-tags.yml @@ -0,0 +1,41 @@ +name: Check @since tags + +# Fails the build if any Java source under CodenameOne/src carries an @since +# javadoc tag whose version does not correspond to an existing git tag in +# this repository. Without this gate, prose can advertise APIs as available +# in versions that never shipped (e.g. "@since 9.0"). + +permissions: + contents: read + +on: + workflow_dispatch: + pull_request: + branches: + - master + paths: + - 'CodenameOne/src/**/*.java' + - 'scripts/check-since-tags.sh' + - '.github/workflows/check-since-tags.yml' + push: + branches: + - master + paths: + - 'CodenameOne/src/**/*.java' + - 'scripts/check-since-tags.sh' + - '.github/workflows/check-since-tags.yml' + +jobs: + check-since-tags: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Full history including all tags so `git tag --list` returns + # the complete set of released versions. + fetch-depth: 0 + fetch-tags: true + + - name: Validate @since references against git tags + shell: bash + run: scripts/check-since-tags.sh diff --git a/.github/workflows/errorprone.yml b/.github/workflows/errorprone.yml new file mode 100644 index 0000000000..1dc55bd6cd --- /dev/null +++ b/.github/workflows/errorprone.yml @@ -0,0 +1,50 @@ +name: Error Prone checks + +# Runs the framework's custom Error Prone BugCheckers (currently just +# BanClassForName) over CodenameOne/src. The check fails the build when +# someone adds a Class.forName(...) call, which silently breaks on +# ParparVM (iOS) because the iOS port cannot resolve classes by string +# name at runtime. + +permissions: + contents: read + +on: + workflow_dispatch: + pull_request: + branches: + - master + paths: + - 'CodenameOne/src/**/*.java' + - 'maven/errorprone-checks/**' + - 'maven/core/pom.xml' + - 'maven/pom.xml' + - '.github/workflows/errorprone.yml' + push: + branches: + - master + paths: + - 'CodenameOne/src/**/*.java' + - 'maven/errorprone-checks/**' + - 'maven/core/pom.xml' + - 'maven/pom.xml' + - '.github/workflows/errorprone.yml' + +jobs: + errorprone: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + # Error Prone requires JDK 11+; we pick 17 to match the + # framework's Android-port JAVA17_HOME requirement. + distribution: temurin + java-version: 17 + cache: maven + + - name: Run Error Prone over the core module + working-directory: maven + run: mvn -B -Perrorprone -pl core -am install -DskipTests diff --git a/docs/developer-guide/.gitignore b/docs/developer-guide/.gitignore index 43c9348f69..0bdb9709a1 100644 --- a/docs/developer-guide/.gitignore +++ b/docs/developer-guide/.gitignore @@ -1,10 +1,12 @@ book-cover.generated.svg book-cover.generated.png # Vale syncs upstream style packages here, but the project-specific vocabulary -# (config/vocabularies/CodenameOne) is tracked so CI runs see the same word -# list local contributors do. +# (config/vocabularies/CodenameOne) and our locally authored rule directory +# (styles/CodenameOneRules) are tracked so CI runs see the same word list +# and custom checks local contributors do. styles/* !styles/config/ +!styles/CodenameOneRules/ styles/config/* !styles/config/vocabularies/ styles/config/vocabularies/* diff --git a/docs/developer-guide/.vale.ini b/docs/developer-guide/.vale.ini index 350e0d1307..424d27009e 100644 --- a/docs/developer-guide/.vale.ini +++ b/docs/developer-guide/.vale.ini @@ -175,7 +175,7 @@ Packages = https://github.com/errata-ai/packages/releases/download/v0.2.0/Micros # removes the conjunction the reader relies on. [*.{adoc,asciidoc}] -BasedOnStyles = Microsoft, proselint, write-good +BasedOnStyles = Microsoft, proselint, write-good, CodenameOneRules TokenIgnores = (?s)// vale-skip:[^\n]*\n[^\n]+\n write-good.Passive = NO diff --git a/docs/developer-guide/styles/CodenameOneRules/NonexistentVersions.yml b/docs/developer-guide/styles/CodenameOneRules/NonexistentVersions.yml new file mode 100644 index 0000000000..abb0be2943 --- /dev/null +++ b/docs/developer-guide/styles/CodenameOneRules/NonexistentVersions.yml @@ -0,0 +1,23 @@ +extends: existence +message: "'%s' refers to a Codename One version that has not been released. The current release line is 7.0.x; do not write 'Codename One 8', 'Codename One 9', or 'Codename One 7.1'." +level: error +ignorecase: false +nonword: true +# `vocab: false` is critical: by default Vale subtracts any term from +# `styles/config/vocabularies/CodenameOne/accept.txt` (which includes +# "Codename") from matches, which would silently swallow every hit because +# our patterns start with "Codename One". +vocab: false +# Match the version number in isolation, then assert "Codename One" precedes it +# in raw text (vale RE2 has no lookbehind, so we use \b on either side and rely +# on the regex matching unique enough strings inside prose). +# +# Block: +# - "Codename One 8"/"Codename One 9"/... (no major version >= 8 has shipped) +# - "Codename One 7.1"/"Codename One 7.2"/... (latest 7.x is 7.0.81; nothing +# past 7.0 has shipped) +# We deliberately allow plain "Codename One 7" and "Codename One 7.0.x" +# because those are the live release line. +tokens: + - '\bCodename One ([89]|[1-9][0-9]+)\b' + - '\bCodename One 7\.[1-9][0-9]*\b' diff --git a/maven/core/pom.xml b/maven/core/pom.xml index 9a0c96f641..a069cc54b1 100644 --- a/maven/core/pom.xml +++ b/maven/core/pom.xml @@ -62,6 +62,81 @@ 1.8 + + + errorprone + + 2.27.1 + + + + + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + true + + true + + -XDcompilePolicy=simple + + -Xplugin:ErrorProne -XepDisableAllChecks -Xep:BanClassForName:ERROR + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.codenameone + errorprone-checks + ${project.version} + + + + + + + diff --git a/maven/errorprone-checks/pom.xml b/maven/errorprone-checks/pom.xml new file mode 100644 index 0000000000..d49630923a --- /dev/null +++ b/maven/errorprone-checks/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + + errorprone-checks + jar + codenameone-errorprone-checks + + Custom Error Prone bug checkers that the framework build runs over + CodenameOne/src. Currently enforces: + - BanClassForName: refuses any use of java.lang.Class.forName(...) + Built only on JDK 11+ (the minimum Error Prone supports); the + framework's primary JDK 8 build path skips this module via the + profile activation below. + + + + 11 + 11 + 2.27.1 + + + + + com.google.errorprone + error_prone_check_api + ${errorprone.version} + provided + + + com.google.errorprone + error_prone_annotations + ${errorprone.version} + provided + + + com.google.auto.service + auto-service-annotations + 1.1.1 + provided + + + + + + + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + + + + com.google.auto.service + auto-service + 1.1.1 + + + + + + + diff --git a/maven/errorprone-checks/src/main/java/com/codenameone/errorprone/BanClassForName.java b/maven/errorprone-checks/src/main/java/com/codenameone/errorprone/BanClassForName.java new file mode 100644 index 0000000000..76c3269e55 --- /dev/null +++ b/maven/errorprone-checks/src/main/java/com/codenameone/errorprone/BanClassForName.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + */ +package com.codenameone.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.method.MethodMatchers; +import com.google.errorprone.matchers.method.MethodMatchers.MethodNameMatcher; +import com.sun.source.tree.MethodInvocationTree; + +/** + * Rejects any call to {@code java.lang.Class.forName(...)} inside framework + * sources. The framework's iOS port translates bytecode to C ahead of time + * via ParparVM, which cannot resolve classes named only by string at runtime. + * Reflective Class lookups silently work on Android and JavaSE but fail (or + * dead-strip) on iOS, leaving cross-platform bugs that only surface in app + * store builds. Use {@code com.codename1.system.NativeLookup} or an explicit + * Class literal instead. + */ +@AutoService(BugChecker.class) +@BugPattern( + summary = "Class.forName is forbidden in Codename One framework code; " + + "ParparVM cannot resolve classes by string name at runtime. " + + "Use NativeLookup or a Class literal instead.", + severity = BugPattern.SeverityLevel.ERROR, + linkType = BugPattern.LinkType.NONE) +public final class BanClassForName extends BugChecker + implements MethodInvocationTreeMatcher { + + private static final MethodNameMatcher CLASS_FOR_NAME = + MethodMatchers.staticMethod() + .onClass("java.lang.Class") + .named("forName"); + + @Override + public Description matchMethodInvocation( + MethodInvocationTree tree, VisitorState state) { + if (CLASS_FOR_NAME.matches(tree, state)) { + return describeMatch(tree); + } + return Description.NO_MATCH; + } +} diff --git a/maven/pom.xml b/maven/pom.xml index 0dca49fedd..0ce73f81ea 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -624,5 +624,19 @@ core-unittests + + + errorprone + + errorprone-checks + + diff --git a/scripts/check-since-tags.sh b/scripts/check-since-tags.sh new file mode 100755 index 0000000000..a738285855 --- /dev/null +++ b/scripts/check-since-tags.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# +# Scan Java sources under CodenameOne/src for @since javadoc tags whose +# referenced version does NOT correspond to a git tag in this repository. +# +# Background: Claude (and human contributors) sometimes write @since markers +# for releases that never shipped, leaving the API reference claiming a +# feature is available in versions readers cannot install. This check fails +# the build until every @since X.Y.Z matches an existing git tag +# (with or without a leading "v"). +# +# Exit codes: +# 0 - all @since values match a tag +# 1 - one or more @since values do not match any tag +# 2 - misconfiguration (missing dirs, no tags fetched, etc.) +# +# Usage: +# scripts/check-since-tags.sh [source-dir] +# +# When no argument is given the script scans CodenameOne/src relative to +# the repository root. +set -euo pipefail + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" +SRC_DIR="${1:-$REPO_ROOT/CodenameOne/src}" + +if [[ ! -d "$SRC_DIR" ]]; then + echo "check-since-tags: source directory not found: $SRC_DIR" >&2 + exit 2 +fi + +if ! command -v git >/dev/null 2>&1; then + echo "check-since-tags: git is not on PATH; cannot enumerate tags." >&2 + exit 2 +fi + +# Build the allowed-versions set from `git tag`, stripping a leading "v" so +# both "v7.0" and "7.0" map to the same allowed value. +ALLOWED_TAGS_FILE="$(mktemp -t cn1-since-tags.XXXXXX)" +trap 'rm -f "$ALLOWED_TAGS_FILE"' EXIT + +git -C "$REPO_ROOT" tag --list \ + | sed -E 's/^v//' \ + | sort -u > "$ALLOWED_TAGS_FILE" + +if [[ ! -s "$ALLOWED_TAGS_FILE" ]]; then + echo "check-since-tags: no git tags found in $REPO_ROOT." >&2 + echo " Make sure the workspace was cloned with tags (e.g. CI uses" >&2 + echo " actions/checkout with fetch-tags: true)." >&2 + exit 2 +fi + +# Collect every '@since ' occurrence with file:line context. +# We deliberately match in any kind of comment (//, /** */, ///) because the +# tag is meaningful in all three forms. +HITS_FILE="$(mktemp -t cn1-since-hits.XXXXXX)" +trap 'rm -f "$ALLOWED_TAGS_FILE" "$HITS_FILE"' EXIT + +# -E for ERE, -H to print filename, -n for line numbers, -r for recurse. +# The version captures digits.dots optionally followed by letters/dashes +# (e.g. "7.0.245", "3.5", "1.0-RC1"). A trailing period is stripped below. +grep -EHnr --include='*.java' \ + '@since[[:space:]]+[0-9][0-9A-Za-z._-]*' \ + "$SRC_DIR" > "$HITS_FILE" || true + +violations=0 + +while IFS= read -r hit; do + # hit format: path:line:full-line + file="${hit%%:*}" + rest="${hit#*:}" + line="${rest%%:*}" + text="${rest#*:}" + + # Extract every @since version on the line (a line may contain only one, + # but the regex is tolerant of multiple). + while IFS= read -r raw_version; do + [[ -z "$raw_version" ]] && continue + # Strip a trailing dot that is sentence punctuation, e.g. + # "@since 3.5. Added the hint..." + version="${raw_version%.}" + if ! grep -Fxq "$version" "$ALLOWED_TAGS_FILE"; then + printf '%s:%s: @since %s does not match any git tag\n' \ + "$file" "$line" "$version" + violations=$((violations + 1)) + fi + done < <(printf '%s\n' "$text" \ + | grep -oE '@since[[:space:]]+[0-9][0-9A-Za-z._-]*' \ + | sed -E 's/@since[[:space:]]+//') +done < "$HITS_FILE" + +if (( violations > 0 )); then + echo "" >&2 + echo "check-since-tags: $violations @since reference(s) point at versions" >&2 + echo " with no matching git tag. Either fix the @since to a released" >&2 + echo " version, or tag the release before merging." >&2 + exit 1 +fi + +echo "check-since-tags: all @since tags reference released versions." From 586f2a314ad9c7d1b5df9d89d2f22479b361751c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 26 May 2026 19:15:52 +0300 Subject: [PATCH 2/3] Address CI failures from the build-guardrails PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check-since-tags: also accept the *next patch* of every X.Y.Z release line, since @since markers are necessarily written before the release is tagged. With the highest 7.0.x tag at v7.0.244, the existing @since 7.0.245 entries are now valid. Next-minor/next-major bumps still require an explicit prior tag because those are the values Claude hallucinates most often. - check-since-tags: only scan javadoc-style @since (lines containing `*` or `///` before the tag), so plain `//` comments — which in vendored MiG Layout code reference the upstream library's changelog, not a Codename One release — no longer trigger the check. - errorprone CI: explicitly add errorprone-checks to -pl. The custom checker is wired in via annotationProcessorPaths, which Maven's -am does not recognise as a build-time dependency. - Suppress BanClassForName at the three deliberate framework-internal reflective lookups (NativeLookup.create, GeofenceManager.getListenerClass, DeviceRunner.runTest) with @SuppressWarnings and a justifying comment. - Drop the "From Codename One 9.0," prefix from the ImageViewer prose in the developer guide — a real preexisting false advertisement of an unreleased version that the new Vale rule correctly flagged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/errorprone.yml | 6 +++- .../codename1/location/GeofenceManager.java | 3 ++ .../com/codename1/system/NativeLookup.java | 4 +++ .../com/codename1/testing/DeviceRunner.java | 4 +++ .../The-Components-Of-Codename-One.asciidoc | 2 +- scripts/check-since-tags.sh | 36 ++++++++++++++++++- 6 files changed, 52 insertions(+), 3 deletions(-) diff --git a/.github/workflows/errorprone.yml b/.github/workflows/errorprone.yml index 1dc55bd6cd..bcc68294e0 100644 --- a/.github/workflows/errorprone.yml +++ b/.github/workflows/errorprone.yml @@ -47,4 +47,8 @@ jobs: - name: Run Error Prone over the core module working-directory: maven - run: mvn -B -Perrorprone -pl core -am install -DskipTests + # `errorprone-checks` must be in -pl explicitly: it is referenced + # from core via annotationProcessorPaths, which `-am` does not + # recognise as a Maven dependency. Without listing it here the + # build fails resolving com.codenameone:errorprone-checks:8.0-SNAPSHOT. + run: mvn -B -Perrorprone -pl errorprone-checks,core -am install -DskipTests diff --git a/CodenameOne/src/com/codename1/location/GeofenceManager.java b/CodenameOne/src/com/codename1/location/GeofenceManager.java index 7314632f74..914f609a07 100644 --- a/CodenameOne/src/com/codename1/location/GeofenceManager.java +++ b/CodenameOne/src/com/codename1/location/GeofenceManager.java @@ -253,6 +253,9 @@ public boolean isBubble(String id) { } /// Gets the currently registered Listener class. + // The listener class name is persisted to Storage during registration; we + // have no Class literal here, so reflective resolution is unavoidable. + @SuppressWarnings("BanClassForName") public synchronized Class getListenerClass() { if (listenerClass == null) { String className = (String) Storage.getInstance().readObject(LISTENER_CLASS_KEY); diff --git a/CodenameOne/src/com/codename1/system/NativeLookup.java b/CodenameOne/src/com/codename1/system/NativeLookup.java index 5e9dafba74..af8e5958b1 100644 --- a/CodenameOne/src/com/codename1/system/NativeLookup.java +++ b/CodenameOne/src/com/codename1/system/NativeLookup.java @@ -72,6 +72,10 @@ public static void setVerbose(boolean aVerbose) { /// /// @return an instance of that interface that can be invoked or null if the native interface isn't /// present on the underlying platform (e.g. simulator platform). + // NativeLookup is itself the sanctioned escape hatch for class-by-name + // resolution; the JavaSE simulator path falls back to a name-derived + // *Impl lookup that the registered map cannot cover. + @SuppressWarnings("BanClassForName") public static T create(Class c) { try { Class cls = interfaceToClassLookup.get(c); diff --git a/CodenameOne/src/com/codename1/testing/DeviceRunner.java b/CodenameOne/src/com/codename1/testing/DeviceRunner.java index fe65c68c0e..40936db3da 100644 --- a/CodenameOne/src/com/codename1/testing/DeviceRunner.java +++ b/CodenameOne/src/com/codename1/testing/DeviceRunner.java @@ -103,6 +103,10 @@ public void runTests() { /// #### Parameters /// /// - `testClassName`: the class name of the test case + // The test runner receives the unit test class as a String (it is the + // entry point invoked from the device-side test bootstrap), so loading + // it by name is the whole point of the method. + @SuppressWarnings("BanClassForName") public void runTest(String testClassName) { try { final UnitTest t = (UnitTest) Class.forName(testClassName).newInstance(); diff --git a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc index aba7c75951..6ad3204325 100644 --- a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc +++ b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc @@ -2565,7 +2565,7 @@ image::img/components-imageviewer-multi.png[An ImageViewer with many elements is Notice that you use a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] to allow swiping between images. -From Codename One 9.0, `ImageViewer` also supports optional side arrows (material font icons) and an optional thumbnail strip for direct image navigation: +`ImageViewer` also supports optional side arrows (material font icons) and an optional thumbnail strip for direct image navigation: [source,java] ---- diff --git a/scripts/check-since-tags.sh b/scripts/check-since-tags.sh index a738285855..d3e5da291d 100755 --- a/scripts/check-since-tags.sh +++ b/scripts/check-since-tags.sh @@ -37,11 +37,39 @@ fi # Build the allowed-versions set from `git tag`, stripping a leading "v" so # both "v7.0" and "7.0" map to the same allowed value. +# +# We also seed the set with the "next patch" of every X.Y.Z tag because +# contributors necessarily write @since BEFORE the release that ships the +# feature is tagged. Concretely, if the highest 7.0.x tag is 7.0.244, we +# also accept @since 7.0.245 so the upcoming release can be referenced +# without flipping the build red on every PR. We deliberately do NOT +# auto-accept next-minor or next-major bumps (e.g. 7.1 or 8.0) — those +# require an explicit prior tag, because they are also exactly the values +# Claude hallucinates most often. ALLOWED_TAGS_FILE="$(mktemp -t cn1-since-tags.XXXXXX)" trap 'rm -f "$ALLOWED_TAGS_FILE"' EXIT +# Pipe through awk to add next-patch entries, then sort -u. git -C "$REPO_ROOT" tag --list \ | sed -E 's/^v//' \ + | awk ' + { print $0 } + # Capture every X.Y.Z (numeric Z) and track the largest Z per X.Y. + /^[0-9]+\.[0-9]+\.[0-9]+$/ { + n = split($0, parts, ".") + prefix = parts[1] "." parts[2] + patch = parts[3] + 0 + if (patch > max_patch[prefix]) { + max_patch[prefix] = patch + } + } + END { + # Emit one extra allowed version per release line: max+1. + for (prefix in max_patch) { + print prefix "." (max_patch[prefix] + 1) + } + } + ' \ | sort -u > "$ALLOWED_TAGS_FILE" if [[ ! -s "$ALLOWED_TAGS_FILE" ]]; then @@ -60,8 +88,14 @@ trap 'rm -f "$ALLOWED_TAGS_FILE" "$HITS_FILE"' EXIT # -E for ERE, -H to print filename, -n for line numbers, -r for recurse. # The version captures digits.dots optionally followed by letters/dashes # (e.g. "7.0.245", "3.5", "1.0-RC1"). A trailing period is stripped below. +# +# We deliberately require the @since to be preceded on the same line by a +# javadoc marker (`*` from /** */ blocks or `///` from Markdown javadoc). +# That excludes plain `// @since X.Y` code comments — which appear in +# vendored libraries (e.g. MiG Layout in ui/layouts/mig) where the value +# references the upstream library's changelog, not a Codename One release. grep -EHnr --include='*.java' \ - '@since[[:space:]]+[0-9][0-9A-Za-z._-]*' \ + '(\*|///).*@since[[:space:]]+[0-9][0-9A-Za-z._-]*' \ "$SRC_DIR" > "$HITS_FILE" || true violations=0 From 37e7e1fdea50253f7467fda14b515a5901cfb087 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 26 May 2026 19:41:58 +0300 Subject: [PATCH 3/3] Install errorprone-checks before compiling core in CI Maven's reactor schedules modules by declared dependencies. `core` references `errorprone-checks` only via the maven-compiler-plugin's `annotationProcessorPaths`, which Maven does not treat as a build-time dependency, so the reactor scheduled core first and failed to resolve com.codenameone:errorprone-checks:8.0-SNAPSHOT. Fix by splitting the CI command into two invocations: first install errorprone-checks to the local repo, then compile core with the errorprone profile active. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/errorprone.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/errorprone.yml b/.github/workflows/errorprone.yml index bcc68294e0..1f4938b466 100644 --- a/.github/workflows/errorprone.yml +++ b/.github/workflows/errorprone.yml @@ -45,10 +45,16 @@ jobs: java-version: 17 cache: maven + - name: Install errorprone-checks + working-directory: maven + # The checker module must be installed to the local repo BEFORE + # core compiles. We can't rely on the reactor ordering: core + # references errorprone-checks only via annotationProcessorPaths, + # which Maven does not treat as a build-time dependency, so the + # reactor would otherwise schedule core first and fail to resolve + # com.codenameone:errorprone-checks:8.0-SNAPSHOT. + run: mvn -B -Perrorprone -pl errorprone-checks -am install -DskipTests + - name: Run Error Prone over the core module working-directory: maven - # `errorprone-checks` must be in -pl explicitly: it is referenced - # from core via annotationProcessorPaths, which `-am` does not - # recognise as a Maven dependency. Without listing it here the - # build fails resolving com.codenameone:errorprone-checks:8.0-SNAPSHOT. - run: mvn -B -Perrorprone -pl errorprone-checks,core -am install -DskipTests + run: mvn -B -Perrorprone -pl core -am install -DskipTests