AGP 8.12+ `Fused Library`로 “b.aar 안에 a.aar” 제대로 말아올리기 (클린 아키텍처 래퍼)

gay0ung·2025년 9월 4일
2

Android

목록 보기
14/14

“fat-aar” 플러그인(kezong)을 쓰려다 AGP 7.x까지만 안정 지원이라 포기… 결국 구글 공식 Fused Library로 결정했고, 실제로 래퍼(b.aar) 안에 a.aar + domain + data를 묶어냈다. 이 글은 그 과정의 시행착오와 최소 설정을 정리한 기록이다.


Fused Library였나

  • 구글 공식 플러그인이며 AGP 8.12+부터 제공. 내부적으로 여러 Android Library 모듈을 단일 AAR로 패키징한다. (Android Developers)
  • kezong/fat-aar-androidAGP 7.x 세대까지만 검증되었고, Gradle/AGP 8대에서 이슈가 잦다. 안정성 측면에서 채택 보류. (GitHub, Stack Overflow)

내가 만든 모듈 구성 (클린 아키텍처 래퍼)

  • :a-wrapper (선택) – a.aar을 로컬 Maven 좌표로 바꿔 담는 래퍼
  • :domain – Android Library (또는 :domain-core + :domain-android로 분리)
  • :data – Android Library
  • :b최종 fused AAR 모듈 (여기에 Fused Library 플러그인을 적용)

포인트: fused 모듈은 “소스 없음”이 기본 전제에 가깝고, 실제 내용물은 포함할(Android) 라이브러리들에서 나온다. 즉, :b는 껍데기이자 패키저 역할.


필수 사전 설정

1) gradle.properties

# Fused Library 켜기 (필수)
android.experimental.fusedLibrarySupport=true

# 기본은 “퍼블리시 전용” 제한. 프로젝트 간 직접 의존으로 쓰고 싶으면 false
# (내 케이스: 메이븐 배포가 목적이 아니라 내부에서 바로 써야 해서 꺼둠)
android.experimental.fusedLibrarySupport.publicationOnly=false
  • true를 켜야 DSL과 태스크가 활성화된다.
  • “퍼블리시 전용” 제한은 기본값이라, 직접 프로젝트 의존으로 쓰려면 publicationOnly=false로 내려야 한다.

2) 버전 카탈로그 (gradle/libs.versions.toml)

[versions]
agp = "8.13.0"

[plugins]
android-fusedlibrary = { id = "com.android.fused-library", version.ref = "agp" }

공식 문서 레시피도 플러그인 alias 사용을 권장한다.


:b 모듈의 최소 Gradle 스크립트

핵심은: 절대 com.android.library를 같이 적용하지 말 것. Fused 모듈은 오직 com.android.fused-library + androidFusedLibrary {} 만 쓴다. (둘 다 적용하면 빌드 에러)

b/build.gradle.kts:

plugins {
    alias(libs.plugins.android.fusedlibrary)
    // 필요 시 퍼블리시도 같이: `maven-publish`
}

androidFusedLibrary {
    namespace = "com.ex.ample"
    minSdk = 26
}

dependencies {
    // 중요: implementation이 아니라 include()를 쓴다
    include(project(":domain"))
    include(project(":data"))
}
  • DSL 블록은 android {}가 아니라 androidFusedLibrary {} 다.
  • 의존 추가는 implementation이 아니라 include(...) 로 묶는다. 트랜지티브는 자동 패키징되지 않으므로 필요한 것만 명시적으로 include.

퍼블리시(선택)와 테스트

로컬에서 합쳐진 산출물을 앱에서 검증하려면, 문서의 샘플처럼 로컬 리포지토리에 퍼블리시해두고 앱 쪽에서 좌표로 붙여 테스트하는 게 깔끔하다. 퍼블리시 시에는 from(components["fusedLibraryComponent"]) 를 사용한다.


Core 모듈 추가

fused-library가 붙은 최종 모듈(:b)은 소스 코드를 가지지 않는 “패키저 전용 모듈”이어야 한다. 실제 노출될 API(파사드/래퍼)는 별도 라이브러리 모듈(예: :core)에 두고, :b에서 include(project(":core"))묶어서 내보낸다.

