재성이가 보내준 자빌메(자바 세상의 빌드를 이끄는 메이븐)을 두번째 읽기 시작했다.

이 책을 처음 읽은 것은 몇 주 전이다. 책을 받은 날 저녁, 반가운 마음에 바로 읽기 시작해서 그날 밤 잠자리에 들기 전에 끝냈다. 책의 내용은 내가 이미 다 알고 있는 것들이라 친숙한데다, 블로그 글을 읽는 듯한 느낌이 들정도로 흥미롭고 편안하게 쓰여져서인지 술술 잘 읽혔다. KSUG 운영진의 한 명인 benelog님은 한 시간 반만에 이 책을 다 읽어다고 하니 메이븐을 이용한 개발과 경험이 있는 사람이라면 그리 어렵지 않게 끝낼 수 있는 책인게 분명하다.

꽤 오래전부터 책을 읽고 서평을 쓰거나 감상을 적는 일은 삼가해 오고 있다. 여러가지 이유가 있겠지만, 가볍고 진지하지 못한 인상비평을 수준의 글을 공개된 매체에 남겨서 다른 이들에게 어떤 방향으로든 오해를 주고 싶지 않기 때문이다. 누군가는 소중한 가치를 얻을 수 있는 책을 꼼꼼히 읽고 고민하지도 않은 주제에 몇 가지 트집을 잡고 빈정대서 책을 제대로 접할 기회조차 박탈시켜 버리거나, 성의 없는 두리뭉실 칭찬으로 자신이 필요로 하지도 않은 책을 읽게 만들어서 바쁜 개발자들의 시간을 낭비하게 만들고 싶지 않았다. 책 읽고 감상 남기는게 뭐 그리 대수라고 깐깐하게 생각하는가 싶겠지만, 내가 스스로 책을 쓰는 데 적지 않은 인생을 소비해보니 그 창작의 수고에 대해 어떤 면으로든 갖춰야 할 예의가 있다는 생각이 절실히 들었던 것 같다.

혹은 남의 책 잘못 깠다가, 나중에 내 책이 역공을 당해 판매가 저조해지면 출판사에 불려가 혼날까봐 겁이 났던 것도 있다. 자기 생각을 F워드를 섞어가며 거침없이 쏟아내던 개빈 킹 조차, VC로부터 거액의 투자를 받고난 뒤  회사의 이미지에 나쁜 영향을 주는 직원의 말과 행동에 강력한 제제가 가해지던 JBoss에 입사하고는 예의바르고 순한 범생처럼 변해간 것과 비슷할지도 모르겠다.

그러던 중에 재성이가 자기가 쓴 "책을 보낼테니 주소를 알려달라"고 연락이 왔다. 앗싸를 외치고는, 메신저로 주소를 적어주고 있었는데, 뒤 따르는 말이 "책에 대한 냉정한 평가를 해줘"였다. "에이, 우리 사이에 어찌 그런 짓을…"이라고 넘기려는데, "그럼 안 보내줄거야"라는 협박이 돌아왔다. 그래서 어쩔 수 없이 책을 읽고 감상을 공개하기로 약속했고, 지금 다시 내 느낌과 생각을 정리하기 위해서 책을 읽고 있다.

 

내 스타일이 원래 그러니, 한번 시작하면 얘기가 길어질 지도 모른다. 그래서 결론부터 얘기하고 가야겠다.

이 책은 메이븐의 도입/사용을 진지하게 고려하고 있는 사람이라면 반드시 읽어야 할 책이다. 메이븐에 관한 책이 영어나 한글로 여러 권이 이미 나와있고, 인터넷에도 많은 자료가 존재한다. 하지만 이 책, 자.빌.메.는 다른 책과 다르다. 다른 모든 책보다 낫다고 쉽게 말할 수는 없다. 하지만 분명한 것은 다른 책과 차별될만한 요소를 가진 독특한 책이다. 그 점이 매력이고, 장점이다. 물론 그것이 어떤 독자에게는 단점될 수도 있겠지만. (재성이는 내 책의 리뷰를 쓰면 스프링 개발 경험을 몇년 쌓은 뒤에나 보라고 할거라고 했는데, 나는 재성이와 달리 까칠하지 않고 착하기 때문에 "빌드 경험 몇년 쌓고 난 뒤에 이 책을 보라"고 하지는 않을 거다. 삽질 경험을 쌓고 나면 더 얻을 수 있는 가치게 있겠지만, 일단 이해는 안되도 책을 읽으며 이런 것이 있구나 하고 기억만 해둬도 큰 도움이 될 것이기 때문이다)

