====== Gradle Multi Project ======
* [[http://www.gradle.org/docs/current/userguide/multi_project_builds.html|Gradle Multi Project Builds]]
* [[http://gradle.org/docs/current/dsl/org.gradle.api.Project.html|Project]] DSL 참조.
====== 최상위 프로젝트의 이름 ======
''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'
}
}
* 모든 서브 프로젝트에 java, eclipse-wtp 플러그인이 적용되고, 지정된 리포지토리와 의존성이 무조건 추가된다.
==== 서브 프로젝트간 의존성 ====
// 어느 서브 프로젝트의 build.gradle
dependencies {
compile project(':shared')
}
// shared 서브 프로젝트에 의존하고 있다.
==== build.gradle 대신 subproject 이름으로 gradle 파일 구성 ====
* 멀티 모듈 프로젝트를 구성하면 지나치게 많은 ''build.gradle'' 파일 때문에 혼란스러워진다.
* 아래와 같이 ''settings.gradle'' 에 설정하여 각각의 sub module 들에 대한 설정 파일을 ''submodule-name.gradle''로 변경할 수 있다.
rootProject.children.each {project ->
project.buildFileName = "${project.name}.gradle"
}
===== 교차 프로젝트 구성 Cross Project Configuration =====
==== 공통 행위 정의하기 ====
* 다음과 같은 구조의 프로젝트가 있다고 하자. water가 부모 프로젝트이다.
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
* Gradle에서는 어떠한 빌드 스크립트에서라도 멀티 프로젝트의 아무 프로젝트에나 접근할 수 있다. Project API에는 ''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
* Project API의 ''allprojects'' 프라퍼티는 현재 프로젝트와 그것의 모든 서브 프로젝트를 리턴한다. ''allprojects''에 클로저를 인자로 주면 클로저의 구문이 ''allprojects''의 프로젝트들로 위임된다. ''allprojects.each''로 이터레이션을 도는 것도 가능하다.
* Gradle은 기본적으로 구성 주입(configuration injection)을 사용한다.
* 또한 다른 빌드 툴 처럼 프로젝트 상속 구조도 가능하다.
===== 서브 프로젝트 구성 =====
''Project.subprojects''로 서브 프로젝트들만 접근하는 것도 가능하다. ''allprojects''는 부모 프로젝트까지 포함한 것이다.
==== 공통 행위 정의 ====
* 서브 프로젝트에만 적용되는 공통 행위 정의하기
allprojects {
task hello << {task -> println "I'm $task.project.name" }
}
// 아래는 서브프로젝트에만 적용된다.
subprojects {
hello << {println "- I depend on water"}
}
* 실행하면
> 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."
}
* ''build.gradle''
allprojects {
task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
hello << {println "- I depend on water"}
}
* 실행하면
> 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.
==== 프로젝트 필터링 Filter Projects ====
''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
* [[http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html#configure%28java.lang.Iterable,%20groovy.lang.Closure%29|Project.configure()]] 메소드는 리스트를 인자로 받아서 리스트 안의 프로젝트에 구성을 추가한다.
=== 프라퍼티로 필터링하기 ===
''ext'' 프라퍼티를 통해 필터링이 가능하다.
* 프로젝트 레이아웃
water/
build.gradle
settings.gradle
bluewhale/
build.gradle
krill/
build.gradle
tropicalFish/
build.gradle
* ''bluewhale/build.gradle''
ext.arctic = true
hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
* ''krill/build.gradle''
ext.arctic = true
hello.doLast {
println "- The weight of my species in summer is twice as heavy as all human beings."
}
* ''tropicalFish/build.gradle''
ext.arctic = false
* ''build.gradle''
allprojects {
task hello << { task -> println "I'm $task.project.name" }
}
subprojects {
hello {
doLast { println "- I depend on water"}
afterEvaluate { Project project ->
if (project.arctic) {
doLast {
println '- I love to spend time in the arctic waters.'
}
}
}
}
}
* 실행하면
> 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'' 태스크가 실행 된다.
* bluewhale 디렉토리로 이동해서 ''hello'' 태스크를 실행하면 bluewhale 프로젝트의 태스크만 실행된다.
* Gradle의 태스크 실행
* 현재 디렉토리에서 시작하여 계층구조를 탐색하여 ''hello''라는 이름의 태스크를 찾고 실행한다.
* Gradle은 항상 모든 프로젝트를 평가하고, 존재하는 모든 태스크 객체를 생성한다.
* 그리고서 태스크의 이름과 현재 디렉토리를 기반으로 실행해야할 태스크를 결정한다.
* Gradle의 교차 프로젝트 구성 때문에 모든 프로젝트는 어떠한 태스크를 실행할 때는 그 전에 먼저 평가 돼야 한다.
* ''bluewhale/build.gradle''
ext.arctic = true
hello << { println "- I'm the largest animal that has ever lived on this planet." }
task distanceToIceberg << {
println '20 nautical miles'
}
* ''krill/build.gradle''
ext.arctic = true
hello << { println "- The weight of my species in summer is twice as heavy as all human beings." }
task distanceToIceberg << {
println '5 nautical miles'
}
* 최상위 프로젝트에서 실행하면
> gradle distanceToIceberg
:bluewhale:distanceToIceberg
20 nautical miles
:krill:distanceToIceberg
5 nautical miles
BUILD SUCCESSFUL
Total time: 1 secs
* 최상위 water 프로젝트에서 실행한다. water와 tropicalFish는 ''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
* water의 :hello, krill의 hello, tropicalFish의 hello 순서로 실행된다.
===== 프로젝트와 태스크의 경로 =====
* 프로젝트 경로의 패턴은 다음과 같다.
* 항상 콜론(:)으로 시작한다. 이는 최상위 프로젝트를 의미한다.
* 최상위 프로젝트만 이름 없이 사용된다.
* '':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:
* 이것은 작동하지 않는다. 왜냐면 명시적으로 정의하지 않으면 Gradle은 **알파벳 순서에 따라** 태스크를 실행하기 때문이다.
* 따라서 '':consumer:action''이 '':producer:action'' 보다 먼저 실행된다.
* producer 프로젝트를 aProducer로 바꾸면 원하는 대로 작동한다.
* aProducer로 바뀐 상태에서 consumer 디렉토리에서 ''action'' 태스크를 실행하면 규칙에 따라 '':aProducer:action''은 실행이 안되므로 ''null''이 찍힌다.
=== 태스크 실행 의존성 선언하기 ===
* ''consumer/build.gradle''
task action(dependsOn: ':producer:action') << {
println("Consuming message: " +
(rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
}
* 최상위와 consumer 디렉토리 어디에서든 실행하면
> gradle -q action
Producing message:
Consuming message: Watch the order of execution.
* '':consumer:action''이 '':producer:action''에 실행시 의존성을 걸고 있기 때문에 항상 '':producer:action''이 먼저 실행된다.
=== 교차 프로젝트 태스크 의존성의 특징 ===
* 의존성을 지정할 때 태스크 이름은 아무 상관이 없다.
==== 구성 시(Configuration Time) 의존성 설정하기 ====
* 태스크에 의존성을 거는 것이 아니라 프로젝트 구성에 의존해야 할 경우가 있다.
* ''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가 producer보다 먼저 평가된다.
* 해결하려면 ''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()'' 메소드를 사용한다.
* 동일 단계의 프로젝트간 구성 순서는 알파벳 순서에 따른다. 가장 일반적인 경우로 모든 서브 프로젝트가 Java 플러그인을 사용하는 것 처럼 공통의 라이프사이클을 공유하는 때가 있다.
* ''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 가 생성된다.
* date와 hello 프로젝트는 webDist 프로젝트의 구성시 의존성을 가진 상태이다. 그리고 빌드 로직도 webDist에서 주입되었다.
* 하지만 실행시 의존성은 webDist가 date와 hello의 빌드된 아티팩트에 의존한다.
* 세번째 의존성으로 webDist가 자식인 date와 hello에 구성시 의존성도 있는데, 이는 ''arhivePath''를 알아야만 하기 때문이다. 하지만 태스크를 실행하는 시점에 요청한다. 따라서 순환 의존성은 아니다.
* [[http://www.gradle.org/docs/current/groovydoc/org/gradle/api/DomainObjectCollection.html#withType%28java.lang.Class%29|withType()]] 메소드. 컬렉션에서 특정 타입인 것만 골라서 새로운 컬렉션으로 만든다.
===== 프로젝트 lib 의존성 =====
한 프로젝트가 다른 프로젝트의 컴파일 결과와 그 의존하는 라이브러리들 모두에 의존하는 경우가 발생한다. 이 때 프로젝트간 의존성을 설정한다.
* 프로젝트 레이아웃
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
* shared, api, personService 프로젝트가 있다. personService는 다른 두 프로젝트에 의존하고, api는 shared에 의존한다.
* ''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')
}
}
* lib 의존성은 실행시 의존성의 특별한 형태이다. 의존성이 걸리게 되면 다른 프로젝트가 먼저 빌드하여 jar를 생성하고 그것을 현재 프로젝트의 클래스패스에 추가한다.
* 따라서 api 디렉토리에서 ''gradle compile''을 실행하면 shared가 먼저 빌드 되고 그 뒤에 api가 빌드 된다. 프로젝트 의존성은 부분적인 멀티 프로젝트 빌드를 가능케 한다.
* Ivy 방식의 매우 상세한 의존성 설정도 가능하다.
* ''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')
}
}
* Java 플러그인은 기본적으로 프로젝트당 모든 클래스를 포함한 하나의 jar를 생성한다. 위 예제에서는 api 프로젝트의 인터페이스만 포함하는 추가적인 jar를 생성하였다.
==== 의존하는 프로젝트의 빌드 금지하기 ====
* 때로는 부분 빌드를 할 때 의존하고 있는 프로젝트를 빌드하지 않기를 바랄 때도 있다. ''-a'' 옵션으로 gradle을 실행하면 된다.
===== 분리된(decoupled) 프로젝트 =====
* 두 프로젝트간의 프로젝트 모델에 접근하지 않는 것을 서로 분리된(decoupled) 프로젝트라고 부른다.
* 분리된 프로젝트는 프로젝트 의존성이나 태스크 의존성으로만 연결되어 있다.
* 그 외의 어떠한 형태의 프로젝트간 소통행위( 다른 프로젝트의 값을 읽거나 수정하는 등)은 두 프로젝트를 엮인(coupled) 프로젝트로 만든다.
* 엮인 프로젝트가 되는 가장 일반적인 상황은 교차 프로젝트 설정에서 구성 주입을 사용할 경우이다.
* ''allprojects'' 혹은 ''subprojects'' 키워드를 사용한 순간 프로젝트들은 엮인 것이다.
===== 멀티 프로젝트 빌드와 테스트 =====
* Java 플러그인의 ''build'' 태스크를 사용하여 컴파일, 테스트, 코드 스타일 검사(CodeQuality 플러그인 사용시)등을 할 수 있다.
* 다중 프로젝트에서 여러 범위의 프로젝트에 대해 빌드를 할 경우가 있는데 이 때 ''buildNeeded'' 와 ''buildDependents'' 태스크를 사용한다.
* "프로젝트 lib 의존성"의 프로젝트로 테스트 해본다.
* ''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'' 객체에서 해당 프라퍼티를 사용할 수 있게 된다.
===== 멀티 프로젝트 단위 테스트간의 의존성 =====
* 이제는 더이상 아래 방법을 사용하지 말고 [[gradle:testfixtures|TestFixtures]]를 통해 합리적으로 해결 가능해졌다.
아래 방법은 사용하지 말 것.
테스트들 간의 의존성을 거는 것은 어쩔 수 없을때만한다.
꼭 필요하다면 테스트를 위한 별도 모듈을 만들고 그에 대해 일반적인 의존을 하게 변경한다.
개인적으로 아래 방법보다는 공통 단위 테스트용 프로젝트를 만들고(예: ''xxx-test-support'') 해당 프로젝트에 각종 테스트용 의존성과 테스트용 유틸리티 클래스를 일반 코드로 작성한 뒤에 다른 프로젝트들이 ''testCompile project(':xxx-test-support')'' 형태로 의존성을 추가하는 것이 더 일관성 있고 깔끔한 방법으로 보인다.
----
ProjectA와 ProjectB의 단위테스트가 존재하는데, ProjectB의 단위테스트가 ProjectA의 단위테스트 클래스 중 일부에 의존하고 있다면, 기본적으로는 클래스를 찾지 못해 예외가 발생한다.
단위 테스트는 프로젝트간 의존성에서 제외되기 때문이다.
[[http://stackoverflow.com/questions/5644011/multi-project-test-dependencies-with-gradle|build - Multi-project test dependencies with gradle]]에 해결책이 있으나 ''classes''가 write-only로 바뀌고 읽을 때는 ''output''을 하도록 바뀌었다.
* ProjectB의 ''build.gradle''
dependencies {
testCompile project(':projectA').sourceSets.test.output // projectA의 단위 테스트 클래스에 의존함.
// 이 방법은 eclipse에서 projecA의 단위 테스트 디렉토리를 라이브러리로 등록하는 문제가 있음.
}
* 위 방법보다는 configuration을 사용하는 다른 방법이 더 유용하다. [[gradle:eclipse|Gradle Ecplise Plugin]] 사용시 설정 필요 ''build.gradle''
configurations {
crosstest
testCompile.extendsFrom crosstest // testCompile이 crosstest에 의존하게 변경
}
dependencies {
crosstest project(':projectA').sourceSets.test.output
}
eclipse {
classpath {
minusConfigurations += configurations.crosstest // 불필요한 classpath 등록 방지
}
}
===== 참조 =====
* [[https://blog.sapzil.org/2018/06/20/gradle-subproject-grouping/|Gradle에서 서브 프로젝트를 한 디렉토리에 몰아넣기 | The Sapzil]]