settings.gradle 파일에서 다음과 같이 최상위 프로젝트 이름을 지정한다. 이는 해당 프로젝트 디렉토리 이름과 무관하게 설정된다.
rootProject.name = '프로젝트이름'
settings.gradle이 필요하다. 여기서 하위 프로젝트를 include해준다.include "shared", "api", "services:webservice", "services:shared"
build.gradle에 모든 서브 프로젝트에 공통된 설정을 넣는다.subprojects { apply plugin: 'java' apply plugin: 'eclipse-wtp' repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4.8.2' } version = '1.0' jar { manifest.attributes provider: 'gradle' } }
// 어느 서브 프로젝트의 build.gradle dependencies { compile project(':shared') } // shared 서브 프로젝트에 의존하고 있다.
build.gradle 파일 때문에 혼란스러워진다.settings.gradle 에 설정하여 각각의 sub module 들에 대한 설정 파일을 submodule-name.gradle로 변경할 수 있다.rootProject.children.each {project -> project.buildFileName = "${project.name}.gradle" }
water/ build.gradle settings.gradle bluewhale/
settings.gradle include 'bluewhale'
Closure cl = { task -> println "I'm $task.project.name" } task hello << cl project(':bluewhale') { task hello << cl }
gradle -q hello로 실행하면> gradle -q hello I'm water I'm bluewhale
project()라는 메소드가 있으며, 이는 프로젝트의 경로를 인자로 받아서 해당 경로의 Project 객체를 리턴한다. 이러한 방법을 교차 프로젝트 구성 cross project configuration이라고 부른다.krill 서브 디렉토리를 만들어서 krill 서브 프로젝트를 선언한다. settings.gradle include 'bluewhale', 'krill'
allprojects { task hello << { task -> println "I'm $task.project.name" } }
> gradle -q hello I'm water I'm bluewhale I'm krill
allprojects 프라퍼티는 현재 프로젝트와 그것의 모든 서브 프로젝트를 리턴한다. allprojects에 클로저를 인자로 주면 클로저의 구문이 allprojects의 프로젝트들로 위임된다. allprojects.each로 이터레이션을 도는 것도 가능하다.
Project.subprojects로 서브 프로젝트들만 접근하는 것도 가능하다. allprojects는 부모 프로젝트까지 포함한 것이다.
> gradle -q hello I'm water I'm bluewhale - I depend on water I'm krill - I depend on water
allprojects { task hello << {task -> println "I'm $task.project.name" } } subprojects { hello << {println "- I depend on water"} } project(':bluewhale').hello << { println "- I'm the largest animal that has ever lived on this planet." }
> gradle -q hello I'm water I'm bluewhale - I depend on water - I'm the largest animal that has ever lived on this planet. I'm krill - I depend on water
build.gradle을 두고 거기에 행위를 추가할 수 있다.water/
build.gradle
settings.gradle
bluewhale/
build.gradle
krill/
build.gradle
bluewhale/build.gradle hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
krill/build.gradle hello.doLast { println "- The weight of my species in summer is twice as heavy as all human beings." }
> gradle -q hello I'm water I'm bluewhale - I depend on water - I'm the largest animal that has ever lived on this planet. I'm krill - I depend on water - The weight of my species in summer is twice as heavy as all human beings.
tropicalFish라는 프로젝트를 추가하고 water 프로젝트 빌드 파일에 행위를 더 추가해보자.
def projects = [project('project-name'), project('project-name-2')]
// 특정 프로젝트들에 대한 설정..
configure(projects) {
....
}
water/
build.gradle
settings.gradle
bluewhale/
build.gradle
krill/
build.gradle
tropicalFish/
settings.gradle include 'bluewhale', 'krill', 'tropicalFish'
build.gradle allprojects { task hello << {task -> println "I'm $task.project.name" } } subprojects { hello << {println "- I depend on water"} } // 이름이 tropicalFish가 아닌 프로젝트만 찾아서 설정 configure(subprojects.findAll { it.name != 'torpicalFish' }) { hello << {println '- I love to spend time in the arctic waters.'} }
> gradle -q hello I'm water I'm bluewhale - I depend on water - I love to spend time in the arctic waters. - I'm the largest animal that has ever lived on this planet. I'm krill - I depend on water - I love to spend time in the arctic waters. - The weight of my species in summer is twice as heavy as all human beings. I'm tropicalFish - I depend on water
ext 프라퍼티를 통해 필터링이 가능하다.
water/
build.gradle
settings.gradle
bluewhale/
build.gradle
krill/
build.gradle
tropicalFish/
build.gradle
tropicalFish/build.gradle ext.arctic = false
> gradle -q hello I'm water I'm bluewhale - I depend on water - I'm the largest animal that has ever lived on this planet. - I love to spend time in the arctic waters. I'm krill - I depend on water - The weight of my species in summer is twice as heavy as all human beings. - I love to spend time in the arctic waters. I'm tropicalFish - I depend on water
afterEvaluate는 서브 프로젝트의 빌드 스크립트를 모두 수행한 뒤에 인자로 넘어온 클로저를 실행하라는 의미이다. arctic 프라퍼티가 서브 프로젝트 빌드 스크립트에 선언 돼 있기 때문이다.hello 태스크를 실행하면 최상위와 그 아래 모든 서브 프로젝트의 hello 태스크가 실행 된다.hello 태스크를 실행하면 bluewhale 프로젝트의 태스크만 실행된다.hello라는 이름의 태스크를 찾고 실행한다.> gradle distanceToIceberg :bluewhale:distanceToIceberg 20 nautical miles :krill:distanceToIceberg 5 nautical miles BUILD SUCCESSFUL Total time: 1 secs
distanceToIceberg 태스크가 없지만 상관없다. 왜냐면 계층 구조를 따라 내려가면서 해당 명칭의 태스크를 실행한다라는 규칙 때문이다.tropicalFish에서 실행한 gradle -q :hello :krill:hello hello > gradle -q :hello :krill:hello hello I'm water I'm krill - I depend on water - The weight of my species in summer is twice as heavy as all human beings. - I love to spend time in the arctic waters. I'm tropicalFish - I depend on water
:bluewhale 은 파일 시스템상에서 water/bluewhale을 뜻한다.:bluewhale:hello는 bluewhale 프로젝트의 hello 태스크의존성과 실행 순서에 대해서 확인해보자.
messages/
settings.gradle
consumer/
build.gradle
producer/
build.gradle
settings.gradle include 'consumer', 'producer'
consumer/build.gradle task action << { println("Consuming message: " + (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null')) }
producer/build.gradle task action << { println "Producing message:" rootProject.producerMessage = 'Watch the order of execution.' }
> gradle -q action Consuming message: null Producing message:
:consumer:action이 :producer:action 보다 먼저 실행된다.action 태스크를 실행하면 규칙에 따라 :aProducer:action은 실행이 안되므로 null이 찍힌다.consumer/build.gradle task action(dependsOn: ':producer:action') << { println("Consuming message: " + (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null')) }
> gradle -q action Producing message: Consuming message: Watch the order of execution.
:consumer:action이 :producer:action에 실행시 의존성을 걸고 있기 때문에 항상 :producer:action이 먼저 실행된다.consumer/build.gradle message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null' task consume << { println("Consuming message: " + message) }
producer/build.gradle rootProject.producerMessage = 'Watch the order of evaluation.'
> gradle -q consume Consuming message: null
consumer/build.gradle evaluationDependsOn(':producer') message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null' task consume << { println("Consuming message: " + message) }
> gradle -q consume Consuming message: Watch the order of evaluation.
evaluationDependsOn은 producer를 consumer보다 먼저 평가하게 만든다.rootProject.producerMessage 값을 바로 읽게 만들기만 해도 된다. consumer/build.gradle task consume << { println("Consuming message: " + (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null')) }
evaluationDependsOnChildren() 메소드를 사용한다.dependsOn을 사용해 서로 다른 두 프로젝트의 실행시 의존성을 지정할 경우 이 메소드는 기본적으로 두 프로젝트간에 구성 의존성을 생성하는 것이다. 따라서 이 때는 구성 의존성을 명시적으로 지정하지 않아도 된다.두 개의 웹 애플리케이션 서브 프로젝트를 가진 최상위 프로젝트가 웹 애플리케이션 배포본을 생성하는 예를 본다. 예제에서는 단 하나의 교차 프로젝트 구성을 사용한다.
webDist/
settings.gradle
build.gradle
date/
src/main/java/
org/gradle/sample/
DateServlet.java
hello/
src/main/java/
org/gradle/sample/
HelloServlet.java
settings.gradle include 'date', 'hello'
build.gradle allprojects { apply plugin: 'java' group 'org.gradle.sample' version = '1.0' } subprojects { apply plugin: 'war' repositories { mavenCentral() } dependencies { compile "javax.servlet:servlet-api:2.5" } } task explodedDist(dependsOn: assemble) << { File explodedDist = mkdir("$buildDir/explodedDist") subprojects.each { project -> project.tasks.withType(Jar).each { archiveTask -> copy { from archiveTask.archivePath into explodedDist } } } }
gradle -q explodedDist를 실행하면 “$buildDir/explodedDist”에 hello-1.0.jar와 date-1.0.jar 가 생성된다.arhivePath를 알아야만 하기 때문이다. 하지만 태스크를 실행하는 시점에 요청한다. 따라서 순환 의존성은 아니다.한 프로젝트가 다른 프로젝트의 컴파일 결과와 그 의존하는 라이브러리들 모두에 의존하는 경우가 발생한다. 이 때 프로젝트간 의존성을 설정한다.
java/
settings.gradle
build.gradle
api/
src/main/java/
org/gradle/sample/
api/
Person.java
apiImpl/
PersonImpl.java
services/personService/
src/
main/java/
org/gradle/sample/services/
PersonService.java
test/java/
org/gradle/sample/services/
PersonServiceTest.java
shared/
src/main/java/
org/gradle/sample/shared/
Helper.java
settinga.gradle include 'api', 'shared', 'services:personService'
build.gradle subprojects { apply plugin: 'java' group = 'org.gradle.sample' version = '1.0' repositories { mavenCentral() } dependencies { testCompile "junit:junit:4.8.2" } } project(':api') { dependencies { compile project(':shared') } } project(':services:personService') { dependencies { compile project(':shared'), project(':api') } }
gradle compile을 실행하면 shared가 먼저 빌드 되고 그 뒤에 api가 빌드 된다. 프로젝트 의존성은 부분적인 멀티 프로젝트 빌드를 가능케 한다.build.gradle subprojects { apply plugin: 'java' group = 'org.gradle.sample' version = '1.0' } project(':api') { configurations { spi } dependencies { compile project(':shared') } task spiJar(type: Jar) { baseName = 'api-spi' dependsOn classes from sourceSets.main.output include('org/gradle/sample/api/**') } artifacts { spi spiJar } } project(':services:personService') { dependencies { compile project(':shared') compile project(path: ':api', configuration: 'spi') testCompile "junit:junit:4.8.2", project(':api') } }
-a 옵션으로 gradle을 실행하면 된다.allprojects 혹은 subprojects 키워드를 사용한 순간 프로젝트들은 엮인 것이다.build 태스크를 사용하여 컴파일, 테스트, 코드 스타일 검사(CodeQuality 플러그인 사용시)등을 할 수 있다.buildNeeded 와 buildDependents 태스크를 사용한다.gradle :api:build : api와 api가 의존하는 모든 프로젝트에 대해 컴파일과 jar를 수행하고 api 프로젝트의 build를 수행한다.gradle -a :api:build : api 프로젝트의 build만 수행한다.gradle :api:buildNeeded : api와 api가 의존하는 모든 프로젝트의 build를 수행한다.gradle :api:buildDependents : api와 api에 의존하는 모든 프로젝트에 대해 build를 수행한다.gradle build : 모든 프로젝트에 대해 build한다.gradle 태스크이름 -P프라퍼티이름=값으로 실행할 경우 모든 project 객체에서 해당 프라퍼티를 사용할 수 있게 된다.테스트들 간의 의존성을 거는 것은 어쩔 수 없을때만한다.
꼭 필요하다면 테스트를 위한 별도 모듈을 만들고 그에 대해 일반적인 의존을 하게 변경한다.
개인적으로 아래 방법보다는 공통 단위 테스트용 프로젝트를 만들고(예: xxx-test-support) 해당 프로젝트에 각종 테스트용 의존성과 테스트용 유틸리티 클래스를 일반 코드로 작성한 뒤에 다른 프로젝트들이 testCompile project(':xxx-test-support') 형태로 의존성을 추가하는 것이 더 일관성 있고 깔끔한 방법으로 보인다.
ProjectA와 ProjectB의 단위테스트가 존재하는데, ProjectB의 단위테스트가 ProjectA의 단위테스트 클래스 중 일부에 의존하고 있다면, 기본적으로는 클래스를 찾지 못해 예외가 발생한다.
단위 테스트는 프로젝트간 의존성에서 제외되기 때문이다.
build - Multi-project test dependencies with gradle에 해결책이 있으나 classes가 write-only로 바뀌고 읽을 때는 output을 하도록 바뀌었다.
build.gradle dependencies { testCompile project(':projectA').sourceSets.test.output // projectA의 단위 테스트 클래스에 의존함. // 이 방법은 eclipse에서 projecA의 단위 테스트 디렉토리를 라이브러리로 등록하는 문제가 있음. }
build.gradle configurations { crosstest testCompile.extendsFrom crosstest // testCompile이 crosstest에 의존하게 변경 } dependencies { crosstest project(':projectA').sourceSets.test.output } eclipse { classpath { minusConfigurations += configurations.crosstest // 불필요한 classpath 등록 방지 } }