결론 끝.

참, 노안이 시작되기 시작한, 또는 안구건조증이나 기타 질병으로 눈이 침침한 분들은 절대로 흐릿한 실내 조명 아래서 이 책을 읽지 말기 바란다. 책의 인쇄상태는 가장 큰 단점이다. 특히 명령창에 나온 메이븐 실행화면은 뭉개지고 흐릿해서 아주 밝은 조명아래가 아닌 곳에서 읽으려면 눈이 충혈되고 아파올 수 있다. 나도 한밤중에 스탠드 불빛 아래서 책을 다 읽느라고, 눈이 빠지는 줄 알았다. 그마나 메이븐 실행 결과 내용에 익숙해서 대충 보고 넘어가서 그렇지, 명령창의 내용을 자세히 읽으려면 눈에 힘주다 짜증나서 책을 덮어버렸을 지도 모른다. 다음 인쇄에서는 개선되기를 가장 바라는 부분이다. 이 문제는 출판사의 실책이 아닌가 싶다. 물론 시력이 1.2이상 되고, 명암 구분에 뛰어나며, 눈에 총기가 도는 젊은 분들이라면 괜찮을지도 모르겠지만.

 

다음 할 얘기는 "이 책이 말하는 빌드란 도대체 무엇인가? 메이븐이 과연 빌드툴인가?" 이다. 처음부터 시비를 걸고싶지는 않지만, 어쨌든 책이 다루는 주제의 정체와 책이 사용하는 용어에 대해 언급하지 않고 시작할 수는 없기에, 이 책이 빌드라는 개념에 대해서 두리뭉실 넘어가고, 일관성 없이 쓴 것을 지적하지 않을 수 없겠다. 이 얘기는 다음 편에서 계속.

갑작스럽게 시드니 출장을 다녀와야 했던 데다 며칠 전에 도착한 iPad 2 가지고 놀기 바빠서 미뤘던 메이븐 애트리뷰트 적용기 두번째 이야기.

먼저 메이븐 pom.xml에 애트리뷰트를 적용하면 어떤 모습이 되야 할지 생각해봤다. 가장 이상적인 것은 엘리먼트와 애트리뷰트 양쪽을 모두 사용할 수 있는 것이다. 스프링이 그런 식이다. 스프링도 처음에는 XML 설정에 엘리먼트의 사용 비율이 높았다. 예를 들어 값 하나와 빈 레퍼런스 하나를 프로퍼티로 가지는 빈이라면 다음과 같이 작성해야 했다.

<bean id=”hello” class=”Hello”>
  <property name="name">
    <value>ahnpig</value>
  </property>
  <property name="message">
    <ref bean="hellomessage" />
  </property>
</bean>

id, name 등은 애트리뷰트로 설정할 수 있어서 그나마 메이븐의 100% 엘리먼트 방식보다는 낫긴 했지만, 그래도 설정이 많아지고 애플리케이션의 규모가 커지면 XML을 읽고 수정하기가 매우 벅찼던 기억이 있다. 그래서 1.2인가부터 <value>, <ref> 태그 등을 다음과 같이 <property>의 애트리뷰트로도 설정할 수 있게 했다.

<bean id=”hello” class=”Hello”>
  <property name="name" value=”ahnpig” />
  <property name="message" ref=”hellomessage” />
</bean>

덕분에 설정 파일은 훨씬 간결해졌다. 동시에 스프링은 리스트와 같은 콜렉션을 값으로 설정하는 경우나 이전 버전에서 작성된 XML 설정을 그대로 사용할 수 있게 자식 엘리먼트로 <value>, <ref>를 사용하는 것을 동시에 지원했다.

