Skip to content

#848 apple klib cross compilation - #895

Open
ExNDY wants to merge 8 commits into
icerockdev:developfrom
ExNDY:#848-apple-klib-cross-compilation
Open

ExNDY wants to merge 8 commits into
icerockdev:developfrom
ExNDY:#848-apple-klib-cross-compilation

Conversation

@ExNDY

@ExNDY ExNDY commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Depends on #894

Closes #848.
Closes #850.
Closes #863.

Resolved issues

Summary

Enable Apple-target KLib compilation on Windows and Linux with Kotlin 2.2.20+
when the project is otherwise eligible for Kotlin/Native cross-compilation, and
support AGP's dedicated Kotlin Multiplatform Android library target.

Previously, Apple resource packaging had two host-specific problems:

  • the KLib unique_name was used directly as the bundle directory name, so a
    value such as kotlin-2-tests:shared failed on Windows because : is not a
    valid file-name character;
  • PackAppleResourcesToKLibAction invoked Xcode's actool during KLib
    compilation, although Xcode is only available on macOS.

This change makes KLib resource packaging host-independent and postpones asset
catalog compilation until the final Apple framework or executable is linked on
macOS.

It also supports AGP's dedicated Android KMP library target. That target reports
the Kotlin platform type as jvm; moko-resources now recognizes the concrete
target as Android and selects the Android resource generator explicitly.

Changes

Android KMP library target support

  • Recognize KotlinMultiplatformAndroidLibraryTarget when the
    com.android.kotlin.multiplatform.library plugin is applied.
  • Normalize that target's reported jvm platform to moko-resources'
    androidJvm generator type.
  • Keep ordinary JVM targets on the JVM generator path, including projects that
    do not apply any Android plugin.
  • Continue wiring generated Android resources through AGP's generated-source
    directory API, so resource packaging has an explicit task dependency.

Portable Apple bundle names

  • Stop using the KLib unique_name as a file-system path component.
  • Use the existing CFBundleIdentifier as the bundle directory name. The
    identifier is derived from <resourcesPackage>.<compilationName>, so the
    physical name is deterministic, readable, and available from public plugin
    configuration.
  • Runtime bundle discovery continues to use the same identifier from
    Info.plist.

Example:

kotlin-2-tests:shared.bundle
    -> template.composemultiplatform.shared.main.bundle

Host-independent KLib packaging

  • Remove xcrun actool from PackAppleResourcesToKLibAction.
  • Continue packaging Info.plist, localizations, files, and other resources
    with JVM APIs.
  • Store generated Assets.xcassets in the Apple KLib without compiling it.
  • Produce the same KLib resource layout on macOS, Windows, and Linux.

Deferred macOS asset compilation

  • Add a shared Apple asset catalog finalizer.
  • When linking a final Apple framework or executable on macOS, copy each bundle
    from the current module and dependency KLibs, then:
    • compile an embedded Assets.xcassets with xcrun actool;
    • verify that Assets.car was produced;
    • remove the raw catalog only after successful compilation.
  • Leave bundles without a raw catalog unchanged. This preserves compatibility
    with older KLibs that already contain Assets.car.
  • Use argument-safe ProcessBuilder invocation and include command output in a
    focused Gradle failure when actool fails.

Automatic CocoaPods dummy-framework resources

  • Configure the generated podspec with the framework bundle glob automatically.
    An explicit user-provided extraSpecAttributes["resource"] value still takes
    precedence.
  • Populate Kotlin CocoaPods' DummyFrameworkTask with placeholder bundle
    directories, so CocoaPods sees resource bundles during pod install before
    the real framework can be linked.
  • Derive placeholder names from:
    • the current module's <resourcesPackage>.main identifier;
    • transitive local Gradle project dependencies that apply moko-resources;
    • resource bundles found in available packed or unpacked dependency KLibs.
  • Do not add KLib producer tasks as dummy-framework dependencies. A real
    framework may depend on Pods that are unavailable until after pod install.
  • Remove stale bundle directories only when they contain the
    moko-resources placeholder marker; real or user-managed bundles are never
    deleted.
  • Remove the custom DummyFrameworkTask action and manual CocoaPods resource
    attribute from samples/compose-resources-gallery.

