Gradle
고친 사람 github-actions[bot]
Gradle 은 프로젝트를 빌드하는 일을 대신 맡아 주는 도구입니다. 소스를 배포할 파일로 만드는 여러 일을 명령 하나로 끝내 줍니다. 무엇을 어떤 순서로 할지는 프로젝트에 둔 스크립트 파일에 적습니다. 자바와 코틀린 프로젝트에서 흔히 씁니다.
쉽고 빠른 이해
Gradle 은 빌드를 대신 돌려 주는 도구입니다. ./gradlew build 한 줄이면 컴파일부터 테스트, 실행 파일 묶기까지 끝납니다. gradlew 는 프로젝트에 같이 들어 있는 실행 스크립트입니다. 이 스크립트가 Gradle 을 불러 줍니다.
이게 없으면 사람이 컴파일러를 손으로 불러야 합니다. 라이브러리를 직접 내려받아 경로에 얹어야 합니다. 파일 순서도 챙겨야 합니다. 파일이 수백 장이면 사람이 따라갈 수 없습니다.
어떻게 도나:
- 스크립트를 읽어 할 일 목록을 만듭니다. 할 일마다 먼저 끝나야 할 일이 붙어 있습니다
- 순서를 정해 하나씩 돌립니다
- 지난번과 입력이 같은 일은 건너뜁니다
자바·코틀린 프로젝트를 빌드할 때 씁니다. 모두가 같은 모양의 빌드 파일을 원하면 다른 빌드 도구인 Maven 이 더 낫습니다.
대가도 있습니다. 스크립트가 코드라서 무엇이든 적을 수 있습니다. 그만큼 남의 빌드를 읽기 어렵습니다. 처음 돌릴 때는 도구 자신과 라이브러리를 내려받느라 오래 걸립니다.
상세
Gradle 은 빌드 도구입니다. 빌드 도구는 소스 코드를 실행하거나 배포할 수 있는 결과물로 바꾸는 과정을 자동으로 돌리는 프로그램입니다. 그 과정에는 컴파일, 테스트, 파일 묶기가 들어갑니다.
예를 들어 자바 프로젝트 하나를 빌드하려면 할 일이 셋입니다.
JAR 은 클래스 파일 여럿을 한 파일로 묶은 것입니다. Gradle 은 이 세 일을 순서대로 불러 줍니다.
손으로 하면 곤란한 점이 셋 있습니다. 쓸 라이브러리를 직접 구해 와야 합니다. 어떤 일이 어떤 일보다 먼저인지 기억해야 합니다. 한 줄만 고쳐도 전부 다시 돌리게 됩니다. Gradle 이 이 셋을 맡습니다.
Gradle 자신도 JVM(Java Virtual Machine, 자바 가상 머신) 위에서 도는 프로그램입니다. 그래서 설치하려면 자바가 먼저 있어야 합니다. 빌드하는 대상은 자바에 묶이지 않습니다. 코틀린·그루비 프로젝트와 안드로이드 앱도 Gradle 로 빌드합니다.
빌드 스크립트
무엇을 빌드할지는 프로젝트 루트의 빌드 스크립트에 적습니다. 빌드 스크립트는 빌드 도구에게 할 일을 알려 주는 파일입니다. Gradle 에서는 이 파일이 설정 목록이 아니라 코드입니다.
스크립트 언어는 둘 중에 고릅니다. 코틀린으로 쓰면 build.gradle.kts, 그루비로 쓰면 build.gradle 입니다. 둘 다 DSL(Domain-Specific Language, 도메인 특화 언어)로 씁니다. DSL 은 한 가지 일에 맞춰 문법을 좁힌 언어입니다.
아래는 코틀린으로 쓴 가장 작은 자바 프로젝트 스크립트입니다.
plugins {
java // 자바 빌드 기능
}
repositories {
mavenCentral() // 라이브러리 받는 곳
}
dependencies {
implementation("com.google.guava:guava:33.0.0-jre")
testImplementation("junit:junit:4.13.2")
}
블록이 셋입니다. plugins 는 어떤 기능을 얹을지, repositories 는 라이브러리를 어디서 받을지, dependencies 는 무엇을 받을지 정합니다. 각 블록은 아래 소절에서 하나씩 봅니다.
여러 모듈(하위 프로젝트)로 나뉜 프로젝트는 settings.gradle.kts 도 둡니다. 이 파일은 어떤 하위 프로젝트가 한 빌드에 들어가는지 적습니다.
태스크와 태스크 그래프
Gradle 이 돌리는 일의 단위는 태스크입니다. 태스크는 빌드 안에서 따로 떼어 돌릴 수 있는 작업 한 덩이입니다. 소스를 컴파일하는 일, 테스트를 돌리는 일, JAR 을 만드는 일이 각각 태스크입니다.
태스크에는 먼저 끝나야 하는 태스크를 걸 수 있습니다. JAR 을 만들려면 컴파일이 먼저 끝나 있어야 하는 식입니다. 이렇게 걸린 관계를 모으면 그래프 하나가 됩니다.
이 그래프는 DAG(Directed Acyclic Graph, 방향 비순환 그래프)입니다. 화살표에 방향이 있는 그래프입니다. 화살표를 따라가도 제자리로 돌아오는 고리가 없습니다. 고리가 있으면 무엇부터 할지 정할 수 없어서 Gradle 은 그런 빌드를 오류로 멈춥니다.
아래는 자바 플러그인이 만드는 태스크 일부입니다. 화살표는 「이것을 하려면 저것이 먼저」를 뜻합니다. assemble 은 결과물을 만드는 태스크 묶음입니다. check 는 테스트 같은 검사를 모은 묶음입니다.
flowchart TD
B["build"] --> A["assemble"]
B --> C["check"]
A --> J["jar"]
C --> T["test"]
J --> CL["classes"]
T --> CL
CL --> CJ["compileJava"]
build 를 부르면 Gradle 은 화살표를 따라 맨 아래 compileJava 까지 찾아 내려갑니다. 그런 다음 거기서부터 거꾸로 올라오며 돌립니다. jar 와 test 가 둘 다 classes 를 요구해도 classes 는 한 번만 돕니다. 한 번의 빌드에서 태스크 하나는 한 번만 실행됩니다.
빌드가 도는 세 단계
명령 한 줄을 넣으면 Gradle 은 세 단계를 차례로 밟습니다. 단계마다 하는 일이 다릅니다.
| 단계 | 하는 일 |
|---|---|
| 초기화 | settings.gradle.kts 를 읽어 어떤 프로젝트가 빌드에 들어가는지 정합니다 |
| 구성 | 빌드 스크립트를 실행해 태스크와 그 관계를 만듭니다 |
| 실행 | 요청받은 태스크와 거기 딸린 태스크를 순서대로 돌립니다 |
구성 단계에서는 스크립트가 실행됩니다. 스크립트에 오래 걸리는 코드를 적어 두면, 어떤 태스크를 부르든 매번 그 코드가 돕니다. 빌드가 이유 없이 느리면 이 단계부터 의심합니다.
플러그인
빈 Gradle 은 자바를 컴파일할 줄 모릅니다. 그 능력은 플러그인이 얹어 줍니다. 플러그인은 태스크와 설정을 한 묶음으로 가져오는 확장입니다.
앞 스크립트의 java 한 줄이 그 예입니다. 이 줄 하나로 compileJava · test · jar · build 같은 태스크가 생깁니다. 소스는 src/main/java, 테스트는 src/test/java 에 둔다는 약속도 같이 들어옵니다.
이런 약속을 설정보다 관례라고 부릅니다. 정해진 폴더에 두기만 하면 따로 적을 것이 없다는 뜻입니다. 약속과 다르게 두고 싶을 때만 스크립트에 적습니다.
안드로이드 앱을 빌드하는 기능도 플러그인으로 들어옵니다. Gradle 본체는 무엇을 빌드하는지 모릅니다. 그 지식은 플러그인이 채웁니다.
의존성 관리
프로젝트가 가져다 쓰는 외부 라이브러리를 의존성이라고 부릅니다. Gradle 에는 이름과 버전만 적습니다. 파일을 구해 오는 일은 Gradle 이 합니다.
라이브러리 이름은 그룹:이름:버전 세 칸으로 씁니다. 앞 스크립트의 com.google.guava:guava:33.0.0-jre 가 그 꼴입니다. Gradle 은 이 이름으로 repositories 에 적힌 저장소를 뒤져 파일을 받습니다.
라이브러리는 또 다른 라이브러리에 기대기도 합니다. 이렇게 딸려 오는 것을 전이 의존성이라고 부릅니다. Gradle 은 이것까지 따라가 받습니다.
두 라이브러리가 같은 라이브러리의 서로 다른 버전을 원할 때가 있습니다. 이때는 하나로 골라야 합니다. Gradle 은 기본으로 더 높은 버전을 고릅니다.
implementation 과 testImplementation 은 그 라이브러리를 어디에 쓰는지 가릅니다. 앞의 것은 본 코드와 테스트 모두에 씁니다. 뒤의 것은 테스트에만 씁니다. 그래서 테스트 도구는 배포할 결과물에 섞여 들어가지 않습니다.
받은 파일은 사용자 홈의 .gradle 폴더에 캐시로 쌓입니다. 다음 빌드에서는 거기서 꺼내 씁니다.
저장소 형식은 다른 빌드 도구인 Maven 과 같습니다. 그래서 Maven 용으로 올라온 라이브러리를 그대로 받을 수 있습니다.
바뀐 것만 다시 하는 증분 빌드
한 줄 고치고 다시 빌드할 때 전부 새로 돌리면 시간이 아깝습니다. Gradle 은 달라진 것이 없는 태스크를 건너뜁니다. 이것을 증분 빌드라고 부릅니다.
판단 근거는 태스크의 입력과 출력입니다. 컴파일 태스크라면 소스 파일이 입력입니다. 클래스 파일은 출력입니다.
Gradle 은 지난번 빌드 때 입력과 출력의 지문을 기억해 둡니다. 지문은 파일 내용이 바뀌면 함께 바뀌는 짧은 요약값입니다. 파일 전체를 쌓아 두지 않고도 달라졌는지 알 수 있습니다.
flowchart TD
S["태스크 차례가 온다"] --> Q1{"입력이 지난번과 같나"}
Q1 -- 아니오 --> RUN["태스크를 돌린다"]
Q1 -- 예 --> Q2{"출력이 지난번 그대로 있나"}
Q2 -- 아니오 --> RUN
Q2 -- 예 --> SKIP["건너뛴다 · UP-TO-DATE"]
건너뛴 태스크는 콘솔에 UP-TO-DATE 로 찍힙니다. 그래서 어떤 태스크가 실제로 돌았는지 빌드 출력만 보고 알 수 있습니다.
같은 생각을 한 발 더 넓힌 것이 빌드 캐시입니다. 증분 빌드는 바로 앞 빌드만 봅니다. 빌드 캐시는 예전 어느 빌드의 출력이든, 입력이 같으면 가져다 씁니다. 캐시를 팀이 함께 쓰는 서버에 두면 동료가 이미 만든 출력을 받아 쓸 수도 있습니다.
Gradle 데몬
Gradle 은 JVM 위에서 돌기 때문에 시작이 굼뜹니다. 명령마다 JVM 을 새로 띄우고 Gradle 을 읽어 들이면 빌드 전에 시간이 한참 갑니다.
그래서 Gradle 은 첫 빌드 때 데몬을 띄워 둡니다. 데몬은 뒤에서 계속 살아 있는 프로세스입니다. 다음 명령은 새 JVM 을 띄우지 않고 이미 떠 있는 데몬에게 일을 넘깁니다. 두 번째 빌드부터 빨라지는 까닭입니다.
대가는 메모리입니다. 빌드가 끝나도 데몬이 메모리를 쥔 채 남아 있습니다. 오래 안 쓰면 스스로 내려갑니다. ./gradlew --stop 으로 직접 내릴 수도 있습니다.
Gradle 래퍼
같은 프로젝트를 여러 사람이 빌드하면 저마다 깔린 Gradle 버전이 다를 수 있습니다. 버전이 다르면 같은 스크립트가 다르게 돌거나 아예 안 돕니다.
Gradle 래퍼가 이 문제를 풉니다. 래퍼는 프로젝트에 같이 넣어 두는 작은 실행 스크립트입니다. 어떤 Gradle 버전을 쓸지는 gradle/wrapper/gradle-wrapper.properties 파일에 적혀 있습니다.
./gradlew build # 적힌 버전으로 빌드
./gradlew tasks # 태스크 목록
gradle 대신 ./gradlew 를 부르면 래퍼가 적힌 버전을 찾습니다. 그 버전이 없으면 내려받은 뒤 빌드합니다. 그래서 Gradle 을 따로 설치하지 않은 기계에서도 빌드가 됩니다. CI(Continuous Integration, 지속적 통합) 서버에서 빌드할 때도 같은 이유로 래퍼를 부릅니다.
Maven 과 갈리는 점
Maven 은 JVM 진영의 다른 빌드 도구입니다. 둘 다 같은 저장소에서 라이브러리를 받습니다. 갈리는 곳은 빌드를 적는 방식입니다.
| Gradle | Maven | |
|---|---|---|
| 빌드 파일 | 코틀린·그루비 코드 | XML(eXtensible Markup Language, 확장 가능한 마크업 언어) 설정 |
| 할 일의 순서 | 태스크 그래프로 자유롭게 짠다 | 미리 정해진 빌드 순서(compile→test→package)를 따른다 |
Gradle 쪽이 자유롭습니다. 그 자유가 곧 대가입니다. 스크립트가 코드라서 사람마다 다르게 짭니다. 남이 짠 빌드를 읽으려면 코드를 따라가야 합니다. Maven 은 적을 수 있는 것이 좁은 대신 어느 프로젝트를 열어도 모양이 비슷합니다.
관련 항목
Gradle 과 같은 역할을 두고 겨루는 빌드 도구
Maven · Ant · Bazel · sbt · 빌드 도구
Gradle 이 대신 부르는 컴파일러와 결과물
javac · kotlinc · 클래스 파일 · JAR · 바이트코드 · JVM · JDK
Gradle 빌드를 이루는 구성 요소
태스크 · 플러그인 · 빌드 스크립트 · DSL · 멀티 프로젝트 빌드 · 빌드 수명 주기 · DAG · 설정보다 관례
Gradle 이 라이브러리를 받는 데 쓰는 개념
의존성 · 전이 의존성 · 의존성 충돌 · Maven Central · 아티팩트 저장소 · 클래스패스
Gradle 이 빌드를 빠르게 하려고 쓰는 수단
증분 빌드 · 빌드 캐시 · 데몬 · Gradle 래퍼 · 병렬 빌드
Gradle 빌드 스크립트를 적는 언어
Gradle 빌드를 부르는 환경
다른 이름: 그래들 · Gradle Build Tool