왜 Core가 필요한가

  • Fused Library는 “여러 Android Library를 단일 AAR로 묶는” 플러그인이고, 소스는 포함하지 않고 라이브러리들을 소스로 사용한다. 즉, :b 자체에는 코드가 없어야 한다. 공개 API가 필요하면 코드를 :core에 두고, :b에서 include로 합쳐야한다!!

모듈 구조 (업데이트)

  • :core공개 API/파사드가 들어있는 Android Library (여기서 domain/data/a를 래핑)
  • :domain – Android Library
  • :data – Android Library
  • :bFused 모듈 (소스 없음, 패키징 전용!!!)
    include(project(":core")), include(project(":domain")), include(project(":data")), include("group:artifact:a:1.0.0")

Gradle 스니펫

core/build.gradle.kts (일반 라이브러리)

plugins { id("com.android.library"); kotlin("android") }

android {
    namespace = "com.ex.ample.core"
    compileSdk = 34
    defaultConfig { minSdk = 26 }
}

dependencies {
    implementation(project(":domain"))
    implementation(project(":data"))
    // 필요 시 a.aar의 API를 감싸는 어댑터 코드 작성
}

b/build.gradle.kts (fused – 소스 없음)

plugins { alias(libs.plugins.android.fusedlibrary) }

androidFusedLibrary {
    namespace = "com.ex.ample"
    minSdk = 26
}

dependencies {
    // 핵심: implementation X, include O
    include(project(":core"))
    include(project(":domain"))
    include(project(":data"))
    // a.aar은 좌표로 include (파일 직접 포함 불가)
    include("com.myco.libs:a:1.0.0")
}

주의사항 요약

  • :b에는 코드 넣지 말 것. “라이브러리 묶개” 역할만 한다.
  • 의존은 include(...) 로만 묶인다(트랜지티브 자동 패키징 안 됨).
  • .aar 파일 직접 include는 불가좌표화해서 include.
  • 퍼블리시 전용 제한gradle.properties에서 해제 가능

내가 겪은 시행착오 & 체크리스트

  • 동시에 두 플러그인 금지: com.android.fused-library + com.android.library를 같은 모듈에 적용하면 바로 실패. Fused 모듈에는 오직 하나만.

  • android {} 금지: Fused 모듈은 androidFusedLibrary {} 를 쓴다(네임스페이스/SDK만 지정).

  • implementation 금지: 반드시 include(...) 로 묶는다. 트랜지티브는 자동 합류 안 됨.

  • .aar 파일 직접 include 불가: a.aar은 좌표화해서 include("g:a:v")로 선언. (로컬/사내 Maven OK)

  • 기본은 퍼블리시 전용: 프로젝트 직접 의존으로 쓰려면 android.experimental.fusedLibrarySupport.publicationOnly=false 설정.

  • AGP 요구사항: AGP 8.12+ 필요. 최신 릴리즈 노트에서 Fused 관련 수정 사항도 확인 권장.

  • fat-aar 대체 이유: AGP 8대에서 이슈 보고가 누적. 장기 안정성 측면에서 공식 플러그인 채택. (GitHub, Stack Overflow)


최소 예시(요약)

gradle.properties

android.experimental.fusedLibrarySupport=true
android.experimental.fusedLibrarySupport.publicationOnly=false

b/build.gradle.kts

plugins { alias(libs.plugins.android.fusedlibrary) }

androidFusedLibrary {
    namespace = "com.ex.ample"
    minSdk = 26
}

dependencies {
    include(project(":domain"))
    include(project(":data"))
    // a.aar -> 로컬/사내 Maven 좌표로 발행 후:
    // include("com.your.group:a:1.0.0")
}

마치며

  • 목적이 “b.aar 하나에 몽땅 말아 오프라인 전달”이라면, 지금 시점(AGP 8.12+)에서 Fused Library가 가장 안정적이고 문서화된 선택지였다. 설정 자체는 생각보다 단순하지만, 핵심은 include와 좌표화다.
  • fat-aar로 겪던 호환성/업그레이드 지옥을 줄이고 싶다면, 공식 플러그인으로 옮겨 타는 걸 추천. 필요시 로컬 퍼블리시만으로도 배포/테스트 플로우가 깔끔하게 정리된다.

0개의 댓글