2.0에서는 더 나아가서 p 네임스페이스를 이용해서 아예 모든 property 설정을 <bean> 태그 안으로 다 가져올 수 있도록도 만들었다. 또한 일정 분량의 설정 자체를 모듈화 해서 아예 전용태그로 만들 수 있는 기능도 제공했다.

뭐, 그렇다고 메이븐에서도 네임스페이스를 이용해서 기존 설정을 더 간결하고 의미를 잘 드러낼 수 있는 방식으로 재정의 하는 것까지는 바라지 않는다. 그저 바라기는 일부 설정 단위를 애트리뷰트로 묶어 낼 수 있는 정도이고, 가능하면 기존 pom.xml 설정과 호환성을 유지할 수 있도록 애트리뷰트와 엘리먼트 양쪽으로 다 설정이 가능하도록 만드는 것이다.

 

하지만 막상 메이븐을 열어보니 애트리뷰트와 엘리먼트의 동시 지원 방식은 쉽지 않아보였다.

스프링과 같이 DI 원리를 충실하게 지켜가며 만들어진 프레임워크라면 당연히 정보를 담고 있는 모델과 이 메타 모델을 직렬화해서 저장해둔 설정파일의 형식이나 구조는 서로 결합되지 않고 깔끔하게 분리되는 것이 기본이다.

메이븐도 Plexus라는 IoC/DI 컨테이너를 사용한다. 메이븐 말고 Plexus를 사용하는 제품이 있는지는 모르겠지만, 아무튼 3.0은 Plexus를 이용해서 나름 충실한 DI 스타일의 개발을 했다고 알고 있다. 심지어 3.1에서는 구글주스 DI도 추가적으로 이용할 수 있게 만드는 계획도 있다고 들었다.

메이븐에서 pom.xml의 정보는 Model 오브젝트와 그 계층구조로 표현된다. pom.xml의 루트 엘리먼트인 <project>가 바로 Model 클래스에 매핑된다. 하위 엘리먼트로 정의되지만 사실상 project의 속성에 해당하는 <modelVersion>, <artifactId>, <name> 따위는 Model 클래스가 프로퍼티 값으로 가지고 있다. 반면에 하나 이상의 정보를 가지고 있어야 하는 <developers>나 <contributors>는 List 프로퍼티로 정의되어 있고, 독립적으로 계층 구조를 가지는 <build>, <dependcie>등은 Build, Dependency  정의되어서 Model의 프로퍼티로 등록되어있다.

결국 pom.xml에는 모두 다 엘리먼트로 만들어지지만 값에 해당하는 부분은 Model 클래스 계층구조 안에서는 String, int와 같이 값으로 정의되고, 하위 구조를 가지는 경우에는 Dependency, Plugin 등의 클래스로 표현된다는 것을 알 수 있다. 이렇게 프로퍼티 값으로 정의되는 부분이라면 XML에서 얼마든지 애트리뷰트로 정의해주어도 문제가 없고, 사실 더 자연스럽다.

Model 클래스 계층구조로 추상적인 메타모델을 가지고 있으니, pom.xml과 같은 외부 설정을 읽고 파싱해서 이를 Model로 만들어주는 부분이 독립적이고, 선택가능하다면 얼마든지 애트리뷰트 확장도 가능하고 XML 외의 소스로부터 프로젝트 Model 정보를 읽어드리는 것이 가능할 것 처럼 보인다.

실제로 IoC/DI가 적용된 부분도 인상적이다.

Model 오브젝트로 표현되는 모델 정보는 ModelReader를 통해서 생성된다. DI 원칙에 충실하게 ModelReader는 인터페이스로 정의되어있고, 이를 구현하는 클래스는 얼마든지 새롭게 만들 수 있다. 그리고 어떤 ModelReader 구현을 사용할지는 IoC/DI를 통해서 ModelProcessor의 구현 클래스로 전달되도록 정의되어있다.

메이븐 모델을 담당하는 역할을 맡은 DefaultModelProcessor의 코드는 다음과 같이 정의되어있다.

