원문 - Build Lifecycle


  • 빌드 단계(Build phases)
  • 세팅 파일(Settings file)
  • 초기화(Initialization)
  • 단일 프로젝트 빌드의 구성 및 실행(Configuration and execution of a single project build)
  • 빌드 스크립트 생명주기에 응답(Responding to the lifecycle in the build script)

빌드 생명주기(Build Lifecycle)

앞서 그레이들의 핵심은 의존성 기반 프로그래밍을 위한 언어라고 말했다. 그레이들 용어로 이는 태스크와 태스크 간의 의존성을 정의할 수 있음을 의미한다. 그레이들은 이러한 태스크들이 의존성 순서에 따라 실행되고 각 태스크가 한 번만 실행되도록 보장한다. 이러한 태스느는 방향성 비순환 그래프를 형성한다. 태스크를 실행할 때 이러한 의존성 그래프를 작성하는 빌드 도구가 있다. 그레이들은 태스크가 실행되기 전에 완전한 종속성 그래프를 작성한다. 이는 그레이들의 핵심이며 다른 방법으로는 불가능했던 많은 일을 가능하게 한다.

빌드 스크립트는 이 의존성 그래프를 구성한다. 따라서 엄밀히 말하면 빌드 구성 스크립트다.

빌드 단계(Build phases)

그레이들 빌드에는 세 가지 단계가 있다.

  • 초기화(Initialization) 그레이들 단일 및 멀티 프로젝트 빌드를 지원한다. 초기화 단계에서 그레이들은 빌드에 참여할 프로젝트를 결정하고 이러한 프로젝트 각각에 대해 Project 인스턴스를 생성한다.
  • 구성(Configuration) 이 단계에서 Project 객체가 구성된다. 빌드의 일부인 모든 프로젝트의 빌드 스크립트가 실행된다.
  • 실행(Execution) 그레이들은 구성 단계에서 생성 및 구성되어, 실행될 태스크의 서브셋(subset)을 결정한다. 서브셋은 gradle 명령과 현재 디렉토리에 전달된 태스크명 아규먼트에 의해 결정된다. 그런 다음 그레이들은 선택된 각 태스크를 실행한다.

세팅 파일(Settings file)

빌드 스크립트 파일 옆에, 그레이들의 세팅 파일을 정의한다. 세팅 파일은 명명 규칙으로 그레이들에 의해 결정된다. 이 파일의 기본 이름은 settings.gradle이다. 이 장의 뒷부분에서는 그레이들이 세팅 파일을 찾는 방법을 설명한다.

세팅 파일은 초기화 단계에서 실행된다. 멀티 프로젝트 빌드에는 멀티 프로젝트 계층 구조의 루트 프로젝트에 settings.gradle 파일이 있어야 한다. 세팅 파일은 멀티 프로젝트 빌드에 참여하는 프로젝트를 정의하기 때문에 필요하다(멀티 프로젝트 빌드 작성 참조). 단일 프로젝트 빌드의 경우 설정 파일은 선택 사항입니다. 포함된 프로젝트를 정의하는 것 외에도 빌드 스크립트 클래스 경로에 라이브러리를 추가하는 데 필요할 수 있습니다(그레이들 프로젝트 구성 참고). 먼저 단일 프로젝트 빌드를 살펴보자.

예제 1. 단일 브로젝트 빌드

그루비 *** settings.gradle

rootProject.name = 'basic'
println '이는 초기화 단계에서 실행된다.'

build.gradle

println '이는 구성 단계에서 실행된다.'

tasks.register('configured') {
    println ':configured가 빌드에 사용되기 때문에 이는 구성 단계에서도 실행된다.'
}

tasks.register('test') {
    doLast {
        println '이는 실행 단계에서 실행된다.'
    }
}

