본문 바로가기

카테고리 없음

Tutorial: Hello World with Ant

Tutorial: Hello World with Ant

이 문서는 Ant를 이용하여 잡 프로그래밍을 수행하기 위한 step by step 튜토리얼을 제공한다. 여기에서는 Java나 Ant에 대해서 자세한 지식에 대해서는 언급하지 않는다. 이 튜토리얼의 목적은 Ant쉽게 새용하기 위한 입문으로 이용할수 있도록 하는데 있다.

Preparing the project

우리는 생성된 파일로부터 소스 파일을 분리 시키기를 원한다. 그래서 java source파일은 src폴더에 저장된다. 생성된 모든 파일은 build 폴더에 저장된다. 그리고 각 단계에 따라 몇몇 하위 디렉토리가 구분되어진다. : 컴파일된 파일을 위해서는 classes 폴더를 jar파일을 위해서는 jar폴더를 필요로 할 것이다.

우리는 오직 src디렉토리만 생성해야한다. (왜냐하면 윈도우에서 작업할것이고, 여기에는 윈도우 쉘을 윈도우로 변경해야 하야 한다.)

md src

다음은 단순하게 고정된 메시지만 표준출력인 STDOUT로 출력하는 단순한 클래스이다. 우리는 다음과 같이 작성할 것이다.
src\oata\HelloWorld.java
.

package oata;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

이제 컴파일 하고, 실행해 보자.

md build\classes
javac -sourcepath src -d build\classes src\oata\HelloWorld.java
java -cp build\classes oata.HelloWorld

결과는 이렇게 나온다. 

Hello World

jar파일을 생성하는 것은 별로 어렵지 않다. 그러나 정적인 jar파일은 더 많은 단계를 필요로 한다. manifest파일을 작성하여 시작클래스를 지정하고, 대상 디렉토리를 만들고 파일을 아카이빙 하는 작업을 한다.

echo Main-Class: oata.HelloWorld>myManifest
md build\jar
jar cfm build\jar\HelloWorld.jar myManifest -C build\classes .
java -jar build\jar\HelloWorld.jar

노트 : >-기호주위에 공백을 두면 안된다. echo Main-Class명령에서 이렇게 공백을 둔다면 그것은 아마도 위조된 것으로 간주한다.

Four steps to a running application

java-only단계가 끝이나면 우리는 빌드 프로세스에 대해서 고려해야한다. 우리는 우리의 코드를 컴파일 해야한다. 그렇지 않으면 우리는 프로그램을 실행할 수 없다. 우리는 우리의 애플리케이션을 패키징 해야한다. 현재 하나의 클래스만 보고 있다. 그러나 다운로드를 제공하기를 원한다면 아무도 수백개의 파일을 다운로드 하기는 어려울 것이다. (복잡한 스윙 GUI에 대해서 생각해 봤을때, jar파일을 만드는 것을 고려해 봐야한다.). 실행 가능한 jar파일은 상당히 좋을 것이다. 그리고 대상을 깨끗하게 관리할 수 있는 좋은 예이며, 모든 생성되는 스텁을 제거하기 쉬운 구조로 만들 수 있다. 많은 문제점을 해결 하기위해서 "clean build"를 이용하여 해결할 수있다.

기본적으로 Ant는 build.xml라는 빌드 파일을 이용하여 빌드 합니다.

<project>

    <target name="clean">
        <delete dir="build"/>
    </target>

    <target name="compile">
        <mkdir dir="build/classes"/>
        <javac srcdir="src" destdir="build/classes"/>
    </target>

    <target name="jar">
        <mkdir dir="build/jar"/>
        <jar destfile="build/jar/HelloWorld.jar" basedir="build/classes">
            <manifest>
                <attribute name="Main-Class" value="oata.HelloWorld"/>
            </manifest>
        </jar>
    </target>

    <target name="run">
        <java jar="build/jar/HelloWorld.jar" fork="true"/>
    </target>

</project>


이제 컴파일 수행할 수있고 패키지와 애플리케이션을 수행할 수 있다.

ant compile
ant jar
ant run

더 짧게 한다면 이렇게 붙여쓸 수 있다.

