
얼마 전에 사이드 프로젝트를 AGP 9.0으로 올렸다가 빌드가 통째로 깨졌습니다. 원인을 파봤더니 composeApp 한 모듈에 앱 플러그인이랑 KMP 플러그인이 같이 박혀 있던 게 문제였더라고요.
AGP 9.0에서 composeApp 단일 모듈이 깨지는 이유
기존 코틀린 멀티플랫폼(Kotlin Multiplatform, KMP) 마법사가 만들어주는 composeApp 모듈은 사실 처음 봤을 때부터 좀 이상했어요. 한 모듈 안에 com.android.application 플러그인이 박혀 있는데, 동시에 kotlin("multiplatform") 도 적용돼서 iOS·데스크톱 타깃이 같이 굴러갑니다. "이게 앱이야 라이브러리야?" 가 처음 입문자 입장에서 헷갈리는 자리였습니다.
AGP 9.0 부터는 KMP 플러그인이 com.android.application/com.android.library 와 같은 모듈에서 호환이 끊겼고, AGP 10 에서는 레거시 API 까지 완전히 빠질 예정이에요. 공식 마이그레이션 가이드 에 비호환 단계가 그대로 정리돼 있습니다. 그래서 기존 composeApp 구조를 유지한 채로 AGP 만 올리면 sync 부터 빨갛게 죽어버려요.
국내 블로그·유튜브 자료 대부분이 옛 composeApp 기준이라, 지금 처음 KMP 를 보러 들어오신 분이라면 혼란을 겪을 가능성이 높아 보입니다. 구글링으로 따라 친 build.gradle.kts 가 막상 새 마법사 결과물이랑 안 맞는 일이 자주 생길 수 있거든요.

새 KMP 기본 구조의 모듈 책임 분담
새 마법사가 만들어주는 구조는 책임이 칼같이 갈라져 있습니다. 정리하면 이렇습니다.
shared— 순수 KMP 라이브러리. 모든 플랫폼 앱이 여기에 의존해서 가져다 씁니다.androidApp—com.android.application만 적용된 안드로이드 진입점. AndroidManifest, MainActivity, 패키징만 담당합니다.desktopApp/webApp/iosApp— 각 플랫폼의main()함수를 들고 있는 진입점 모듈입니다.
공유 코드 자체도 한 번 더 쪼갤 수 있게 됐어요. sharedLogic 은 Compose 의존성이 전혀 없는 순수 비즈니스 로직, sharedUI 는 Compose Multiplatform 으로 작성된 공통 UI 쪽입니다. 이렇게 나눠두면 iOS 쪽이 공통 UI 안 쓰고 SwiftUI 로 가더라도 sharedLogic 만 끌어다 쓰는 게 깔끔해지더라고요.
풀스택 템플릿으로 가면 server(Ktor 같은 백엔드) 와 core(서버·클라이언트 공유 모델·검증 로직) 가 추가됩니다. DTO·검증 코드를 양쪽에서 한 벌로 쓰는 패턴이 공식 템플릿으로 정리된 대목이에요.

새 마법사로 KMP 프로젝트 시작해본 첫인상
마법사에서 안드·iOS·데스크톱 다 체크하면 모듈이 한 번에 6개씩 만들어집니다. 처음 열었을 때는 솔직히 "이걸 다 들고 가야 하나" 싶었어요.
근데 막상 굴려보면 증분 빌드 쪽에서 이득이 꽤 컸습니다. :androidApp:installDebug 만 돌리면 그래들이 데스크톱·iOS 컴파일 그래프는 아예 건드리지도 않거든요. 단일 composeApp 시절엔 무조건 전체 타깃을 한 번씩 평가하고 들어가던 구간이 이번엔 깔끔하게 잘립니다.
개인적으로는 화면 두세 개짜리 토이 프로젝트라면 이 구조가 좀 과해 보일 수 있다고 봅니다. 모듈 5~6개를 관리하느라 본 코드보다 빌드 스크립트 만지는 시간이 더 길어질 수 있어요. 그래도 플랫폼 두 개 이상을 같이 굴릴 계획이라면 처음부터 새 구조로 시작하는 쪽이 훨씬 마음 편합니다.

