Java

Gradle - Task

hwlee9905 2025. 1. 19. 02:42

Gradle의 개요

Gradle은 빌드 자동화 도구로, 다양한 언어와 플랫폼을 지원한다. 특히 Java, Groovy, Kotlin 등과 같은 JVM 기반 언어에서 많이 사용되며, 유연한 빌드 구성을 제공한다. Gradle은 Groovy 또는 Kotlin DSL을 사용하여 빌드 스크립트를 작성할 수 있다. 이러한 유연성 덕분에 개발자들은 자신만의 빌드 프로세스를 쉽게 정의할 수 있다.

Gradle의 주요 구성 요소
Gradle은 여러 구성 요소로 이루어져 있으며, 그중에서도 Project, Task, Plugin이 가장 핵심적인 요소이다. Project는 Gradle 빌드의 기본 단위이며, Task는 특정 작업을 수행하는 단위이다. Plugin은 이러한 Task를 확장하거나 새로운 기능을 추가하는 역할을 한다.

Gradle 빌드 프로세스 흐름

Gradle의 빌드 프로세스는 여러 단계로 나뉘어 있다. 일반적으로 'build.gradle' 파일을 통해 빌드 구성을 정의하고, Gradle은 이 파일을 읽어 필요한 Task를 실행한다. 빌드 프로세스는 다음과 같은 흐름으로 진행된다:

  1. 소스 코드 및 설정 파일 읽기: Gradle은 프로젝트의 소스 코드와 설정 파일을 읽는다.
  2. Task 실행: 정의된 Task가 순차적으로 실행된다.
  3. 결과물 생성: 최종적으로 JAR, APK, ZIP 등의 결과물이 생성된다.

Gradle Task

Task는 Gradle에서 수행할 수 있는 작업의 단위이다. 예를 들어, Java 소스 코드를 컴파일하거나, 테스트를 실행하거나, JAR 파일을 생성하는 등의 작업이 Task로 정의된다. 각 Task는 독립적으로 실행될 수 있으며, 다른 Task와의 의존성을 설정할 수 있다.

 

Task와 Plugin의 관계

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.1'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
}


Plugin은 Task를 정의하고 구성하는 데 중요한 역할을 한다. 예를 들어, Java Plugin을 적용하면 Gradle은 자동으로 여러 기본 Task를 생성한다. 이러한 Task는 Java 프로젝트에서 일반적으로 필요한 작업을 수행하도록 설계되어 있다. 따라서 Plugin을 통해 Task의 기능을 확장할 수 있다. Task 전용 라이브러리라고 생각하면 편하다.

PS D:\repository\gradlePractice> .\gradlew classes --info
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE

화살표의 방향은 먼저 실행되어야 하는 의존관계를 나타낸다.

classes를 실행하였더니, 의존하고 있는 메소드를 먼저 실행하는 모습.

 

Task 간의 의존성

Task 간의 의존성은 Gradle의 강력한 기능 중 하나이다.

Gradle에서는 작업(task) 간의 실행 순서와 의존성 관리를 위해 약한 연관관계(weak dependency)와 강한 연관관계(strong dependency)가 존재한다.

1. 강한 연관관계 

강한 연관관계는 특정 작업이 실행되기 전에 반드시 다른 작업이 완료되어야 함을 의미한다.

 

특정 Task가 실행되기 전에 다른 Task가 먼저 실행되어야 할 경우, 'dependsOn'을 사용하여 의존성을 설정할 수 있다. 예를 들어, 'build' Task는 'compileJava'와 'test' Task에 의존할 수 있다. 

 

이 경우, 의존하는 작업이 실패하면 해당 작업도 실행되지 않는다. 이러한 의존성 설정은 빌드 프로세스를 더욱 효율적으로 만들어 준다.

 

dependsOn

tasks.register('taskA'){
    doFirst{
        println(">>>>>>>>>>>>>>> taskA")
    }
}

tasks.register('taskB'){
    dependsOn 'taskA'
    doFirst{
        println(">>>>>>>>>>>>>>> taskB")
    }
}
PS D:\repository\gradlePractice> .\gradlew taskB                                                                                                                                                                                                                                              
> Task :taskA
>>>>>>>>>>>>>>> taskA

> Task :taskB
>>>>>>>>>>>>>>> taskB


위의 예에서 taskB는 taskA에 의존하므로, taskA가 먼저 실행된 후에 taskB가 실행된다.

 

finalizedBy

finalizedBy는 Gradle에서 작업 간의 관계를 정의하는 데 사용되는 메서드로, 특정 작업이 완료된 후에 다른 작업을 실행하도록 지정한다. 

 

dependsOn과는 다르게, finalizedBy는 의존하는 작업이 실행되기 전에 해당 작업이 완료되어야 한다는 조건이 없다. 즉, 의존하는 작업이 실패하더라도 실행되어야 하는 작업을 지정할 때 사용된다. 이 메서드는 특히 후처리 작업이나 청소 작업을 정의할 때 유용하다.

 

