메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Ant 1.7: Antlib 사용하기

한빛미디어

|

2006-09-07

|

by HANBIT

13,091

제공: 한빛 네트워크
저자: Kev Jackson, 이대엽 역
원문: http://www.onjava.com/pub/a/onjava/2006/08/09/ant-1-7-using-antlibs.html

새로운 버전의 Ant가 릴리즈될 때이므로 Java 개발자가 가지고 놀 수 있을만한 Ant의 가장 멋진 기능중의 하나인 antlib들을 소개하기에 적기인 것 같다. antlib는 Java 개발자에게는 사용자 정의 Ant 태스크, 타입, 그리고 매크로를 작성하고 배포하는데 있어서 더 나은 방식이며, Ant 개발자에게는 Ant 배포(distribution)를 포함한 임의의 태스크들을 배포할 수 있는 훨씬 더 나은 방식이다.

그렇다면 기존의 taskdef 에서 절실히 고쳐져야 할 잘못된 점은 무엇이었나?
  • Classpath가 taskdef 클래스를 탐색함 - 사람들은 여전히 JUnit이 Ant와 잘 작동하도록 만드는 데 어려움을 느낀다.
  • 사용자 정의 태스크들을 배포하는 표준화된 방법이 없다.
  • 임의의 작업들이 Ant 핵심 모듈에 강하게 결합되어 있다.
Antlib는 이러한 문제들을 줄이는 데 도움을 준다:
  • 기본적으로 antlib들은 $ANT_HOME/lib에 위치하며 XML 네임스페이스를 사용하여 지정되어 있다.
  • Antlib 포맷은 배포에 대한 잘 표준화된 방법을 제공한다(XML 서술자(XMLdescriptor)와 함께 태스크, 타입, 매크로를 포함하는 하나의 .jar 파일).
  • 임의의 태스크들은 Ant 핵심 코드로부터 분리될 수 있으며 서로 다른 스케줄에 따라 배포될 수 있다.
Antlib 사용하기

만약 이미 antlib들을 가지고 있다면 그것들을 몇 가지 다른 방법으로 빌드 파일에 사용할 수 있다. 만약 antlib가 클래스와 example-antlib.xml 서술자 디렉토리에 함께 위치해 있으면 을 사용하면 된다. 이것은 보통의 외부 Ant 태스크와 거의 흡사하다.

또 다른 방법은 아래 코드를 사용하는 것이다:

이것은 antlib가 클래스와 antlib 서술자의 .jar 파일일 때 적합한 방법이다. 따라서 이 방법은 다음의 xmlns(XML 네임스페이스) 선언과 함께 사용될 수 있다:

...

이것은 example-antlib.xml 파일 안에 정의되어 있는 태스크를 호출하는 태스크가 있음을 가정하고 있다.

마지막으로 antlib을 이용하는 가장 편리한 방법이 있다. 만약 다음의 조건들을 만족한다면 Ant는 자동적으로 antlib안에 선언되어 있는 모든 태스크와 타입의 정의 내용들을 불러들일 것이다.
  • antlib .jar 파일이 $ANT_HOME/lib 에 위치해 있음
  • antlib .jar 파일이 antlib.xml 라는 이름의 파일을 포함하고 있음
  • build.xml이 아래와 같이 정의되어 있음:

  
    
  

Antlib 개관

우리만의 antlib 작성을 시작하기 전에 먼저 우리들 자신에게 질문을 하나 해보자: antlib이 정확히 무엇이며, 그리고 그것의 구성요소는 무엇인가? 우선 antlib는 단순히 XML 서술자(XMLDescriptor)로 묶여진 클래스들의 묶음이다. 일반적으로 antlib는 .jar 파일 형태로 배포되나 엄격한 요구조건은 아니다. XML 파일의 루트 엘리먼트는 이다. 어떠한 클래스도 antlib의 구성요소가 될 수 있으나 단 몇 개의 클래스들만이 antlib.xml 파일내에 선언될 수 있다. 다음은 antlib.xml 안에 선언될 수 있는 것들이다.
  • org.apache.tools.ant.taskdefs.AntLibDefinition를 상속하는 임의의 하위 클래스
