2014-04-16

Gradle + Eclipse Expert

일단 이 글은 Gradle을 가지고 제대로 삽질 한 번쯤 해봤으면 쉽게 이해할 수 있다.
Gradle 1.11 기준이다. 난 최신을 좋아하지 (물론 삽질도...)

시작하기 전에...

당연히 Eclipse Plugin을 사용해야 된다.
일반 프로젝트라면
apply plugin: 'eclipse'
웹 프로젝트라면
apply plugin: 'eclipse-wtp'

http://kwonnam.pe.kr/wiki/gradle/eclipse도 참고하면 매우 좋다.

Package Explorer와 Project Explorer 정리

Gradle로 그냥 이클립스 프로젝트를 만들면 Dependencies JAR 파일들이 Package Explorer와 Project Explorer를 점령하게 된다. 이제 그것들을 몰아낼 시간이다.

build.gradle에 다음을 추가한다.
eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                def entries = classpath.entries
                entries.findAll { it.kind == 'lib' }*.exported = false
            }
        }
    }
}
그리고 Gradle -> Refresh All을 하면 Maven 쓸 때처럼 깔끔하게 정리된 모습을 볼 수 있다.

WTP Facet 설정

Gradle은 WTP Facet을 알아서 무지 구식으로(!!!) 설정해준다. 진짜로. Servlet 2.3이라니 잘도 이런 구석기 시대 세팅을!
따라서 이런 사태를 방지하기 위해 facet 설정을 따로 해줘야 한다.

아래 설정은 Java 1.7, Servlet 3.0을 사용하는 facet 설정이다.
eclipse {
    wtp {
        facet {
            facet name: 'jst.web', version: '3.0'
            facet name: 'java', version: '1.7'
            facet name: 'wst.jsdt.web', version: '1.0'
        }
    }
}

Project Dependency Correction with Hierarchical Project Name

Spring 프로젝트에서 제공하는 이클립스 Gradle IDE 플러그인이 꽤 괜찮은 기능을 제공하는데, 그 중 하나가 Hierarchical Project Name 지원으로, 이클립스상의 프로젝트명을 root.child 같이 만들어준다. Gradle 프로젝트 임포트시 Use hierarchical project names를 체크하면 이 기능을 사용할 수 있다. 이를 이용해서 여러 프로젝트에 같은 이름의 서로 다른 하위 프로젝트를 가지고 있더라도 아무 문제없이 사용할 수 있다.
다른 프로젝트를 참조하기 전 까지는...

Hierarchical Project Name을 사용한 프로젝트를 dependencies에 추가할 경우 플러그인이 해당 프로젝트를 찾지 못해서 정신줄을 놓게 된다!

뭐 별 거 있나... 직접 수정하면 될 것을...

build.gradle에 다음을 추가한다.

For WTP Project

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/resource') }.each { entry ->
                        def name = entry.@'handle'.substring('module:/resource/'.length(), entry.@'handle'.lastIndexOf('/'))
                        entry.@'handle' = "module:/resource/${rootProject.name}.${name}/${rootProject.name}.${name}"
                        entry.@'archiveName' = "${name}.jar"
                    }
                }
            }
        }
    }
}

For Normal Project

eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                def entries = classpath.entries
                entries.findAll { it.kind == 'src' && it.path.startsWith('/') }.each {
                    def name = it.path.substring(1)
                    it.path = "/${rootProject.name}.${name}"
                }
            }
        }
    }
}

그리고 Gradle -> Refresh All을 하면 정상적으로 프로젝트 참조를 인식한다.

[2016-08-26] 어... 플러그인이 buildship으로 바뀌면서 이건 더이상 못쓰는데, 사실 더 쉽고 간단한 방법이 있었다. (...)
http://divinespear.blogspot.com/2016/08/gradle-eclipse-expert-2016-edition.html 를 보시라.

Web App Libraries 라이브러리 그룹 비우기

Web App Libraries 라이브러리 그룹은 Maven으로 프로젝트 만들 시절에는 구경도 못해본 거였는데, 이게 가끔 말썽을 부린다.
그래서 이클립스에서 이를 무시하도록 설정을 좀 봐줘야 한다.

build.gradle에 다음을 추가한다.
eclipse {
    classpath {
        containers.removeAll(EclipseWtpPlugin.WEB_LIBS_CONTAINER)
    }
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/classpath') }*.replaceNode {}
                }
            }
        }
    }
}
그리고 Gradle -> Refresh All을 하면 Web App Libraries에 프로젝트 참조를 제외한 모든 JAR 참조가 날아가 있는 것을 볼 수 있다.

WAR Overlay

Gradle의 WAR Overlay 자체는 참 간단하긴 한데... 이클립스 플러그인이 이를 인식하지 못하니 참으로 통탄할 일이로다.

그래서 역시 설정을 직접 고치면 된다. 안되는게 어디있나... 안되면 되게하라.

build.gradle에 다음을 추가한다.

Eclipse 4.4 이전

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
                        entry.'dependency-type'.each {
                            it.value = 'consumes'
                        }
                    }
                }
                whenMerged { component ->
                    // 현재 프로젝트: 무조건 제일 위에 와야함
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    // overlay할 WAR 프로젝트들을 밑에 나열한다.
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/prj/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    ...
                }
            }
        }
    }
}

Eclipse 4.4 이후

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
                        entry.@'handle' = entry.@'handle'.replaceAll('module:/overlay', 'module:/resource')
                        entry.'dependency-type'.each {
                            it.value = 'consumes'
                        }
                    }
                }
                whenMerged { component ->
                    // overlay할 WAR 프로젝트들만 밑에 나열한다. 존나좋군?
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/OverlayProject/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    ...
                }
            }
        }
    }
}
OverlayProject는 overlay할 WAR 프로젝트명을 써준다. 여러 개의 프로젝트를 overlay할 경우 overlay 순서에 유의해서 추가해주면 된다.

그리고 Gradle -> Refresh All을 하면 정상적으로 WAR Overlay를 인식하고, WTP Server Deploy도 잘 작동한다.

잡담: 이거 삽질이 가장 힘들었다.
Share: