====== Gradle Build Lifecycle ====== * [[http://www.gradle.org/docs/current/userguide/build_lifecycle.html|Build Lifecycle]] Gradle은 의존성 기반의 프로그래밍용 언어이다. 이 말은 태스크를 정의하고 또한 태스크들 사이의 의존성도 정의 할 수 있다는 뜻이다. **Gradle은 태스크들이 의존성의 순서에 따라 실행되고, 오직 한 번만 실행될 것임을 보장한다.** Gradle은 태스크를 실행하기 전에 완전한 의존성 그래프를 구축한다. ===== 빌드 단계 ===== Gradle 빌드는 3 단계로 구분된다. * 초기화 : 단일/멀티 프로젝트 빌드 지원. 초기화 단계에서는 어느 프로젝트를 빌드하는지 결정하고 각각에 대해 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html|Project]] 객체를 생성한다. * 구성 : 빌드에 속하는 모든 프로젝트의 빌드 스크립트를 실행한다. 이를 통해 프로젝트 객체를 구성한다. * 실행 : 구성 단계에서 생성하고 설정된 태스크 중에 실행할 것을 결정한다. 이 때 ''gradle'' 명령행에 인자로 지정한 태스크 이름과 현재 디렉토리를 기반으로 태스크를 결정하여 선택된 것들을 실행한다. ===== 설정 파일 ===== 빌드 파일 말고도 설정 파일도 있다. 설정 파일은 명명규칙에 따라 Gradle이 자동 인식한다. 기본 파일명은 ''settings.gradle''이다. 설정 파일은 초기화 단계에서 실행된다. 멀티 프로젝트는 무조건 최상위 프로젝트에 ''settings.gradle''이 있어야 한다. 어느 프로젝트가 멀티 프로젝트 빌드에 속하는지를 여기서 정한다. [[gradle:multiproject|Gradle Multi Project]] 참조. 단일 프로젝트 빌드에서는 설정 파일이 없어도 된다. * 단일 프로젝트에서의 ''settings.gradle'' println 'This is executed during the initialization phase.' * ''build.gradle'' println 'This is executed during the configuration phase.' task configured { println 'This is also executed during the configuration phase.' } task test << { println 'This is executed during the execution phase.' } * 실행하면 > gradle test This is executed during the initialization phase. This is executed during the configuration phase. This is also executed during the configuration phase. :test This is executed during the execution phase. BUILD SUCCESSFUL 빌드 스크립트에서는 프라퍼티 접근과 메소드 호출이 project 객체로 위임 된다. 유사하게 설정 파일에서는 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.initialization.Settings.html|Settings]] ''settings'' 객체로 위임 된다. ===== 멀티 프로젝트 빌드 ===== 멀티 프로젝트 빌드는 Gradle을 한 번실행하는 동안 하나 이상의 프로젝트를 빌드한다. 설정 파일에 소속 프로젝트를 지정해준다. [[gradle:multiproject|Gradle Multi Project]] 참조. ==== 프로젝트 위치 ==== 멀티 프로젝트는 항상 단일 최상위 아래에 트리 구조로 표현된다. 트리의 각 요소는 프로젝트를 나타낸다. 프로젝트는 경로로 표현된다. 보통 경로는 프로젝트의 파일 시스템에서의 물리적 위치로 구성된다. 하지만 이것도 설정 가능하다. 프로젝트 트리는 ''settings.gradle'' 파일에서 생성되며 기본적으로 최상위 프로젝트 아래에 있다고 가정한다. 하지만 최상위 프로젝트의 위치도 설정 파일에서 바꿀 수 있다. ==== 트리구조 구축 ==== === 계층적 레이아웃=== * ''settings.gradle'' include 'project1', 'project2', 'project2:child1' ''include'' 메소드는 인자로 프로젝트 경로를 받는다. 이 경로는 파일 시스템에서의 상대 경로로 간주된다. 예를들어 경로가 'services:api'이면 최상위 프로젝트에 상대 경로로 'services/api' 디렉토리로 간주된다. 트리의 잎(leaf)만 지정한다. 이 말은, 'services:hotels:api'를 지정하면 세개의 프로젝트인 'services', 'services:hotels', 'services:hotels:api'가 생성된다는 뜻이다. === 단층(flat) 레이아웃 === * ''settings.gradle'' includeFlat 'project3', 'project4' ''includeFlat'' 메소드는 디렉토리 이름을 인자로 받는다. 이 디렉토리들은 최상위 디렉토리와 같은 레벨에 존재해야 한다. 이 디렉토리들의 위치는 멀티 프로젝트 트리에서 최상위 프로젝트의 자식 프로젝트로 간주된다. === 프로젝트 트리의 요소 수정 === 설정 파일에서 만들어진 멀티 프로젝트 트리는 프로젝트 기술자(project descriptors)라는 것으로 만들어진다. 이 기술자를 변경할 수 있다. * 프로젝트 트리의 요소 수정 전 ''settings.gradle'' println rootProject.name println project(':projectA').name * 수정해보자 ''settings.gradle'' rootProject.name = 'main' project(':projectA').projectDir = new File(settingsDir, '../my-project-a') project(':projectA').buildFileName = 'projectA.gradle' [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/initialization/ProjectDescriptor.html|ProjectDescriptor]]를 참조한다. ===== 초기화 ===== Gradle이 단일 혹은 멀티 프로젝트 빌드를 실행할지 판단하는 기준이 있다. 설정 파일이 있는 디렉토리에서 멀티 프로젝트를 빌드를 실행하면 판단이 쉽다. 하지만 서브 프로젝트 아무데서나 빌드를 실행할 수도 있다. ''settings.gradle''이 없는 곳에서 빌드를 실행하면 * 현재 디렉토리와 동일한 계층 단계의 ''master''라는 디렉토리에서 ''settings.gradle''을 찾는다. * 없으면, 부모 디렉토리에서 ''settings.gradle''을 찾는다. * 없으면, 단일 프로젝트로 빌드를 실행한다. * ''settings.gradle''가 존재하면 현재 프로젝트가 멀티 프로젝트 계층에 속하는지 판단한다. 아니라면 단일 프로젝트로 실행하고 맞다면 멀티 프로젝트로 빌드를 실행한다. 이런 식으로 작동하는 이유는 멀티 프로젝트 일 경우 모든 멀티프로젝트 빌드 구성을 생성해야하기 때문이다. ''-u'' 명령행 옵션을 주면 부모 디렉토리에서 설정파일 찾는 것을 막는다. 이 경우에는 항상 단일 프로젝트로 실행한다. ''settings.gradle'' 파일이 있는 곳에서 ''-u''는 아무 기능도 없다. 설정 파일 자동 탐색은 물리적으로 계층/단층 레이아웃인 멀티 프로젝트에서만 작동한다. 단층 레이아웃에서는 위에서 기술한 명명규칙을 지켜야 한다. Gradle은 멀티 프로젝트에 임의의 물리적 레이아웃을 지원하지만 이 때는 항상 설정 파일이 있는 곳에서 빌드를 실행해야 한다. Gradle은 빌드에 참여하는 모든 프로젝트에 대해 Project 객체를 생성한다. 각 프로젝트는 기본적으로 탑레벨 디렉토리를 ''name''으로 갖는다. 최상위를 제외한 모든 프로젝트는 부모 프로젝트가 있고, 자식 프로젝트를 가질 수 있다. ===== 빌드 스크립트 라이프사이클에 반응하기 ===== 라이프싸이클을 진행하는 동안 빌드 스크립트에서 알림을 받을 수 있다. 알림은 특별한 리스너 인터페이스를 구현하거나 혹은 알림이 발생했을 때 실행할 클로저를 제공해 주는 두가지 방식으로 구현한다. ==== 프로젝트 평가 ==== 프로젝트를 평가하기 직전과 직후에 알림을 받을 수 있다. 빌드 스크립트에서 모든 정의가 적용된 이후에 추가적인 구성을 수행할 때나 로깅 혹은 프로파일링을 하고자 할 때 사용한다. * ''hasTest'' 프라퍼티가 ''true''인 프로젝트들에 ''test'' 태스크 추가하기 ''build.gradle'' allprojects { afterEvaluate { project -> if (project.hasTests) { println "Adding test task to $project" project.task('test') << { println "Running tests for $project" } } } } * ''projectA.gradle'' hasTests = true * 실행하면 > gradle -q test Adding test task to project ':projectA' Running tests for project ':projectA' [[http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html#afterEvaluate%28groovy.lang.Closure%29|Project.afterEvaluate()]]를 사용하여 프로젝트 평가 뒤에 실행할 클로저를 추가 하였다. 아무 프로젝트든 평가한 뒤에 알림을 받는 것도 가능하다. ''afterProject''는 프로젝트 평가의 성공 여부와 무관하게 호출된다. * 프로젝트의 성공 여부를 보여주는 ''build.gradle'' gradle.afterProject {project, projectState -> if (projectState.failure) { println "Evaluation of $project FAILED" } else { println "Evaluation of $project succeeded" } } * 실행하면 > gradle -q test Evaluation of root project 'buildProjectEvaluateEvents' succeeded Evaluation of project ':projectA' succeeded Evaluation of project ':projectB' FAILED [[http://www.gradle.org/docs/current/dsl/org.gradle.api.invocation.Gradle.html|Gradle]] 객체에 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/ProjectEvaluationListener.html|ProjectEvaluationListener]] 를 추가해도 된다. * 최상위 ''build.gradle''에서 특정 하위 프로젝트를 콕 집어서 지정 project(':sub-project').afterEvaluate { // .... } ==== 태스크 생성 ==== * 프로젝트에 태스크가 추가된 직후에 알림을 받을 수 있다. 기본값을 설정하거나 태스크가 빌드에 노출되기전에 행위를 추가하고자 할 때 사용한다. * 각 태스크가 생성된 뒤에 ''srcDir''을 설정하는 ''build.gradle'' tasks.whenTaskAdded { task -> task.srcDir = 'src/main/java' } task a println "source dir is $a.srcDir" * 실행하면 > gradle -q a source dir is src/main/java [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/tasks/TaskContainer.html|TaskContainer]]에 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/Action.html|Action]]을 추가해도 된다. ==== 태스크 실행 그래프가 정해진 뒤에 ==== * 실행할 태스크에 ''release''가 있는지 여부에 따라 버전 변경하는 ''build.gradle'' task distribution << { println "We build the zip with version=$version" } task release(dependsOn: 'distribution') << { println 'We release now' } gradle.taskGraph.whenReady {taskGraph -> if (taskGraph.hasTask(release)) { version = '1.0' } else { version = '1.0-SNAPSHOT' } } ''release'' 태스크의 실행여부에 따라 버전값이 달라진다. [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/execution/TaskExecutionGraph.html|TaskExecutionGraph]]에 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/execution/TaskExecutionGraphListener.html|TaskExecutionGraphListener]]를 추가해도 된다. ==== 태스크 실행 ==== 어떤 태스크이든 실행 직전과 직후에 알림을 받을 수 있다. * 태스크 실행과 종료를 로그로 남기는 ''build.gradle'' task ok task broken(dependsOn: ok) << { 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" } } * 실행하면 > gradle -q broken executing task ':ok' ... done executing task ':broken' ... FAILED [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/execution/TaskExecutionGraph.html|TaskExecutionGraph]]에 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/execution/TaskExecutionListener.html|TaskExecutionListener]]를 추가해도 된다.