메이븐은 빌드 시 pom.xml에 기술된 의존관계를 분석하여, 빌드에 필요한 jar 파일을 자동으로 아티팩트 레포지토리에서 다운로드 받아, 사용자의 로컬 레포지토리에 설치해줍니다. 그런데 가끔은 jar 파일을 수동으로(커맨드라인에서) 다운받고 싶을 때가 있습니다.

당연히 이런 기능을 제공하는 메이븐 플러그인이 있을 것으로 짐작하고 찾아보지만 잘 안보입니다. 좀더 집요하게 검색해보니 2개 정도 나옵니다.

1. http://www.agaetis.fr/public/maven-download-plugin/

2. http://maven.apache.org/shared/maven-downloader/

1번 플러그인은  mvn download:download -Dartifact=group:artifact:version:jar 식으로 실행하면 된다고합니다. 하지만 이 플러그인은 메이븐 메인레포지토리에 아직 등록되지 않았고, http://mvnrepository.com/ 같은 플러그인 검색사이트에서 검색해도 안나오는걸 보면 다른 공공(?) 레포지토리에도 등록하지 않은 듯합니다. 게다가 JDK6을 요구하기 때문에 그 이하 버전의 JDK 환경(저처럼)에서는 실행되지 않는 문제가 있습니다.

2번 플러그인은 메이븐 공식 플러그인 같기도합니다만 실행가능한 goal 등 어떻게 사용하면 되는지 설명이 전혀 없습니다. 그리고 실제로 mvn org.apache.maven.shared:maven-downloader:download 이렇게 실행해보면 일단 플러그인은 다운로드가 되지만 PluginDescriptor 에러가 납니다.

java.lang.IllegalStateException: The PluginDescriptor for the plugin Plugin [org.apache.maven.shared:maven-downloader] was not found.

에혀 이런저런 삽질이 모두 물거품이 되었네요. 구관이 명관인가요. 결국 아티팩트 다운로드 전용 pom.xml을 만드는 것으로 합의(?)를 봤습니다.

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>download</groupId>
     <artifactId>download</artifactId>
     <version>1.0</version>
     <packaging>jar</packaging>
     <name>DOWNLOAD ARTIFACTS</name>
     <description>for artifact downloads(다운로드 받을 라이브러리 지정후 mvn dependency:resolve 실행)</description>

     

  2.  <dependencies>
      <dependency>
       <groupId>다운로드 받을 라이브러리의 groupId</groupId>
       <artifactId>다운로드 받을 라이브러리의 artifactId</artifactId>
       <version>다운로드 받을 라이브러리의 version</version>
      </dependency>
     </dependencies>
  3. </project>

이렇게 pom.xml을 만들어두고 필요시마다 goupId, artifactId 등을 수정하여 mvn dependency:resolve 를 실행하면 됩니다. 테스트해보니 잘되네요. 이정도에서 만족해야겠습니다. ^^;

이 글은 스프링노트에서 작성되었습니다.

Posted by 에코지오
,

메이븐에서는 기본적으로 자바 소스 폴더를 한 개만 지정할 수 있습니다.

 <build>
    <sourceDirectory>src/main/java</sourceDirectory>
 ....
 </build>

하지만 때로는 자바 소스가 여러 폴더에 흩어져 있는 경우도 있습니다.
메이븐에서는 그럴 경우 프로젝트를 분리하라고 권장합니다.
그러나 프로젝트를 분리할 형편(?)이 안되거나 분리하기 싫다면 어떻게 할까요?

Build Helper Maven 플러그인을 이용하면 됩니다.
build-helper 플러그인의 add-source 모조는 POM에 소스 디렉토리를 추가해줍니다. 아래처럼 사용할 있습니다.

  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>1.2</version>
    <executions>
     <execution>
      <id>add-source-dir</id>
      <phase>generate-sources</phase>
      <goals>
       <goal>add-source</goal>
      </goals>
      <configuration>
       <sources>
         <source>another/src/main/java</source>
         <source>others/src</source>
       </sources>
      </configuration>
     </execution>
    </executions>
  </plugin>

 

Posted by 에코지오
,

메이븐에서 이게 가능한가요? 