tasks.register('testBoth') {
	doFirst {
	  println '이는 실행 단계에서 먼저 실행된다.'
	}
	doLast {
	  println '이는 실행 단계에서 마지막으로 실행된다.'
	}
	println ':testBoth가 빌드에 사용되기 때문에 이는 구성 단계에서도 실행된다.'
}

코틀린 *** settings.gradle.kts

rootProject.name = "basic"
println("이는 초기화 단계에서 실행된다.")

build.gradle.kts

println("이는 구성 단계에서 실행된다.")

tasks.register("configured") {
    println(":configured가 빌드에 사용되기 때문에 이는 구성 단계에서도 실행된다.")
}

tasks.register("test") {
    doLast {
        println("이는 실행 단계에서 실행된다.")
    }
}

tasks.register("testBoth") {
    doFirst {
        println("이는 실행 단계에서 먼저 실행된다.")
    }
    doLast {
        println("이는 실행 단계에서 마지막으로 실행된다.")
    }
    println(":testBoth가 빌드에 사용되기 때문에 이는 구성 단계에서도 실행된다")
}

gradle test testBoth의 출력

> gradle test testBoth
이는 초기화 단계에서 실행된다.

> Configure project :
이는 구성 단계에서 실행된다.
:testBoth가 빌드에 사용되기 때문에 이는 구성 단계에서도 실행된다

> Task :test
이는 실행 단계에서 실행된다.

> Task :testBoth
이는 실행 단계에서 먼저 실행된다.
이는 실행 단계에서 마지막으로 실행된다.

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

빌드 스크립트의 경우, 프로퍼티 접근 및 메서드 호출이 Project 객체에 위임(delegated)된다. 마찬가지로 세팅 파일 내의 프로퍼티 접근 및 메서드 호출은 Settings 객체에 위임된다. 자세한 내용은 API 설명서의 Settings 클래스를 참고하자.

초기화(Initialization)

그레이들은 단일 프로젝트 빌드를 수행할지 멀티 프로젝트 빌드를 수행할지 어떻게 알 수 있나? settings.gradle 파일이 있는 디렉토리에서 멀티 프로젝트 빌드를 트리거하면 그레이들은 이를 사용하여 빌드를 구성한다. 그레이들을 사용하면 빌드에 참여하는 모든 서브 프로젝트 내에서 빌드를 실행할 수도 있다.[1] settings.gradle 파일 없이 프로젝트 내에서 그레이들을 실행하는 경우 그레이들은 다음과 같은 방법으로 settings.gradle 파일을 찾는다.

  • 상위 디렉토리에서 settings.gradle을 찾는다.
  • 찾을 수 없는 경우 빌드는 단일 프로젝트 빌드로 실행된다.
  • settings.gradle 파일이 발견되면 그레이들은 현재 프로젝트가 발견된 settings.gradle 파일에 정의된 멀티 프로젝트 계층 구조의 일부인지 확인한다. 그렇지 않은 경우 빌드는 단일 프로젝트 빌드로 실행된다. 그렇지 않으면 멀티 프로젝트 빌드가 실행된다.

이 행동의 목적은 무엇일까? 그레이들은 현재 진행 중인 프로젝트가 멀티 프로젝트 빌드의 서브 프로젝트인지 여부를 확인해야 한다. 물론 서브 프로젝트인 경우 서브 프로젝트와 해당 의존 프로젝트만 빌드되지만 그레이들은 전체 멀티 프로젝트 빌드에 대한 빌드 구성을 생성해야 한다(구성 및 실행 참고). 현재 프로젝트에 settings.gradle 파일이 포함되어 있으면 빌드는 항상 다음과 같이 실행된다.

  • settings.gradle 파일이 멀티 프로젝트 계층 구조를 정의하지 않는 경우 단일 프로젝트 빌드
  • settings.gradle 파일이 멀티 프로젝트 계층 구조를 정의하는 경우 다중 프로젝트 빌드.

settings.gradle 파일에 대한 자동 검색은 프로젝트 경로가 디스크의 물리적 서브 프로젝트 레이아웃과 일치하는 기본 프로젝트 레이아웃을 사용하는 멀티 프로젝트 빌드에 대해서만 작동한다. 그레이들은 멀티 프로젝트 빌드에 대한 임의의 물리적 레이아웃을 지원하지만 이러한 임의 레이아웃의 경우 세팅 파일이 있는 디렉토리에서 빌드를 실행해야 한다. 루트에서 부분 빌드를 실행하는 방법에 대한 자세한 내용은 정규화된 이름으로 태스크 실행을 참고하자.

그레이들은 빌드에 참여하는 모든 프로젝트에 대해 Project 객체를 생성한다. 멀티 프로젝트 빌드의 경우 이는 Settings 객체에 지정된 프로젝트(및 루트 프로젝트)다. 각 Project 객체에는 기본적으로 최상위 디렉토리명과 동일한 이름이 있으며 루트 프로젝트를 제외한 모든 프로젝트에는 상위 프로젝트가 있다. 모든 프로젝트에는 서브 프로젝트가 있을 수 있다.

단일 프로젝트 빌드의 구성 및 실행(Configuration and execution of a single project build)

단일 프로젝트 빌드의 경우 초기화 후 단계의 워크플로는 매우 간단하다. 빌드 스크립트는 초기화 단계에서 생성된 프로젝트 객체에 대해 실행된다. 그런 다음 그레이들은 명령줄 아규먼트로 전달된 이름과 동일한 이름을 가진 태스크를 찾는다. 이러한 태스크 이름이 존재하는 경우 전달한 순서대로 별도의 빌드로 실행된다. 멀티 프로젝트 빌드의 구성 및 실행은 구성 및 실행에서 설명한다.

빌드 스크립트의 생명 주기에 응답(Responding to the lifecycle in the build script)

빌드 스크립트는 생명 주기를 통해 빌드가 진행됨에 따라 알림을 받을 수 있다. 이러한 알림은 일반적으로 두 가지 형식을 취한다. 특정 리스너 인터페이스를 구현하거나 알림이 실행될 때 실행할 클로저를 제공할 수 있다. 아래 예제에서는 클로저를 사용한다. 리스너 인터페이스 사용 방법에 대한 자세한 내용은 API 설명서를 참고하자.

프로젝트 평가(Project evaluation)

프로젝트 평가 전후에 즉시 알림을 받을 수 있다. 이는 빌드 스크립트의 모든 정의가 적용된 후 추가 구성을 수행하거나 일부 커스텀 로깅 또는 프로파일링을 수행하는 등의 태스크를 수행하는 데 사용할 수 있다.

다음은 hasTests 프로퍼티 값이 true인 각 프로젝트에 테스트 태스크를 추가하는 예제다.

예제 2. 특정 프로퍼티가 설정된 각 프로젝트에 테스트 태스크 추가

그루비 *** build.gradle

allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') {
                doLast {
                    println "Running tests for $project"
                }
            }
        }
    }
}

project-a.gradle

hasTests = true


build.gradle.kts

allprojects {
    // 기본값 설정
    extra["hasTests"] = false

    afterEvaluate {
        if (extra["hasTests"] as Boolean) {
            println("Adding test task to $project")
            tasks.register("test") {
                doLast {
                    println("Running tests for $project")
                }
            }
        }
    }
}

project-a.gradle.kts

extra["hasTests"] = true

gradle -q test의 출력

> gradle -q test
Adding test task to project ':project-a'
Running tests for project ':project-a'

이 예제에서는 Project.afterEvaluate() 메서드를 사용하여 프로젝트가 평가된 후(즉, 구성 단계의 끝) 실행되는 클로저를 추가한다.

프로젝트가 평가되면 알림을 받을 수도 있다. 이 예에서는 프로젝트 평가에 대한 일부 커스텀 로깅을 수행한다. 프로젝트 평가 성공 여부와 관계없이 afterProject 알림이 수신되거나 예외가 발생하여 실패한다.

