Simulator: data-driven menu hooks for cn1libs#4988
Merged
Conversation
Adds a small framework feature that lets cn1libs contribute items to the
JavaSE simulator's menu bar via a properties file, without referencing
any Swing types. Each cn1lib drops a
META-INF/codenameone/simulator-hooks.properties on its classpath:
name=Bluetooth
item1.label=Add demo peripheral
item1.action=com.example.bt.sim.Hooks#addDemoPeripheral
The new SimulatorHookLoader scans every jar on the simulator classpath
via getResources(), parses the file, resolves each action's static
method against the classloader that loaded Display, and pre-binds a
Runnable that dispatches on the CN1 EDT. JavaSEPort.installMenu groups
the result by menu name and renders one JMenu per group between the
existing menus and the Help menu.
Why data-driven instead of a Java SPI: the simulator UX is going to be
rewritten and we don't want cn1libs to depend on JMenu/JMenuItem (or
have to be recompiled when the UX shape changes). The neutral
SimulatorHook record (menuName, label, Runnable) is the contract; the
UI shell on top is replaceable.
Tests in maven/javase cover well-formed parsing, declaration-order
preservation, and skip-on-error for every malformed case
(missing name, dangling label, unknown class, non-static target,
malformed action string).
Documentation lives in docs/developer-guide/Maven-Creating-CN1Libs.adoc
with the cn1-bluetooth lib as a worked example.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 11 screenshots: 11 matched. |
Contributor
Cloudflare Preview
|
Collaborator
Author
|
Compared 20 screenshots: 20 matched. |
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
CI's vale gate flagged 10 prose issues in the simulator-menu-hooks section: "backend" hits Microsoft.Avoid (treated as a noise token since it's our standard term for a swappable implementation), "e.g.", a missing Oxford-style comma inside quoted enumerations, a stray "freely" adverb, heading punctuation, and "do not" instead of "don't". Adds "[Bb]ackend" to the project vocabulary and rewrites the affected sentences. No semantic change to the section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog
added a commit
to codenameone/bluetoothle-codenameone
that referenced
this pull request
May 19, 2026
…latform plumbing Every cn1lib CI job failed on its first run with a mix of issues that all map to the same shape: the OS-activated native-helper Maven profiles tried to do too much, and the framework setup composite action didn't actually work cross-OS. Concretely: - The OS-activated profiles (linux-native-ble-helper, etc.) fired whenever a build ran on the matching OS, so the simulator-tests, ios-native-tests, and android-native-tests jobs all tried to compile the Rust helper too. Those runners don't have cargo (or libdbus-1-dev on Linux) installed, and they don't need the helper. Each profile now also requires `!skipNativeBleHelper` to activate, and every workflow / script that builds the cn1lib but doesn't need the helper passes -DskipNativeBleHelper=true. - Composite action used a hard-coded /tmp path. Windows runners fail with "directory name is invalid" because Git Bash there doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout. - Composite action requested JDK 8 from Temurin, which doesn't ship Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could not find satisfied version for SemVer '8'". Switched to Zulu. - device-test.yml never ran the framework setup; it tried to fetch codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the composite action and the skip flag. - TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks (the framework PR's branch). Once codenameone/CodenameOne#4988 merges, flip back to 'master'. Comment in the action calls this out so the followup isn't forgotten. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Original feedback: CN1 tests live in the cross-platform common/
project and can't import JavaSE-port classes (no reflection, no
JavaSE-only types). The previous design forced tests to either
reflect on SimulatorHookLoader or directly instantiate cn1lib
internals, both of which break on iOS/Android/JavaScript.
This commit adds the missing piece: every hook gets a stable
`namespace:id` identifier and is registered with a new core class,
SimulatorHookExecutor. CN.executeHook(String hookId) delegates to
that executor and returns false on platforms where the registry is
empty (i.e., every non-simulator target). Tests in common/ can
drive simulator-only behavior with one cross-platform call:
CN.executeHook("bluetooth:addDemoPeripheral");
Also lifts the menu-label restriction: hooks may now declare an
id and action without a label, in which case they are registered
with the executor (callable from tests) but hidden from the
simulator's menu. Useful for test fixture scaffolding ("seed N
peripherals", "prime next-call failure") that would clutter the
menu UX.
Properties-file grammar additions:
namespace=<token> # defaults to slugified `name`
itemN.id=<token> # defaults to the property key (item1, item2)
itemN.label=... # NOW OPTIONAL; absent = API-only hook
Documentation in Maven-Creating-CN1Libs.adoc updated with the new
shape and a CN.executeHook test example.
12 JUnit tests on the framework parser pass; existing menu rendering
is unchanged for any hook that ships a label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The action wrapper used Display.callSerially, which is fire-and-forget. That broke CN.executeHook callers running off the EDT (every CN1 UnitTest's runTest()) — they'd assert state changes before the EDT had run the action and false-fail. Switched to Display.callSeriallyAndWait. From the EDT the body runs inline (CN1's existing semantics); from any other thread the call blocks until the action completes. CN.executeHook now returns true only after the hook has actually executed, so tests can immediately assert on the side effects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog
added a commit
to codenameone/bluetoothle-codenameone
that referenced
this pull request
May 20, 2026
Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.
simulator-hooks.properties:
- explicit namespace=bluetooth
- explicit item.id for every hook (matches the executor key shape)
- new item8 = primeReadFailure: a label-less hook the test uses
to script a one-shot read failure. Demonstrates the API-only
branch (no menu entry, still callable from tests).
BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.
Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The framework's docs-style gate (build-test JDK 8/17/21) rejects classic /** Javadoc markers in core/CLDC classes — CN1 standardized on Java 25 /// markdown comments instead. Convert SimulatorHookExecutor to the project style. No semantic change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous design added CN.executeHook + nested itemN.id/.label/.action
keys. Both miss the mark:
- We already have CN/Display.execute(String url) for native dispatch
on every platform; the JavaSE port can intercept hook urls before
handing the rest to the OS browser. Adding executeHook duplicates
that surface and forces app code to learn a second method.
- Items are positional: item1 is the first menu entry, item2 the
second. The previous design treated itemN as an opaque pairing
token (any string worked) and allowed reordering — that's wrong;
the simulator UX renders in numeric order and the loader should
too. The loop now stops at the first missing itemN, matching
what the user-facing menu would do.
- The compound key syntax (itemN.label, itemN.action, itemN.id) is
redundant when the namespace is already in the file's `name`/
`namespace` field. Replaced with parallel arrays: itemN holds
the action, labelN holds the label.
Final shape:
name=Bluetooth
namespace=bluetooth # optional; defaults to slugified `name`
item1=fqcn#method # required; the Nth menu item's action
label1=Toggle adapter # optional; if absent, hook is API-only
item2=fqcn#method2
label2=Add demo peripheral
# No labelN -> registered with the executor but not in the menu
item3=fqcn#primeReadFailure
JavaSEPort.execute intercepts urls matching a registered hook (key
shape: "namespace:itemN") and routes them through the existing
SimulatorHookExecutor; non-matching urls fall through to the
browser launcher. JavaSEPort.canExecute reports TRUE for registered
hook urls so tests can guard cross-platform.
SimulatorHookExecutor stays in core. Tests use CN.execute(...) plus
CN.canExecute(...) as a "are we in a simulator?" gate; no
JavaSE-only imports needed. 12 JUnit tests pin the positional loop,
slugify rules, gap-stops behavior, executor registration, and the
API-only branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Android port's javac uses ASCII encoding (see feedback memory),
so any non-ASCII character in a .java file under CodenameOne/ or
Ports/JavaSE/ trips "unmappable character" errors in build-test
(8/17). The redesign sneaked em-dashes ('--'), arrows ('->') and a
right single quote into a handful of new/edited files. Sweep them
out: every modified .java is now pure ASCII.
Also drops a stray "e.g." from the dev guide section that Vale's
Microsoft.Foreign rule flags; replaced with "for example".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PMD's AvoidUsingVolatile rule (enforced by the build-test JDK 8 "Generate static analysis HTML summaries" step) flagged the `private static volatile Map<String, Runnable> hooks` field. The semantics we want -- safe replacement of the registry from the JavaSE port while readers see either the old or new map atomically -- map cleanly to AtomicReference, which avoids the volatile keyword and keeps PMD happy. No observable behavior change. 12 framework JUnit tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rence) The volatile-vs-AtomicReference choice was a false binary: - volatile: trips PMD's AvoidUsingVolatile gate on the JDK 8 PR CI run. - AtomicReference: triggers "package java.util.concurrent.atomic does not exist" in the JavaSE port's javase-simulator-tests Ant step, which compiles the framework core against the CLDC subset. CLDC doesn't ship j.u.c.atomic. Both gates fail on the new class. The portable third option is plain synchronized accessors guarding the registry field with a private lock object -- same memory-visibility guarantee, compiles under every supported target, no PMD warning. Hook actions still run OUTSIDE the lock so a long-running hook can't deadlock a concurrent register() call. 12 framework JUnit tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog
added a commit
to codenameone/bluetoothle-codenameone
that referenced
this pull request
May 20, 2026
Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up the simulator hook intercept added in codenameone/CodenameOne#4988. That's wrong for a cn1lib: the framework feature isn't released yet, and tying this PR to an in-flight master commit means everything downstream (CI, anyone reviewing) has to also build CN1 from source. Reverting the cn1lib to the current release (7.0.243): - pom.xml: cn1.version / cn1.plugin.version = 7.0.243. - Drop the local-snapshots repository entry and the setup-cn1-framework composite action that cloned + built CN1 master on every CI job. The workflows now use the same plain curl-the-build-client pattern they had pre-PR. - ios-native-tests no longer needs continue-on-error: the iOS codegen on the released line generates the same dispatch shim the bridge has always been targeting (it worked under 7.0.71 and carries through 7.0.243), so the lib's ObjC selector naming (param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was upstream churn, not a bridge bug. - cn1-framework-pin.txt removed. The new hook surface ships dormant on 7.0.243: - simulator-hooks.properties and BluetoothSimulatorHooks are still there; on 7.0.243 the simulator simply doesn't read them, the menu items don't render, and CN.canExecute("bluetooth:item1") returns false. - BluetoothSimulatorHooksTest now opens with that exact canExecute guard. On 7.0.243 it returns true (test "passes" without doing anything); on a future CN1 release that intercepts the URLs (whenever #4988 ships), every existing assertion fires. Local verification: - mvn install of every module succeeds against 7.0.243. - mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ecute The new SimulatorHookExecutor / SimulatorHook / SimulatorHookLoader files were authored for Codename One, not imported from Oracle, so the Oracle/Classpath header that older OpenJDK-derived files carry doesn't belong on them. Strip the header — recent CN1-authored files ship without one. Also revert the doc additions to CN.execute and fold them into Display.execute, which is where the URL execute API actually lives and where this behavior should be documented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every CN1-authored source file in this repo carries the GPL v2 + Classpath header. The previous commit stripped the bogus Oracle attribution but left the new files headerless; this restores the proper Codename One header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog
added a commit
to codenameone/bluetoothle-codenameone
that referenced
this pull request
May 21, 2026
* Native BLE backend for the JavaSE port + cn1lib simulator menu
Splits the JavaSE port's BluetoothNativeBridgeImpl into a thin
dispatcher and two backends:
- SimulatorBluetoothBackend keeps the existing scriptable in-memory
behavior — every test continues to drive it, and the simulator menu
drives it manually via BluetoothSimulatorHooks.
- NativeBleBackend talks to real BLE hardware through a bundled Rust
helper built from javase/src/main/rust/cn1-ble-helper. The helper
wraps the btleplug crate, which covers macOS (CoreBluetooth), Linux
(BlueZ via D-Bus) and Windows (WinRT) with a single source tree;
protocol is JSON lines over stdin/stdout (see
javase/src/main/rust/PROTOCOL.md).
Selection is via the cn1.bluetoothle.javase.backend system property
("simulator" / "nativeBle"), the CN1_BLUETOOTHLE_BACKEND env var, or
the simulator menu's new "Switch backend →" items. Swap is hot:
BluetoothNativeBridgeImpl holds the current backend in a volatile
static so a cached Bluetooth instance immediately sees the change.
The cn1lib's simulator menu uses the framework's new
SimulatorHookLoader (companion CodenameOne PR). Two new sets of
items: scripted simulator drivers (Toggle adapter, Add demo
peripheral, Disconnect all, Push notification, Clear) and the
backend toggle. Each action is a public static method on
BluetoothSimulatorHooks, dispatched on the CN1 EDT.
Maven integration:
- Three OS-activated profiles in javase/pom.xml run `cargo build
--release` and copy the helper to a per-OS classpath resource
(com/codename1/bluetoothle/native/{macos,linux,windows}/...). Hosts
without cargo skip cleanly and the dispatcher falls back to
simulator with a clear stderr message.
- `BluetoothSimulator` gains two public introspection methods,
registeredPeripheralCount() and isPeripheralRegistered(), used by
the new BluetoothSimulatorHooksTest to assert hook side effects
without going through scanning.
CI:
- New composite action `.github/actions/setup-cn1-framework` clones
codenameone/CodenameOne master and installs 8.0-SNAPSHOT into
~/.m2, caching on the contents of `.github/cn1-framework-pin.txt`.
All existing jobs (maven.yml, simulator-tests, ios-native-tests,
android-native-tests) now use it.
- New native-ble-helper matrix job runs on macos-14, ubuntu-latest
and windows-latest. Each builds the helper, verifies it ended up
at the right OS-keyed resource path inside the cn1lib jar, then
runs scripts/native-tests/run-native-ble-helper-smoke.sh which
asserts the helper emits a stateChanged event before clean exit
(hosted runners typically report `unsupported` since they have no
adapter — the only failure mode is "helper never emitted
anything", which catches build / linking / runtime regressions).
Depends on codenameone/CodenameOne#4988 (SimulatorHookLoader);
the composite action builds that PR's master at run time, so this
PR will turn green once the framework PR is merged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: gate the Rust helper build behind -DskipNativeBleHelper and fix platform plumbing
Every cn1lib CI job failed on its first run with a mix of issues that
all map to the same shape: the OS-activated native-helper Maven
profiles tried to do too much, and the framework setup composite
action didn't actually work cross-OS.
Concretely:
- The OS-activated profiles (linux-native-ble-helper, etc.) fired
whenever a build ran on the matching OS, so the simulator-tests,
ios-native-tests, and android-native-tests jobs all tried to
compile the Rust helper too. Those runners don't have cargo (or
libdbus-1-dev on Linux) installed, and they don't need the helper.
Each profile now also requires `!skipNativeBleHelper` to activate,
and every workflow / script that builds the cn1lib but doesn't
need the helper passes -DskipNativeBleHelper=true.
- Composite action used a hard-coded /tmp path. Windows runners
fail with "directory name is invalid" because Git Bash there
doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout.
- Composite action requested JDK 8 from Temurin, which doesn't ship
Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could
not find satisfied version for SemVer '8'". Switched to Zulu.
- device-test.yml never ran the framework setup; it tried to fetch
codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the
composite action and the skip flag.
- TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks
(the framework PR's branch). Once codenameone/CodenameOne#4988
merges, flip back to 'master'. Comment in the action calls this
out so the followup isn't forgotten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: disable CN1's auto-download-cn1-binaries profile
Every cn1lib job that runs the framework-setup composite was failing
with "destination path 'cn1-binaries' already exists and is not an
empty directory" — six different jobs, same root cause:
CN1's reactor pom has a download-cn1-binaries profile that activates
by default. It does `git clone cn1-binaries` on every reactor module
that hits process-resources, into a path the framework picks itself.
We already clone cn1-binaries to $RUNNER_TEMP and tell the build to
look there via -Dcn1.binaries, but the download profile fires anyway
on the first module, succeeds, then collides with itself on every
subsequent module.
Adding !download-cn1-binaries to the profile list disables it. This
matches what CN1's own scripts/setup-workspace.sh passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: install build client on every run + harden helper-jar verify
Two fixes for issues exposed by the second-round CI run:
- Composite action's "Install CN1 build client" step was gated on
cache-miss; cache-hit jobs found ~/.m2 restored from cache but
CodeNameOneBuildClient.jar missing (it lives in ~/.codenameone),
and every job failed with "CodeNameOneBuildClient.jar not found".
Always install: copy from $RUNNER_TEMP if we just cloned, else
download from GitHub raw.
- native-bluetooth-tests.yml's "Verify helper binary..." step used
`jar tf cn1-bluetooth-javase-*.jar`. On CI three jars match
(main / -sources / -javadoc) and `jar tf` reads only the first,
alphabetically the -javadoc one, which has no native resources.
Filter out the classifier jars and emit diagnostic output on miss
so future regressions are easier to read.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: Rust helper emits stateChanged on adapter-init failure + Android cn1:build needs JAVA17_HOME
Two more CI fixes uncovered by the second round of runs:
- ubuntu-latest native-ble-helper job: build + jar packaging passed
but the smoke test failed. Hosted runners have no Bluetooth adapter
and btleplug's Manager::new() / .adapters() returns errors there;
the Rust helper was bailing out without producing any stdout, so
the smoke test's "did stateChanged land" check saw silence and
failed. Hardened main() to catch the adapter-init Err branch the
same way as the no-adapter branch: log to stderr, emit
{"event":"stateChanged","state":"unsupported"}, then drain stdin
until EOF or shutdown.
- build-and-test (device-test workflow) and android-native-tests:
Both invoke cn1:build for Android, which forks a Gradle 8 build.
The CN1 plugin requires JAVA17_HOME (uppercase, no _X64 suffix)
pointed at a Java 17 install, separate from the JAVA_HOME the
rest of the build uses. setup-java only sets JAVA_HOME_17_X64;
exported JAVA17_HOME from that for both workflows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: install ripgrep + fall back to xcodeproj on macos iOS job
The macos-14 runner image stopped shipping ripgrep, and
run-ios-native-tests.sh uses `rg` to:
- guard against Cordova refs (under `!`, which silently masks
command-not-found as a pass)
- decide whether the pbxproj test target needs edits (`! rg -q`
on missing rg evaluated as true, so the edits ran anyway, but
only by accident)
Made it explicit: `brew install ripgrep` once at the top of the job.
Second iOS issue: cn1:build for ios-source in 8.0-SNAPSHOT generates
only BTDemo.xcodeproj, no companion BTDemo.xcworkspace. The script's
final xcodebuild invocation hardcoded `-workspace BTDemo.xcworkspace`
and bailed with exit 66 ("workspace does not exist"). Auto-detect
and fall back to `-project BTDemo.xcodeproj` when no workspace is
generated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: cap MAVEN_OPTS for android jobs, mark iOS continue-on-error
- android-native-tests + build-and-test: cn1:build forks a sub-Maven
for gradle. On a runner that already has the Android emulator
taking ~3-4 GB, the fork's default JVM heap ergonomics try to
allocate more than what's free and the sub-process dies in VM init
("Error occurred during initialization of VM"). Pinned
MAVEN_OPTS="-Xms256m -Xmx1g" to fit alongside the emulator.
- ios-native-tests: hits a CN1 master codegen bug. BluetoothLePlugin
has Cordova-style `-(void)stopScan:(CDVInvokedUrlCommand*)command`
selectors; the bridge has no-arg `-(BOOL)stopScan`. The ParparVM
dispatch shim generated by 8.0-SNAPSHOT picks the void overload
and clang errors with "initializing 'JAVA_BOOLEAN' with an
expression of incompatible type 'void'". The iOS source project
itself still builds and is usable; only the XCTest target fails
to compile, and the fix has to land in codenameone/CodenameOne
rather than the cn1lib. Marked continue-on-error: true with a
comment until the framework codegen is fixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Tests: drive hooks via CN.executeHook + add API-only primeReadFailure
Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.
simulator-hooks.properties:
- explicit namespace=bluetooth
- explicit item.id for every hook (matches the executor key shape)
- new item8 = primeReadFailure: a label-less hook the test uses
to script a one-shot read failure. Demonstrates the API-only
branch (no menu entry, still callable from tests).
BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.
Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: invalidate CN1 framework cache (CN.executeHook is new)
The cn1lib's framework-setup composite action keys its cache on
this file. Previous cn1lib CI runs cached the framework m2 artifacts
from BEFORE CN.executeHook + SimulatorHookExecutor existed; after
the framework PR added them, this round's cn1lib CI restored the
stale cache and the new test failed to compile ("cannot find symbol:
method executeHook").
Bumping the contents to force a fresh framework rebuild.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Switch to positional itemN/labelN + CN.execute("bluetooth:itemN")
Aligns the cn1lib with the redesigned framework contract:
- simulator-hooks.properties uses parallel arrays. itemN is the
action method; labelN is the optional menu text. No more
itemN.action / itemN.id / itemN.label compound keys.
- Items are positional: item1 first, item2 second, etc. The
primeReadFailure hook lives at item8 (no label8) so it's
invisible in the menu but still callable from tests.
- BluetoothSimulatorHooksTest drops the experimental CN.executeHook
in favor of the existing CN.execute(url) entry point. The JavaSE
port intercepts "bluetooth:itemN" urls and dispatches the matching
static method on the CN1 EDT. Each test also guards with
CN.canExecute so the suite stays platform-portable.
- Cache pin bumped to force CI to rebuild the framework with the
redesigned SimulatorHookExecutor + JavaSEPort.execute intercept.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: enable KVM for android-emulator-runner
ubuntu-latest stopped auto-granting /dev/kvm to the runner user;
the ReactiveCircus android-emulator-runner step aborts with
"This user doesn't have permissions to use KVM (/dev/kvm)" before
booting the AVD. The canonical fix from the action's README is a
udev rule that opens KVM to all users. Adding it as the first
step of android-native-tests so the emulator boots again.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: patch deprecated gradle configs in generated Android source
CN1 master's android codegen still emits the Gradle 5-removed
`androidTestCompile`/`testCompile`/`compile` dependency configurations
(now `androidTestImplementation`/`testImplementation`/`implementation`).
The emulator-runner step uses Gradle 8, which refuses the old names
and bails on app/build.gradle line 97 with
"Could not find method androidTestCompile()".
Patch the generated file before running gradle, same shape as the
existing compileSdkVersion / support-lib version rewrites. Anchored
on leading whitespace so the substitution doesn't touch coincidental
substrings elsewhere in the gradle file.
This is a workaround for the upstream codegen bug; the real fix
belongs in codenameone/CodenameOne when the Android source generator
gets a Gradle-8 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: always use androidTestImplementation when injecting test deps
The script's TEST_DEP_CONF fallback to "androidTestCompile" was
triggered when no top-level "implementation" line was found in the
generated app/build.gradle. Combined with the Gradle 5 removal of
that name in modern Gradle 8 (used by the emulator-runner step),
the appended test-dependency block at app/build.gradle:97
re-introduced the bad name and the build died on the very line
the previous fix tried to clean up.
Drop the fallback. androidTestImplementation works on AGP 3.x+,
which is everything the test runner library is compatible with
anyway; the conditional was a safety net for a Gradle version we
no longer support.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: migrate android instrumentation test to AndroidX
The script-injected Android test imported pre-AndroidX
android.support.test.{InstrumentationRegistry,runner.AndroidJUnit4}
and pulled in com.android.support.test:runner:1.0.2. Those package
paths haven't resolved cleanly under modern AGP + Gradle 8 since
AndroidX shipped in 2018, and the generated project's other
support-lib deps were already being version-pinned by sibling
perl-substitutions in this script.
Migrate to the AndroidX equivalents:
android.support.test.InstrumentationRegistry
-> androidx.test.platform.app.InstrumentationRegistry
android.support.test.runner.AndroidJUnit4
-> androidx.test.ext.junit.runners.AndroidJUnit4
android.support.test.runner.AndroidJUnitRunner
-> androidx.test.runner.AndroidJUnitRunner
com.android.support.test:runner:1.0.2
-> androidx.test:runner:1.6.1
+ androidx.test.ext:junit:1.2.1
InstrumentationRegistry.getTargetContext()
-> InstrumentationRegistry.getInstrumentation().getTargetContext()
Also enable android.useAndroidX + android.enableJetifier in the
generated project's gradle.properties so the legacy support-v4 /
appcompat-v7 deps the CN1 codegen still emits get jetified at
build time instead of clashing with the AndroidX classpath.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Pin to released CN1 7.0.243 and let the hook test no-op there
Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up
the simulator hook intercept added in codenameone/CodenameOne#4988.
That's wrong for a cn1lib: the framework feature isn't released yet,
and tying this PR to an in-flight master commit means everything
downstream (CI, anyone reviewing) has to also build CN1 from source.
Reverting the cn1lib to the current release (7.0.243):
- pom.xml: cn1.version / cn1.plugin.version = 7.0.243.
- Drop the local-snapshots repository entry and the
setup-cn1-framework composite action that cloned + built CN1
master on every CI job. The workflows now use the same plain
curl-the-build-client pattern they had pre-PR.
- ios-native-tests no longer needs continue-on-error: the iOS
codegen on the released line generates the same dispatch shim
the bridge has always been targeting (it worked under 7.0.71 and
carries through 7.0.243), so the lib's ObjC selector naming
(param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was
upstream churn, not a bridge bug.
- cn1-framework-pin.txt removed.
The new hook surface ships dormant on 7.0.243:
- simulator-hooks.properties and BluetoothSimulatorHooks are still
there; on 7.0.243 the simulator simply doesn't read them, the
menu items don't render, and CN.canExecute("bluetooth:item1")
returns false.
- BluetoothSimulatorHooksTest now opens with that exact canExecute
guard. On 7.0.243 it returns true (test "passes" without doing
anything); on a future CN1 release that intercepts the URLs
(whenever #4988 ships), every existing assertion fires.
Local verification:
- mvn install of every module succeeds against 7.0.243.
- mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert all the Android/iOS workarounds added for 8.0-SNAPSHOT
The previous patches in this PR (gradle config rename,
androidTestCompile -> androidTestImplementation fallback removal,
AndroidX migration, ripgrep install, xcodeproj fallback) were
chasing regressions in CN1 master (8.0-SNAPSHOT). Now that we pin
to the released CN1 7.0.243, none of those regressions apply:
master worked with `com.android.support.test:runner:1.0.2` +
`androidTestCompile` and with the original Xcode project layout
before this PR, and 7.0.243 generates the same surface. Restore
the pre-PR scripts so the cn1lib's CI stops fighting upstream
churn that isn't its problem.
Only difference vs the pre-PR baseline: each mvn invocation now
passes -DskipNativeBleHelper=true so the OS-activated Rust helper
profile sits out the native-tests jobs (those runners don't have
cargo and the dedicated native-ble-helper matrix in
native-bluetooth-tests.yml covers it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Restore Android + iOS workarounds: 7.0.243 codegen needs them too
I removed the Android (AndroidX migration, androidTestImplementation,
gradle config rename) and iOS (xcworkspace -> xcodeproj fallback)
workarounds thinking they were only required for the 8.0-SNAPSHOT
codegen regression. Wrong call: between CN1 7.0.71 (which the
original master branch is pinned to and where the pre-PR scripts
pass) and 7.0.243 (the current release), the codegen bumped the
emitted Gradle / Xcode templates enough that the original
"androidTestCompile com.android.support.test:runner:1.0.2" and
the workspace-based xcodebuild invocation no longer apply.
Latest CI failure on this branch with the pre-PR scripts:
> Could not find method androidTestCompile() for arguments
[com.android.support.test:runner:1.0.2] ...
> xcodebuild: error: 'BTDemo.xcworkspace' does not exist.
Restoring the workarounds verbatim from commit bdf9515:
- AndroidX migration (test imports + testInstrumentationRunner +
androidx.test:runner / androidx.test.ext:junit deps),
- useAndroidX + enableJetifier flags in the generated project,
- compile/testCompile/androidTestCompile -> implementation/etc.
rename in the generated build.gradle,
- unconditional androidTestImplementation when injecting the test
dependency block,
- iOS xcodebuild falls back to -project when the workspace isn't
generated.
These workarounds applied to 8.0-SNAPSHOT AND apply to 7.0.243.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* iOS: prefix Cordova plugin methods with cn1_ to disambiguate selectors
The bridge declares no-arg `-(BOOL)stopScan`, `-(BOOL)requestLocation`
etc.; BluetoothLePlugin (a Cordova-style plugin under the hood) had
matching `-(void)stopScan:(CDVInvokedUrlCommand*)command` etc. Strict
selectors -- the colon makes them different -- but clang's
-Wobjc-multiple-method-names treats the *first word* as the conflict
key, and the CN1 ParparVM-generated dispatch shim for the bridge
uses `[ptr requestLocation]` where ptr is `id`. With CN1 7.0.243's
codegen + Xcode 15.4 the warning becomes a hard error:
initializing 'JAVA_BOOLEAN' (aka 'int') with an expression of
incompatible type 'void'
(clang picked the void overload because both first-word matches were
visible and the receiver was untyped).
Fix in the lib: rename all 47 Cordova action methods in
BluetoothLePlugin.{h,m} from `<action>:` to `cn1_<action>:` so no
first-word collisions remain. Update the bridge's executeCommand
dispatcher to build the prefixed selector when looking up the
plugin method by action name. The action-name surface seen by Java
callers is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* iOS: forward-declare BluetoothLePlugin in bridge header
BluetoothNativeBridgeImpl.h was importing BluetoothLePlugin.h directly,
which transitively pulled <CoreBluetooth/CoreBluetooth.h> and the
CoreLocation headers into every file that included the bridge header.
The CN1 ParparVM-generated dispatch shim
(native_com_codename1_bluetoothle_BluetoothNativeBridgeImplCodenameOne.m)
is one such consumer: with `id`-typed receivers and Apple's
`-(void)stopScan` (CBCentralManager) / `-(void)requestLocation`
(CLLocationManager) in scope alongside the bridge's `-(BOOL)stopScan` /
`-(BOOL)requestLocation`, clang picked the void overload and the
JAVA_BOOLEAN return-value assignment failed with "initializing
'JAVA_BOOLEAN' with an expression of incompatible type 'void'".
Use `@class BluetoothLePlugin;` in the header (sufficient for the
`BluetoothLePlugin* _bluetoothPlugin` instance variable declaration)
and move the full `#import` into the .m where the implementation
actually dereferences plugin methods. The codegen shim no longer
sees CoreBluetooth / CoreLocation transitively, and its
`[ptr stopScan]` resolves unambiguously to the bridge's BOOL method.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* iOS: cast `ptr` to the bridge type in the generated CN1 dispatch shim
After the forward-declaration fix in the bridge header, the codegen
shim still failed with the same JAVA_BOOLEAN / void clang error.
Reason: the shim's own (CN1-generated) `#import` block pulls in
CoreLocation and CoreBluetooth directly -- it's not a transitive
include from our header, so `@class BluetoothLePlugin;` in the .h
doesn't help. With `ptr` declared as `id` and Apple's
`-(void)requestLocation` (CLLocationManager) / `-(void)stopScan`
(CBCentralManager) visible at the call site, clang picks the void
overload and the assignment fails.
Post-process the generated shim file
(BTDemo-src/native_..._BluetoothNativeBridgeImplCodenameOne.m) with
a perl substitution that wraps every `[ptr <selector>]` send in a
typed cast:
[ptr requestLocation]
-> [(com_codename1_bluetoothle_BluetoothNativeBridgeImpl*)ptr requestLocation]
Same shape as the existing CN1_THREAD_STATE_MULTI_ARG perl tweak in
this script. The cast forces clang to resolve the selector against
the bridge's interface specifically, so the BOOL return-type
matches the codegen's local declaration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ios native tests: force-link CoreBluetooth via OTHER_LDFLAGS
The CN1 7.0.243 ios-source generator uses different PBXProject UUIDs
for the test target's Frameworks build phase than 8.0-SNAPSHOT, so the
awk-driven CoreBluetooth.framework insertion silently no-ops and the
test bundle fails to link against CBCentralManager. Passing
OTHER_LDFLAGS as an xcodebuild build setting bypasses the pbxproj
entirely and works across generator versions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* device-test: use fully-qualified plugin coordinate for cn1:build
After 'mvn install' writes local com.codenameone group metadata, the
cn1 prefix lookup against that metadata can miss the
codenameone-maven-plugin entry (the install step doesn't update the
prefix mappings), leaving the second invocation to fail with 'No
plugin found for prefix cn1'. Bypassing the prefix resolution with the
fully-qualified groupId:artifactId:version:goal form makes the call
deterministic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Codename One license header to new javase + test files
The new NativeBleBackend, SimulatorBluetoothBackend, and
BluetoothSimulatorHooksTest files shipped without a header. Add the
project's standard GPL v2 + Classpath header so every CN1-authored
file in the lib carries a license declaration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CI: use fully-qualified cn1 plugin coordinate in every script
The android-native-tests job intermittently failed with 'No plugin
found for prefix cn1' after a successful retry — same root cause that
the device-test workflow hit yesterday. The 'mvn install' step writes
local m2 group metadata for com.codenameone that doesn't always list
the codenameone-maven-plugin prefix mapping, leaving the next mvn
invocation unable to resolve cn1:.
Bypassing the prefix lookup with the
groupId:artifactId:version:goal form removes the dependency on the
local metadata being complete. Applied to all three call sites:
run-android-native-tests.sh, run-ios-native-tests.sh, and the
simulator-tests workflow step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SimulatorHookLoaderto the JavaSE port: cn1libs ship aMETA-INF/codenameone/simulator-hooks.propertiesfile and get a menu in the simulator's menu bar, with no Swing types in the contract.JavaSEPort.installMenuconsumes the neutral hook list and emitsJMenu/JMenuItems today — replaceable when the simulator UX is rewritten without breaking any cn1lib.docs/developer-guide/Maven-Creating-CN1Libs.adocdocuments the contract with thecn1-bluetoothcn1lib as a worked example.Contract
Actions are
fqcn#staticMethodreferences topublic static voidno-arg methods. The loader resolves them viaDisplay.class.getClassLoader()and pre-binds aRunnablethat dispatches on the CN1 EDT viaDisplay.callSerially. Multiple cn1libs each contribute one menu; discovery usesClassLoader.getResources()so they merge cleanly.The neutral
SimulatorHookrecord (menuName,label,Runnable) is the contract; today's Swing menu rendering is just one consumer.Test plan
mvn test -pl maven/javase— 7 new tests, all pass:name=.actioncn1-bluetoothlib on the classpath shows a "Bluetooth" menu with all of the lib's items; clicking each fires the matching static method on the CN1 EDT.mvn cn1:test) still passes — the loader is additive and the menu construction path is unchanged for the existing three menus.Companion change
The
cn1-bluetoothcn1lib at codenameone/bluetoothle-codenameone has a companion PR using this mechanism. The lib's CI builds CN1 from source via a composite action so once this lands the lib's PR can be merged on top.🤖 Generated with Claude Code