Android System & AOSP Engineering/AOSP Framework & Custom Services

AOSP 안드로이드 빌드 시스템 총정리: Android.mk(Make)에서 Android.bp(Soong)로의 진화

임베디드 친구 2025. 3. 17. 08:42
반응형

안드로이드 기반의 단말기를 개발하거나 AOSP(Android Open Source Project) 소스 코드를 내려받아 플랫폼 레벨의 빌드를 진행하다 보면, 수많은 빌드 설정 파일들을 마주하게 됩니다. 과거에는 친숙한 Android.mk 파일이 주를 이루었지만, 최신 안드로이드 버전을 열어보면 구조가 완전히 다른 Android.bp 파일이 소스 트리 곳곳을 채우고 있는데요.

수백 기가바이트에 달하는 소스코드를 효율적으로 빌드하기 위해 구글은 오랜 기간 빌드 시스템을 진화시켜 왔습니다. 이번 포스팅에서는 기존 GNU Make 기반 시스템의 한계를 극복하기 위해 도입된 Soong 빌드 시스템의 구조를 살펴보고, 핵심 컴포넌트들의 역할과 차이점을 명확하게 정리해 보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 안드로이드 초기에는 GNU Make 기반의 **Android.mk**를 사용했으나, 거대한 소스 규모로 인한 빌드 속도 저하 및 유지보수 문제를 해결하기 위해 안드로이드 7.0부터 Soong 빌드 시스템이 도입되었습니다.
  2. Soong은 독자적인 Blueprint DSL 구조를 따르는 Android.bp 설정을 기반으로 작동하며, 최종적으로는 고속 병렬 처리에 특화된 Ninja 빌드 엔진을 통해 빌드를 실행합니다.
  3. 레거시 Make 스크립트와의 하위 호환성을 유지하기 위해 Kati라는 변환 도구를 함께 사용하여, 구형 및 신형 빌드 규칙을 모두 Ninja 파일로 통합해 처리합니다.

1. 안드로이드 빌드 아키텍처 핵심 컴포넌트

안드로이드의 빌드 과정은 여러 도구가 유기적으로 엮여 작동합니다. 빌드 엔진과 스크립트 파서의 복잡한 관계를 표로 요약했습니다.

컴포넌트 이름 역할 정의 대상 파일 가이드 주요 특징 및 메커니즘
Soong 안드로이드 핵심 빌드 시스템 Android.bp Make 시스템을 대체하기 위해 고안된 새로운 빌드 엔진
Blueprint 빌드 설정 언어(DSL) 파서 Android.bp JSON과 유사한 선언형 구조로 이루어진 bp 파일을 해석하는 도구
Kati GNU Make 스크립트 변환기 Android.mk, Makefile 레거시 Make 규칙들을 Soong이 이해할 수 있는 Ninja 규칙으로 번역
Ninja 최하단 빌드 실행 엔진 *.ninja 규칙을 읽어 실제 컴파일과 링크를 병렬로 빠르게 수행하는 최적화 엔진

2. 레거시: GNU Make 기반 빌드 시스템

안드로이드 초기부터 오랜 기간 핵심을 지켜온 전통적인 방식입니다. Android.mk라는 텍스트 파일에 빌드할 모듈의 사양을 명시하는 구조입니다.

2.1 Android.mk 작성 규칙 및 예제

전형적인 실행 파일 빌드 스크립트 예시입니다. GNU Make의 변수 문법을 그대로 따르고 있습니다.

Makefile
 
# 현재 소스 파일이 위치한 디렉터리 경로 확보
LOCAL_PATH := $(call my-dir)

# 이전 모듈 설정 변수들을 깔끔하게 초기화
include $(CLEAR_VARS)

# 빌드 결과물(모듈)의 이름과 대상 소스 파일 정의
LOCAL_MODULE := my_module
LOCAL_SRC_FILES := main.cpp

# 실행 파일(Executable) 형태로 빌드하도록 빌드 매크로 호출
include $(BUILD_EXECUTABLE)

2.2 Make 시스템의 한계

GNU Make는 유연하다는 장점이 있지만, 프로젝트 규모가 스마트폰 OS 수준으로 무지막지하게 커지면서 한계에 부딪혔습니다. 다중 뎁스의 Makefile을 탐색하며 변수를 해석하는 과정에서 스레드 병렬 처리가 비효율적으로 이루어졌고, 빌드 환경을 분석하는 자체 시간(Configuration Time)이 너무 오래 걸려 전반적인 개발 생산성을 크게 떨어뜨렸습니다.


3. 현대화: Soong 빌드 시스템

구글은 이러한 성능 저하를 극복하고자 안드로이드 7.0(Nougat)부터 Go 언어로 작성된 Soong 빌드 시스템을 표준으로 채택했습니다. Soong은 계산 복잡도가 높은 논리 제어문을 빌드 스크립트에서 배제하고, 선언적인 형태의 Blueprint DSLAndroid.bp 파일을 사용합니다.

3.1 Android.bp 작성 규칙 및 예제

위의 Make 스크립트와 정확히 동일한 역할을 수행하는 Soong 기반의 코드입니다. JSON 구조와 매우 흡사하여 가독성이 높습니다.

코드 스니펫
 
// 1. C/C++ 실행 바이너리를 빌드하겠다는 선언적 모듈 타입 지정
cc_binary {
    name: "my_binary",       // 모듈의 고유 이름 정의
    srcs: ["main.cpp"],     // 컴파일 대상 소스 파일 배열 선언
    static_libs: ["libfoo"], // 링크할 정적 라이브러리 지정
}

3.2 Soong 빌드 시스템의 장점

  • 압도적인 가독성: 불필요한 조건문이나 복잡한 함수 호출이 배제되어 오타로 인한 빌드 에러가 크게 줄어듭니다.
  • Ninja 빌드와의 연동: 빌드 의존성 지도를 완벽하게 그린 뒤 병렬 연산에 최적화된 *.ninja 파일로 곧바로 변환하므로, 변경된 소스 코드만 솎아내어 다시 빌드하는 증분 빌드(Incremental Build) 속도가 비약적으로 상승합니다.

4. GNU Make vs Soong 빌드 시스템 핵심 비교

두 시스템의 세부 특성을 다각도로 비교해 보았습니다.

비교 항목 기존 GNU Make 시스템 최신 Soong 빌드 시스템
주요 설정 파일 Android.mk, Makefile Android.bp
언어 및 가독성 GNU Make 문법 (조건문, 함수 혼용으로 복잡함) Blueprint DSL (JSON 기반의 명확한 선언형 구조)
파싱 및 빌드 속도 다중 매크로 연산으로 대규모 프로젝트에서 느림 Go 언어 기반 파싱 및 고속 Ninja 파이프라인으로 매우 빠름
의존성 관리 개발자가 수동 제어 혹은 암시적 결합으로 파악이 어려움 빌드 그래프를 명시적으로 형성하여 엄격하게 추적
병렬 컴파일 효율 타겟 간 의존성 파악 지연으로 병렬 처리 효율이 낮음 Ninja 엔진이 코어 수를 한계까지 활용해 병렬 연산 극대화

5. 실제 AOSP 빌드 파이프라인 및 실행 과정

실제 AOSP 소스 트리 루트에서 빌드를 돌릴 때 내부 도구들은 아래와 같은 순서로 바톤 터치를 하며 동작합니다.

[Android.bp] ----> ( Blueprint ) ----\
                                      -----> [ Soong ] ----\
