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 등록 방지 } }