그러면 이제 무엇이 antlib로 들어가는지 알았으므로, 손수 하나 만들어 보기로 하자.

새로운 Antlib 작성하기

보다 더 나은 인터페이스를 제공하는 명령행 실행파일을 래핑하는 간단한 목표를 갖고 시작해 보도록 하자. 이를 위해 나는 많은 오픈소스 버전관리 시스템중의 하나인 Arch를 사용하기로 결정하였다. 먼저 Arch를 설치해 보도록 하자(아직 설치되어 있지 않다면): 시스템에 맞는 패키지를 찾아 설치 안내 문서에 나와있는 대로 따라하기만 하면 되는데, 최종적으로 "tla help"라고 입력했을 때 그림 1과 비슷한 화면을 보게 된다면 시스템에 제대로 설치된 것이다.

그림1
그림 1. GNU Arch가 설치된 화면

명령행 도구를 래핑하는 Ant 태스크를 위한 알맞은 시작점은 대상 환경의 메인 설치를 처리하는 기반 클래스를 빌드한 다음 제공하기를 원하는 각각의 명령에 대한 하위 클래스를 생성하는 것이다. 예를 들어 Ant SVN 라이브러리는 AbstractSvnTask를 가지고 있는데, svn diff 명령을 수행하는 데 있어 전문화된 SvnRevisionDiff와 SvnTagDiff와 같은(AbstractSvnTask를 상속하는) 하위클래스를 통해 임의의 Svn 태스크 처리가 가능하다.

GNU Arch가 Subversion과 비슷한 툴이므로(여기서는 전체 배포 대 클라이언트/서버에 대한 논쟁은 생략하기로 하자) 기반 클래스를 작성하고 나서 전문화된 하위 클래스를 작성하는 식의 전략이 잘 맞을 것이다. 단 유일한 문제는 Ant Svn이 (아직) ant를 탑재하고 있지 않다는 점이다.

하지만 Ant가 이미 Svn 태스크와 동일한 방식으로 작동하는(사실 Svn 태스크는 Cvs 태스크를 모델로 하여 만들어진 것이다) CVS를 위한 태스크들을 포함하고 있기 때문에 일일이 필요한 모든 것들을 사용자가 직접 만들 필요는 없다. Ant의 CVS용 태스크는 명령행 툴을 래핑하기도 하며 버전 관리를 거의 완벽하게 처리한다.

일전에 선택했던 "기반 클래스 + 전문화된 하위 클래스" 전략에 기반하여 코드로 구현해볼 예제를 준비한 다음 antlib 작성을 시작해 보도록 하자.

우리가 작성하고 있는 것이 antlib이기 때문에 코드를 분리된 Java 패키지/네임스페이스에 작성해 넣어야 한다. 사실 완전히 새로운 프로젝트가 이것을 해보는데 오히려 적합해 보인다. 필자는 Eclipse를 사용하고 있지만 어떠한 IDE(혹은 IDE를 사용하지 않더라도)를 선택하는 것과 antlib가 개발되는 방법간에는 아무런 관련이 없다. 패키지명으로 필자는 org.apache.ant.tla를 선택했고 AbstractCvsTask의 예제에 따라 기반 클래스명을 AbstractTlaTask로 명명하였다.