@Component( role = ModelProcessor.class )
public class DefaultModelProcessor  implements ModelProcessor {
    @Requirement
    private ModelLocator locator;
    @Requirement
    private ModelReader reader;
    …

스프링의 @Component와 @Autowired에 익숙하다면 이 코드가 어떤 의미를 가지는지 이해할 수 있을 것이다. ModelLocator는 모델이 위치한 폴더를 주면 모델 파일을 찾아내는 책임을 정의한 것이다. 기본은 pom.xml 파일을 찾는 것이지만, 꼭 pom.xml 파일일 필요는 없으니 이 책임을 분리하고 DI로 적용할 수 있게 만든 것이다.

ModelReader는 File이나 InputStream으로 모델 정보를 담은 물리적인 파일 정보를 넘기면 Model 오브젝트 계층구조를 리턴하도록 정의된 메소드를 가진 인터페이스다. 스프링의 BeanDefinitionReader와 비슷하다. XmlBeanDefinitionReader를 사용하면 XML에서, PropertiesBeanDefinitionReader를 사용하면 프로퍼티 파일에서 빈 설정을 읽어오는 것처럼 메이븐도 ModelReader를 구현한 PomXmlModelReader를 DI하도록 해주면 pom.xml에서, GroovyModelReader를 주면 Groovy로 작성된 모델정보를 가져올 수 있다는 식이다. 일단 IoC/DI를 적용해 놓아서 일단 이런 가능성은 열어주긴 했지만 아직까지 ModelReader를 구현한 클래스는 pom.xml을 읽는 DefaultModelReader 뿐이다.

어쨌든 ModelReader 구현을 새롭게 만들거나, 기본 DefaultModelReader를 일부 수정해서 애트리뷰트까지 적용가능하도록 만들면 될 것 같다.

그런데 좀 더 코드를 살펴보니 생각보다 쉽지 않겠다는 생각이 들었다. 메이븐 개발자들이 왜 pom.xml에 애트리뷰트를 사용하도록 쉽게 만들지 못하고 있는지도 알게되었다. 그 이유는 다음에.

재성이가 보내준 메이븐 책을 읽다가 예전에 생각만 하고 실행에 옮기지 못했던 일이 하나 떠올랐다.  pom.xml 파일의 작성 방식을 바꾸는 일이다.

메이븐의 pom.xml을 직접 다뤄본 경험이 있는 사람이라면 의존 라이브러리가 늘어나고 플러그인 설정이 복잡해지면서 급격하게 양이 증가하는 pom.xml 파일에 당황해본 경험이 있을 것이다. 메이븐의 기본 관례와 페이즈를 그대로 준수하고 플러그인의 기본 설정을 바꾸지 않고 사용하는 경우라면 그나마 낫다. 하지만 의존 라이브러리 갯수가 늘어나는 데다 엉망으로 설정된 의존성 전이를 맞추느라 <exclusion>을 가득 차워야 하고, 플러그인의 기본 설정을 바꿔쓰고, 여러가지 추가 플러그인을 적용하다 보면 pom.xml이 수 백라인으로 늘어나는 것을 경험하는 것은 어렵지 않다.

Spring ROO가 만들어 주는 프로젝트는 메이븐 기반으로 되어있다. ROO 배포판에 있는 샘플 프로젝트인 clininc은 스프링과 하이버네이트 기본 기능만 사용하는 매우 간단한 웹 애플리케이션이다. 그런데 이 clinic 프로젝트의 pom.xml은 503라인이나 된다. 메이븐을 수 년간 써오면서 나름 pom.xml 파일에 익숙한 나 조차도 한 눈에 빌드 내용을 파악하기가 쉽지 않다. 프로젝트 pom.xml에는 나오지 않는 기본 설정(effective pom을 봐야 알 수 있는)까지 의식하면서 봐야 하는 이유도 있지만, 스크롤 하면서 앞 뒤의 내용을 살펴야 하는 불편 때문이기도 하다.

메이븐 pom.xml을 살펴보면 XML을 설정 파일로 사용하는 다른 제품들과 확연하게 드러나는 특징을 발견할 수 있다. 메이븐 pom.xml에서는 애트리뷰트 없이 엘리먼트만 사용한다는 점이다. pom.xml에는 메이븐 개발자들이 가진 똥고집 수준의 강력한 XML 문서 작성 철학이 적용되어있다. 엘리먼트만 사용해서 XML 문서를 작성하면 간단한 문서의 경우에 깔끔해 보인다는 장점도 있다. 파싱하기도 더 편할지 모르겠다. 하지만 문서의 길이가 대폭적으로 증가하고, 그 때문에 가독성이 떨어진다는 단점이 있다. 물론 엘리먼트만 사용해서 문서를 작성하는 것이 더 낫다고 생각하는 사람도 있을 것이다. 반대로 애트리뷰트로 세부 설정을 빼주는 것을 선호하는 개발자들도 많다. 

예를 들어보자. 

다음은 전형적인 메이븐의 의존 라이브러리 설정이다. 하나만 놓고 보면 나쁘지는 않다. 하지만 의존 라이브러리가 수 십개쯤 되는 프로젝트라면 여러 페이지를 스크롤 해야 전체 라이브러리의 종류가 어떤 것인지 파악할 수 있을 것이다.

 

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
<scope>provided</scope>
</dependency>

 

애트리뷰트를 이용해서 작성할 수 있다면 어떨까? 그렇다면 다음과 같이 한 줄이면 충분할 것이다.

 

<dependency groupId=”org.hsqldb” artifactId=”hsqldb” version=”1.8.0.10″ scope=”provided” />

 

실제로 메이븐의 <dependency>와 비슷한 역할을 하는 Ant/Ivy의 <dependency>는 다음과 같이 애트리뷰터를 사용해서 간결하게 작성할 수 있다.

 

<dependency org=”com.jamonapi” name=”com.springsource.com.jamonapi” rev=”2.4.0″ conf=”optional, jamon->compile”/>

 

Ivy의 경우 적지 않은 라이브러리를 사용하는 프로젝트라도 한 눈에 사용하는 라이브러리의 목록을 다 파악할 수 있고, 쉽게 편집이 가능하다.

메이븐의 엘리먼트 전용 XML 문서의 장황스러움은 엉성하게 작성된 전이적 의존 설정과 맞물리면 더 심각해진다. A 프로젝트가 B라는 라이브러리를 사용했을 경우에, B가 다시 의존하는 C,D,E,F,G 중에서 A 프로젝트가 실제 필요한 것은 일부인 경우가 대부분이다. B 라이브러리의 모든 잡다한 기능을 다 사용하는 것이 아니기 때문이다. 그렇다면 마땅히 B 라이브러리의 pom.xml을 작성하는 개발자는 C,D,E,F,G를 optional로 설정해주고, A 프로젝트 개발자가 이 중에서 원하는 것만 등록해서 쓰도록 해야 한다. 스프링의 경우 자신이 의존하는 라이브러리가 100개가 넘지만, commons-logging 빼고는 거의 다 optional로 되어있다. 반면에 간단한 유틸리티인데도 코드 내에서 참고하는 모든 라이브러리를 필수 의존 라이브러리로 잡아놓는 경우가 종종 있다. 그래서 테스트 라이브러리 하나 의존했을 뿐인데, 각종 DB관련 라이브러리가 모두 딸려 오거나, 심지어 jboss, tomcat이 통채로 끌려오는 경우가 있다. 메이븐의 의존성 전이 기능이 도움이 되기 보다는 피해를 주는 경우인데, 이런 폐혜가 많이 알려진 지금도 이런 문제는 자주 발견된다.

그래서 프로젝트의 pom을 작성하는 사람이 <dependency>로 가져오는 라이브러리의 pom.xml을 다시 살펴서 필요없는 라이브러리를 일일이 제외시켜줘야 한다. 

ROO가 만들어준 프로젝트에 포함된 commons-dbcp 라이브러리 설정을 살펴보자.

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xerces</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>

commons-dbcp가 의존하는 라이브러리 중에서 4가지를 제외시켜주는 것이다. 이 설정 하나에 30줄이나 된다. 의존 라이브러리 전이 문제는 그렇다 치더라도 이 30줄이나 되는 설정을 애트리뷰트로 사용할 수 있게만 해줘도 훨씬 간결해질 것이다. 다음과 같이 고치면 6줄이면 끝이다.

<dependency groupId=”commons-dbcp” artifactId=”commons-dbcp” version=”1.3″>
<exclusion groupId=”commons-logging” artifactId=”commons-logging” />
<exclusion groupId=”commons-pool” artifactId=”commons-pool” /> 
<exclusion groupId=”xerces” artifactId=”xercesImpl” /> 
<exclusion groupId=”xml-apis” artifactId=”xml-apis” /> 
</dependency>

애트리뷰트를 쓰면 더 읽기가 불편하다고 생각하는 삐딱한 눈을 가진 사람이라면 메이븐 스타일에 만족하고 쓰면 되겠다. 하지만 나는 애트리뷰트를 쓰는 게 더 좋다. 

문제는 메이븐은 이런 개발자의 취향을 인정하지 않는다는 것이다. 메이븐은 /src/main/java 같은 디렉토리 구조부터 시작해서 메이븐 개발자들의 독특한 취향을 강요하고 강제한다. 그나마 디렉토리 구조는, 잘 알려져 있지 않긴 하지만 그래도 변경할 여지라도 줬다. 하지만 XML 작성 방법은 개발자들의 수 많은 요구가 있었음에도 3.0.3까지 나온 지금까지 요지부동이다. 

베타 버전이기는 하지만  메이븐 Polyglot 프로젝트가 만들어져서 ruby, scala, atom 등으로 메이븐 POM 파일을 작성하는 방법까지 가능해졌다. 하지만 여전히 XML 작성 방식은 그대로다.

그래서 지난 주말에 잠시 짬을 내서 메이븐 pom.xml을 애트리뷰트를 사용해서 작성할 수 있도록 메이븐을 조금 수정해봤다. 생각보다 어렵지 않게 가능했다. 

올해 들어서 처음 쓰는 블로그는 그 얘기를 해보려고 하는데… 벌써 업무시간이 되었네. 다음에 계속.

Maven 3.0 정식버전이 출시됐다. (http://maven.apache.org/docs/3.0/release-notes.html)

Maven 3.0은 2.x와의 호환성을 최대한 유지하면서 성능을 대폭 개선했고 안정성을 높였다고 한다. 이클립스에서 m2eclipse 플러그인을 사용하고 있다면 이미 Maven 3.0 엔진을 사용하고 있는 셈이다. m2e 플러그인 설정에서 별도로 설치된 2.x버전을 선택했다고 하더라도 프로젝트에 직접 맞물려서 돌아가는 의존 라이브러리 관리 기능은 내장형 3.0 엔진으로 동작한다.

Maven 2.x로 작업하면서 하도 고생을 많이 해서 한동안 ANT/Ivy로 넘어갈까도 생각했지만, m2eclipse라는 화려한 이클립스 플러그인의 지원과 Sonatype을 중심으로 한 Maven 개발자들의 열정 때문에 그대로 Maven으로 남기로 했다. 사실 ANT/Ivy로 깔끔한 빌드 체계를 만드는 것은 결코 쉬운 일도 아니기도 하고. 또, Maven의 많은 단점이 3.0에서 대폭 개선될 것이라는 기대도 한몫했을 것이다.

최근에 시작한 프로젝트에 Maven 3.0을 적용하고 있는데 여러모로 맘에 든다. 확실히 경고 메시지가 이전보다 꼼꼼하고 상세하게 나와서 POM 작성에 도움이 된다. 성능은 멀티 모듈을 쓰지 않으니 잘 모르겠고.

사실 가장 맘에 드는 것은 예전에 나를 괴롭혔던 Maven 버전 체계다. 특히 OSGi 버전 포맷과의 충돌 때문에 스프링소스 리포지토리의 번들을 사용할 때 짜증났던 점들이 모두 개선됐다.

Maven 2.x대의 버전 문제에 대해서는 예전에 기록해둔 글이 두 개 있다.

Maven의 버전 포맷과 이를 해석하는 방법을 정확하게 이해하지 않으면 버전 범위를 적용하거나 버전 충돌이 있는 경우에 까다로운 문제가 발생할 수 있다.

Maven의 기본 버전 포맷은 a.b.c-d 구조로 되어있다. a, b, c는 각각 숫자로 된 버전 번호고 d는 추가 식별자이다. 1.0.0-M1 이라고 하면 각각 1, 0, 0, M1으로 해석된다. 문제는 기존 Maven은 정확히 이 포맷을 따르지 않으면 버전 전체를 식별자 취급을 해버린다는 점이다. 그래서 OSGi 버전 포맷을 따르는 스프링 번들이 3.0.0.RELEASE고 되어있으면 3, 0, 0, RELEASE로 해석되는 대신 0, 0, 0, 3.0.0.RELASE라고 해석되버린다. OSGi 버전포맷은 -대신 .을 사용하기 때문이다. 그래서 3.0.0.RELEASE는 1.0.0보다도 더 낮은 버전이 되버리는 문제가 발생한다.

Maven 2.x에서 스프링 3.0.x 대의 가장 최신 버전을 자동 선택하고 싶어서 [3.0.0, 3.1.0)와 같은 버전 범위를 사용하면 3.0.0.RELASE, 3.0.5.RELASE와 같은 버전은 아예 찾지 못해서 에러가 난다. 의존 관계를 따져보다 버전이 충돌이 났을 때 2.5.6과 3.0.5.RELASE 두개의 같은 라이브러리가 발견되면 2.5.6을 더 최신으로 해석해버리기도 한다. 3.0.5.RELASE와 같은 포맷을 제대로 인식 못하기 때문이다. 게다가 Maven은 a,b,c 없이 d만 존재하는 버전을 허용하기 때문에 에러도 안난다. 그냥 0.0.0 버전이면서 식별자가 3.0.5.RELEASE라고 지정된 것이라고 해석해버린다. 게다가 내부에서 해석한 버전 정보는 아무리 로그레벨을 낮춰도 출력이 안되기 때문에 개발자 입장에서는 이게 안되면 매우 난처할 수 밖에 없다.

이 문제는 오래전 2.0.x 시절부터 계속 지적된 것이지만 Maven 개발자들은 3에서 개선하겠다고만 하고 2.x대는 버전 처리 방식은 그대로 방치해 두었다. 오픈소스긴 하지만 오픈 프로젝트는 아닌지라 커뮤니티의 불만이나 의견 따위는 독선적인 소수 커미터들에게 그렇게 무시당하기 일수다.

아무튼.. 소스를 디버깅해가면서 버전 처리 방식을 연구하게 만들만큼 한동안 나를 괴롭혔던 이 문제는 3.0에서 해결 됐다.

이제는 [3.0.0,3.1.0)이라고 버전을 지정하면 정확하게 3.0.5.RELASE를 가져온다. STS 2.5.0에서 Maven 2.2.1과 3.0을 바꿔가며 비교해봤을 때 확실하게 버전 처리 방식이 달라진 것을 확인할 수 있었다. 이제는 3.0.0.RELASE를 3, 0, 0, RELEASE로 정확히 해석하는 것 같다. 내부 리포지토리로 Nexus 1.8을 적용했는데 3.0의 새로운 버전 해석 방식과 잘 호환된다.

개선된 버전 처리 방식 하나 때문에라도 앞으로는 3.0을 사용해야겠다.

Maven의 공용설정이 담긴 setting.xml 파일이 있다. 여기에 들어가는 server 섹션에는 배포를 위해서 필요로하는 서버의 인증정보들이 보통 담겨져 있다. 문제는 암호화 되지 않은 단순 텍스트로 저장된다는 것. 보안상 위험을 초래할 수 있는 대표적인 취약점이다. 배포 서버의 계정 비밀번호등이 노출된다는 것은 결코 바람직하지 않다.

이를 위해서 2.1 에서는 엔코딩된 비밀번호를 넣을 수 있는 기능이 추가되었다.

이제는 다음처럼 암호화된 비밀번호를 settings.xml에 담아놓고 사용할 수 있는 기능이 추가되었다.

<server> 
    <id>my.server</id>  
    <username>foo</username>
    <password>{COQLCE6DU6GtcS5P=}</password>
</server>

자세한 내용은 New Feature: Maven Settings Password Encryption에 나와 있다.

문제는 언제 나올지 모르는, 이제 겨우 M2-SNAPSHOT 버전인 2.1에만 적용됐다는 점이다.

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha