Files
Bitsy 82ffd7cf4d feat: migrate to Compose Multiplatform 1.12.0 + SDL3 3.4.16
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.
2026-09-12 00:03:13 +02:00

142 lines
7.8 KiB
Markdown

# compose-fork - vendored Compose sources
This directory is the tooling that vendors **byte-for-byte verbatim** source
files from [`JetBrains/compose-multiplatform-core`](https://github.com/JetBrains/compose-multiplatform-core)
into the repo. See [`../../CLAUDE.md`](../../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:
```bash
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:
```bash
# 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
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`):
1. Create the module in the repo if needed (`material3/build.gradle.kts` etc.),
and add its `include(":compose:material3:material3")` to `settings.gradle.kts`.
2. Create `material3/compose-fork.txt` with the standard header (any comment;
`format-manifest.py` will fix the layout). Populate it either by hand or by
letting `--discover` seed it:
```bash
# 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
```
3. Uncomment the entries you actually want to vendor, then re-run `sync.sh
:material3`.
## Adding a file to an existing module's vendor set
1. Find the upstream path in the module's `compose-fork.txt` - it may already
be listed, commented out.
2. 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.sh` re-canonicalizes.
3. Run `sync.sh :<module>` (canonicalizes the manifest, then copies).
4. 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.