Skip to content

Enforce Apple's iOS 27 launch-screen and scene-lifecycle rules - #5855

Merged
shai-almog merged 4 commits into
masterfrom
ios27-launch-metadata-enforcement
Sep 18, 2026
Merged

shai-almog merged 4 commits into
masterfrom
ios27-launch-metadata-enforcement

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Fixes #5839.

Apps linked with the iOS 27 SDK are rejected unless the bundle declares a launch screen, and fail to launch unless they adopt the UIScene lifecycle (iOS & iPadOS 27 release notes, UIKit 168247372 and 141837548; TN3187, TN3192). The default Maven build already satisfies both. Two opt-out paths did not, and neither said so.

ios.generateSplashScreens=true

It skipped the launch-key injection outright, and the translator's template-Info.plist declares none of the four keys either, so the bundle went out with no launch metadata at all.

The hint named the legacy Default*.png generator. That generator is gone: generateLaunchScreen() now runs unconditionally and writes both Launch.Foreground.png and LaunchScreen.storyboard whatever the hint says. So the suppression was not preserving a custom launch experience -- there was nothing left to preserve, and nothing else here would have filled the key in. UIRequiresFullScreen, which the hint does still inject, is not a substitute (TN3192).

The hint keeps its iPad multitasking effect and now logs a deprecation notice. No build that works today stops working.

ios.uiscene=false

It skipped UIApplicationSceneManifest unless the app used CarPlay, and kept NSMainNibFile. Refused outright against SDK 27 and later.

Refused at the top of build() rather than in injectToPlist: the same hint selects CN1_USE_UI_SCENE in the generated Objective-C, so by the time the plist is written an hour of build has already been spent on an archive Apple documents as not launching. Forcing the lifecycle back on instead would be a silent override of an explicit hint, which is the one thing the issue asks not to do.

Conditional on the SDK, not on the Xcode

Both rules read the major of the SDK this build actually links against (activeIosSdkName), falling back to the Xcode version only when xcrun cannot answer. That fallback is sound in the direction it is used: the two have only matched since the Xcode 26 renumbering, and every Xcode predating it reports a version far below the floor being compared against, so a wrong answer there can never turn the rules on for a build the SDK exempts. An SDK 26 build is unchanged in every path.

The version parse returns the major alone. "27.2" read by gathering its digits is 272, which compares greater than every floor in the file at once -- the API 37 trap, which has already cost a fix once.

The backstop

launchMetadataRejection reads the finished Info.plist, not the generator strings. Every producer in injectToPlist decides whether to add a key and steps aside when the injection already names one, so an ios.plistInject that mentions a launch key inside a comment, or declares it below the root dictionary, silences the generator and leaves UIKit nothing to read. Only the finished document tells those apart from a real declaration. All four of Apple's launch keys are accepted -- the check is that a launch experience exists, never that it is ours.

This also covers CI by construction: scripts/build-ios-app.sh cannot produce a non-compliant bundle without failing.

Also here