Apple platform mapping

Select the actool platform from the Kotlin/Native link target instead of
always using iphoneos:

Kotlin/Native target actool platform
iOS device iphoneos
iOS simulator iphonesimulator
macOS macosx
tvOS device appletvos
tvOS simulator appletvsimulator
watchOS device watchos
watchOS simulator watchsimulator

The configured iOS minimum deployment target is forwarded for iOS device and
simulator catalogs. Platform names and the deployment-target requirement are
represented by a dedicated enum rather than repeated string comparisons.

Regression coverage

  • Add focused tests for Android KMP target normalization and ordinary JVM
    target preservation.
  • Add samples/android-kmp-library, matching the Last version Not Work build error #850 configuration with
    Gradle 8.13, Kotlin 2.2.20, and AGP 8.13.0.
  • Build that sample in the dedicated check-android-kmp-library job on macOS,
    Windows, and Linux and
    assert that:
    • Android output contains res/values/multiplatform_strings.xml and an
      R.string accessor;
    • Android res/ contains no JVM .properties file;
    • the sibling JVM target still contains its .properties bundle and
      ClassLoader accessor.
  • Add unit tests for identifier-based bundle naming, generated Info.plist,
    and Apple target mapping.
  • Add a compatibility test proving that an existing Assets.car is preserved
    without invoking actool.
  • Add tests for bundle discovery in packed KLibs, unpacked KLib roots, and the
    unpacked-manifest file shape exposed by Kotlin/Native tasks.
  • Add tests for dummy bundle creation and stale-placeholder cleanup while
    preserving real bundle directories.
  • Add an image resource to samples/kotlin-2-tests.
  • Extend the sample check to verify:
    • Apple KLib compilation succeeds;
    • the KLib bundle name contains no Windows-invalid : character;
    • the KLib contains raw Assets.xcassets and no Assets.car;
    • a linked macOS-produced framework contains Assets.car and no raw catalog.
  • Ensure mavenLocal() has priority in the regression sample so CI exercises
    the plugin artifact built by the current workflow rather than a released
    artifact with the same version.

Resulting build pipeline

Windows / Linux / macOS
    generate resources
        -> package portable bundle and raw Assets.xcassets into Apple KLib

macOS
    consume KLib
        -> link Apple framework/executable
        -> compile Assets.xcassets with actool
        -> ship final bundle containing Assets.car

Final Apple binaries still require macOS and Xcode. This change only makes the
library/KLib stage cross-platform, matching Kotlin 2.2.20 cross-compilation
capabilities.

Compatibility notes

  • Runtime lookup remains compatible because moko-resources searches bundles by
    CFBundleIdentifier.
  • Physical bundle directory names change. Projects or custom build scripts that
    hardcode the previous <project>:<module>.bundle path must stop relying on
    that name or use the predictable
    <resourcesPackage>.<compilationName>.bundle name.
  • Older KLibs containing a precompiled Assets.car remain consumable.
  • Kotlin's existing macOS requirements for cinterop, CocoaPods, final binary
    linking, and Apple tests are unchanged.

0.28.0 consumer plugin requirement

This requirement applies when a published KLib contains moko-resources images
or colors and another Gradle project consumes that KLib to link an Apple
framework or executable.

Before 0.28.0, actool ran while the library KLib was being built. The
published KLib therefore contained a ready-to-use Assets.car, and the
consumer plugin only needed to copy the bundle.

Starting with 0.28.0, the published KLib intentionally contains raw
Assets.xcassets. This is what makes the artifact publishable from Windows and
Linux. This is a breaking KLib resource-format change when image or color
resources are present. The 0.28.0+ plugin performs the missing step in the
consuming macOS build:

