Post hero imagePost hero image
Outline

Notes and sources, not prose. Rewrite each section before publishing. Sources are in the aardink repo unless noted. If this runs long, split “The build fought back” into its own post.

TODO: opening. Aardink 0.4.1 was an Android library with apps depending on it (aardflex’s Android app is on 0.4.0). 0.5.0 had to add two targets without an Android user noticing.

Checks first, then changes

  • PR 0, before any code moved: Roborazzi screenshot tests committed, tools/consumer-smoke/ (an Android app that depends on the published coordinate) and scripts/verify-consumer.ps1.
  • Every later PR: screenshots unchanged, the consumer app builds, and from PR 3 an ABI dump that may only grow.
  • Real failure: verify-consumer.ps1 printed the dependency tree but never asserted that aardink-android was in it. The review caught it (KMP_MIGRATION_REVIEW.md C7).

The riskiest change goes first

  • The editor used BasicTextField(TextFieldValue) with a VisualTransformation, which is deprecated. Moving to TextFieldState with InputTransformation and OutputTransformation was the biggest behaviour change.
  • Done in PR 1 on the Android-only tree, so a regression there could not be mistaken for a multiplatform problem.
  • Side fix: a hardware Ctrl+Z went to the text field’s own undo and skipped the editor’s, so field and document drifted apart. Intercepting keys in onPreviewKeyEvent fixed that on Android too.
  • Source: KMP_MIGRATION_PLAN.md sections 4 and 5.1.

What stopped the code compiling as common Kotlin

  • org.json in EditorThemeParser, Android only. Ported to kotlinx.serialization.json with the same public signature. The only new dependency in :editor.
  • LspClient: ConcurrentHashMap, CopyOnWriteArrayList, AtomicLong, five @Synchronized methods, @Volatile, and Dispatchers.IO as a constructor default. Rewritten on kotlinx.coroutines.sync.Mutex. Rejected atomicfu (a compiler plugin and runtime dependency for every consumer) and kotlin.concurrent.atomics (still experimental).
  • StreamLspTransport uses java.io streams, so it moved unchanged into jvmAndAndroidMain. The browser gets WebSocketLspTransport.
  • 23 test files moved from JUnit 5 to kotlin.test, and about 70 runBlocking calls became runTest, because wasm has no runBlocking.
  • Source: KMP_MIGRATION_PLAN.md sections 3 and 8.

The Mutex rewrite broke what @Synchronized did not

  • A finally block called lock.withLock, which throws CancellationException in a cancelled coroutine before the body runs. Cancel the host’s scope and the transport never closed and pending requests never failed. Fix: withContext(NonCancellable).
  • Adding and removing listeners fell back to scope.launch when the lock was busy, so they could run out of order. @Synchronized gave program order for free.
  • TODO: before and after snippet. Point: swapping a lock is not a mechanical change.
  • Source: KMP_MIGRATION_REVIEW.md B1 to B3.

The build fought back

  • CI was red from PR 3 to PR 5. The Android KMP plugin (com.android.kotlin.multiplatform.library) has no test or lint task, so :editor:test and :editor:lint simply failed. Android Lint is gone for the libraries.
  • The Compose plugin is applied to :languages and :languages-lsp, which have no Compose code, because wasm browser tests need the bundled Skiko runtime to run at all. Same reason for binaries.executable() on library targets (CMP-4906).
  • A buildSrc convention plugin put the Kotlin plugin on the build classpath, and Gradle then rejected each module’s own versioned plugin alias. buildSrc was deleted.
  • Dokka 2’s Markdown output: the task succeeds and writes zero files. Dropped.
  • The Kotlin daemon ran out of heap at 2 GB linking four wasm test executables. Raised to 4 GB.
  • Source: KMP_MIGRATION_PLAN.md sections 2.4a, 2.4b and 2.5.