반응형
빌드 과정을 처리해주는 Ant Task를 직접 구현해본다.
원하는 기능을 수행하는 Ant Task 구현하기
요즘은 많은 프로젝트에서 Ant를 이용해서 개발을 진행하고 있다. Ant는 기본적으로 제공하는 Task가 풍부할 뿐만 아니라 확장성이 뛰어나며, 실제로 추가적으로 제공되는 Ant Task를 인터넷상에서 손쉽게 찾아볼 수 있다.
Ant는 기본적으로 제공되는 기능만 잘 사용해도 대단하지만, Ant에서 기본적으로 제공하지 않는 아주 사소한 기능때문에 아쉬웠던적도 있을 것이다. (어쩌면, 잘 몰라서 없다고 생각했을지도 모르지만...) 그래서, 본 글에서는 아주 간단하지만 새로운 Task를 만들어서 Ant를 확장하는 방법을 살펴보고자 한다.
구현해볼 Ant Task 기능
본 글에서 만들어보려는 기능은, 알집에서 새로운 zip파일을 만들때 기존 zip파일이랑 겹치지 않게 뒤에 괄호를 붙여서 파일명을 만드는 기능과 비슷하다. 즉, 기존에 backup.zip 이 있었다면 그 다음에는 backup(2).zip, 또 그 다음에는 backup(3).zip와 같이 ZIP 파일을 만들어주는 기능을 구현해보고자 한다.
본 글에서는 아래와 같이 날짜와 시분 그리고 일렬번호를 덧붙여주는 Ant Task를 작성해볼 것이다.
일단 소스코드부터 살펴보도록 하겠습니다. 앞서 설명했던 기능을 제공하는 Ant Task의 구현체는 아래와 같습니다.
새로운 Ant Task를 만드려면 위 코드와 같이 Task 클래스를 상속받으면 된다. (물론, MatchingTask와 같이 원하는 기능 중 일부를 기본적으로 제공해주는 Task를 상속받아도 된다.)
private으로 선언된 필드들을 보면 dir, prefix, property, extName, pattern이 있는데, 이중에서 extName과 pattern은 지정하지 않을 경우 기본값을 갖도록 처리하였다. 이 필드들의 값들은 xml에서 <mytask> 태그의 속성으로 받아오며(예: <mytask dir="c:/test" ...>), Ant가 내부적으로 리플렉션을 이용해서 각 필드의 값을 설정해준다. 예를 들어, dir의 속성값을 build.xml에서 받고 싶은 경우에는 public void setDir(String dir) 이라는 메소드를 만들어 주면 된다. 즉, set변수명() 메소드가 호출되서 우리는 값을 받아올수 있는 것이다. (즉, 자바빈 규약에 따라 작성된 프로퍼티의 값을 설정해준다.)
만일 프로퍼티를 이용해서 값을 지정하지 않고, 아래와 같이 중첩된 요소를 사용해서 값을 지정한다면(아래 코드에서는 <fileset>을 통해 값을 지정하고 있다) create엘리먼트명(), add엘리먼트명(), addConfigured엘리먼트명() 등의 메소드를 호출하면 되는데, 이때도 역시 Ant에서 이 메소드를 자동으로 호출해준다. (이에 대한 보다 자세한 내용은 Ant 문서를 참고하기 바란다.)
마지막으로, Ant에 의해서 호출되는 메소드는 execute() 메소드이다. execute() 메소드는 Task가 실제로 처리할 작업에 대한 로직 코드를 포함한다. (스트러츠가 로직을 처리하기 위해 execute() 메소드를 호출해 주는 것처럼, Ant도 Task를 실행하기 위해 execute() 메소드를 호출한다.)
execute()메소드를 보면 아래 부분이 있다.
위 코드는 이는 build.xml안에서 <property name="xxx" value="yyy"/> 와 같이 프로퍼티를 새롭게 설정해 주는 것이다. 이렇게 프로퍼티 값을 설정하면, 빌드 파일의 다른 부분에서 ${xxx}와 같은 코드로 프로퍼티 값을 참조해서 사용할 수 있게 된다.
구현한 Task를 빌드 파일에서 사용하기
Ant Task 클래스를 구현했으므로 이제 남은 일은 실제로 Ant Task를 사용하는 것 뿐이다. 새로 구현한 Ant Task를 사용하기 위해서는 빌드 파일에 Ant Task를 정의해주어야 한다. 아래와 같이 빌드 파일에서 <taskdef> 태그를 사용해서 사용할 Ant Task를 명시해주어야 한다.
<taskdef> 태그는 새로운 Task를 정의할 때 사용된다. name 속성은 새로운 Task의 태그명을 입력하며, classname 속성은 Task를 구현한 클래스의 이름을 입력한다. 일단 taskdef 태그를 사용해서 커스텀 Task를 지정하면, name 속성에서 명시한 이름을 통해서 커스텀 Task를 사용할 수 있게 된다.
결론
소스등을 백업하기 위해서, (첨부된 build.xml 파일의 예와 같이) 기존 소스를 하나의 zip으로 묶어서 backup디렉토리에 넣어 놓을때 이용할 수 있을 것이다. 이미, ant에 이런 기능이 있을꺼란 생각이 들기도 하지만, 필자의 경우 이런 기능이 참으로 아쉬웠던 적이 있어서 직접 만들어 보았다. 본 글이 새로운 Ant Task를 구현하고 싶은 개발자에게 조금이나마 도움이 되기를 바란다.
관련링크:
본 글의 저작권은 작성자(홍순풍)에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.
원하는 기능을 수행하는 Ant Task 구현하기
요즘은 많은 프로젝트에서 Ant를 이용해서 개발을 진행하고 있다. Ant는 기본적으로 제공하는 Task가 풍부할 뿐만 아니라 확장성이 뛰어나며, 실제로 추가적으로 제공되는 Ant Task를 인터넷상에서 손쉽게 찾아볼 수 있다.
Ant는 기본적으로 제공되는 기능만 잘 사용해도 대단하지만, Ant에서 기본적으로 제공하지 않는 아주 사소한 기능때문에 아쉬웠던적도 있을 것이다. (어쩌면, 잘 몰라서 없다고 생각했을지도 모르지만...) 그래서, 본 글에서는 아주 간단하지만 새로운 Task를 만들어서 Ant를 확장하는 방법을 살펴보고자 한다.
구현해볼 Ant Task 기능
본 글에서 만들어보려는 기능은, 알집에서 새로운 zip파일을 만들때 기존 zip파일이랑 겹치지 않게 뒤에 괄호를 붙여서 파일명을 만드는 기능과 비슷하다. 즉, 기존에 backup.zip 이 있었다면 그 다음에는 backup(2).zip, 또 그 다음에는 backup(3).zip와 같이 ZIP 파일을 만들어주는 기능을 구현해보고자 한다.
본 글에서는 아래와 같이 날짜와 시분 그리고 일렬번호를 덧붙여주는 Ant Task를 작성해볼 것이다.
- backup_20050515_1426.zip -> 처음 생성시
- backup_20050515_1426(2).zip -> 두번째
- backup_20050515_1426(3).zip -> 세번째
일단 소스코드부터 살펴보도록 하겠습니다. 앞서 설명했던 기능을 제공하는 Ant Task의 구현체는 아래와 같습니다.
package anttest;
import java.io.File;
import java.text.SimpleDateFormat;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* @author 홍순풍(rocklike@empal.com)
*
* 알집에서는 새로운 압축파일을 만들때 기존의 이름뒤에 (2)이런 식으로
* 해서 만든다. (예 : backup.zip, backup(2).zip)
* 요 테스크는 그런 비슷한건데, 추가로 연월일_시분을 붙여서 만든다.
* ( 예:backup_20050515_1539.zip, backup_20050515_1539(2).zip )
*
* ant.jar를 classpath로 잡고서 요 파일을 실행시키면, c:\ 디렉토리에 샘플
* 파일이 생성되는걸 확인할수 있다.
*/
public class NewFilenameTask extends Task {
//--- 필수
private String dir; // 디렉토리
private String prefix; // 파일명 앞쪽에 붙일꺼.
private String property; // setting해줄 Property name
//--- 선택
private String extName = "zip"; // 확장자
private String pattern; // 이름만들때 쓰일 패턴 (기본:yyyyMMdd_HHmm)
//--- 속성 setting
public void setDir(String _dir) {
this.dir = _dir;
}
public void setPrefix(String _prefix) {
this.prefix = _prefix;
}
public void setExtName(String _extName) {
this.extName = _extName;
}
public void setPattern(String _pattern) {
this.pattern = _pattern;
}
public void setProperty(String _property) {
this.property = _property;
}
/**
* 기본 validation 체크
*/
public void validate() throws BuildException{
// dir, prefix, property 속성은 반드시 지정해야 한다.
if(dir==null || "".equals(dir)){
throw new BuildException("dir속성은 반드시 넣어야 합니다. [dir값:"
+ dir + "]");
}
if(prefix==null) {
throw new BuildException("prefix속성은 반드시 넣어야 합니다. [prefix값:"
+ prefix + "]");
}
if(property==null || "".equals(property)) {
throw new BuildException("property속성은 반드시 넣어야 합니다. [property값:"
+ property + "]");
}
}
/**
* execute() 메소드
*/
public void execute() throws BuildException {
validate();
File newFile = getNewFile(dir, prefix, extName, pattern);
getProject().setProperty(property, newFile.getAbsolutePath());
}
/**
* 새로운 파일을 리턴.
*
*/
public File getNewFile(String dir, String prefix, String extName,
String pattern) throws BuildException {
return getNewFile(new File(dir), prefix, extName, pattern);
}
/**
* 날짜랑 시간을 붙인 붙여서 새로 만든 파일을 리턴.
*
*/
public File getNewFile(File dir, String prefix, String extName,
String pattern){
if(!dir.isDirectory() || !dir.exists()){
throw new BuildException("디렉토리가 존재하지 않습니다. [dir명 => " + dir +"]");
}
// pattern이 null이라면 "yyyyMMdd_HHmm" 으로 하자.
if(pattern==null){
pattern = "yyyyMMdd_HHmm";
}
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
String middleName = formatter.format(new java.util.Date());
// 예 : backup_20050515_1745.zip
String newFileName = prefix + "_" + middleName + "." + extName;
File newFile = new File(dir, newFileName);
for(int i=2; newFile.exists(); i++){
newFileName = prefix + "_" + middleName + "(" + i + ")." + extName;
newFile = new File(dir, newFileName);
}
return newFile;
}
/**
* 테스트용..
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
String dir = "c:/";
File newFile =
new NewFilenameTask().getNewFile(dir, "backup", "zip", null);
System.out.println("AbsolutePath : " + newFile.getAbsolutePath());
boolean result = newFile.createNewFile();
System.out.println("result : " + result);
}
}
import java.io.File;
import java.text.SimpleDateFormat;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* @author 홍순풍(rocklike@empal.com)
*
* 알집에서는 새로운 압축파일을 만들때 기존의 이름뒤에 (2)이런 식으로
* 해서 만든다. (예 : backup.zip, backup(2).zip)
* 요 테스크는 그런 비슷한건데, 추가로 연월일_시분을 붙여서 만든다.
* ( 예:backup_20050515_1539.zip, backup_20050515_1539(2).zip )
*
* ant.jar를 classpath로 잡고서 요 파일을 실행시키면, c:\ 디렉토리에 샘플
* 파일이 생성되는걸 확인할수 있다.
*/
public class NewFilenameTask extends Task {
//--- 필수
private String dir; // 디렉토리
private String prefix; // 파일명 앞쪽에 붙일꺼.
private String property; // setting해줄 Property name
//--- 선택
private String extName = "zip"; // 확장자
private String pattern; // 이름만들때 쓰일 패턴 (기본:yyyyMMdd_HHmm)
//--- 속성 setting
public void setDir(String _dir) {
this.dir = _dir;
}
public void setPrefix(String _prefix) {
this.prefix = _prefix;
}
public void setExtName(String _extName) {
this.extName = _extName;
}
public void setPattern(String _pattern) {
this.pattern = _pattern;
}
public void setProperty(String _property) {
this.property = _property;
}
/**
* 기본 validation 체크
*/
public void validate() throws BuildException{
// dir, prefix, property 속성은 반드시 지정해야 한다.
if(dir==null || "".equals(dir)){
throw new BuildException("dir속성은 반드시 넣어야 합니다. [dir값:"
+ dir + "]");
}
if(prefix==null) {
throw new BuildException("prefix속성은 반드시 넣어야 합니다. [prefix값:"
+ prefix + "]");
}
if(property==null || "".equals(property)) {
throw new BuildException("property속성은 반드시 넣어야 합니다. [property값:"
+ property + "]");
}
}
/**
* execute() 메소드
*/
public void execute() throws BuildException {
validate();
File newFile = getNewFile(dir, prefix, extName, pattern);
getProject().setProperty(property, newFile.getAbsolutePath());
}
/**
* 새로운 파일을 리턴.
*
*/
public File getNewFile(String dir, String prefix, String extName,
String pattern) throws BuildException {
return getNewFile(new File(dir), prefix, extName, pattern);
}
/**
* 날짜랑 시간을 붙인 붙여서 새로 만든 파일을 리턴.
*
*/
public File getNewFile(File dir, String prefix, String extName,
String pattern){
if(!dir.isDirectory() || !dir.exists()){
throw new BuildException("디렉토리가 존재하지 않습니다. [dir명 => " + dir +"]");
}
// pattern이 null이라면 "yyyyMMdd_HHmm" 으로 하자.
if(pattern==null){
pattern = "yyyyMMdd_HHmm";
}
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
String middleName = formatter.format(new java.util.Date());
// 예 : backup_20050515_1745.zip
String newFileName = prefix + "_" + middleName + "." + extName;
File newFile = new File(dir, newFileName);
for(int i=2; newFile.exists(); i++){
newFileName = prefix + "_" + middleName + "(" + i + ")." + extName;
newFile = new File(dir, newFileName);
}
return newFile;
}
/**
* 테스트용..
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
String dir = "c:/";
File newFile =
new NewFilenameTask().getNewFile(dir, "backup", "zip", null);
System.out.println("AbsolutePath : " + newFile.getAbsolutePath());
boolean result = newFile.createNewFile();
System.out.println("result : " + result);
}
}
새로운 Ant Task를 만드려면 위 코드와 같이 Task 클래스를 상속받으면 된다. (물론, MatchingTask와 같이 원하는 기능 중 일부를 기본적으로 제공해주는 Task를 상속받아도 된다.)
private으로 선언된 필드들을 보면 dir, prefix, property, extName, pattern이 있는데, 이중에서 extName과 pattern은 지정하지 않을 경우 기본값을 갖도록 처리하였다. 이 필드들의 값들은 xml에서 <mytask> 태그의 속성으로 받아오며(예: <mytask dir="c:/test" ...>), Ant가 내부적으로 리플렉션을 이용해서 각 필드의 값을 설정해준다. 예를 들어, dir의 속성값을 build.xml에서 받고 싶은 경우에는 public void setDir(String dir) 이라는 메소드를 만들어 주면 된다. 즉, set변수명() 메소드가 호출되서 우리는 값을 받아올수 있는 것이다. (즉, 자바빈 규약에 따라 작성된 프로퍼티의 값을 설정해준다.)
만일 프로퍼티를 이용해서 값을 지정하지 않고, 아래와 같이 중첩된 요소를 사용해서 값을 지정한다면(아래 코드에서는 <fileset>을 통해 값을 지정하고 있다) create엘리먼트명(), add엘리먼트명(), addConfigured엘리먼트명() 등의 메소드를 호출하면 되는데, 이때도 역시 Ant에서 이 메소드를 자동으로 호출해준다. (이에 대한 보다 자세한 내용은 Ant 문서를 참고하기 바란다.)
<mytask >
<fileset dir="/test">
<include name="**"/>
</fileset>
</mytask>
<fileset dir="/test">
<include name="**"/>
</fileset>
</mytask>
마지막으로, Ant에 의해서 호출되는 메소드는 execute() 메소드이다. execute() 메소드는 Task가 실제로 처리할 작업에 대한 로직 코드를 포함한다. (스트러츠가 로직을 처리하기 위해 execute() 메소드를 호출해 주는 것처럼, Ant도 Task를 실행하기 위해 execute() 메소드를 호출한다.)
execute()메소드를 보면 아래 부분이 있다.
getProject().setProperty(property, newFile.getAbsolutePath());
위 코드는 이는 build.xml안에서 <property name="xxx" value="yyy"/> 와 같이 프로퍼티를 새롭게 설정해 주는 것이다. 이렇게 프로퍼티 값을 설정하면, 빌드 파일의 다른 부분에서 ${xxx}와 같은 코드로 프로퍼티 값을 참조해서 사용할 수 있게 된다.
구현한 Task를 빌드 파일에서 사용하기
Ant Task 클래스를 구현했으므로 이제 남은 일은 실제로 Ant Task를 사용하는 것 뿐이다. 새로 구현한 Ant Task를 사용하기 위해서는 빌드 파일에 Ant Task를 정의해주어야 한다. 아래와 같이 빌드 파일에서 <taskdef> 태그를 사용해서 사용할 Ant Task를 명시해주어야 한다.
<target name="init" description="내가 만든 task를 이용해 보자...">
<echo> ${ant.home} </echo>
<taskdef name="mytask" classname="anttest.NewFilenameTask" />
<mytask dir="${basedir}"
prefix="backup"
property="newFilename" />
<echo> New Filename : ${newFilename} </echo>
</target>
<echo> ${ant.home} </echo>
<taskdef name="mytask" classname="anttest.NewFilenameTask" />
<mytask dir="${basedir}"
prefix="backup"
property="newFilename" />
<echo> New Filename : ${newFilename} </echo>
</target>
<taskdef> 태그는 새로운 Task를 정의할 때 사용된다. name 속성은 새로운 Task의 태그명을 입력하며, classname 속성은 Task를 구현한 클래스의 이름을 입력한다. 일단 taskdef 태그를 사용해서 커스텀 Task를 지정하면, name 속성에서 명시한 이름을 통해서 커스텀 Task를 사용할 수 있게 된다.
결론
소스등을 백업하기 위해서, (첨부된 build.xml 파일의 예와 같이) 기존 소스를 하나의 zip으로 묶어서 backup디렉토리에 넣어 놓을때 이용할 수 있을 것이다. 이미, ant에 이런 기능이 있을꺼란 생각이 들기도 하지만, 필자의 경우 이런 기능이 참으로 아쉬웠던 적이 있어서 직접 만들어 보았다. 본 글이 새로운 Ant Task를 구현하고 싶은 개발자에게 조금이나마 도움이 되기를 바란다.
관련링크:
본 글의 저작권은 작성자(홍순풍)에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.