ant compile jar run

빌드파일을 보면 우리는 Ant와 java-only 커맨드와 동일함을 알 수 있다.

java-only Ant
md build\classes
javac
    -sourcepath src
    -d build\classes
    src\oata\HelloWorld.java
echo Main-Class: oata.HelloWorld>mf
md build\jar
jar cfm
    build\jar\HelloWorld.jar
    mf
    -C build\classes
    .



java -jar build\jar\HelloWorld.jar
  
<mkdir dir="build/classes"/>
<javac
    srcdir="src"
    destdir="build/classes"/>
<!-- automatically detected -->
<!-- obsolete; done via manifest tag -->
<mkdir dir="build/jar"/>
<jar
    destfile="build/jar/HelloWorld.jar"

    basedir="build/classes">
    <manifest>
        <attribute name="Main-Class" value="oata.HelloWorld"/>
    </manifest>
</jar>
<java jar="build/jar/HelloWorld.jar" fork="true"/>
  

Enhance the build file

이제 빌드 파일을 이용하여 좀더 발전된 방법으로 작업을 할 수있다. : 매번 동일한 디렉토리를 참조하거나, 매인클래스와 jar-name은 하드코딩 되어 있다. 그리고 또한 올바른 빌드 과정을 기억해야한다. 이러한 상황에서 발전된 방식으로 바뀔 수 있다.

첫번째 그리고 두번째 포인트는 properties를 기술하는 것이고, 새번째는 특정 프로퍼티 와 태그를 <project>태그에 기술하는 것이고, 4번째는 사용될 의존성에 대한 기술로 문제를 해결한다.

<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir"     value="src"/>

    <property name="build.dir"   value="build"/>
    <property name="classes.dir" value="${build.dir}/classes"/>
    <property name="jar.dir"     value="${build.dir}/jar"/>

    <property name="main-class"  value="oata.HelloWorld"/>

    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
            </manifest>
        </jar>
    </target>

    <target name="run" depends="jar">
        <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
    </target>

    <target name="clean-build" depends="clean,jar"/>

    <target name="main" depends="clean,run"/>

</project>

이제 쉽게, ant를 수행하면 된다.

Buildfile: build.xml

clean:

compile:
    [mkdir] Created dir: C:\...\build\classes
    [javac] Compiling 1 source file to C:\...\build\classes

jar:
    [mkdir] Created dir: C:\...\build\jar
      [jar] Building jar: C:\...\build\jar\HelloWorld.jar

run:
     [java] Hello World

main:

BUILD SUCCESSFUL

Using external libraries

아무튼 syso-statements를 사용하지 않도록 하는 것이다. log-Statement는 Logging-API를 커스터마이즈가능다. Log4J를 이용하는 이유는 다음과 같다.

  • JDK(1.4+)의 한 부분이 아니다. 우리는 외부 라이브러리를 이용하여야 한다.
  • JDK 1.2에 하에서 수행될 수 있다.
  • 설정이 매우 편리하다.
  • 아파치에서 만들어 졌다.

우리는 외부 라이브러리를 lib디렉토리에 저장할 것이다. Log4J는 downloaded [1]에서 Logging 홈페이지에서 다운 받을 수 있다. lib디렉토리를 생성하고, log4j-1.2.9.jar파일을 lib디렉토리에 넣으면 된다. 우리는 우리의 자바 소스를 변경하고 우리의 빌드 파일에서 컴파일과 실행을 할 수 있도록 추가 할 것이다.

Log4j와 작업을 수행하기 위한 문서를 보라. 여기에 우리는 MyApp-example를 Short Manual [2]에서 볼 수 있다. 첫번째로 java소스에 로깅 프레임워크를 이용할 수 있도록 변경하라.

package oata;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class HelloWorld {
    static Logger logger = Logger.getLogger(HelloWorld.class);

    public static void main(String[] args) {
        BasicConfigurator.configure();
        logger.info("Hello World");          // the old SysO-statement
    }
}

대부분의 변경은 "framework overhead"로 한번만 수행하면 된다. 파란색 라인으로 쓰여진 것은 우리가 사용한 "old System-out"구분이었던 부분이다.

