본문 바로가기
기술 부채 상환 중....

Migrating from Maven to Gradle

by 닮은 2018. 5. 10.

gradle

원문

메이븐에서 그레들로 마이그레이션

Making a case for migration

  • 기능 비교는 여기
  • 그레들은 3.0 이후로 빌드 캐싱, 컴파일 회피(?), 개선된 점증적인 자바 컴파일러 등과 같이 더 빨리 빌드 하는 것에 많은 투자를 했다. 그래서 지금은 메이븐보다 2~10배 정도 빠르다고 한다.
  • 더 많은 마이그레이션 케이스는 여기에서 살펴 볼 수 있다.

Understanding the functional differences

  • 그레들은 태스크 의존성 그래프를 기반으로 동작한다. 메이븐은 고정된 선형의 phases에 골을 추가하여 동작하는데, 이렇게 다름에도 마이그레이션은 쉽다.
  • 빌드 스캔 웹 기반의 특정 빌드의 스냅샷으로 협업 디버깅 및 세분화된 성능 분석을 할 수 있게 해준다.
  • 일단 메이븐 빌드에 대한 인풋, 아웃풋을 기록하는 것부터 시작해야 그레들 빌드로 마이그레이션 했을 때 기능적으로 같다는 것으로 증명할 수 있다.

Automated conversion

  • $ gradle init으로 프로젝트를 생성할 수도 있지만 기존 메이븐 프로젝트를 그레들로 마이그레이션 해주기도 한다. 루트 프로젝트 디렉토리에서 실행하면 POM들을 파싱하고, 만약 멀티 프로젝트 빌드라면 setting.gradle도 만들어 준다.
  • 어셈블리는 자동으로 변환되지 않아서 직접 해줘야할 것들이 좀 있다.
  • 마이그레이션이 잘 되었다면 $ gradle build 빌드한다.

Bills of Materials (BOMs)

  • 메이븐은 pom 파일의 dependencyManagement 내부에 <packaging>pom</packaging>를 정의하여 의존성에 대한 제약사항을 공유하는데, 이런 특수한 타입의 pom(bom)은 다른 pom에 임포트 하여 프로젝트에서 일괄된 라이브러리를 가지도록 하게 해준다. 그레들도 4.6 이상에서, (5.0 이하에서는 명시적으로)settings.gradle을 추가하여 이것을 지원한다.

setting.gradle

enableFeaturePreview("IMPROVED_POM_SUPPORT")

build.gradle

dependencies { 
    // 스프링 부트 의존성 BOM implementation 
    'org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE'

    //버전 없이도 의존성이 해결된다. 
    implementation 'com.google.code.gson:gson' 
    implementation 'dom4j:dom4j' 
}

Provided scope and optional dependencies

그레들은 설정을 통해 의존성을 관리한다는 점에서 유연하다고 할 수 있지만, war 플러그인을 쓰지 않는 이상, provided 스코프를 가지고 있지 않다. 대신 비슷한 행동을 그레들 2.12부터 추가된 compileOnly 설정으로 할 수 있다.

선택적 의존성은 그레들 4.6 이상에서 pom 파일을 사용할 때 종속성 제약 조건을 생성하고, 5.0 이전 버전의 경우에는 setting.gradle에 enableFeaturePreview('IMPROVED_POM_SUPPORT')을 추가해야 한다.

4.5 버전 이전에서는 선택적 의존성을 지원하지 않는데, 대신 optional-base plugin을 사용해서 가능하게 할 수 있다.

Maven profiles and properties

메이븐은 다양한 종류의 속성을 사용하여 빌드를 매개 변수화 한다. 어떤 것은 프로젝트 모델의 읽기 전용 속성이고, 어떤 것은 유저가 직접 pom 에서 정의 하는 것이다. 또한 시스템 속성을 프로젝트 속성으로 쓸 수도 있다.

그레들 또한 비슷한 프로젝트 속성 시스템을 가지고 있다. 다음과 같은 곳에서 정의할 수 있따.

  • 빌드 파일
  • 루트 프로젝트 디렉토리의 gradle.properties
  • $HOME/.gradle 디렉토리의 gradle.properties