예제 3. 알림

build.gradle

gradle.afterProject { project ->
    if (project.state.failure) {
        println "Evaluation of $project FAILED"
    } else {
        println "Evaluation of $project succeeded"
    }
}

build.gradle.kts

gradle.afterProject {
    if (state.failure != null) {
        println("Evaluation of $project FAILED")
    } else {
        println("Evaluation of $project succeeded")
    }
}

gradle -q test의 출력

build.gradle

> gradle -q test
Evaluation of root project 'build-project-evaluate-events' succeeded
Evaluation of project ':project-a' succeeded
Evaluation of project ':project-b' FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/project-b.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':project-b'.
> broken

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

build.gradle.kts

> gradle -q test
Evaluation of root project 'build-project-evaluate-events' succeeded
Evaluation of project ':project-a' succeeded
Evaluation of project ':project-b' FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/project-b.gradle.kts' line: 1

* What went wrong:
broken

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

그레이들에 ProjectEvaluationListener를 추가하여 이러한 이벤트를 수신할 수도 있다.

태스크 생성(Task creation)

프로젝트에 태스크가 추가되면 즉시 알림을 받을 수 있다. 이는 빌드 파일에서 태스크를 사용할 수 있게 되기 전에 일부 기본값을 설정하거나 동작을 추가하는 데 사용할 수 있다.

다음 예제에서는 각 태스크가 생성될 때 해당 태스크의 srcDir 프로퍼티를 설정한다.

예제 4. 모든 태스크에 특정 프로퍼티 설정

build.gradle

tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

tasks.register('a')

println "source dir is $a.srcDir"

build.gradle.kts

tasks.whenTaskAdded {
    extra["srcDir"] = "src/main/java"
}

val a by tasks.registering

println("source dir is ${a.get().extra["srcDir"]}")

Output of gradle -q a

> gradle -q a
source dir is src/main/java

TaskContainer에 태스크를 추가하여 이러한 이벤트를 수신할 수도 있다.

태스크 실행 그래프 준비(Task execution graph ready)

태스크 실행 그래프가 채워지면 즉시 알림을 받을 수 있다.

TaskExecutionGraphTaskExecutionGraphListener를 추가하여 이러한 이벤트를 수신할 수도 있다.

태스크 실행(Task execution)

태스크가 실행되기 전후에 즉시 알림을 받을 수 있다.

다음 예제에서는 각 태스크 실행의 시작과 끝을 기록한다. 태스크가 성공적으로 완료되었는지 또는 예외로 인해 실패했는지 여부에 관계없이 afterTask 알림이 수신된다.

예제 5. 각 태스크 실행의 시작과 끝을 로깅

build.gradle

tasks.register('ok')

tasks.register('broken') {
    dependsOn ok
    doLast {
        throw new RuntimeException('broken')
    }
}

gradle.taskGraph.beforeTask { Task task ->
    println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println "FAILED"
    }
    else {
        println "done"
    }
}

build.gradle.kts

tasks.register("ok")

tasks.register("broken") {
    dependsOn("ok")
    doLast {
        throw RuntimeException("broken")
    }
}

gradle.taskGraph.beforeTask {
    println("executing $this ...")
}

gradle.taskGraph.afterTask {
    if (state.failure != null) {
        println("FAILED")
    } else {
        println("done")
    }
}

gradle -q broken의 출력

build.gradle

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/build.gradle' line: 6

* What went wrong:
Execution failed for task ':broken'.
> broken

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

build.gradle.kts

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':broken'.
> broken

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

TaskExecutionGraphTaskExecutionListener를 사용하여 이러한 이벤트를 수신할 수도 있다.

  1. 그레이들은 부분적인 멀티 프로젝트 빌드를 지원한다(멀티 프로젝트 빌드 실행 참고).