아래에 코드의 가장 중요한 부분인 runCommand() 메소드의 내용이 나타나 있는데, AbstractTlaTask 클래스의 다른 모든 코드들은 실질적인 작업을 처리하는 이 메소드를 호출하기 전에 환경을 설정하는데 필요한 것들이다. 모든 antlib가 runCommand() 메소드를 가져야 하는 것은 아니나 이번 예제에서 명령행 도구와 인터페이스해야 하는데 있어 AbstractCvsTask와 잘 작동하는 상호작용의 수준(level of interaction)을 달성하는 한 방법이기 때문에 runCommand() 메소드를 작성하였다. 아래 코드에서 볼 수 있듯이 시스템 명령어를 실행하려면 여러가지의 잠재적으로 일어날 수 있는 예외상황을 처리해야 한다. 이 태스크에 대한 전체 소스코드를 보려면 샘플 코드 링크를 확인하라.
protected void runCommand(Commandline toExecute) 
        throws BuildException {
    Environment env = new Environment();
    Execute exe = new Execute(
        getExecuteStreamHandler(), null
    );
    exe.setAntRun(getProject());
    exe.setCommandline(
        toExecute.getCommandline()
    );
    exe.setEnvironment(env.getVariables());
    try {
        String actualCommandLine = 
            executeToString(exe);
        log(
            actualCommandLine, 
            Project.MSG_VERBOSE
        );
        int retCode = exe.execute();
        log(
            "retCode=" + retCode, 
            Project.MSG_DEBUG
        );
        if (failOnError && 
            Execute.isFailure(retCode)) {
            throw new BuildException(
            "tla exited with error code "
                + retCode
                + StringUtils.LINE_SEP
                + "Command line was ["
                + actualCommandLine + "]", 
                getLocation()
            );
        }
    } catch (IOException e) {
        if (failOnError) {
            throw new BuildException(
                e, getLocation()
            );
        } else {
            log(
                "Caught exception: " + 
                e.getMessage(), 
                Project.MSG_WARN
            );
        }
    } catch (BuildException e) {
        if (failOnError) {
            throw (e);
        } else {
            Throwable t = e.getException();
            if (t == null) {
                  t = e;
            }
            log(
                "Caught exception: " + 
                t.getMessage(), 
                Project.MSG_WARN
            );
        }
    } catch (Exception e) {
        if (failOnError) {
            throw new BuildException(
                e, getLocation()
            );
        } else {
            log(
                "Caught exception: " + 
                    e.getMessage(), 
                Project.MSG_WARN
            );
        }
    }
}
이제 시스템에 명령어 전달을 처리할 수 있는 코드를 작성하였고 어느 정도는 쓸만한 Arch용 래퍼가 가지는 구현해야 하는 명령어 중 하나를 살펴보기로 하자. 가장 먼저 antlib가 할 수 있어야 하는 일은 아카이브(archive)를 등록(register-archive)하는 것이다. 그림 2는 우리가 시스템에 전달할 필요가 있는 명령어의 형식을 보여준다. archive는 URL의 형태를 띠고 있는데, 아카이브 이름을 전달하는 것은 우리가 작성하고 있는 태스크가 지원해야 할 선택적인(optional) 파라미터임을 알 수 있다.

그림2
그림 2. 아카이브 등록(register-archive) 명령어의 형식

우리가 이미 AbstractTlaTask 클래스에 대부분의 코드를 작성하였으므로 RegisterArchive는 상당히 간단하다. 아래 코드에서 볼 수 있듯이, execute 메소드를 오버라이드 하였으며 필요한 파라미터들을 받아들인다. 명령어를 실행하기 전에 validate 메소드가 실제로 필요한 파라미터들이 있는지 확인하기 위해 호출된다.
package org.apache.ant.tla;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Commandline;

public class RegisterArchive 
            extends AbstractTlaTask {
    
    private String archive;
    
    private void validate() 
            throws BuildException {
        if (null == getRepoURL() || 
            getRepoURL().length() == 0) {
            throw new BuildException(
            "You must specify a url for the repo."
            );
        }
        if (null != archive && 
            archive.indexOf("@") == -1) {
            throw new BuildException(
            "If you specify an archive name" +
            ",you must specify it correctly" +
            "see the GNU arch documentation"
            );
        }
    }
    public void execute() 
            throws BuildException {
        validate();
        Commandline c = new Commandline();
        c.setExecutable("tla");
        c.createArgument(true).setValue(
            "register-archive"
        );
        if (null != archive && 
            archive.length() > 0) {
            c.createArgument().setValue(archive);
        }
        c.createArgument().setValue(
            this.getRepoURL()
        );
        this.addConfiguredCommandline(c);
        super.execute();
    }

    public String getArchive() {
        return archive;
    }

    public void setArchive(String archive) {
        this.archive = archive;
    }
    
}
Antlib 컴파일하기