이 외에도 다양하게 적용할 수 있으며 자세한 것은 이곳에서 참조하자.

같은 속성이 외부 프로퍼티 파일과 빌드 파일에서 동시에 정의된 경우에는 빌드 파일의 것이 우선 순위가 높다.

메이븐 프로필로 돌아가서, 환경, 대상 플랫폼 등등에 따라 다른 설정들은 활성화 비활성화 하는 방법이 있다. 논리적으로 이것들은 조건문에 지나지 않는다. 그레들에는 이런 조건을 선언하는 더 강력한 방법이 있기 때문에 앞에서 말한 프로필 지원이 필요가 없다. (의존성의 pom 파일 제외하고)

환경에 따라 다른 배포를 해야한다고 생각해보자. (로컬 환경, 테스트 환경, 프로덕션) 프로필 처럼 동작하게 하기 위해 먼저 루트 프로젝트에 각 환경을 위한 빌드 파일 profile-default.gradle, profile-test.gradle, profile-prod.gradle 를 만든다. 그리고 메인 빌드 파일에는 이렇게 작성한다.

if (!hasProperty('buildProfile')) ext.buildProfile = 'default'
apply from: "profile-${buildProfile}.gradle"

다음으로는 환경을 특정 짓는 설정을 해주면 되는데, 특정 프로필을 확설화 시키기 위해서는 그에 맞는 프로젝트 설정을 커맨드 라인에서 넘기면 된다.

$ gradle -PbuildProfile=test build

하지만 조건문은 빌드를 이해하고 유지하기가 쉽지 않다. 그레들은 메이븐이 요구하는 프로필 사용을 피하기 위해서 변형 (variants) 등을 제공한다. 이 글은 많은 도움을 줄 것이다!

Resource filtering

메이븐에는 process-resource라는 페이즈가 있다. 이 과정에서 웹 리소스, 패키지 속성 파일 등과 같은 다양한 파일에 대한 변수를 대체할 수 있다.

그레들에서는 자바 플러그인의 processResources 태스크가 같은 일을 한다.

build.gradle

processResources {
    expand(version: version, buildNumber: currentBuildNumber)
}

왼쪽이 토큰 이름이고 오른쪽이 프로젝트 속성이다. 이 변수 대체는 모든 리소스 파일에 적용된다. (보통 src/main/resources 에 있는 것들)

그레들은 속성을 처리하는 또 다른 강력크한 방법을 가지고 있다. 클로져를 통해 필터 안에서 훅 해서 컨텐츠를 한줄 한줄 처리하거나 FilterReader를 구현해서 추가할 수 있다. 더 자세한 것은 여기에서 확인하자.

Integration tests

유닛 테스트가 있지만 그것이 모든 것을 보장하지는 않는다. 객체 간의 인터렉션이나, 환경에 따른 인터렉션에서 버그가 생기기 때문에 많은 프로젝트가 통합, 기능, 또는 수용 테스트라고 불리는 더 높은 테스트를 하게 된다.

메이븐은 pre-integration-test, integration-test. post-integration-test. verify 등의 부가적인 페이즈를 제공하여 이러 유형들의 테스트를 지원한다.

또 하나 생각해야할 것은 통합 테스트 클래스가 어디에 있느냐 하는 것이다. 초기 접근은 유닛 테스트 클래스를 한데 모으는 것이지만 이상적이지는 않다. 대체적으로 프로필을 사용하여 두가지 타입의 테스트를 따로 수행한다.

그레들에서는 플러그인이 아니라 기본적으로 자바 프로젝트에 이미 포함되어 있는 유닛 테스트를 소스셋으로 설정하여 테스트 하도록 한다. 통합 테스트를 여기에 작성하면 된다.

build.gradle