Ant에서는 classpath 엘리먼트를 통해서 클래스가 포함된 디렉토리를 컴파일 패스에 추가할 수 있습니다.

    <classpath>
      <pathelement location="mydir/classes"/>
    </classpath>

이것과 동일한 기능을하는 메이븐 설정/플러그인이 있나요?

아무리 구글링하고 찾아봐도 방법을 모르겠습니다.

아시는 분은 좀 알려주시길...

Posted by 에코지오
,

메이븐의 특징 중 하나는 메이븐이 참조 라이브러리 의존관계를 체계적으로 관리해준다는 것이다. 메이븐이 의존성을 다루는 의존성 관리 메커니즘에는 의존성 전이(Transitive Dependencies)라는 게 있는데,  
A가 B에 의존하고 B가 C에 의존한다고 할 때, 즉 A -> B -> C 관계에서 A는 B에 대한 의존관계만 설정하면 메이븐이 알아서 C까지도 가져와서 적절한 스코프에 포함시켜 준다는 것이다.

그러나, B의 type이 war 이면 의존성 전이 메커니즘은 작동하지 않는다.

 <dependency>
   <groupId>mycom</groupId>
   <artifactId>B</artifactId>
   <version>1.0</version>
   <type>war</type>
 </dependency>

그러니까 메이븐이 자동으로 B.war가 의존하는 C.jar를 포함시켜주지 않는다는 얘기다.
게다가 B.war에 포함된 클래스들(WEB-INF/classes) 또한 A의 의존성에 포함되지 않는다. A의 소스가 B.war의 클래스를 참조한다면 컴파일시 에러가 발생할 것이다.

이클립스에서 A프로젝트의 빌드패스에 B 웹프로젝트를 추가하면 이클립스는 B 웹프로젝트의 빌드패스에 포함된 C.jar와 B 웹프로젝트의 클래스들을 A프로젝트에 자동으로 추가해준다.

아쉽지만 메이븐에서는 이게 자동으로 안된다.

Posted by 에코지오
,
메이븐에서 ear 패키지를 만들어 주는 플러그인은 maven-ear-plugin이다.
ear 파일에 포함되어 함께 패키징될 아카이브 모듈들은 <modules> 엘리먼트 하위에 <ejbModule>, <jarModule>, <webModule> 식으로 설정해서 끼워 넣는다.

그런데 xxxModule 설정 옵션을 보자면, 모두가 groupId와 artifactId를 통해서 끼워넣을 모듈을 지정하고 있다. 아무리 찾아봐도 모듈 파일의 경로를 설정하는 건 없다 ! 내가 원하는 건 임의의 경로에 있는 아카이브를 ear에 포함하고 싶은건데.... ㅜ.ㅜ

여기까지만 보면, 메이븐 저장소에 등록된 모듈만 ear에 포함시킬 수 있다는 결론이 나온다. 내가 만든 모듈을 ear에 포함시키고 싶으면 먼저 모듈을 로컬이나 리모트 저장소에 등록해주어야 한다는 거다. 이 무슨 시츄에이션인가?

그러나 저장소에 등록하지 않고 포함시킬수 있는 방법이 있다. 포함되는 모듈의 의존성 scope를 system으로 잡고, 파일의 절대경로를 지정하면 된다.

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-ear-plugin</artifactId>
 <version>2.3.1</version>
 <configuration>
  <modules>
   <ejbModule>
    <groupId>mycom</groupId>
    <artifactId>myapp-ejb</artifactId>
   </ejbModule>
   <webModule>
    <groupId>mycom</groupId>
    <artifactId>myapp-web</artifactId>
   </webModule>
  </modules>
 </configuration>
</plugin>
.....
......
<dependencies>
 <dependency>
  <groupId>mycom</groupId>
  <artifactId>myapp-web</artifactId>
  <version>1.0</version>
  <type>war</type>
  <scope>system</scope>
  <systemPath>${project.basedir}/myapp-web.war</systemPath>
 </dependency>
 <dependency>
  <groupId>mycom</groupId>
  <artifactId>myapp-ejb</artifactId>
  <version>1.0</version>
  <type>ejb</type>
  <scope>system</scope>
  <systemPath>${project.basedir}/myapp-ejb.jar</systemPath>

 </dependency>
 .....
</dependencies>

이렇게 잡아놓고 mvn package를 날리면 저장소에 등록되지 않은 myapp-ejb.jar, myapp-web.war 파일이 ear에 함께 패키징되는 것을 확인할 수 있다.

Posted by 에코지오
,
Ant는 세밀하게 빌드를 설정하기 좋지만 미리 정해진 규칙이나 프로세스가 없기 때문에 알파부터 오메가까지 모든걸 내가 다 정의해야한다. Ant는 나에게 거의 모든 자유를 주며, 복잡한 빌드환경에서도 적절히 대처할 수 있는 온갖 task를 제공해준다. 그러나 자유에는 책임이 따르듯이 규칙과 절차를 만드는 몫은 나에게 있다. 이렇게 수립된 규칙과 절차는 프로젝트마다 제각각이어서 대부분 호환되지 않는다.

Maven은 Ant에 비해 자유도는 낮지만 규칙을 잘 따른다면 작업을 쉽게 처리할 수 있다. 널리 통용되는 빌드 프로세스(phase)와 수행할 작업이 내장되어 있어서 설정을 최소화해주며, 의존관계의 라이브러리를 체계적으로 다루는 능력이 뛰어나다.(원래 ant는 의존성 관리를 하지 않으나 ivy를 이용하면 ant에서도 충분히 maven처럼 의존성 관리를 할 수 있다고 함) 그러나 Ant만큼 풍부하고 작은 단위의 task는 제공하지 않고 대신, plugin이라는 다소 큰 덩어리의 도구를 제공한다. 딱딱한 빌드절차, 묵직한 plugin 등 커스터마이징이 극히 제한적이어서 국내 SI프로젝트처럼 국제적인 상식(?)을 잘 따르지 않는 복잡한 빌드환경에서는 적합하지 않을 수 있다.

그럼 Ant의 높은 자유도와 Maven의 다듬어진 프로세스를 두루 갖는 그런 빌드 도구는 없는가?
최근 Ant와 Maven을 뛰어넘기 위해 스크립트 언어 기반으로 빌드를 설정하려는 시도가 많아지고 있다.

찾아보면 BuildrGradle, Raven 대충 이정도 나온다.

Raven과 Buildr은 ruby 언어로, Gradle은 groovy 언어로 빌드를 작성한다.
Raven은 Ant 스타일과 비슷하고 Buildr과 Gradle은 Maven의 사상이 들어있다.

자 이쯤에서 하나 골라잡아 공부해야 하는건가?
Posted by 에코지오
,

이전 글에서 리소스 설정치환 방법에 대해 얘기했습니다. 그럼 메이븐 환경에서 리소스 설정치환 작업은 어떤 phase에서, 어떤 리소스를 대상으로 이루어져야 할까요?

1. 클래스패스에 포함되는 리소스

(1) 리소스 필터링 방식
메이븐이 process-resource 단계에서 타겟 디렉토리로 복사된 리소스를 대상으로 필터링을 해줍니다. 이때 원본 리소스는 변경이 없습니다. 그러니까 /src/jdbc.properties 파일에 password=${jdbc.password} 처럼값을 설정해놓으면 /target/classes/jdbc.properties 파일에는 그 부분이 password=my_password 식으로 바뀌어 있다는 말입니다.

(2) 리소스 교체 방식
타겟 디렉토리에 복사된 리소스 파일에 대해서 교체 처리해줍니다. 어차피 process-resource 단계에서는 resources:resources 골이 가장 먼저 실행되기 때문에 교체 작업(ant의 copy 타스크 이용)은 그냥 process-resources 단계에 바인딩하면 됩니다. 역시 원본 리소스 파일은 변경이 없습니다. 예를 들어 /src 밑의 jdbc.properties와 jdbc.properties.dev가 /target/classes 밑에 복사된 후 /target/classes/jdbc.properties.dev 파일이 /target/classes/jdbc.properties 파일을 덮어쓰게 됩니다.

 

2. WEB-INF 밑에 있는 리소스(web.xml, struts-config.xml, applicationContext.xml 등등)

WEB-INF 밑의 리소스에 대한 설정치환은 좀 지저분합니다. 
메이븐은 package 단계에서 war:war를 실행하는데, war:war는 무조건 war:exploded 를 먼저 실행하고 나서 exploded된 웹어플리케이션을 war 파일로 묶어냅니다. 우리가 원하는 것은 exploded 된 웹어플리케이션의 리소스 파일에 대해 리소스설정치환 작업을 하고나서 war 파일로 패키징하는 것인데, 중간에 끼어들 여지가 원천 봉쇄된 것입니다.

뭐 package 단계나 war:war를 실행하지 않으면 되긴합니다. war:exploded를 명시적으로 실행한 뒤에 설정치환을 하면 됩니다. 그런데 설정치환하는 execution을 어느 phase에 바인딩하는게 좋을지... 하여간 원본 리소스 파일을 건드리지 않은 상태로 리소스 치환을 하려니 지저분해지는군요.

만약 원본 리소스를 건드려도 무방하다면 package 전 단계(prepare-package 단계는 maven 2.1부터 지원하기 때문에 2.1 이전 버전이라면 process-resources 단계가 괜찮을까요)에서 원본 리소스를 대상으로 설정치환 작업을 처리하면 됩니다. 
Posted by 에코지오
,

- 메이븐에 내가 만든 phase를 추가할 수 있는가?

- skip 옵션을 지원하지 않는 goal이 어떤 phase에 이미 바인딩된 경우에, 과연 런타임시에 그 goal의 실행을 막을 수는 없는 것인가? 예를 들어 package phase에서 war:war가 실행되지 않길 바란다면?

- 어떤 플러그인에 대해서 execution을 여러개 정의한 경우에 (바인딩된 phase를 통하지 않고) 특정 execution만 골라서 실행 가능한가? 반대로 특정 execution만 실행에서 제외할 수 있는가? execution의 id를 어떻게 활용할 방법은 없나?

Posted by 에코지오
,

보안을 위해 서버 패스워드 같은 민감한 데이터를 pom.xml에서 감추는 방법.

1. 사용되는 부분을 프로퍼티로 처리하고 

<sshexec host="myserver" username="admin" password="${wasserver.password}"  command="xxxx" />

 mvn 실행시 -D옵션으로 값을 넘겨준다.

mvn deploy -Dwasserver.password=xxx

(그러나 허드슨 사용 환경에서 허드슨 빌드 console 화면에 데이터가 노출되는 문제가 있다)

2. 감추려는 데이터가 서버 접속 정보라면 settings.xml에 서버정보를 설정하여 pom.xml에서 데이터를 감출수 있다.

<settings> 
  <servers>
    <server>
      <id>server001</id>
      <username>my_login</username>
      <password>my_password</password>
      <privateKey>${user.home}/.ssh/id_dsa</privateKey>
      <passphrase>some_passphrase</passphrase>
      <filePermissions>664</filePermissions>
      <directoryPermissions>775</directoryPermissions>
      <configuration></configuration>
    </server>
  </servers>
  ...
</settings>
Posted by 에코지오
,

메이븐다운(?) 방법으로 웹어플리케이션을 원격서버로 전송하기 위해서 이것저것 찾아보다가 알게 된 거 정리해본다.

1. deploy:deploy
packaging한 artifacts를 리모트의 메이븐저장소로 전송한다. 원격 저장소는  <distributionManagement>엘리먼트의 <repository>에 설정한다. 임의의 파일을 전송하는 건 불가능하다. package 가 war 이고 저장소 경로가 ftp://repository.mycompany.com/repository 라면, 실제로 war 파일은 저장소 layout에 맞춰서 ~/repository/groupId/artifactId/version 디렉토리에 전송된다.

2. site:deploy
 site phase에서 생성된 사이트를 리모트 (웹)서버로 전송한다. 리모트 서버정보는<distributionManagement>엘리먼트의 <site>에 설정한다. inputDirectory 옵션을 통해 target/site 디렉토리가 아닌 다른 디렉토리의 파일들을 전송할 수 있다. 그러나 <site>는 하나만 설정가능하기 때문에 2군데 이상의 서버로 파일들을 전송하는 건 불가능하다.