dependency KLib with Assets.xcassets
    -> consumer links Apple framework/executable on macOS
    -> new moko-resources Gradle plugin runs actool
    -> final bundle contains Assets.car

Gradle plugins are not inherited transitively from library dependencies. A
library published with 0.28.0+ cannot automatically upgrade the plugin
used by the consuming application or shared module. The project that performs
the final Apple link must therefore apply moko-resources plugin 0.28.0 or a
newer compatible version.

If a plugin older than 0.28.0 consumes a 0.28.0+ KLib, it copies the bundle but
does not compile the embedded asset catalog. The final bundle can consequently contain
Assets.xcassets without Assets.car. Strings, plurals, and raw files can
still be present, but images and colors backed by the asset catalog will not be
available through the normal Apple runtime lookup.

Compatibility matrix:

Produced artifact Consumer Result
KLib < 0.28.0 containing Assets.car Any plugin Supported
KLib >= 0.28.0 without image/color assets Plugin < 0.28.0 Supported; no asset compilation is needed
KLib >= 0.28.0 containing raw Assets.xcassets Plugin < 0.28.0 Not supported for images/colors
KLib >= 0.28.0 containing raw Assets.xcassets Plugin >= 0.28.0 Supported; actool runs during final linking
Already linked framework/XCFramework containing Assets.car Any integration that copies the finished framework Supported; the raw KLib handoff is not involved

This is a build-tool compatibility requirement rather than a runtime API or
binary API change. It should be called out in the release notes as a minimum
consumer plugin version for libraries published with cross-compiled Apple
resources.

Verification

Completed locally on macOS:

  • ./gradlew :resources-generator:check
    • plugin compilation;
    • detekt;
    • plugin validation;
    • 76 unit tests.
  • samples/android-kmp-library/local-check.sh
    • clean :core:assemble with Android, JVM, iOS Arm64, iOS Simulator Arm64,
      and iOS x64 targets;
    • verified Android XML/R.string output and absence of .properties in
      Android res/;
    • verified the sibling JVM target retains .properties/ClassLoader
      output;
    • verified configuration-cache reuse.
  • samples/compose-jvm-app
    • assembled the JVM-only module to verify that referencing the Android KMP
      target API does not introduce an Android-plugin runtime requirement.
  • samples/compose-resources-gallery
    • generated a podspec without a manual resource attribute and verified the
      automatic shared.framework/*.bundle glob;
    • generated the CocoaPods dummy framework without a custom task action and
      verified the predictable current-module bundle name;
    • verified local project-dependency placeholder propagation without compiling
      its KLib;
    • verified stale placeholder cleanup after removing that dependency;
    • verified configuration-cache reuse.
  • samples/kotlin-2-tests/local-check.sh
    • Android build;
    • Apple KLib compilation;
    • raw asset catalog assertions;
    • iOS device framework link and Assets.car assertions;
    • iOS simulator tests;
    • JVM, JS, and Android tests/build checks.
  • samples/kotlin-2-sample
    • uncached compileKotlinIosArm64 with PNG, SVG, and color resources;
    • uncached linkDebugFrameworkIosArm64;
    • built the Xcode app through the direct static-framework integration without
      CocoaPods, including the copyFrameworkResourcesToApp build phase;
    • inspected raw Assets.xcassets in the KLib and final Assets.car in the
      framework and copied application bundle.
  • bash -n samples/kotlin-2-tests/local-check.sh.
  • git diff --check.

The dedicated check-android-kmp-library workflow job runs the sample on
windows-latest, ubuntu-latest, and macOS-latest.

@ExNDY ExNDY added this to the 0.28.0 milestone Sep 23, 2026
@ExNDY ExNDY self-assigned this Sep 23, 2026
@ExNDY
ExNDY requested a review from Alex009 September 23, 2026 07:46
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