sourceSets {
    integTest {
        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
}
configurations {
    integTestCompile.extendsFrom testCompile
    integTestRuntime.extendsFrom testRuntime
}

integTest라는 소스셋을 생성한다. 어플리케이션, 라이브러리 클래스, 의존성들이 통합 테스트를 컴파일 할때 클래스패스에 포함되어 있는지 확인한다.

통합 테스트는 서드파티 라이브러리를 사용할 수 있고, 그것도 컴파일 클래스패스에 추가해야 한다. 이건 그냥 dependencies 블록에 넣어주면 된다.

build.gradle

dependencies {
    // ...
    integTestCompile 'org.codehaus.groovy:groovy-all:2.4.3'
    integTestCompile 'org.spockframework:spock-core:0.7-groovy-2.0', {
        exclude module: 'groovy-all'
    }
}

이렇게 하면 컴파일이 되고, Test라는 태스크를 작성하자. build.gradle

task integTest(type: Test) {
    dependsOn startApp
    finalizedBy stopApp
    testClassesDirs = files(sourceSets.integTest.output.classesDirs)
    classpath = sourceSets.integTest.runtimeClasspath
    mustRunAfter test
}

어플리케이션 서버를 따라 적절한 시간에 시작하고 종료하도록 한다. Test 설정에 관한 더 자세한 것은 여기에서 확인하자.

이제 남은건 태스크 그래프에 integTest를 포함시키는 방법이다. 이건 하고 싶은대로 알아서 하면 된다. 예를 들면 build 할때 하게 한다든가.

Common plugins

그레들도 메이븐처럼 플러그인을 통해 빌드를 확장한다. 표면적으로 플러그인 시스템이 다른 것 같지만 다음과 같이 기능 기반의 플러그인을 많이 공유한다.

  • Shade/Shadow
  • Jetty
  • Checkstyle
  • JaCoCo
  • AntRun

    ... org.apache.maven.pluginsmaven-checkstyle-plugin2.17

    <execution>
      <id>validate</id>
      <phase>validate</phase>
      <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <encoding>UTF-8</encoding>
        <consoleOutput>true</consoleOutput>
        <failsOnError>true</failsOnError>
        <linkXRef>false</linkXRef>
      </configuration>
      <goals>
        <goal>check</goal>
      </goals>
    </execution>
    

    ...

마이그레이션 할 때 configuration 블락 바깥은 깨끗하게 무시되는 경우가 있다. 이런 경우에는 다음과 같이 그레들 설정으로 바꾸면 된다.

checkstyle {
    config = resources.text.fromFile('checkstyle.xml', 'UTF-8')
    showViolations = true
    ignoreFailures = false
}

CheckStyle 태스크는 check 태스크의 의존성으로 자동으로 추가되는데, 이것은 test 또한 포함한다. 테스트 전에 CheckStyle이 실행하는지 확인하고 mustRunAfter() 메소드로 우선순위를 지정하면 된다.

test.mustRunAfter checkstyleMain, checkstyleTest

마이그레이션에서 소스셋을 꼭 기억하라. 메이븐이 제공하는 것보다 훨씬 우아한 방법으로 통합 테스트를 처리하기 때문에 만드시 마이그레이션 계획에 고려해야 한다.

Ant goals

많은 메이븐 빌드는 커스텀 메이븐 플러그인 구현에 대한 부담 없이 앤드 플러그인에 의존해서 빌드를 커스터마이징 한다. 그레들은 and 객체를 통해 Ant가 first-class 시티즌이 되기 때문에 그런 플러그인이 없다.

task sayHello {
    doLast {
        ant.echo message: 'Hello!'
    }
}

심지어 앤트 속성과 파일셋이 기본적으로 지원된다. 더 자세한 것은 여기에서 참조한다.

Plugins you don’t need

메이븐을 대체하기 위한 그레들 플러그인이 필요하지 않을 수도 있다. 예를 들면 메이븐 Enforcer 플러그인으로 의존성 버전과 환경 요소를 관리하지만 이것들은 일반 그레들 빌드 스크립트로 설정할 수 있다.

Uncommon and custom plugins

흔지 않은 플러그인과 커스텀 플러그인은 직접 작성해야 할 수도 있다. 한땀 한땀 배워서 잘 작성하라는 내용인 것 같다.

Next steps

그레들 커뮤니티에 온 것을 환영해! 빌드 로직 구성하기빌드 성능 향상시키기를 더 읽어 보면 좋겠다.