====== 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]]