Skip to content

Fix iOS camera native build break + gate new Camera API on actual usage#5177

Merged
shai-almog merged 11 commits into
masterfrom
fix/camera-native-ios-gating
Jun 5, 2026
Merged

Fix iOS camera native build break + gate new Camera API on actual usage#5177
shai-almog merged 11 commits into
masterfrom
fix/camera-native-ios-gating

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Problem

The new com.codename1.camera native bridge (CN1Camera.m, #5126) failed to compile in cloud builds:

CN1Camera.m: error: call to undeclared function 'fromNSData';
             ISO C99 and later do not support implicit function declarations

A customer build (using the old modal Capture API) hit this in code it never uses, and no CI build ever caught it. Three related root causes:

1. fromNSData never existed

The code assumed a helper symmetric to fromNSString. The real NSData→byte[] helper is nsDataToByteArr(NSData*) in IOSNative.m (it pulls thread state via getThreadLocalData() internally, so it takes no thread-state arg). Fixed both call sites + added the extern declaration.

2. The native gate was keyed to a permission string, not API usage

CN1Camera.m was wrapped in #ifdef INCLUDE_CAMERA_USAGE, which IPhoneBuilder flips whenever an app declares an NSCameraUsageDescription. So any app that set a camera permission for the old Capture API dragged in the new, never-compiled AVFoundation natives — exactly how the break surfaced.

Introduced a dedicated INCLUDE_CN1_CAMERA define that IPhoneBuilder flips only when the bytecode scan sees com.codename1.camera.* (mirrors the existing usesNfc / CN1_INCLUDE_NFC pattern, and stays in lockstep with the AVFoundation framework injection already keyed on the same scan via AiDependencyTable). The old Capture natives in IOSNative.m keep INCLUDE_CAMERA_USAGE.

3. CI never compiled the camera natives

Because no test app referenced the API, the #ifdef compiled it out of every CI iOS/Mac build. scripts/hellocodenameone now calls Camera.isSupported()/getCameras() at startup (no session opened, no permission prompt) and declares the usage description — so the natives compile on every iOS/Mac CI run and CameraX is pulled into the Android build.

Companion PR

The cloud build server runs its own fork of this builder — see the matching BuildDaemon PR for the INCLUDE_CN1_CAMERA flip there (without it, the new camera code would never compile on cloud builds).

Testing

  • mvn -pl codenameone-maven-plugin compile → BUILD SUCCESS.
  • Native gate change verified by inspection against the proven NFC pattern; CI (scripts-ios / scripts-mac-native) will now actually compile CN1Camera.m.

🤖 Generated with Claude Code

The new com.codename1.camera native bridge (CN1Camera.m, #5126) failed to
compile in cloud builds: "call to undeclared function 'fromNSData'". Three
related problems:

1. fromNSData() was never defined anywhere. The real NSData->byte[] helper is
   nsDataToByteArr(NSData*) in IOSNative.m (it pulls thread state internally,
   no thread-state arg). Fixed both call sites + added the extern declaration.

2. The new natives were gated on INCLUDE_CAMERA_USAGE, which IPhoneBuilder
   flips whenever an app declares an NSCameraUsageDescription. So any app that
   set a camera permission for the OLD modal Capture API dragged in the new,
   never-compiled AVFoundation natives -- exactly how this surfaced on a
   customer build that doesn't use the new API. Introduce a dedicated
   INCLUDE_CN1_CAMERA define that IPhoneBuilder flips only when the bytecode
   scan sees com.codename1.camera.* (mirrors usesNfc / CN1_INCLUDE_NFC). The
   old Capture natives in IOSNative.m keep INCLUDE_CAMERA_USAGE.

3. CI never compiled this code because no test app referenced the API, so the
   bug shipped unseen. scripts/hellocodenameone now calls
   Camera.isSupported()/getCameras() at startup (no session, no permission
   prompt) and declares the usage description, so the natives compile on every
   iOS/Mac CI run and CameraX is pulled into the Android build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Android screenshot updates

Compared 124 screenshots: 123 matched, 1 updated.

  • ValidatorLightweightPicker — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    ValidatorLightweightPicker
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ValidatorLightweightPicker.png in workflow artifacts.

Native Android coverage

  • 📊 Line coverage: 13.17% (7876/59798 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.67% (39021/365629), branch 4.54% (1564/34422), complexity 5.59% (1845/33024), method 9.80% (1514/15453), class 15.97% (346/2166)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1137.000 ms
Base64 CN1 encode 214.000 ms
Base64 encode ratio (CN1/native) 0.188x (81.2% faster)
Base64 native decode 1015.000 ms
Base64 CN1 decode 263.000 ms
Base64 decode ratio (CN1/native) 0.259x (74.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

The native iOS/Mac CI builds (which now actually compile CN1Camera.m thanks to
the hellocodenameone camera usage) failed with "error: expected ')'" on four
fromNSString(...) calls. fromNSString takes (threadState, NSString*), so it
needs CN1_THREAD_GET_STATE_PASS_ARG (expands to "getThreadLocalData(),") --
the code used CN1_THREAD_GET_STATE_PASS_SINGLE_ARG (no trailing comma), which
is only valid when thread state is the sole argument.

These four were present in the original error.txt alongside the fromNSData
errors; the earlier commit fixed fromNSData but missed the fromNSString calls
because that code path had never been compiled until CI exercised it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Compared 95 screenshots: 95 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Compared 124 screenshots: 124 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 144 seconds

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 765.000 ms
Base64 CN1 encode 1478.000 ms
Base64 encode ratio (CN1/native) 1.932x (93.2% slower)
Base64 native decode 392.000 ms
Base64 CN1 decode 899.000 ms
Base64 decode ratio (CN1/native) 2.293x (129.3% slower)
Base64 SIMD encode 386.000 ms
Base64 encode ratio (SIMD/native) 0.505x (49.5% faster)
Base64 encode ratio (SIMD/CN1) 0.261x (73.9% faster)
Base64 SIMD decode 410.000 ms
Base64 decode ratio (SIMD/native) 1.046x (4.6% slower)
Base64 decode ratio (SIMD/CN1) 0.456x (54.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 70.000 ms
Image createMask (SIMD on) 23.000 ms
Image createMask ratio (SIMD on/off) 0.329x (67.1% faster)
Image applyMask (SIMD off) 246.000 ms
Image applyMask (SIMD on) 66.000 ms
Image applyMask ratio (SIMD on/off) 0.268x (73.2% faster)
Image modifyAlpha (SIMD off) 142.000 ms
Image modifyAlpha (SIMD on) 72.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.507x (49.3% faster)
Image modifyAlpha removeColor (SIMD off) 186.000 ms
Image modifyAlpha removeColor (SIMD on) 174.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.935x (6.5% faster)
Image PNG encode (SIMD off) 1132.000 ms
Image PNG encode (SIMD on) 881.000 ms
Image PNG encode ratio (SIMD on/off) 0.778x (22.2% faster)
Image JPEG encode 478.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Compared 124 screenshots: 124 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 393 seconds

Build and Run Timing

Metric Duration
Simulator Boot 107000 ms
Simulator Boot (Run) 1000 ms
App Install 21000 ms
App Launch 9000 ms
Test Execution 404000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1453.000 ms
Base64 CN1 encode 2322.000 ms
Base64 encode ratio (CN1/native) 1.598x (59.8% slower)
Base64 native decode 779.000 ms
Base64 CN1 decode 1945.000 ms
Base64 decode ratio (CN1/native) 2.497x (149.7% slower)
Base64 SIMD encode 764.000 ms
Base64 encode ratio (SIMD/native) 0.526x (47.4% faster)
Base64 encode ratio (SIMD/CN1) 0.329x (67.1% faster)
Base64 SIMD decode 598.000 ms
Base64 decode ratio (SIMD/native) 0.768x (23.2% faster)
Base64 decode ratio (SIMD/CN1) 0.307x (69.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 110.000 ms
Image createMask (SIMD on) 27.000 ms
Image createMask ratio (SIMD on/off) 0.245x (75.5% faster)
Image applyMask (SIMD off) 244.000 ms
Image applyMask (SIMD on) 171.000 ms
Image applyMask ratio (SIMD on/off) 0.701x (29.9% faster)
Image modifyAlpha (SIMD off) 466.000 ms
Image modifyAlpha (SIMD on) 332.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.712x (28.8% faster)
Image modifyAlpha removeColor (SIMD off) 663.000 ms
Image modifyAlpha removeColor (SIMD on) 440.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.664x (33.6% faster)
Image PNG encode (SIMD off) 4188.000 ms
Image PNG encode (SIMD on) 1653.000 ms
Image PNG encode ratio (SIMD on/off) 0.395x (60.5% faster)
Image JPEG encode 1663.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Compared 124 screenshots: 124 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 276 seconds

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 1000 ms
App Install 13000 ms
App Launch 5000 ms
Test Execution 264000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 933.000 ms
Base64 CN1 encode 2142.000 ms
Base64 encode ratio (CN1/native) 2.296x (129.6% slower)
Base64 native decode 411.000 ms
Base64 CN1 decode 1223.000 ms
Base64 decode ratio (CN1/native) 2.976x (197.6% slower)
Base64 SIMD encode 672.000 ms
Base64 encode ratio (SIMD/native) 0.720x (28.0% faster)
Base64 encode ratio (SIMD/CN1) 0.314x (68.6% faster)
Base64 SIMD decode 446.000 ms
Base64 decode ratio (SIMD/native) 1.085x (8.5% slower)
Base64 decode ratio (SIMD/CN1) 0.365x (63.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 102.000 ms
Image createMask (SIMD on) 18.000 ms
Image createMask ratio (SIMD on/off) 0.176x (82.4% faster)
Image applyMask (SIMD off) 287.000 ms
Image applyMask (SIMD on) 103.000 ms
Image applyMask ratio (SIMD on/off) 0.359x (64.1% faster)
Image modifyAlpha (SIMD off) 224.000 ms
Image modifyAlpha (SIMD on) 100.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off) 330.000 ms
Image modifyAlpha removeColor (SIMD on) 144.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.436x (56.4% faster)
Image PNG encode (SIMD off) 1590.000 ms
Image PNG encode (SIMD on) 1658.000 ms
Image PNG encode ratio (SIMD on/off) 1.043x (4.3% slower)
Image JPEG encode 730.000 ms

shai-almog and others added 5 commits June 5, 2026 17:56
DesktopModeScreenshotTest was added in #5170 ("Deepen desktop integration")
but no reference screenshots were committed, so every CI screenshot run
reports DesktopMode as "missing reference" (non-fatal, but it means the test
has no baseline). This seeds the Android baseline from the full-resolution
emulator render produced by the scripts-android CI job, so the Android
instrumentation screenshot suite now has a golden to compare against.

The captured screen is the desktop-mode list (title bar + colored rows),
320x640, matching the other Android goldens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… hang)

The strict screenshot guard only counted "missing_actual" entries from the
comparison JSON. Those entries exist only for tests the runner *registered* an
actual for. When a suite hangs and the app is killed mid-run (SIGTERM), every
remaining test is never registered at all -- so it is invisible to that check.

That is exactly how the iOS Metal suite reported "72/72 matched" and passed
while 51 of its 123 stored references never produced an image: it hung on
ChartRotatedScreenshotTest, the app was terminated, and the ~51 tests after it
were silently dropped from the comparison set rather than flagged.

Add a reference-coverage guard: every PNG in the reference dir must have a
corresponding produced actual. Uncaptured references beyond CN1SS_ALLOWED_MISSING
fail with exit 17 (the same code/intent the surrounding comment already
described as "fewer screenshots than stored references"). Tolerance reuses
CN1SS_ALLOWED_MISSING; bypass with CN1SS_SKIP_COUNT_CHECK=1 when seeding a new
baseline set. Implemented bash-3.2-safe for the macOS runners.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…topMode

ChatView_{dark,light} and ToolbarTheme_{dark,light} drifted when #5174
regenerated Themes/iOSModernTheme.res (the Mac Catalyst native theme), changing
the toolbar/chat chrome. The Mac native screenshot goldens were never
regenerated because Themes/*.res changes don't trigger scripts-mac-native
(it keys on native-themes/ios-modern/** and scripts/**). Updated from the
full-resolution Mac Catalyst CI render. Also seeds the missing DesktopMode
baseline (DesktopModeScreenshotTest, added in #5170).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DesktopMode

Same drift as the Mac goldens: #5174 regenerated Themes/iOSModernTheme.res,
changing ChatView_{dark,light} and ToolbarTheme_{dark,light} chrome, but the
iOS screenshot goldens (both the GL set in scripts/ios/screenshots and the
Metal set in scripts/ios/screenshots-metal) were never regenerated because
Themes/*.res changes don't trigger scripts-ios. Updated from the full-res CI
renders (GL from the build-ios job, Metal from build-ios-metal). Also seeds the
missing DesktopMode baseline for both backends.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…5170)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 5, 2026

Compared 11 screenshots: 11 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

…wait

The iOS Metal screenshot suite intermittently hung during readback (observed
on ChartRotatedScreenshotTest): the app stalls with no crash and no Metal
validation error, the runner SIGTERMs it, and every test after the stall is
silently dropped (the suite reported 72/123 "matched" and still passed before
the cn1ss reference-coverage guard was added).

Root cause class: [cb waitUntilCompleted] in the mutable-image readback path
blocks the calling thread forever when the command buffer was created (a
mutable image's Begin) but never committed (readback racing ahead of End), and
blocks indefinitely on a genuinely stuck GPU buffer. Either mode is an
unrecoverable hang.

Replace the three unbounded waits (CN1MetalFlushMutableImageSync + the two
blit-to-shared readbacks) with cn1MetalWaitCommandBufferBounded():
  - returns immediately for an uncommitted buffer (NotEnqueued/Enqueued) --
    it will never be submitted, so there is nothing to await;
  - polls status with an 8s deadline for committed/scheduled buffers as a
    backstop against a stuck GPU.
On a non-completed buffer the caller reads back whatever is in the texture, so
the affected screenshot fails visibly against its golden instead of hanging the
whole suite. Verified the file compiles clean (clang -fsyntax-only, modules,
iphonesimulator26.2 SDK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shai-almog and others added 3 commits June 5, 2026 19:14
… suite

The reference-coverage guard flagged scripts/javase/screenshots as having 12
references but the JavaSE simulator-integration suite only produces 11 (window
modes, inspector, network monitor, test recorder, 4 native themes).

graphics-inscribed-triangle-grid.png is a device-runner graphics-test golden
(it lives in every platform's screenshots dir) that was accidentally committed
into the javase simulator-integration dir in #4939. That suite never emits a
graphics-* screenshot and is the only consumer of scripts/javase/screenshots,
so the file is a true orphan. Removing it makes the dir match what the suite
produces. (This is exactly the kind of silent cruft the new guard surfaces.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reference-coverage guard failed build-ios: scripts/ios/screenshots had 128
references but the suite produces 124. The 4 extras are stale CamelCase goldens
-- GraphicsPipeline, GraphicsShapesAndGradients, GraphicsStateAndText,
GraphicsTransformations -- left over from before the graphics tests were renamed
to kebab-case (graphics-*). They exist only in the iOS GL dir (no other
platform), no test produces them, and the current kebab-case graphics-* goldens
are produced and matched. The old missing_actual check never saw these (no
test registers an actual for them), so they shipped silently; the new guard
surfaces them. Removing makes the GL dir match what the suite emits.

The TimeApiTest / LocalNotificationOverrideTest CN1SS:ERR failures visible in
the same job are pre-existing on master (master's build-ios is green with them)
and unrelated to this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed tests)

The reference-coverage guard added earlier compares every stored golden against
the produced actuals and fails when any golden has no actual. That conflates two
different things: a hung/killed suite (the metal case it was meant to catch) AND
tests that a platform intentionally skips but still keeps goldens for. The latter
false-failed the JavaScript suite -- HTML5 parks ~14 tests (ChatView, ChatInput,
Sheet, chart-rotated-pie, chart-transform, css-gradients, ...) via
Cn1ssDeviceRunner.shouldForceTimeoutInHtml5 yet keeps their goldens, so the guard
reported them as "uncaptured" even though the JS comparison itself was green
(95/95 matched).

Reverting it. The metal screenshot hang it was meant to backstop is already
prevented at the source by the bounded command-buffer wait in CN1Metalcompat.m
(this PR). A precise, skip-safe hang detector (require CN1SS:SUITE:FINISHED in the
device log) is the correct replacement and can be added separately rather than
blocking this fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 349a7f3 into master Jun 5, 2026
30 of 31 checks passed
@shai-almog shai-almog deleted the fix/camera-native-ios-gating branch June 5, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant