Gradle
contents
Maven이 정해진 규칙(XML)을 엄격히 따르는 "공장 관리자" 라면, Gradle은 코드를 작성해서 무엇이든 지을 수 있는 "스크립트형 건축가" 입니다.
다음은 Gradle의 내부 아키텍처, 생명주기, 그리고 성능의 비밀에 대한 상세 분석입니다.
1. 핵심 철학: "빌드 로직도 코드다"
Maven과 Gradle의 가장 큰 차이점은 사용하는 언어입니다.
- Maven: XML(
pom.xml)을 사용합니다. 선언적(Declarative) 입니다. 즉, 무엇(What) 을 원하는지만 묘사할 수 있습니다. - Gradle: Groovy나 Kotlin 기반의 DSL(도메인 특화 언어)을 사용합니다. 명령형(Imperative) 입니다. 빌드 스크립트 내부에서
if문, 반복문, 변수 정의 같은 프로그래밍 로직을 직접 작성할 수 있습니다.
예시 (build.gradle):
// 실제로 코드를 짤 수 있습니다!
if (project.hasProperty('production')) {
apply plugin: 'com.newrelic.agent'
}
task copyDocs(type: Copy) {
from 'src/main/doc'
into 'build/target/doc'
}
2. 아키텍처: 빌드 생명주기 (Build Lifecycle)
이것이 가장 중요한 기술적 개념입니다. 위에서 아래로 단순히 실행되는 스크립트와 달리, Gradle은 3단계의 뚜렷한 과정을 거쳐 실행됩니다.
1단계: 초기화 (Initialization)
- 목표: 빌드에 참여하는 프로젝트가 무엇인지 결정합니다.
- 동작: Gradle은
settings.gradle(또는settings.gradle.kts) 파일을 찾습니다. - 결과: 멀티 모듈 프로젝트라면, 루트 프로젝트와 설정에 나열된 모든 서브 모듈에 대해
Project인스턴스를 생성합니다.
2단계: 구성 (Configuration) - ⚠️ 위험 구역
- 목표: 태스크 그래프(Task Graph) 를 만듭니다.
- 동작: 참여하는 모든 프로젝트의
build.gradle파일 내부에 있는 코드를 실행합니다. - 중요 디테일: 이때는 태스크를 실행하는 것이 아닙니다! 단지 의존성 관계를 파악하기 위해 설정을 읽는 것입니다.
- 흔한 실수: 만약
build.gradle본문(body)에 네트워크 호출 같은 무거운 로직을 (doLast블록 밖에서) 직접 작성하면,gradle help같은 간단한 명령만 쳐도 그 로직이 매번 실행되어 IDE가 느려집니다.
3단계: 실행 (Execution)
- 목표: 실제 작업을 수행합니다.
- 동작: 2단계에서 만든 태스크들의 DAG(방향성 비순환 그래프) 를 보고 올바른 순서대로 실행합니다 (예:
compileJava$\rightarrow$test$\rightarrow$jar).
3. "DAG" (방향성 비순환 그래프)
Gradle은 단순히 태스크 목록을 실행하는 것이 아니라, 그래프를 구축합니다.
gradle build를 실행한다고 가정해 봅시다:
build는check와assemble에 의존합니다.check는test에 의존합니다.test는compileTestJava에 의존합니다.compileTestJava는compileJava에 의존합니다.
Gradle은 이 트리를 계산합니다. 만약 두 태스크가 서로 독립적이라면(예: 문서 생성과 테스트 컴파일), Gradle은 서로 다른 CPU 코어에서 병렬(Parallel) 로 실행하여 속도를 높입니다.
4. 성능: 왜 Gradle이 더 빠른가?
Gradle은 성능 최적화에 집착합니다. Maven을 이기기 위해 주로 3가지 기술을 사용합니다.
A. 증분 빌드 (Incremental Builds - "Up-to-date")
태스크를 실행하기 전에 Gradle은 두 가지를 확인합니다.
- 입력(Inputs): 소스 파일이 변경되었는가? (해시/체크섬 계산)
- 출력(Outputs): 목적지 폴더(예:
build/classes)가 이미 존재하는가?
만약 입력이 변하지 않았다면, Gradle은 태스크를 UP-TO-DATE로 표시하고 실행을 건너뜁니다.
B. Gradle 데몬 (The Daemon)
JVM을 시작하고 라이브러리를 로딩하는 데는 시간이 걸립니다.
- 메커니즘: Gradle은 빌드가 끝나도 백그라운드 프로세스(데몬)를 RAM에 살려둡니다.
- 이점: 다음에 빌드할 때 JVM은 이미 "예열(Warm)"되어 있고(JIT 컴파일 완료), 라이브러리도 메모리에 있습니다. 반복적인 빌드가 매우 빨라집니다.
C. 빌드 캐시 (Build Cache)
- 로컬: 프로젝트를
clean하고 다시 빌드할 때, Gradle은 이전 빌드의 결과물을 하드 드라이브에 기억하고 있다가 다시 컴파일하는 대신 복원합니다. - 원격 (Remote): 큰 기업에서는 공유 서버 캐시를 사용합니다. 동료가 버그를 고치고 빌드했다면, 내 컴퓨터에서 다시 컴파일할 필요 없이 동료가 만든 바이너리를 다운로드해서 씁니다.
5. 의존성 관리: api vs implementation
Maven에는 주로 compile 하나만 있지만, Gradle(특히 java-library 플러그인)에는 두 가지 방식이 있습니다. 이는 컴파일 속도에 결정적입니다.
implementation (기본값)
- 동작: 의존 라이브러리가 내 코드 에서는 보이지만, 나를 의존하는 다른 모듈에게는 숨겨집니다.
- 이점: 내가
implementation으로 된 라이브러리 버전을 바꿔도, Gradle은 내 모듈 만 다시 컴파일합니다. 나를 의존하는 다른 모듈들은 재컴파일할 필요가 없습니다. 훨씬 빠릅니다.
api
- 동작: 의존 라이브러리가 외부로 유출(Leaked)됩니다. 모듈 A가 모듈 B를
api로 쓰고, 모듈 C가 A를 쓴다면, C에서도 B를 볼 수 있습니다. - 단점: B를 수정하면, Gradle은 A뿐만 아니라 C까지 모두 다시 컴파일해야 합니다. 느려집니다.
6. 래퍼 (The Wrapper - gradlew)
실무 CI/CD 파이프라인에서는 gradle 명령어를 거의 보지 못하고, ./gradlew를 보게 됩니다.
- 무엇인가? 프로젝트 Git 저장소에 포함된 쉘 스크립트와 작은 jar 파일입니다.
- 왜 쓰는가? 팀원 모두(그리고 CI 서버)가 정확히 똑같은 버전의 Gradle을 사용하도록 보장합니다.
- 워크플로우:
./gradlew build를 실행하면, 해당 프로젝트에 맞는 Gradle 버전을 자동으로 다운로드하여 실행합니다. OS에 Gradle을 별도로 설치할 필요가 없습니다.
7. 비교표: Maven vs. Gradle
| 특징 | Maven | Gradle |
|---|---|---|
| 언어 | XML (선언적). | Groovy 또는 Kotlin (명령형). |
| 성능 | 느림 (보통 clean 빌드 필요). | 빠름 (증분 빌드 + 캐시 + 데몬). |
| 유연성 | 딱딱함 (생명주기 고정). | 매우 유연함 (커스텀 로직/태스크). |
| 러닝 커브 | 낮음 (읽기 쉬움). | 높음 (API를 배워야 함). |
| 의존성 범위 | compile, provided, test |
implementation, api, compileOnly, runtimeOnly |
| 주 사용처 | 전통적인 엔터프라이즈(SI). | 안드로이드, 모던 스프링 부트, 빅테크 기업. |
개발자를 위한 요약
- 전역 설치된
gradle대신./gradlew를 사용하세요. - 오픈소스 라이브러리를 만드는 게 아니라면
api대신implementation을 사용하세요. - 생명주기를 이해하세요: 무거운 로직은
build.gradle최상단이 아니라doLast { ... }블록 안에 넣으세요. - Kotlin DSL (
build.gradle.kts) 이 미래입니다. Groovy에는 없는 IntelliJ 자동 완성 기능을 제공합니다.
references