아직 ant를 실행하지마라, 아마도 컴파일 에러가 날 것이다. Log4J는 가 클래스 패스에 존재하지 않으므로 우리는 여기서 약간의 작업을 해야한다. 그러나 classpath환경 변수는 바꾸지 않을 것이다. 이것은 오직 이 프로젝트를 위한 것이다. 아마도 다른 환경에서는 깨져버릴 수 있다.(여기는 대개 중요한 실수로 나타나는 부분이다.) 우리는 Log4J에 대해서 우리의 빌드 파일에 추가할 것이다.

<project name="HelloWorld" basedir="." default="main">
    ...
    <property name="lib.dir"     value="lib"/>

    <path id="classpath">
        <fileset dir="${lib.dir}" includes="**/*.jar"/>
    </path>

    ...

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
    </target>

    <target name="run" depends="jar">
        <java fork="true" classname="${main-class}">
            <classpath>
                <path refid="classpath"/>
                <path location="${jar.dir}/${ant.project.name}.jar"/>
            </classpath>
        </java>
    </target>

    ...

</project>

이 예제에서 애플리케이션을 실행해 볼 수 있다. Main-Class의 manifest-attribute를 거치지 않고서도, 우리는 jarname과 classpath를 제공하지 않을 수 있기 때문이다. 그래서 붉은색 라인으로 이미 정의된 패스를 정의할 수 있다. ant를 실행해서 다음과 같이 켬파일 결과를 받을 수 있을 것이다.

[java] 0 [main] INFO oata.HelloWorld  - Hello World

설명해 보면 :

  • [java] Ant 태스크가 실행되었음을 의미
  • 0은 잘 모르겠다. 아마도 Log4J의 부분이다.
  • [main]은 애플리케이션에서 실행되는 쓰레드에 대한 정보이다.
  • INFO는 실행되는 구문의 로그 레벨이다.
  • oata.HelloWorld는 소스의 부분을 나타낸다.
  • - 공백
  • Hello World는 출력되는 메시지이다.
다른 레이아웃을 보면 Log4J의 문서에 대해 살펴볼 것이다. 어떠한 패턴 레이아웃을 이용했는지에 대한 정보가 나타난다.

Configuration files

우리는 왜 Log4J를 사용하는가? 그것은 설정이 매우 편리하기 때문인가? 아니다. 모두 하드코드 되어 있다. 이것은 Log4J의 몫이 아니고 우리의 몫이다. 우리는 BasicConfigurator.configure();로 단순히 포함시키면 된다. 그러나 설정은 하드코드 되어 있다. 더욱 편리한 점이라면 프로퍼티 파일을 이용하는 것이다. main() 자바 소스는 BasicConfiguration을 지운다.(그리고 관련된 구문만 임포트 한다.) Log4J는 설정된 내용을 찾는다. 그러므로 src/log4j.properties파일을 새로 생성하고, Log4J의 설정을 기본으로 작성하여, 우리는 이름으로 그것을 사용할 수 있기 때문에 삶을 편하게 만든다.

log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%m%n

이 설정은 출력 채널을 stdout라는 이름으로 ("Appender") 채널을 이용하여 출력한다. 이것은 출력 메시지인 (%m) 다음 라인으로 (%n)을 이용하여 System.out.println()과 동일한 작업을 수행한다. 좋다. 그러나 아직 끝나지 않았다. 우리는 설정 파일역시 전달해야한다. 우리는 다음과 같이 빌드 파일을 변경할 수 있다.

    ...
    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
        <copy todir="${classes.dir}">
            <fileset dir="${src.dir}" excludes="**/*.java"/>
        </copy>
    </target>
    ...

모든 리소스를 복사하고 디렉토리를 빌드한다. 우리는 애플리케이션을 실행하기 위해서 5개의 jar파일을 연결하여 사용할 수 있게 되었다.

Testing the class

이번 단계에서는 JUnit[3]인 테스트 프레임워크와 함께 Ant를 연동하여 사용하는 법에 대해서 소개할 것이다. 그러므로 Ant는 JUnit 3.8.2를 이용하여 바로 사용할 수 있게 한다. 테스트 클래스를 src/HelloWorldTest.java를 만든다.