이제 기본적인 antlib을 만들 준비를 마쳤으므로 antlib을 실행시키기 위한 "특별한 양념"를 가미할 필요가 있으며, 그런 다음에야 비로소 우리가 만든 녀석에 대한 테스트에 착수할 수 있다. 모든 antlib에는 XML 문서와 우리가 작성한 코드(클래스 파일이 위치한 패키지내에 있는 것들)를 포함시켜야 한다.


  

지금은 하나의 태스크만이 antlib에 정의되어 있으므로 antlib.xml 에도 한 개의 엔트리만이 필요하다. AbstractTlaTask 클래스는 포함하지 않았는데, 왜냐하면 그것은 단순히 지원 클래스이며 사용자가 그것에 직접적으로 그 클래스에 접근할 수 있기를 원치 않기 때문이다. 최종적으로 antlib를 jar 파일로 만들어 $ANT_HOEME/lib 디렉토리나 클래스패스에 위치시킬 필요가 있다.

주의사항: antlib 기능이 Ant 1.7+ 버전의 확장형태로 사용되고 있으나 antlib을 불러오는 것은 현재 릴리즈되고 있는 버전인 Ant 1.6.5 에서만 가능하다.

지금까지 설명한 내용을 정확하게 수행하였는가? 이제 새로운 antlib .jar 파일을 클래스패스에다 위치시켰고 다른 와 같이 정의된 태스크를 사용할 수 있다. 하지만 그것이 실제로 작동하는가?

태스크와 Antlib 테스트하기

보통 Java 개발자들은 JUnit을 단위 테스트를 생성하기 위해 사용한다. 하지만 Ant에서 제공되는 기능들을 이용한다면 Ant 태스크와 antlib에 대한 테스트가 좀 더 쉬워진다.

TestCase를 상속하는 대신 Ant는 드라이버로써 작동하는 빌드 파일을 사용하여 Ant 태스크를 테스트할 수 있도록 해주는 래퍼 BuildFileTest를 제공하고 있다. 이것은 실생활에서 작동할 방식과 비슷한 방법으로 수행되기 때문에 코드에 대한 더할 나위 없는 테스트이다. 하지만 어떤 사람들은 이러한 단위 테스트들을 호출하는 것이 단위 테스트가 아닌 통합 테스트 혹은 시스템 테스트와 좀더 유사하기 때문에 이러한 방식을 반대할지도 모른다.

RegisterArchive 태스크에 대해 그것을 훑어봄으로써 테스트할 수 있는 즉석 빌드 파일을 만들어 볼 것이다(너무 걱정하지는 마시라. 샘플 코드에는 진짜 BuildFileTest가 포함되어 있다). 즉석 테스트와 BuildFileTest에 대해 동일한 빌드 파일을 사용할 수 있다. 테스트를 해보기 위해 Arch 문서에서 언급되어 있는 Arch 저장소를 선택하였다.


        
                
                
        

위에서 작성한 새로운 antlib와 위에 나타나 있는 빌드 파일에 대한 Ant 실행결과는 다음과 같다:
Spikefish:~/projects/ant-tla/trunk kj$ ant -f src/etc/testcases/registerarchive.xml 
Buildfile: src/etc/testcases/registerarchive.xml

register:
[tla:registerarchive] Registering archive: atai@atai.org--public

BUILD SUCCESSFUL
Total time: 3 seconds
AntUnit으로 테스트하기

Ant가 제공하는 또 다른 새로운 기능은 AntUnit antlib이다. JUnit TestCase나 BuildFileTest와는 달리 AntUnit은 사용자로 하여금 어떠한 자바 코드를 사용하지 않고도 테스트를 지정할 수 있도록 해준다. AntUnit 자체는 antlib으로서 제공되므로, 따라서 $ANT_HOME/lib 디렉토리에 위치하거나 classpath에 지정되어 있어야 한다. 되풀이되는 편리한 방법으로 AntUnit은 Ant와 AntUnit 둘 모두에 대한 테스트를 수행하는데 사용된다.

