====== Gradle Custom Plugins ======
* [[http://www.gradle.org/docs/current/userguide/custom_plugins.html|Gradle Custom Plugins]]
* [[http://www.javacodegeeks.com/2012/08/gradle-custom-plugin.html|Gradle Custom Plugin 만들기 참조]]
Gradle 플러그인은 재사용 가능한 빌드 로직을 패키지화하여 여러 서로다른 프로젝트와 빌드에서 사용할 수 있도록 한 것이다.
플러그인은 아무 언어로 작성해도 되며 Java 바이트코드로 컴파일해서 제공하기만 하면 된다.
===== 플러그인 패키징 =====
==== 빌드 스크립트 ====
빌드 스크립트에 직접 플러그인 소스를 작성해도 된다. 자동으로 컴파일되어 빌드 스크립트의 클래스패스에 추가된다. 플러그인을 선언한 빌드 스크립트 외부에서는 접근할 수 없다.
==== buildSrc 프로젝트 ====
추천하는 방식.
[[gradle:organizing_build_logic|Gradle Organizing Build Logic]]에 나온 ''buildSrc'' 프로젝트에 넣는다. ''rootProjectDir/buildSrc/src/main/groovy'' 디렉토리 아래에 넣는 것이다. Gradle이 자동으로 컴파일, 테스트, 클래스패스 추가를 해준다.
이렇게 할 경우 모든 프로젝트의 빌드 스크립트에서 이 플러그인에 접근 가능하다.
==== 독립 프로젝트 ====
플러그인용 독립 프로젝트를 만들고 Jar로 묶어서 배포한다.
===== 간단한 플러그인 작성 =====
[[http://www.gradle.org/docs/current/javadoc/org/gradle/api/Plugin.html|Plugin]]을 구현하여 사용자 정의 플러그인을 만든다. Gradle은 플러그인 객체를 생성하고 프로젝트 객체를 인자로 하여 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/Plugin.html#apply(T)|Plugin.apply()]] 메소드를 호출한다.
* 사용자 정의 플러그인 ''build.gradle''
apply plugin: GreetingPlugin
class GreetingPlugin implements Plugin {
void apply(Project project) {
project.task('hello') << {
println "Hello from the GreetingPlugin"
}
}
}
* 실행하면
> gradle -q hello
Hello from the GreetingPlugin
플러그인이 적용되는 모든 프로젝트에 대해서 플러그인 인스턴스가 각각 하나씩 생성된다.
===== 빌드에서 입력 받기 =====
플러그인들은 대부분 빌드 스크립트에서 설정을 해야할 필요가 있다. 이때 확장 객체(extension object)를 사용한다. Gradle의 Project는 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/plugins/ExtensionContainer.html|ExtensionContainer]] 객체를 통해 플러그인에 전달되는 설정과 프라퍼티들을 추적할 수 있도록 해준다. 확장 컨테이너를 통해 사용자 입력을 받을 수 있다. 입력을 받으려면 확장 컨테이너의 확장 목록에 자바 빈에 호환되는 클래스를 추가하면 된다.
* ''greeting'' 확장 객체를 프로젝트에 추가하기 ''build.gradle''
apply plugin: GreetingPlugin
// 확장 객체 값 사용
greeting.message = 'Hi from Gradle'
class GreetingPlugin implements Plugin {
void apply(Project project) {
// 'greeting' 확장 객체 추가
project.extensions.create("greeting", GreetingPluginExtension)
// 관련 설정을 사용하는 태스크 추가
project.task('hello') << {
println project.greeting.message
}
}
}
class GreetingPluginExtension {
def String message = 'Hello form GreetingPlugin'
}
* 실행하면
> gradle -q hello
Hi from Gradle
이 예제에서 ''GreetingPluginExtension''은 ''message'' 필드를 가지고 있는 POGO이다. ''greeting'' 이라늠 이름으로 확장 객체가 플러그인에 추가 되었다. 이 객체는 동일한 이름으로 프로젝트의 프라퍼티로서 접근할 수 있다.
종종 지정할 프라퍼티가 많은 경우 확장 객체에 구성 클로저 블록을 추가하여 한번에 설정할 수 있도록 해준다.
* 구성 클로저를 사용하는 플러그인 ''build.gradle''
apply plugin: GreetingPlugin
// 확장 객체의 프라퍼티들을 한 번에 설정한다.
greeting {
message = 'Hi'
greeter = 'Gradle'
}
class GreetingPlugin implements Plugin {
void apply(Project project) {
project.extensions.create("greeting", GreetingPluginExtension)
project.task('hello') << {
println "${project.greeting.message} from ${project.greeting.greeter}"
}
}
}
class GreetingPluginExtension {
String message
String greeter
}
* 실행하면
> gradle -q hello
Hi from Gradle
클로저 블럭의 이름(''greeting'')은 확장 객체의 이름과 같다. Groovy 클로저 위임 기능에 따라 클로저가 실행될 때 확장 객체의 필드는 클로저에 있는 변수와 매핑된다.
===== 사용자 정의 태스크와 플러그인에서 파일 다루기 =====
[[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:file(java.lang.Object)|Project.file()]] 메소드로 파일들의 값을 최대한 늦게 결정하도록 하는 것이 좋다.
* 파일 프라퍼티를 늦게 평가하기 ''build.gradle''
class GreetingToFileTask extends DefaultTask {
def destination
File getDestination() {
project.file(destination)
}
@TaskAction
def greet() {
def file = getDestination()
file.parentFile.mkdirs()
file.write "Hello!"
}
}
task greet(type: GreetingToFileTask) {
destination = { project.greetingFile }
}
task sayGreeting(dependsOn: greet) << {
println file(greetingFile).text
}
greetingFile = "$buildDir/hello.txt"
* 실행하면
> gradle -q sayGreeting
Hello!
위 예제에서는 태스크의 ''destination'' 프라퍼티를 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:file(java.lang.Object)|Project.file()]] 메소드를 사용하여 최대한 늦게 평가하도록 하였다. 빌드 스크립트에서 태스크 선언 보다 더 뒤에 ''greetingFile'' 프라퍼티를 추가한 것을 볼 수 있다. 이 늦은 초기화 기법을 적극적으로 사용하라.
===== 독립 프로젝트 =====
독립 프로젝트로 만들면 jar로 배포하여 다른 사람들과 공유할 수 있다. 보통 다음과 같은 최소한의 빌드 스크립트로 시작한다.
* 사용자 정의 플러그인을 위한 ''build.gradle''
apply plugin: 'groovy'
dependencies {
compile gradleApi()
groovy localGroovy()
}
Gradle은 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/Plugin.html|Plugin]] 구현체를 ''META-INF/gradle-plugins''에서 플러그인 이름과 같은 프라퍼티 파일을 통해 찾는다.
* ''src/main/resources/META-INF/gradle-plugins/greeting.properties''
implementation-class=org.gradle.GreetingPlugin
프라퍼티 파일의 이름이 플러그인의 이름이 된다. ''implementation-class'' 프라퍼티는 Plugin 구현 클래스를 가리킨다.
==== 다른 프로젝트에서 플러그인 사용하기 ====
''buildscript { }'' 블럭을 통해 클래스패스에 사용자 정의 플래그인 클래스를 추가한다. [[gradle:organizing_build_logic|Gradle Organizing Build Logic]] 참조. 다음 예제는 로컬 리포지토리에 플러그인을 저장해서 사용하는 것을 보여준다.
* 다른 프로젝트에서 사용자 정의 플러그인을 사용하는 ''build.gradle''
buildscript {
repositories {
maven {
url uri('../repo')
}
}
dependencies {
classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
}
}
apply plugin: 'greeting'
==== 플러그인의 테스트 작성하기 ====
[[http://www.gradle.org/docs/current/javadoc/org/gradle/testfixtures/ProjectBuilder.html|ProjectBuilder]] 클래스를 사용하여 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html|Project]] 인스턴스를 만들어낼 수 있다. 이를 통해 플러그인 구현체를 테스트한다.
* ''src/test/groovy/org/gradle/GreetingPluginTest.groovy''
class GreetingPluginTest {
@Test
public void greeterPluginAddsGreetingTaskToProject() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'greeting'
assertTrue(project.tasks.hello instanceof GreetingTask)
}
}
===== 다중 도메인 객체 처리하기 =====
Gradle은 빌드 언어와 잘 작동하는 객체의 컬렉션을 다루는 도우미 클래스를 제공해주고 있다.
* 도메인 객체를 다루는 ''build.gradle''
apply plugin: DocumentationPlugin // DocumentaionPlugin.apply()가 실행된다.
// books NamedDomainObjectContainer 값을 구성한다. apply보다 나중에 실행되지만 'books.all { }'의 행위가 다 적용된다.
books {
quickStart {
sourceFile = file('src/docs/quick-start')
}
userGuide {
}
developerGuide {
}
}
task books << {
books.each { book ->
println "$book.name -> $book.sourceFile"
}
}
class DocumentationPlugin implements Plugin {
void apply(Project project) {
def books = project.container(Book)
books.all { // 현재 있는, 그리고 앞으로 컬렉션에 추가될 객체를 모두 돌면서 아래 수행
sourceFile = project.file("src/docs/$name")
}
project.extensions.books = books
}
}
class Book {
final String name // 'name' 필드는 필수이며 상수이고 유일한 값이어야 한다.
File sourceFile
Book(String name) {
this.name = name
}
}
* 실행하면
developerGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/developerGuide
quickStart -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/quick-start
userGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/userGuide
[[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:container(java.lang.Class)|Project.container()]]는 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/NamedDomainObjectContainer.html|NamedDomainObjectContainer]]의 인스턴스를 생성한다. 이 클래스에는 객체를 관리하고 구성하는 편리한 메소드들이 들어있다.
''project.container'' 메소드를 통해 사용할 객체의 타입은 항상 **''name''** 이라는 프라퍼티를 가지고 있어야 하며, 이 필드는 객체의 이름으로써 유일한 값이면서 상수여야 한다.
''project.container(Class)'' 메소드는 객체의 새로운 인스턴스를 생성하면서 하나의 문자열을 인자로 받아 객체의 이름(''name'')으로 지정하려고 시도한다.
[[http://www.gradle.org/docs/current/javadoc/org/gradle/api/NamedDomainObjectContainer.html|NamedDomainObjectContainer]]는 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/DomainObjectCollection.html|DomainObjectCollection]]을 상속하고 있다. [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/DomainObjectCollection.html#all%28org.gradle.api.Action%29|DomainObjectCollection.all()]] 메소드는 컬렉션에 현재 있는 항목들과 그 이후 추가될 항목들까지 돌면서 클로저의 내용을 실행한다. 따라서 여기서 ''books'' 선언이 플러그인 적용보다 늦게 발생했으나 ''books.all {}'' 블럭의 내용이 모두 자동으로 실행되게 된다.
===== 태스크간의 의존성 =====
플러그인 태스크 간의 의존성은 다음과 같은 형태로 정의할 수 있다.
project.task('sometask') {
dependsOn(project.tasks.a, project.tasks.b)
}
===== 플러그인에 의존성 주입 =====
* [[https://github.com/gradle/gradle/blob/master/design-docs/dependency-injection-for-plugins.md|플러그인 개발시 필요한 의존성을 생성자를 통해 주입하기]]
* ''@Inject'' 애노테이션을 사용한다.
* [[https://github.com/gradle/gradle/blob/master/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java|MavenPlugin.java]]에서 ''@Inject'' 사용예를 볼 수 있다.
===== 참조 =====
* [[https://dzone.com/articles/functional-tests-gradle-plugin|How to Test Gradle Plugins - DZone Java]]