Upstream baseline - Pin both vendored refs to the STABLE v1.12.0 tag (core + umbrella); the native and JVM-parity legs are back in lockstep, no dev-build skew. - Catalog: compose 1.12.0, composeRuntime 1.12.0. material3 stays 1.12.0-alpha03 - that IS the version CMP 1.12.0 ships (its own train). - Re-synced 1560 vendored files; drift + vendor-clean both clean. - Retire the SelectionLayout.kt manual vendor: upstream fixed the same out-of-bounds at the source (lastSlot = currentSlot, not currentSlot + 1), so the fork's index clamps are redundant. Back to verbatim vendoring. - Re-stamp 10 manual vendors to @ v1.12.0 (upstream unchanged base..pin). - Regenerate the klib API dumps. These had already drifted on main (the last dump predates the scrollbar / IME changes), so the diff covers that too. SDL3 3.4.16, skiko fork 0.150.1-mingw.2 - Rebuild the static libSDL3.a from release-3.4.16. - CMP 1.12.0 uses skiko 0.150.1, unchanged, so the fork's Skia base still matches; bump to the -mingw.2 build and centralise it as `skikoMingw` in the version catalog instead of three hardcoded copies. - Resolve the fork from the PUBLIC maven.bitsycore.com/releases instead of the credentialed GitHub Packages repo: building the Windows target no longer needs a GitHub token. Gradle module paths mirror the directory - Drop every projectDir redirect: :ui -> :compose:ui:ui, :animation-core -> :compose:animation:animation-core, and so on. - INVARIANT (documented in settings.gradle.kts + CLAUDE.md): the leaf directory IS the published artifactId. KMP derives a target publication's coordinate as <project.name>-<target>, and a target disabled on the building host (all Apple targets on Windows) has no publication object to retarget - while the root kotlinMultiplatform metadata, which the WINDOWS publish job owns, still emits an available-at for it. A mismatched leaf silently ships a root module pointing at a nonexistent <leaf>-macosarm64. So compose/desktop/native/window and components/resources/library are renamed to their artifactIds. Published coordinates are unchanged. Manual-vendor audit - New scripts/compose-fork/audit-exclusions.py: classifies every `!` manifest exclusion as manual vendor (VENDOR-BASE, drift-tracked), reimplementation (VENDOR-REIMPL, deliberately not tracked), or orphan, and fails on any unclassified one. - 14 project reimplementations were invisible to the drift checker by accident; mark them VENDOR-REIMPL. None needed reconciling (all verified <= 0.53 similarity to their upstream counterparts, none changed upstream between the old pin and v1.12.0). Fix the JVM apidemo build - Declare components-resources on jvmMain (decodeToImageBitmap / decodeToSvgPainter) and add the two missing cross-package imports. The JVM parity target had never compiled. Verified on Windows: both native executables link and render, apidemo JVM builds and launches, apiCheck green, common metadata compiles (the Windows publish gate), publishToMavenLocal emits the same coordinates as before.
7.8 KiB
compose-fork - vendored Compose sources
This directory is the tooling that vendors byte-for-byte verbatim source
files from JetBrains/compose-multiplatform-core
into the repo. See ../../CLAUDE.md → "Compose API Fidelity"
for the three fix strategies (pull-verbatim / surface-match / intentional-custom).
The vendored files are NOT committed
<module>/src/vendor/ is gitignored - it is a generated artifact, not
source. Only the tooling here and the per-module manifests are tracked:
| File | Purpose |
|---|---|
sync.sh |
Idempotent script that canonicalizes the selected manifest(s) then copies each active entry verbatim from the pinned upstream ref. |
format-manifest.py |
Canonicalizes a compose-fork.txt in place (see "Manifest layout"). Run by sync.sh; also runnable standalone. |
compose.properties |
NAME=value variable declarations - the pinned upstream refs, tagged in one place. Manifests reference them as <NAME> in SET_REPO=<url>@<NAME>. |
README.md |
This file. |
../../<module>/compose-fork.txt |
Per-module manifest - lives alongside the module's build.gradle.kts. Today only :core has one; a future :material3 module would add its own. |
Manifest layout - per module, co-located with build.gradle.kts
Each Gradle module that vendors upstream code carries its own compose-fork.txt
next to its build.gradle.kts. That file lists every upstream file the module
vendors:
compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt src/vendor/common/kotlin/androidx/compose/ui/Modifier.kt
The destination is relative to the manifest's own directory (so core/
paths don't have a core/ prefix - the manifest already lives in core/).
Entries are grouped by androidx package in hierarchy (alphabetical) order under
# ── androidx.compose.<pkg> ── headers. Within each package the vendored
(uncommented) entries come first, then the not-yet-vendored (commented)
candidates below.
format-manifest.py regenerates exactly this layout - deduping by dest (last
active line wins, matching sync.sh's copy order) and dropping stray comments.
It is idempotent and a pure re-layout: the set of active upstream → dest
pairs is preserved, so the vendored tree is byte-identical. sync.sh runs it
automatically before copying; run it yourself after hand-editing:
python3 scripts/compose-fork/format-manifest.py # rewrite EVERY <module>/compose-fork.txt
python3 scripts/compose-fork/format-manifest.py --manifest core/compose-fork.txt # single manifest
python3 scripts/compose-fork/format-manifest.py --check # exit 1 if any manifest is not canonical
python3 scripts/compose-fork/format-manifest.py --discover PATH # + surface new upstream files
Discovering new upstream files
--discover <clone> (or $CMP_REF) scans, for each manifest, every
compose/<area>/<module> upstream module the manifest already references, and
adds any .kt not already listed as a commented candidate under its
package section, with a best-guess dest (refine when you vendor it; only the
upstream path is deduped, so curated entries keep their dests). sync.sh runs
--discover $CMP_REF automatically after cloning per manifest, so a
compose.properties bump surfaces newly-added upstream files as candidates.
Source sets mirrored: commonMain, nonJvmMain, nativeMain, nonAndroidMain,
skikoMain. JVM / Android / JS / test are intentionally skipped.
Because the copies are verbatim, provenance lives entirely in
<module>/compose-fork.txt + compose.properties. Never hand-edit a file under
<module>/src/vendor/ - change the manifest or the ref and re-run sync.sh
instead. Hand-written glue (shims, expect/actual actuals, project-specific
code) lives outside the vendor tree and IS committed.
Bootstrapping a fresh checkout
The build needs the vendored files present on disk. After cloning this repo
you must populate <module>/src/vendor/ once before building:
# Clones/updates the sparse upstream checkout at $CMP_REF (default ../cmp-ref)
# to the ref in compose.properties, then copies every manifest entry verbatim.
CMP_REF=../cmp-ref bash scripts/compose-fork/sync.sh
Syncing selectively - per-module
sync.sh accepts one or more arguments identifying which manifests to run:
bash scripts/compose-fork/sync.sh # every <module>/compose-fork.txt in the repo
bash scripts/compose-fork/sync.sh :core # Gradle path
bash scripts/compose-fork/sync.sh core # module name (same thing)
bash scripts/compose-fork/sync.sh :material-symbols:outlined # nested Gradle path
bash scripts/compose-fork/sync.sh core/compose-fork.txt # direct path to a manifest
bash scripts/compose-fork/sync.sh :core :material # multiple
Argument resolution:
| Form | Resolves to |
|---|---|
:foo:bar |
foo/bar/compose-fork.txt |
foo/bar |
foo/bar/compose-fork.txt |
foo |
foo/compose-fork.txt |
path/to/file.txt |
that path |
Re-run any time after editing a manifest or bumping compose.properties.
The script is idempotent.
Adding an upstream module
If you want to vendor a Compose module we don't yet cover (e.g. material3):
- Create the module in the repo if needed (
material3/build.gradle.ktsetc.), and add itsinclude(":compose:material3:material3")tosettings.gradle.kts. - Create
material3/compose-fork.txtwith the standard header (any comment;format-manifest.pywill fix the layout). Populate it either by hand or by letting--discoverseed it:# Add ONE commented candidate to seed the upstream module reference, then # let discover fill in the rest: printf '# compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Placeholder.kt src/vendor/common/kotlin/androidx/compose/material3/Placeholder.kt\n' > material3/compose-fork.txt CMP_REF=../cmp-ref bash scripts/compose-fork/sync.sh :material3 - Uncomment the entries you actually want to vendor, then re-run
sync.sh :material3.
Adding a file to an existing module's vendor set
- Find the upstream path in the module's
compose-fork.txt- it may already be listed, commented out. - Add / uncomment its
<upstream-path> <dest>line under the matching# ── androidx.compose.<pkg> ──section. Destinations are relative to the module dir:commonMain→src/vendor/common/,nonJvmMain/nativeMain/skikoMain→src/vendor/native/. Exact placement / ordering doesn't matter -sync.shre-canonicalizes. - Run
sync.sh :<module>(canonicalizes the manifest, then copies). - Build and add any hand-written glue the new file needs (shims / actuals) outside the vendor tree.
Bumping the upstream ref
Edit the ref variable (e.g. COMPOSE_CORE_REF) in compose.properties, re-run
sync.sh, then rebuild. A
verbatim re-sync makes git status of a temporary tracked copy show exactly
what upstream changed between refs.