이미 임시 테스트에 사용된 빌드 파일을 만들어 놓았으므로 이번에는 그 빌드 파일이 AntUnit을 이용하여 더욱 많은 반복적인 테스트가 가능하도록 수정해 보도록 하자. 먼저 AntUnit 네임스페이스를 선언해야 하므로 빌드 파일의 헤더부분은 아래와 같아진다:

AntUnit은 JUnit과 비슷한 방식으로 작동하며 테스트를 시작할 대상을 먼저 탐색하므로 대상 등록(register target)을 테스트 등록(test-register)로 변경해야 한다. JUnit과 마찬가지로 AntUnit 역시 테스트가 통과할지 안할지에 대한 어떤 검증(assertion) 방법을 필요로 하는데. 이번 경우에는 실행 결과가 기대한 것과 일치하는지 여부를 체크하는 assertLogContains 매크로를 사용할 것이다. 새로운 테스트 등록(test-register)에 대한 코드는 아래와 같다:

    
    

마지막으로 추가할 것은 테스트 대상이 실행(go)되도록 하는 것이다. AntUnit을 아래 코드와 같이 대상에 맞춰 설정할 필요가 있다:

    
        

        
    

아래에서 볼 수 있듯이 테스트를 통과하고 있다. 또 다른 테스트를 추가하는 것은 단순히 test로 시작하는 이름을 가지는 새로운 대상을 추가하는 것 뿐이다. 컴파일할 필요도 없고, 자바 코드를 작성할 필요도 없다 : Ant 태스크를 테스트하는 훨씬 간단한 방법이다.
Spikefish:~/projects/ant-tla/trunk kj$ ant -f src/etc/testcases/au-registerarchive.xml 
Buildfile: src/etc/testcases/au-registerarchive.xml

go:
[au:antunit] Build File: /Users/kj/projects/ant-tla/trunk/src/etc/testcases/au-registerarchive.xml
[au:antunit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3.378 sec
[au:antunit] Target: test-register took 3.329 sec

BUILD SUCCESSFUL
Total time: 7 seconds
임의의 혹은 사용자 정의 태스크 리팩토링 하기

마지막으로 표준화된 사용자 정의 태스크를 antlib로 바꾸는데 얼마만큼의 작업이 필요한지 알아보기로 하자.

먼저 원본 VSS 태스크에 대한 코드를 갖고 시작해 보자. antlib로 바꾸는 데 필요한 최소한의 코드의 양은 얼마일까? 실질적으로 필요한 것은 antlib.xml 파일 하나를 추가하는 것 뿐이다:

  
  
  
... (code elided)

물론 또한 임의의 태스크를 주 Ant 소스로부터 분리하여 전체 Ant 배포본을 릴리즈하는 것이 아니라 독립적으로 임의의 태스크(이제 antlib)로 옮길 수 있기를 원한다. 하지만 실제로 이것을 하는데 필요로 하는 모든 작업은 단지 패키지 네임스페이스와 antlib.xml 파일을 변경하는 것 뿐이며 결과적으로 임의의 태스크는 분리된 antlib가 된다.

요약

지금까지 Ant 1.7의 주요 새로운 기능 두 가지인 antlib와 AntUnit에 대해 알아보았다.

antlib의 기능은 Ant 태스크 개발자로 하여금 프로그램 수정 및 업데이트 사항을 주 Ant 배포본으로부터 독립적으로 분리시켜 주며, AntUnit antlib는 Ant 태스크에 대한 단위 테스트를 작성하는 데 있어 훨씬 더 간편한 방법이다.

이 기사에서는 SCM 시스템 Arch에 대해 antlib 메커니즘을 이용하여 새로운 태스크를 개발하는 것이 얼마나 손쉬운 일인지를 보여주는 antlib 선수 작업을 진행해 보았다. 또한 AntUnit 테스트를 통해 작성한 antlib을 테스트해 보았다.

참고자료 Kev Jackson은 소프트웨어 개발자이자 아파치 Ant 프로젝트의 커미터이다.
TAG :
댓글 입력
자료실

최근 본 책0