public class HelloWorldTest extends junit.framework.TestCase {

    public void testNothing() {
    }
    
    public void testWillAlwaysFail() {
        fail("An error message");
    }
    
}

우리는 실제적으로 비즈니스로직을 테스트 하지 않기 때문에 테스트 클래스는 매우 작다. : 단지 어떻게 실행하고 앞으로 JUnit document에 대한 정보와 junit작업에 대한 매뉴얼을 볼 것이다. 이제 우리느 junit를 우리의 빌드 파일에 추가할 것이다.

    ...

    <target name="run" depends="jar">
        <java fork="true" classname="${main-class}">
            <classpath>
                <path refid="classpath"/>
                <path id="application" location="${jar.dir}/${ant.project.name}.jar"/>
            </classpath>
        </java>
    </target>
    
    <target name="junit" depends="jar">
        <junit printsummary="yes">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>
            
            <batchtest fork="yes">
                <fileset dir="${src.dir}" includes="*Test.java"/>
            </batchtest>
        </junit>
    </target>

    ...

우리가 소유한 jar파일 run-target에 ID를 주어 정의한 것을 재사용할 수 있다. printsummary=yes는 "FAILED"와 "PASSED"메시지 보다 더욱 상세한 정보를 볼 수 있도록 해준다. 얼마나 많은 작업이 실패했는가? 몇개의 에러가 났는가? Printsummary는 우리에게 정보를 준다. classpath는 우리의 클래스를 찾을 수 있도록 정보를 준다. run 테스트는 batchtest에서 이용하여 테스트 클래스를 더욱 쉽게 추가하고 *Test.java의 이름만 이용하여 더 많은 테스트를 할 수 있게 한다. 여기에 공통적인 이름의 스키마가 있다.

After a ant junit you'll get:

...
junit:
    [junit] Running HelloWorldTest
    [junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0,01 sec
    [junit] Test HelloWorldTest FAILED

BUILD SUCCESSFUL
...

우리는 또한 리포트도 새성할 수 있다. 가끔 쉘이 닫히고 난뒤에 내용을 보길 원할것이다. 이렇게 하기 위해서는 2가지 단계가 있으며 1번째는 <junit>로그 정보이고 2번재는 이러한 내용을 읽을 수 있도록 브라우징 하는 것이다.

    ...
    <property name="report.dir"  value="${build.dir}/junitreport"/>
    ...
    <target name="junit" depends="jar">
        <mkdir dir="${report.dir}"/>
        <junit printsummary="yes">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>
            
            <formatter type="xml"/>
            
            <batchtest fork="yes" todir="${report.dir}">
                <fileset dir="${src.dir}" includes="*Test.java"/>
            </batchtest>
        </junit>
    </target>
    
    <target name="junitreport">
        <junitreport todir="${report.dir}">
            <fileset dir="${report.dir}" includes="TEST-*.xml"/>
            <report todir="${report.dir}"/>
        </junitreport>
    </target>
   

우리는 많은 파일을 생성하고 이러한 파일은 기본적으로 현재 디렉토리에 쓰여진다. 우리는 리포트 디렉토리를 정의하고, junit 실행하기전에 생성하고 로깅을 리다이렉트 한다. 로그포맷은 XML이며 junitreport가 파싱된다. 두번째 타겟은 junitreport가 생성되고 HTML-report로 브라우저된다. 이제 ${report.dir}\index.html을 열고 결과를 확인해보자, JavaDoc형태로 출력될 것이다.
개인적으로 나는 2개의 다른 타겟을 지정한다. junit와 junitreport이다. HTML리포팅을 생성하는것은 시간이 필요하다. 그래서 HTML리포트는 테스트를 위해서 작성하는것이 좋다. 만약 에러를 수정하고 서버가 수행하는 작업을 통합했을 경우 따로 작업하는것이 좋다.

Resources

    [1] http://www.apache.org/dist/logging/log4j/1.2.13/logging-log4j-1.2.13.zip
    [2] http://logging.apache.org/log4j/docs/manual.html
    [3] http://www.junit.org/index.htm