task compileJava {
    doLast {
        println '자바 코드 컴파일 중...'
    }
}

task clean {
    doLast {
        println '청소 중..'
    }
}

// compileJava 작업이 완료된 후 clean 작업을 실행
compileJava.finalizedBy clean


2. 약한 연관관계

약한 연관관계는 특정 작업이 다른 작업의 실행 순서에 영향을 받지 않지만, 특정 작업이 먼저 실행되도록 권장하는 경우를 의미한다.

 

Gradle에서는 mustRunAfter 또는 shouldRunAfter를 사용하여 약한 의존성을 설정한다. 이 경우, 의존하는 작업이 실패하더라도 다른 작업은 실행될 수 있다.

강한 연관관계와 비슷하다고 생각할 수 있지만, 예제로 보면 그 차이점을 알 수 있을 것이다.

task taskC {
    doLast {
        println 'Task C'
    }
}

task taskD {
    mustRunAfter taskC
    doLast {
        println 'Task D'
    }
}
PS D:\repository\gradlePractice> .\gradlew taskD                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
> Task :taskD
Task D

PS D:\repository\gradlePractice> .\gradlew taskD taskC                                                                                                                                                                                                                                        
> Task :taskC
Task C

> Task :taskD
Task D

Task를 강한 연관관계로 설정했을 때는 하나의 Task만 실행하여도 그 하위의 Task가 모두 실행되지만,

약한 연관관계로 지정한 Task는 실행할 때 지정해주지 않으면 실행되지 않는다. 따라서 단순히 '순서'만 지정하는 것이다.

 

mustRunAfter taskC로 설정했으므로 taskD, taskC로 명령을 실행했지만 순서를 정해줬으므로 taskC -> taskD 순서로 실행되는 것을 볼 수 있다.

 

약한 연관관계는 mustRunAfter과 shouldRunAfter이 있는데 그 차이점은 finalizedBy과 dependsOn의 차이점과 같다

 

특성 mustRunAfter shouldRunAfter
실행 강제성 강제 (필수적으로 실행됨) 권장 (시도하지만 강제적이지 않음)
실패 처리 실패하면 실행되지 않음 실패해도 실행될 수 있음

Task Life Cycle

Gradle의 작업(Task) 라이프 사이클과 구성 회피(Configuration Avoidance)는 빌드 성능을 최적화하고, 작업을 효율적으로 관리하는 데 중요한 개념이다. 

1. 작업(Task) 라이프 사이클
Gradle의 작업 라이프 사이클은 다음과 같은 단계로 구성된다.

생성
작업은 task 또는 tasks.register를 사용하여 생성된다. 이 단계에서 작업의 이름, 타입, 동작 등을 정의한다.

 

구성
Gradle은 모든 작업의 속성을 설정하고, 필요한 의존성을 해결한다. 이 단계에서 작업이 실행될 준비가 된다. 이 과정에서 다른 작업과의 의존성이 설정될 수 있다.


실행
구성 단계가 완료된 후, Gradle은 작업을 실행한다. 이 단계에서는 doLast, doFirst와 같은 동작이 수행된다.

 

종료
작업이 완료되면 Gradle은 리소스를 해제하고, 결과를 출력한다.


2. 구성 회피(Configuration Avoidance)
구성 회피는 Gradle에서 성능을 최적화하기 위해 작업을 불필요하게 구성하지 않도록 하는 기법이다. 이를 통해 불필요한 작업 구성으로 인한 성능 저하를 방지할 수 있다.

 

지연 구성(Lazy Configuration):
작업이 실제로 실행될 때까지 구성하지 않도록 설정할 수 있다. 예를 들어, tasks.register 메서드를 사용하여 작업을 등록하면, 필요한 경우에만 구성된다.

 

register 사용X

tasks.register('taskA'){
    doFirst{
        println(">>>>>>>>>>>>>>> taskA")
    }
}

task taskB{
    println(">>>>>>>>>>>>>>> taskB 구성 중...")
    doFirst{
        println(">>>>>>>>>>>>>>> taskB")
    }
}

PS D:\repository\gradlePractice> .\gradlew taskA                                                                                                                                                                                     

> Configure project :
>>>>>>>>>>>>>>> taskB 구성 중...

> Task :taskA
>>>>>>>>>>>>>>> taskA

 

register 사용

tasks.register('taskA'){
    doFirst{
        println(">>>>>>>>>>>>>>> taskA")
    }
}

tasks.register('taskB'){
    println(">>>>>>>>>>>>>>> taskB 구성 중...")
    doFirst{
        println(">>>>>>>>>>>>>>> taskB")
    }
}
PS D:\repository\gradlePractice> .\gradlew taskA                                                                                                                                                                                     

> Task :taskA
>>>>>>>>>>>>>>> taskA
Task를 생성할때는 꼭 register 메소드를 사용하자.

register를 사용하지 않았을 때는 taskA만 실행했음에도 불구하고 taskB의 구성부분이 실행되는 것을 볼 수 있다.