기존 composeApp 을 새 구조로 마이그레이션한 현실
이번에 사이드 프로젝트에서 손으로 옮겨본 작업 순서는 이렇습니다.
androidApp모듈을 새로 하나 만들고com.android.application을 거기로 옮깁니다.AndroidManifest.xml,MainActivity, Application 클래스(Hilt 라면@HiltAndroidApp붙은 그 친구) 를androidApp으로 이동시킵니다.- 기존
composeApp을shared로 이름 바꾸고, 플러그인을 안드 KMP 라이브러리 쪽으로 교체합니다. settings.gradle.kts의include를 갱신하고,androidApp의 dependencies 에implementation(projects.shared)를 추가합니다.
이 과정에서 BuildConfig 참조 위치가 바뀌면서 빨갛게 줄 그어지는 자리가 꽤 나옵니다. @HiltAndroidApp 이 붙은 Application 클래스도 androidApp 모듈로 같이 옮겨가야 하는데, 패키지 이름까지 바뀌면 manifest 의 android:name 도 같이 손봐야 해서 처음엔 좀 어수선합니다.
여기서 한 가지 짚어둘 점은, AGP 9.0 업데이트와 구조 마이그레이션을 동시에 하지 말자는 겁니다. 빌드가 깨졌을 때 원인이 둘 중 어느 쪽인지 구분이 잘 안 됩니다. AGP 8.x 상태에서 먼저 구조부터 옮긴 다음, sync 가 깨끗하게 통과하는 걸 확인하고 AGP 9.0 으로 올리는 순서가 훨씬 편했어요. 그 다음 단계로 shared 모듈에 새 com.android.kotlin.multiplatform.library 플러그인을 적용해서 안드 타깃 설정을 정리하는 흐름이 가장 사고가 적었습니다.
참고로 저는 이번에 git switch -c kmp-restructure 로 브랜치 하나 따로 따서 옮겼습니다. 한 번에 한 가지씩만 바꿔야 디버깅이 깔끔하니까요.

코틀린 멀티플랫폼 2026 진입 타이밍
새 구조는 사실 안드 개발자한테 더 익숙한 모양입니다. app 모듈 + core·feature 라이브러리 모듈을 분리하던 그 패턴이랑 결이 비슷해서, 처음 KMP 를 보러 오는 안드 개발자가 받아들이는 부담이 줄어들었다고 봐요.
지금 처음 KMP 를 접하는 분이라면 옛 국내 자료보다 JetBrains 공식 문서랑 새 마법사 결과물을 기준 삼는 쪽이 안전합니다. AGP 9.0 이 굴러오면서 마이그레이션은 어차피 피할 수 없는 길이 된 상황이라, 사이드 프로젝트 한 개 정도로 미리 손에 익혀두면 본 프로젝트 옮길 때 사고가 확 줄어들어요.
composeApp 단일 모듈은 이제 레거시 패턴으로 정리됐어요. 새 KMP 프로젝트를 띄우실 일이 생기면 그냥 새 마법사가 뽑아주는 그대로 두고 시작하시는 게 지금 시점에서는 가장 깔끔한 출발점이라고 봅니다.
'Android 개발 > Gradle・빌드' 카테고리의 다른 글
| CI에서만 죽는 Gradle OutOfMemory, -Xmx 를 줄여야 살아나는 이유 (0) | 2026.07.21 |
|---|---|
| 카톡으로 .apk 보내던 시절 끝 — Firebase App Distribution으로 QA 배포 자동화한 기록 (0) | 2026.07.19 |
| Claude Code Opus 4.8 dynamic workflows로 본인 안드 멀티 모듈 마이그레이션 돌려본 닷새 (0) | 2026.06.20 |
| Aluminium OS 가을 출시 D-150, 안드로이드 앱이 챙길 첫 PR 5가지 (0) | 2026.05.29 |
| Gradle 빌드가 갑자기 느려졌을 때 가장 먼저 보는 곳 (0) | 2026.05.05 |