Анатомия KMP-проекта: source sets
Где физически лежит общий код, где платформенный, и как компилятор решает, что куда подключать.
Source set (исходный набор) — каталог исходников, привязанный к одной или нескольким целям компиляции;
commonMainвиден всем целям, платформенные — только своим.
Три ключевых каталога
Структура общего модуля строится вокруг исходных наборов. Минимальный набор для мобильного KMP:
shared/
src/
commonMain/kotlin/ // общий код для всех целей
androidMain/kotlin/ // только Android (JVM)
iosMain/kotlin/ // только iOS (Native)
commonTest/kotlin/ // общие тесты
androidUnitTest/... // тесты под Android
iosTest/kotlin/ // тесты под iOScommonMain — сердце проекта. Сюда идёт всё, что не зависит от платформы. androidMain и iosMain содержат платформенные реализации того, что в общем коде только объявлено.
Кто что видит
Правило видимости простое и одностороннее: платформенные наборы видят общий, но не наоборот. Код в androidMain может вызывать классы из commonMain и использовать Android SDK. Код в commonMain не может обращаться ни к androidMain, ни к Android SDK напрямую — иначе он перестанет собираться под iOS.
commonMain ← androidMain (видит общий + Android SDK)
↑
└──────── iosMain (видит общий + iOS-интероп)Иерархия и промежуточные наборы
Между commonMain и листовыми наборами бывают промежуточные. Например, если целей iOS несколько (arm64 для устройства, x64/arm64 для симулятора), их объединяет общий iosMain. Современный шаблон KMP настраивает такую иерархию автоматически через default hierarchy template, и вам редко приходится описывать её руками.
Как работает под капотом
Каждая цель компиляции собирает объединение своих исходных наборов: цель iosArm64 берёт commonMain + (промежуточный) iosMain + свой листовой набор. Цель Android берёт commonMain + androidMain. Компилятор для каждой цели видит ровно тот срез кода, что ей положен, и проверяет типы именно в этом контексте. Поэтому expect-объявление в общем наборе обязано иметь actual в каждом платформенном — иначе цель соберёт неполный API.
Частые ошибки
Новички складывают всё подряд в commonMain, включая платформенные импорты, и удивляются, что Android собирается, а iOS — нет. Помните: commonMain — самый «бедный» по доступному API набор. Если что-то нужно из платформы, это либо expect/actual, либо код прямо в платформенном наборе. Ещё ошибка — путать androidMain (основной Android-код) с androidUnitTest (тесты); зависимости и видимость у них разные.
Итоги
commonMain— общий код;androidMain/iosMain— платформенный.- Платформенные наборы видят общий, общий их — нет.
- Цель компилируется как объединение своих source set'ов.
- Платформенный API в
commonMainнедопустим — только черезexpect/actual.