[Android.mk] ----> (   Kati    ) ----/                      -----> [ *.ninja ] ===( Ninja 엔진 )===> [최종 결과물]
  1. 환경 초기화: 빌드 도구들을 경로에 등록하는 셸 스크립트를 로드하고 타겟 디바이스 보드를 선택합니다.
  2. Bash
     
    source build/envsetup.sh
    lunch aosp_arm64-userdebug
    
  3. 빌드 트리 파싱: m 명령어를 입력하면 SoongKati가 동시에 출격합니다. Blueprint는 Android.bp를 파싱하고, Kati는 레거시 Android.mk를 긁어모아 공통 템플릿인 out/build-*.ninja 파일로 통합 생성합니다.
  4. 최종 컴파일 연산: 마침내 Ninja 엔진이 생성된 ninja 파일을 가리키며 실제 컴파일러(Clang/GCC)를 병렬로 마구 실행하여 바이너리 및 시스템 이미지들을 뽑아냅니다.

💡 안드로이드 시스템 빌드를 위한 실전 팁

  1. 개별 모듈 타겟 빌드로 시간 절약하기: 전체 AOSP 이미지를 빌드하는 데는 수 시간이 걸립니다. 내가 수정한 특정 C++ 모듈이나 프레임워크 라이브러리만 빠르게 검증하고 싶다면, m 명령어 뒤에 모듈명을 명시하거나(m my_binary), 해당 소스 디렉터리로 이동하여 mm 혹은 mmm [디렉터리 경로] 명령어를 활용해 부분 빌드를 수행하는 것이 생산성에 크게 유리합니다.
  2. Android.bp 내 조건부 빌드 처리법: Android.bp는 가독성을 위해 내부에 if/else 같은 조건문을 직접 쓸 수 없습니다. 만약 특정 디바이스 보드나 국가 옵션에 따라 소스코드를 다르게 컴파일해야 한다면, cc_defaults를 정의해 두고 별도의 Go 언어 파일(art/build/soong 내부 구조 참고)로 빌드 플러그인을 작성하여 동적으로 모듈 속성을 변경해 주어야 합니다.

⚠️ 흔히 하는 실수

  1. Android.mk와 Android.bp에서 동일한 모듈 이름 사용: 레거시 코드 수정을 위해 기존 Android.mk 내용을 신형 Android.bp로 이주할 때, 간혹 양쪽 파일 모두에 같은 모듈 이름(LOCAL_MODULE과 name 필드)을 남겨두는 실수를 범합니다. 이 경우 Soong과 Kati가 파일들을 통합하는 과정에서 중복 모듈 정의 에러(duplicate module 에러)를 뿜으며 빌드가 초기에 뻗어버리니 주의해야 합니다. 이주가 완료되면 기존 Android.mk 파일은 삭제하거나 주석 처리해야 안전합니다.
  2. 빌드 캐시 오염으로 인한 원인 불명의 에러: 소스 코드 내부에서 헤더 파일의 구조를 크게 바꾸거나 의존 라이브러리 설정을 바꾼 뒤 곧바로 빌드를 돌리면, Ninja 엔진이 기존 빌드 캐시와 꼬여 링킹 에러를 발생시키는 경우가 종종 있습니다. 소스 코드에 문제가 없는데 빌드가 실패한다면 make clean 또는 rm -rf out/을 통해 빌드 출력 디렉터리를 완전히 날려버리고 깨끗한 상태에서 빌드(Clean Build)를 다시 시도해 보는 것이 정신 건강에 좋습니다.

6. 결론

안드로이드 빌드 시스템은 거대한 오픈소스 프로젝트를 효율적으로 관리하기 위해 Make에서 Soong-Ninja 시스템으로 완전히 패러다임이 시프트되었습니다. 현재 과도기적 단계로 일부 하드웨어 벤더사 코드에 Makefile이 남아있긴 하지만, 구글의 공식 방향성은 Android.bp로의 완전한 통합입니다.

AOSP 환경에서 새로운 드라이버나 시스템 라이브러리 모듈을 추가하려는 개발자라면, 오늘 정리한 빌드 시스템의 메커니즘을 명확히 이해하고 Android.bp 파일의 구조적 특성에 익숙해지는 것이 필수적입니다.

반응형