3. cargo 플러그인
일부 컨테이너에 대해 remote container에 expanded war(war 파일의 압축을 풀어놓은 것. exploded war)를 배포할 수 있다고 cargo 웹사이트에 나온다. 하지만 과연 이게 가능할지는 의구심이 든다. 실제로 작동여부를 테스트해봐야 할 듯하지만, 아마도 war 파일만 원격배포가 가능하지 않을까 싶다. 아직 최신 버전의 상용 WAS에 대한 지원이 미비하다.

4. myfaces의 wagon-maven 플러그인 
임의의 디렉토리 내의 파일들을 원격 서버로 전송한다. 임의의 디렉토리를 2군데 이상의 원격서버에 전송할 수 있다. 실제 프로젝트에서 개발서버 배포를 이 플러그인으로 처리했다. target/webapp 디렉토리의 파일들은 WAS서버로 전송하고, target/htdocs 디렉토리의 파일들은 WEB서버로 전송하도록 말이다. 
아쉽다면, 지정된 디렉토리 내의 모든 파일들을 전송하며 그 디렉토리의 몇몇 파일들만 골라서 전송할 수는 없다는 것이다. 즉 fileset 개념이 없다(사실 이건 이 플러그인의 문제라기 보다 이 플러그인이 이용하는 메이븐 wagon의 문제이다. 몇몇파일만 골라내는 건 ant copy를 써서 target/htdocs처럼 별도의 디렉토리에 전송할 대상만 따로 모아놓으면 해결할 수 있다).
이 플러그인의 장점이라면 디렉토리를 압축하여 전송후 리모트에서 압축을 해제하는 방식의 wagon 기능을 이용하기 때문에 전송할 파일이 많은 경우에 ant의 ftp/scp 타스크를 이용하는 것보다 전송시간이 현저히 줄어든다는 거.
아래는 실제 pom.xml의 일부인데 별거 아닌게 내용이 긴거 같아 맘이 편하지만은 않다.

   <plugin>
    <groupId>org.apache.myfaces.buildtools</groupId>
    <artifactId>myfaces-wagon-plugin</artifactId>
    <version>1.0.0</version>
    <executions>
     <!-- config 배포 작업 -->
     <execution>
      <id>deploy-conf</id>
      <phase>pre-integration-test</phase>
      <goals>
       <goal>deploy</goal>
      </goals>
      <configuration>
       <id>deploy-config</id>
       <url>
        scp://${wasserver.username}:${wasserver.password}@${wasserver.ip}:${wasserver.config.dir}
       </url>
       <inputDirectory>${config.home.dir}</inputDirectory>
      </configuration>
     </execution>
     <!-- 웹어플리케이션 배포 작업 -->
     <execution>
      <id>deploy-web</id>
      <phase>pre-integration-test</phase>
      <goals>
       <goal>deploy</goal>
      </goals>
      <configuration>
       <id>deploy-web</id>
       <url>
        scp://${wasserver.username}:${wasserver.password}@${wasserver.ip}:${wasserver.web.dir}
       </url>
       <inputDirectory>${web.output.exploded.dir}</inputDirectory>
      </configuration>
     </execution>
     <!-- 웹파일 배포 작업 -->
     <execution>
      <id>deploy-html</id>
      <phase>pre-integration-test</phase>
      <goals>
       <goal>deploy</goal>
      </goals>
      <configuration>
       <id>deploy-html</id>
       <url>
        scp://${webserver.username}:${webserver.password}@${webserver.ip}:${webserver.htdocs.dir}
       </url>
       <inputDirectory>${web.output.html.dir}</inputDirectory>
      </configuration>
     </execution>
    </executions>
   </plugin>


현재까지 패턴을 적용하여 임의의 파일/디렉토리를 내 맘대로 전송할 수 있는 방법은 메이븐에서는 antrun을 이용하는 방법밖에 없는 듯싶다. maven의 wagon api와 file management api 이용하여 직접 플러그인을 만드는 것도 재밌을거 같다.

Posted by 에코지오
,