desktop.themeMode is declared in the build-hint catalog. It went in undeclared with the native desktop themes (#5845) and fails check-build-hint-catalog on master today, which would have failed this PR's CI too.

Verification

  • IPhoneBuilderLaunchMetadataTest, 13 cases: the version parse (including the unversioned "unknown" answer), both refusals and their SDK scoping, all four accepted launch keys, and the two ways a key can be present without being a declaration (nested below the root, named only in a comment).
  • Full codenameone-maven-plugin suite: 2379 tests, 0 failures.
  • SpotBugs on codenameone-maven-plugin: 0 findings.
  • check-build-hint-catalog.sh (533 hints, all described), gen-build-hint-annotations.sh --check, check-copyright-headers.sh --base master (5 files), check-control-characters.py, validate-java25-markdown-docs.sh.
  • Developer guide: Vale 0 alerts, LanguageTool 0 matches on the changed prose, Asciidoctor lint clean, plus the structure / xref / code-block / alt-text / API-name checks.
  • Faithfulness probe: the validator accepts a real generated HelloCodenameOne-Info.plist unchanged, and refuses that same document when either the launch key or the scene manifest is removed from it.

Not covered, as the issue notes: no iOS 27 runtime leg exists, so cold-launch and resize behaviour on a real iOS 27 device or simulator is still unverified. The BuildDaemon half is a separate repo and is not in this PR.

🤖 Generated with Claude Code

Apps linked with the iOS 27 SDK are rejected unless the bundle declares a
launch screen, and fail to launch unless they adopt the UIScene lifecycle
(iOS & iPadOS 27 release notes, UIKit 168247372 and 141837548). The default
Maven build already satisfies both. Two opt-out paths did not, and neither
said so:

- ios.generateSplashScreens=true skipped the launch-key injection outright.
  The hint named the legacy Default*.png generator, and that generator is
  gone -- generateLaunchScreen() runs unconditionally now -- so all the
  suppression still did was produce a bundle with no launch key in it at
  all, which nothing else here would fill in. UIRequiresFullScreen, which
  the hint does still inject, is not a substitute (TN3192). The hint keeps
  its iPad multitasking effect and logs a deprecation notice.

- ios.uiscene=false skipped UIApplicationSceneManifest. Refused outright
  against SDK 27 and later, and refused at the top of build() rather than in
  injectToPlist: the same hint selects CN1_USE_UI_SCENE in the generated
  Objective-C, so by the time the plist is written an hour has been spent on
  an archive Apple documents as not launching. Forcing the lifecycle back on
  instead would be a silent override of an explicit hint.

Both conditional on the SDK major, read from the SDK this build actually
links against and only falling back to the Xcode version when xcrun cannot
answer -- sound in the direction it is used, since every Xcode predating the
26 renumbering reports far below the floor. An SDK 26 build is unchanged.

The backstop is launchMetadataRejection, which reads the finished
Info.plist rather than the generator strings. Every producer here decides
whether to ADD a key and steps aside when the injection already names one,
so an ios.plistInject that mentions a launch key in a comment, or declares
it below the root dictionary, silences the generator and leaves UIKit
nothing to read. Only the finished document can tell those apart. All four
of Apple's launch keys are accepted; the check is that a launch experience
exists, not that it is ours.

Also declares desktop.themeMode in the build-hint catalog. It went in
undeclared with the native desktop themes and fails check-build-hint-catalog
on master, which would have failed this PR's CI too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-18T05:26:12.190642Z 7dcc84c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a2d6a6e12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// resolves it there but fails to render the same image from an
// actool compiled imageset, so do NOT move it into
// Images.xcassets.
if (!inject.contains("UILaunchScreen") && !inject.contains("UILaunchStoryboardName")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor custom UILaunchStoryboards before adding the default

When a project uses ios.generateSplashScreens=true to suppress the generated launch metadata and supplies a valid UILaunchStoryboards dictionary through ios.plistInject, removing the outer hint guard now reaches this condition, but neither substring check recognizes that plural key. The builder consequently appends its own UILaunchScreen, so the developer's custom launch configuration competes with or is bypassed by the generated Launch.Foreground screen despite the new documentation saying any of the four accepted launch keys can supply the custom experience. Check all four accepted top-level launch keys before injecting the default.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No point in keeping deprecated shit

Comment on lines +17103 to +17105
String rejection = launchMetadataRejection(b.toString(), iosSdkMajor);
if (rejection != null) {
throw new BuildException(rejection);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the launch-metadata rejection message

When this validation rejects an SDK 27 plist, the BuildException propagates to the injectToPlist() call in build(), whose surrounding catch (Exception) replaces it with a cause-less BuildException("Failed to inject into plist"). The detailed message added here—which identifies the missing key and the ios.plistInject remedy—is therefore never shown to the developer, leaving a newly enforced build failure with no actionable diagnosis. Re-throw existing BuildExceptions or preserve the original exception as the cause/message.

Useful? React with 👍 / 👎.

Comment on lines +2184 to +2185
String lifecycleRejection = sceneLifecycleOptOutRejection(
request.getArg("ios.uiscene", "true"), iosSdkMajor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize UIScene values consistently before enforcing the rule

The new preflight rejects only values that become exactly false after trimming, while the actual UIScene branches later enable the lifecycle only when the raw value is exactly true ignoring case. Thus a value such as ios.uiscene=true or ios.uiscene=0 passes this check but disables CN1_USE_UI_SCENE, retains NSMainNibFile, and omits the phone window scene; for a CarPlay app, usesCar still adds a manifest, so the final presence-only validator also passes and the SDK 27 build can produce an app that terminates at launch. Normalize the hint once for all consumers, or make this rejection predicate match the builder's effective opt-out semantics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove entirely

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

Follow-up on review: the first pass added enforcement around the opt-outs
instead of deleting them. There is nothing left for either hint to select, so
they are gone rather than deprecated, and so is the code each one guarded.

ios.uiscene is removed from the @iOS annotation, so an app that still sets it
fails to COMPILE, and from the build-hint catalog, so a build that sets it in
codenameone_settings.properties is refused by name. What it selected is gone
with it: CN1_USE_UI_SCENE no longer exists, the scene delegate and the app
delegate's scene hooks are compiled unconditionally, the scene manifest is
written on every build, and the legacy didFinishLaunching path that installed
a root view controller into a window UIKit had not created yet is deleted.

NSMainNibFile went with it. It was already stripped from every plist this
builder produced -- MainWindow.xib auto-instantiates a UIWindow with
visibleAtLaunch=YES, which under the scene lifecycle is a window with no scene
and a launch FrontBoard terminates -- so the template no longer declares it,
the strip is gone, and MainWindow.xib and MainWindowMETAL.xib are deleted
along with the four builders' exclusion lists that named them. The IBOutlet
qualifiers on the app delegate went too; nothing loads a nib into that class.

ios.generateSplashScreens is removed. It named the pre-storyboard Default*.png
generator, which this builder has not contained for years; all it still did
was suppress the launch screen and disable iPad multitasking. Every build now
declares UILaunchScreen.

ios.launchStoryboardName is removed as a consequence: it named the storyboard
for UILaunchStoryboardName, which only the legacy lifecycle emitted. Keeping a
hint nothing reads is the failure the build-hint catalog exists to prevent.

All three are refused by name, with a message that says what replaced them,
and the refusal reads the request's supplied keys rather than calling getArg
-- getArg is how a hint is READ, and a removed hint has no reader. Several
in-tree users had to go first: our own build-ios-app.sh passed ios.uiscene on
every CI iOS build, and three sample apps set it through @iOS.

Also from review:

- The generated launch key is skipped when the injection names ANY of Apple's
  four launch keys, not the two this builder can emit. A project supplying
  UILaunchStoryboards through ios.plistInject was getting a second launch
  experience appended beside its own.
- A BuildException raised inside injectToPlist is re-thrown instead of being
  replaced by a cause-less "Failed to inject into plist", which threw away the
  diagnosis the refusal exists to give. The generic wrap now keeps its cause.
- desktop.themeMode is NOT added to the catalog. It is already declared by the
  DesktopBuild annotation; check-build-hint-catalog reported it missing
  locally because it reads the first cn1-build-hints.json it finds and
  maven/javase/target held a stale one from before that hint existed.

The developer guide's launch-screen section described a storyboard the launch
has not used since UIScene became the default, and a Launch.Background.png
only that storyboard reads. It now says what the build actually declares and
how to opt into the storyboard through ios.plistInject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5718acb476

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +587 to +589
// The root view controller is installed by CodenameOne_GLSceneDelegate, not here: under
// the scene lifecycle self.window is nil at this point, because UIKit has not connected
// a scene yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Generate scene metadata for standalone iOS translations

When ByteCodeTranslator is invoked directly in ios mode, handleAppleOutput() copies template-Info.plist into the generated Xcode project without the later IPhoneBuilder.injectToPlist() pass. That template now contains neither UIApplicationSceneManifest nor launch metadata, while this code no longer installs the root controller through the legacy lifecycle and the MainWindow NIB was deleted. Consequently, standalone translated projects never connect CodenameOne_GLSceneDelegate, launch without a window, and are also invalid when linked with the iOS 27 SDK; generate the required metadata in the translator path or retain a working fallback.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD float-mul (64K x300) java 72ms / native 4ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 173.000 ms
Base64 CN1 decode 123.000 ms
Base64 SIMD encode 103.000 ms
Base64 encode ratio (SIMD/CN1) 0.595x (40.5% faster)
Base64 SIMD decode 94.000 ms
Base64 decode ratio (SIMD/CN1) 0.764x (23.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 11.000 ms
Image createMask (SIMD on) 6.000 ms
Image createMask ratio (SIMD on/off) 0.545x (45.5% faster)
Image applyMask (SIMD off) 68.000 ms
Image applyMask (SIMD on) 67.000 ms
Image applyMask ratio (SIMD on/off) 0.985x (1.5% faster)
Image modifyAlpha (SIMD off) 48.000 ms
Image modifyAlpha (SIMD on) 63.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.313x (31.3% slower)
Image modifyAlpha removeColor (SIMD off) 57.000 ms
Image modifyAlpha removeColor (SIMD on) 63.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.105x (10.5% slower)

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 5ms = 12.6x speedup
SIMD float-mul (64K x300) java 67ms / native 5ms = 13.4x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 198.000 ms
Base64 CN1 decode 138.000 ms
Base64 SIMD encode 99.000 ms
Base64 encode ratio (SIMD/CN1) 0.500x (50.0% faster)
Base64 SIMD decode 100.000 ms
Base64 decode ratio (SIMD/CN1) 0.725x (27.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 26.000 ms
Image createMask ratio (SIMD on/off) 2.600x (160.0% slower)
Image applyMask (SIMD off) 59.000 ms
Image applyMask (SIMD on) 77.000 ms
Image applyMask ratio (SIMD on/off) 1.305x (30.5% slower)
Image modifyAlpha (SIMD off) 71.000 ms
Image modifyAlpha (SIMD on) 69.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.972x (2.8% faster)
Image modifyAlpha removeColor (SIMD off) 65.000 ms
Image modifyAlpha removeColor (SIMD on) 79.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.215x (21.5% slower)

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.24% (9184/99417 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.99% (47193/524920), branch 3.55% (1767/49739), complexity 3.52% (1868/53026), method 5.43% (1514/27888), class 10.89% (407/3736)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.24% (9184/99417 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.99% (47193/524920), branch 3.55% (1767/49739), complexity 3.52% (1868/53026), method 5.43% (1514/27888), class 10.89% (407/3736)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 219ms / native 121ms = 1.8x speedup
SIMD float-mul (64K x300) java 80ms / native 83ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 59.000 ms
Base64 CN1 decode 67.000 ms
Base64 native encode 318.000 ms
Base64 encode ratio (CN1/native) 0.186x (81.4% faster)
Base64 native decode 260.000 ms
Base64 decode ratio (CN1/native) 0.258x (74.2% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

…te (#5855 review)

ByteCodeTranslator copies template-Info.plist into the generated project and
nothing else writes to it: injectToPlist is IPhoneBuilder's, so a project
translated directly gets exactly what the template says. That was survivable
while the port's natives could still run the legacy lifecycle -- the template's
NSMainNibFile and the absent scene manifest agreed with each other, because the
CN1_USE_UI_SCENE define was IPhoneBuilder's too and was absent along with them.

Deleting the legacy lifecycle from the natives broke that agreement in one
direction only. The natives now require a scene manifest to get a window at
all, and the only producer that writes one is the builder. A native that
depends on a key one of its two producers writes is a key it can lose.

So the template declares both, where NSMainNibFile used to be, and the builder
strips them back out before writing its own -- it has to write its own, because
CarPlay adds a second role and ios.plistInject can replace either outright, and
a property list takes the LAST of a duplicated key, which would make the one
UIKit reads a function of where the injection was spliced. All four launch keys
come out, not just the one the template uses: whichever is in that file is ours,
since ios.plistInject is a separate fragment added after the strip.

The launch-key half of this was already true before this branch -- the template
has never carried one -- so this also closes the standalone path against the
iOS 27 rejection the rest of the PR is about.

handleIosOutputGeneratesProjectStructure now asserts the generated plist wires
CodenameOne_GLSceneDelegate and declares a launch key. Verified it bites:
removing the two keys from the template again fails it on the scene manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 4ms = 13.7x speedup
SIMD float-mul (64K x300) java 54ms / native 4ms = 13.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.264x (73.6% faster)
Base64 SIMD decode 61.000 ms
Base64 decode ratio (SIMD/CN1) 0.477x (52.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 25.000 ms
Image applyMask (SIMD on) 19.000 ms
Image applyMask ratio (SIMD on/off) 0.760x (24.0% faster)
Image modifyAlpha (SIMD off) 41.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.293x (70.7% faster)
Image modifyAlpha removeColor (SIMD off) 22.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.591x (40.9% faster)

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 156.000 ms
Base64 CN1 decode 92.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.167x (83.3% faster)
Image applyMask (SIMD off) 51.000 ms
Image applyMask (SIMD on) 39.000 ms
Image applyMask ratio (SIMD on/off) 0.765x (23.5% faster)
Image modifyAlpha (SIMD off) 34.000 ms
Image modifyAlpha (SIMD on) 27.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.794x (20.6% faster)
Image modifyAlpha removeColor (SIMD off) 36.000 ms
Image modifyAlpha removeColor (SIMD on) 27.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.750x (25.0% faster)

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7dcc84c71c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// plistForMacSlice.
if ((useUISceneManifest || usesCar)
&& !plistDeclaresKey(inject, "UIApplicationSceneManifest")) {
if (!plistDeclaresKey(inject, "UIApplicationSceneManifest")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route pending share content through scene activation

When an app has CN1ShareAppGroup configured and is activated after receiving share-extension content, this unconditional scene manifest causes UIKit to use sceneDidBecomeActive: instead of applicationDidBecomeActive:. The scene delegate only invokes cn1ApplicationDidBecomeActive, while the sole call to getPendingSharedContent/fireSharedContentFromNative remains after that wrapper in applicationDidBecomeActive: (CodenameOne_GLAppDelegate.m:831-847), so migrated ios.uiscene=false apps no longer deliver shared content to Java. Move that delivery into the shared activation path or invoke it from the scene delegate.

Useful? React with 👍 / 👎.

Comment on lines +10403 to +10407
"ios.generateSplashScreens has been removed. It selected the legacy Default*.png "
+ "splash-image generator, which iOS stopped using long ago and which this builder no "
+ "longer contains; all the hint still did was suppress the launch screen, and an app "
+ "linked with the iOS 27 SDK is rejected without one (iOS & iPadOS 27 release notes, "
+ "UIKit 168247372). Delete it from your build hints. Every build now declares "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve fullscreen behavior in the splash-hint migration

For projects that set ios.generateSplashScreens=true, the removed branch also forced multitasking=false, which caused UIRequiresFullScreen to be emitted; it did not merely suppress the launch screen as this rejection message states. Following the message and simply deleting the hint now falls back to ios.multitasking=true, unexpectedly opting those iPad apps into split-screen behavior. Tell these users to add ios.multitasking=false (or otherwise preserve that side effect) when migrating.

Useful? React with 👍 / 👎.

Comment on lines 768 to 772
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0))
{
UISceneConfiguration *sceneConfiguration = [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
sceneConfiguration.delegateClass = [CodenameOne_GLSceneDelegate class];
return sceneConfiguration;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the CarPlay delegate for CarPlay scene sessions

When an app references com.codename1.car, the generated manifest correctly assigns CPTemplateApplicationSceneSessionRoleApplication to CodenameOne_CarPlaySceneDelegate, but this now-unconditional callback replaces that configuration for every role with one whose delegate is CodenameOne_GLSceneDelegate. A CarPlay scene is not a UIWindowScene, so that delegate immediately returns from willConnectToSession: and never initializes the CarPlay interface. Branch on connectingSceneSession.role to return the CarPlay delegate/configuration, or allow the manifest's CarPlay configuration to be selected.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 84ms / native 12ms = 7.0x speedup
SIMD float-mul (64K x300) java 49ms / native 3ms = 16.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 154.000 ms
Base64 CN1 decode 93.000 ms
Base64 native encode 524.000 ms
Base64 encode ratio (CN1/native) 0.294x (70.6% faster)
Base64 native decode 228.000 ms
Base64 decode ratio (CN1/native) 0.408x (59.2% faster)
Base64 SIMD encode 50.000 ms
Base64 encode ratio (SIMD/CN1) 0.325x (67.5% faster)
Base64 SIMD decode 44.000 ms
Base64 decode ratio (SIMD/CN1) 0.473x (52.7% faster)
Base64 encode ratio (SIMD/native) 0.095x (90.5% faster)
Base64 decode ratio (SIMD/native) 0.193x (80.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.125x (87.5% faster)
Image applyMask (SIMD off) 44.000 ms
Image applyMask (SIMD on) 30.000 ms
Image applyMask ratio (SIMD on/off) 0.682x (31.8% faster)
Image modifyAlpha (SIMD off) 33.000 ms
Image modifyAlpha (SIMD on) 26.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.788x (21.2% faster)
Image modifyAlpha removeColor (SIMD off) 31.000 ms
Image modifyAlpha removeColor (SIMD on) 27.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.871x (12.9% faster)

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 698 total, 0 failed, 57 skipped

Benchmark Results

  • Execution Time: 15721 ms

  • Hotspots (Top 20 sampled methods):

    • 6.83% com.codename1.tools.translator.BytecodeMethod.equals (84 samples)
    • 6.59% java.util.ArrayList.indexOf (81 samples)
    • 4.31% java.lang.StringBuilder.append (53 samples)
    • 3.99% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (49 samples)
    • 3.58% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (44 samples)
    • 2.93% java.lang.System.identityHashCode (36 samples)
    • 2.60% org.objectweb.asm.tree.analysis.Analyzer.analyze (32 samples)
    • 2.20% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (27 samples)
    • 2.12% java.lang.String.equals (26 samples)
    • 2.03% com.codename1.tools.translator.Parser.classIndex (25 samples)
    • 1.95% java.io.FileOutputStream.open0 (24 samples)
    • 1.71% com.codename1.tools.translator.BytecodeMethod.optimize (21 samples)
    • 1.71% java.util.HashMap.hash (21 samples)
    • 1.55% com.codename1.tools.translator.BytecodeMethod.updateInlinableFieldDependencies (19 samples)
    • 1.46% java.util.IdentityHashMap$KeySet.toArray (18 samples)
    • 1.38% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (17 samples)
    • 1.38% java.lang.StringCoding.encode (17 samples)
    • 1.22% org.objectweb.asm.ClassReader.readCode (15 samples)
    • 1.14% java.util.HashMap.putVal (14 samples)
    • 1.06% com.codename1.tools.translator.BytecodeMethod.appendMethodC (13 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 56000 ms
Simulator Boot (Run) 1000 ms
App Install 13000 ms
App Launch 33000 ms
Test Execution 341000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 65ms / native 6ms = 10.8x speedup
SIMD float-mul (64K x300) java 73ms / native 3ms = 24.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 152.000 ms
Base64 native encode 483.000 ms
Base64 encode ratio (CN1/native) 0.509x (49.1% faster)
Base64 native decode 542.000 ms
Base64 decode ratio (CN1/native) 0.280x (72.0% faster)
Base64 SIMD encode 70.000 ms
Base64 encode ratio (SIMD/CN1) 0.285x (71.5% faster)
Base64 SIMD decode 100.000 ms
Base64 decode ratio (SIMD/CN1) 0.658x (34.2% faster)
Base64 encode ratio (SIMD/native) 0.145x (85.5% faster)
Base64 decode ratio (SIMD/native) 0.185x (81.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.300x (70.0% faster)
Image applyMask (SIMD off) 57.000 ms
Image applyMask (SIMD on) 46.000 ms
Image applyMask ratio (SIMD on/off) 0.807x (19.3% faster)
Image modifyAlpha (SIMD off) 44.000 ms
Image modifyAlpha (SIMD on) 40.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.909x (9.1% faster)
Image modifyAlpha removeColor (SIMD off) 62.000 ms
Image modifyAlpha removeColor (SIMD on) 42.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.677x (32.3% faster)

…iew)

Raised against the BuildDaemon PR: if a project can pin an older iOS port while
running a newer builder, it has the same exposure. Checked the Maven plugin.

Through supported configuration it cannot. codenameone-ios:bundle is a
dependency OF codenameone-maven-plugin, version-managed to the plugin's own
version, and Executor.getResourceAsStream is Executor.class.getResourceAsStream
-- the plugin realm. The generated app pom declares the plugin with no
<dependencies> block (the only plugin-level one it has is on surefire, pinning
the CN1 plugin itself), and no mojo resolves codenameone-ios from the project.
So cn1.version governs codenameone-core on the compile classpath and has no
say in which iOSPort.jar/nativeios.jar is unzipped.

A hand-written <dependencies> override on the plugin declaration does reach it,
and Maven honours it. That combination used to be merely odd; deleting the
legacy lifecycle from the port's natives made it fatal and silent, so it is
refused here instead.

Measured on the real artifacts rather than argued:

- codenameone-ios 7.0.214's nativeios.jar carries no CodenameOne_GLSceneDelegate
  at all.
- The pre-branch 8.0 bundle carries it behind #ifdef CN1_USE_UI_SCENE, a define
  this plugin no longer injects. Compiling those exact sources against the iOS
  27 SDK: 19432 bytes of object code with the define, 1248 without -- no class,
  no methods, no diagnostic.

Either way the Info.plist this build writes names CodenameOne_GLSceneDelegate as
the scene delegate, so UIKit looks up a class the binary does not contain. The
build stays green and the app fails at launch.

Worth recording because it corrects the obvious guess: the main NIB is not the
fallback it looks like. An older bundle still ships MainWindow.xib -- 7.0.214
does -- so deleting it from this tree is not what removes the legacy path for a
pinned port. NSMainNibFile comes from the translator template, which is
codenameone-parparvm at the PLUGIN's version, so the key that would load that
nib is gone whatever the port carries.

The reverse skew needs nothing: a newer port under an older plugin fails loudly
already, because that plugin's Metal branch copies MainWindowMETAL.xib and
Executor.copy opens the source with new FileInputStream.

Verified the guard against all three real inputs: 7.0.214 refused, stale 8.0
refused, this branch's nativeSources accepted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit 2697dcf into master Sep 18, 2026
86 of 87 checks passed
@shai-almog
shai-almog deleted the ios27-launch-metadata-enforcement branch September 18, 2026 07:40
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.

[Platform deprecation][Apple] Enforce iOS 27 launch prerequisites before 2026-09-09

1 participant