Shinnara's Blog
Talking with Shinnara :: NaraTalk.com

Regular Expression을 실제 작업에 이용하기에 이은 두번째 글입니다. 이번 글에서는 지난 글에서 소개하지 못했던 자바 소스에 대해서 간략히 설명을 할까 합니다. 그리고는 고수님들의 조언을 받아서 리팩토링도 하고, 또 다른 언어로 바꾸어볼까도 생각합니다. 지난 글에서 밝혔듯이 처음에 작성하고 섣불리 팀내에 배포했다가 몇가지 패턴을 인식못하고  또 잘못 바꾸어주는 덕에 테스트의 중요성을 실감한 사건이기도 했습니다. 정말이지 테스트는 무지 중요합니다. ^^


먼저 소스 코드입니다. 너무 짧은 코드라서 설명할 거리가 별로 없기도 합니다.

전체 소스 코드 보기


 배포했다가 버그를 발견하면서 Regular Expression에 대해 좀더 많은 고려를 해야 한다는 것을 느끼게 되었습니다. 다른 글을 통해 Greedy와 Ungreedy에 대한 짧은 글도 썼는데, 정말이지 Regular Expression의 특성은 꼭 알고 가셔야 합니다. 그래야 저처럼 실수 하지 않겠지요.. ^^

 제가 작성한 프로그램과 똑같은 일을 수행하는 코드를 Regular Expression을 이용하지 않고 단순히 indexOf 만으로도 충분히 구현이 가능하고 오히려 버그의 개입 여지가 더 없을 수도 있으리라고 생각합니다. 하지만 Regular Expression을 실제 업무에 사용하는 것이 이 글의 목적이기때문에 이에 대한 이해해 주시기 바랍니다.

    public static void showUsage()
    {
        System.out.println("Event Log Remover (build 003)");
        System.out.println("Usage:");
        System.out.println(" options file file ...\n");
        System.out.println("[OPTIONS]");
        System.out.println(" -v    verbose mode");
        System.out.println(" -a    add preprocessing statements");
        System.out.println(" -d    delete event log message");
        System.out.println(" -r    restore original event log");
        System.out.println(" -i    apply indentation");
        System.out.println("");
        System.out.println("2008.12.15\nHyun-Kyu Shin\n");       
    }

위의 코드는 프로그램의 사용법을 나타내주는 부분입니다. 이것을 먼저 보여드리는 이유는 이 프로그램이 무엇을 하는지를 먼저 보여드리기 위해서입니다.  앞의 글에서 설명했듯이 C 프로그램내에 존재하는 event_log에 대해서 원하는 작업을 수행하는 것이 목적입니다. LogRemover는 모두 세개의 모드를 지원합니다. #ifdef~#else~#endif 를 새롭게 적용하는 -a 모드, 모든 이벤트 메시지를 제거하는 -d 모드, #ifdef~#else~#endif 구문을 다시 제거하는 -r 모드. 그리고 -v 는 처리 과정을 표시하고,-i는 들여쓰기를 지원합니다. 처리 대상이 되는 파일은 동시에 여러개를 입력할 수 있고, 디렉토리인 경우는 하위 디렉토리에 대해서 모두 처리를 수행하게 됩니다.

프로그램의 다른 부분은 별도의 설명이 필요없을 정도로 단순하므로 처리의 핵심인 void doJob(File srcFile) 부분에 대해서만 살펴보도록 하겠습니다.

    public void doJob(File srcFile)
    {   
        totalFiles ++;
       
        if( isVerbose ) System.out.print("["+totalFiles + "]" + srcFile.getName());
       
        int replaced = 0;
       
        String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
        String replaceRegExp2 = "$1\\);";
       
        try{
            File tempFile = new File("temp_"+srcFile.getName());
           
            BufferedReader br = new BufferedReader(new FileReader(srcFile));
            BufferedWriter bw = new BufferedWriter(new FileWriter(tempFile));
           
            String line;
           
            boolean isIfDefStarted = false;
           
           
            while( (line = br.readLine()) != null)
            {           
                if( line.matches("^.*#ifdef EVENT_DEBUG.*"))
                {
                    isIfDefStarted = true;
                }
               
                boolean isLongEventStatement = line.matches("^\\s*event_log.*,.*;.*");
                String indentationStr ="";
               
                if( isLongEventStatement )
                {
                    indentationStr = line.substring(0, line.indexOf("event_log") );
                }
               
                if( isIfDefStarted )
                {
                    /*
                     * 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
                     * AddPreStatement 모드인 경우는 그대로 출력하고
                     * 만약 AddPreStatement 모드가 아닌 경우, 즉
                     * #ifdef 없이  단순히 EventLog를 삭제하는 경우는
                     * #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
                     */
                    if( isAddPreStatement )   
                    {                       
                        bw.write(line + NEWLINE);
                    } else {                       
                        if( isLongEventStatement )
                        {
                            if(isRecoveryMode)
                                bw.write( line + NEWLINE);
                            else
                                bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                           
                            replaced ++;
                        }
                    }                   
                } else {  /* IfDef 블럭이 아닌 경우 */
                   
                    if( isLongEventStatement )
                    {
                        if( isAddPreStatement )
                        {
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
                            bw.    write(line + NEWLINE);
                           
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#else" + NEWLINE);
                            bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                           
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#endif" + NEWLINE);
                            replaced++;
                        } else {
                            if( isRecoveryMode )
                            {
                                bw.write(line + NEWLINE);
                            }else{
                                bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                                replaced++;
                            }
                        }
                    } else {
                        bw.write(line + NEWLINE);
                    }                   
                }
                               
                if( line.matches("^.*#endif.*"))
                {
                    isIfDefStarted = false;
                }
               
            }
           
            bw.close();
            br.close();
           
            if( replaced > 0 )
            {
           
                String destFileName = srcFile.getAbsolutePath();
                srcFile.delete();
               
                tempFile.renameTo(new File(destFileName));
           
            } else {
                tempFile.delete();               
            }
           
            if( isVerbose ) System.out.println( "  ..." + replaced + " replaced. Success");
           
           
           
        }catch(Exception e)
        {
            e.printStackTrace();
            failureFiles++;
            if( isVerbose ) System.out.println( "FAILED!!");
        }
       
        processedEvents += replaced;
    }

위의 코드에서 가장 문제가 많았던 부분이 바로

        String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
        String replaceRegExp2 = "$1\\);";


이 부분입니다. 이벤트 메시지 부분을 제거하고 이벤트 아이디만을 남기로독 하는 부분인데, 이게 메시지 부분에 여러가지 변형사항이 있다보니 이상한 결과들을 내놓는 경우가 있었습니다.  위의 코드에서 replaceRegExp 는 검색을 하는 데 사용되고, replaceRegExp2는 그 결과를 이용해서 치환할 때 사용되는 식입니다. $1 는 replaceRegExp를 통해 검색된 내용 중 ,(comma)의 앞부분을 의미하게 됩니다.

만약 처리하는 파일에 다음과 같은 라인이 있다고 하면,

     event_log ( EVENT_01 , "Hello Event" );

이에 대한 처리 결과는

   event_log( EVENT_01 );

이 되도록 하는 것입니다.

그런데, Regular Expression을 잘못 쓰게 되면

   event_log( EVENT_02 , "Hello, Sir");



   event_log( EVENT_02, "Hello );

와 같은 현상이 나타나기도 했습니다.

몇 번의 수정을 통해 위와 같은 Regular Expression을 쓰게 되었고, 다행히 지금까지는 별다른 에러 케이스를 찾지 못했습니다. ^^

                if( isIfDefStarted )
                {
                    /*
                     * 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
                     * AddPreStatement 모드인 경우는 그대로 출력하고
                     * 만약 AddPreStatement 모드가 아닌 경우, 즉
                     * #ifdef 없이  단순히 EventLog를 삭제하는 경우는
                     * #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
                     */
                    if( isAddPreStatement )   
                    {                       
                        bw.write(line + NEWLINE);
                    } else {                       
                        if( isLongEventStatement )
                        {
                            if(isRecoveryMode)
                                bw.write( line + NEWLINE);
                            else
                                bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                           
                            replaced ++;
                        }
                    }                   
                }

이 부분은 #ifdef EVENT_DEBUG  블럭 안쪽일 경우의 처리를 나타냅니다. 만약 -a 모드인 경우는 있는 그대로 표현하면 됩니다. 어차피 하고자 하는 일이 #ifdef ~ #else ~ #endif 를 추가하는 것이기 때문에 이미 있는 부분은 그대로 유지하고자 하는 것입니다. 만약 -a 모드가 아니라면 기본적으로는 해당 부분을 삭제하게 됩니다.  다만 이벤트 메시지를 포함한 부분인 경우에 대해서만 추가적으로 처리하게 되는데, -r 모드인 경우는 이벤트 메시지를 포함한 전체 문장으로, -d 모드인 경우는 이벤트 메시지가 삭제된 형태로 출력하는 것입니다.


                } else {  /* IfDef 블럭이 아닌 경우 */
                   
                    if( isLongEventStatement )
                    {
                        if( isAddPreStatement )
                        {
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
                            bw.write(line + NEWLINE);
                           
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#else" + NEWLINE);
                            bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                           
                            if(isApplyIndentation) bw.write(indentationStr);
                            bw.write("#endif" + NEWLINE);
                            replaced++;
                        } else {
                            if( isRecoveryMode )
                            {
                                bw.write(line + NEWLINE);
                            }else{
                                bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
                                replaced++;
                            }
                        }
                    } else {
                        bw.write(line + NEWLINE);
                    }                   
                }


#ifdef EVENT_DEBUG 블럭이 아닌 경우에는 이벤트 메시지를 포함하는 문장이 나오는 경우에 대해서만 추가 처리를 해주면 됩니다. -a 모드라면 #ifdef~#else~#endif 를 적용하고, -r 모드라면 원본 문장 그대로, -d 모드의 경우에는 이벤트 메시지를 삭제한 형태가 출력됩니다.

설명하고 나니 무척이나 간단합니다. 이처럼 간단한 프로그램에서 왜 그리 실수가 많았는지 모르겠네요 ^^

소스 코드에 대한 조언은 언제든지 환영입니다. 오히려 시간 내어 봐주시는 분들께 제가 더 큰 감사를 드려야 겠지요. (근데, 여기 오셔서 보시는 분들이 몇분이나 될 지는 의문입니다 ^^)

좋은 하루 되세요.

0 Trackback, 1 Comment

TRACKBACK :: http://naratalk.com/trackback/271 관련글 쓰기

댓글을 달아 주세요

  1. Favicon of http://naratalk.com BlogIcon Shinnara  댓글주소  수정/삭제  댓글쓰기

    패턴 검색 부분을 아래와 같이 변경하였습니다.
    그동안 참 엉뚱한 짓을 했군요. ^^

    String replaceRegExp = "(event_log[^,]+),.*";

    2008/12/15 11:12


 요즘 간혹 올리는 글들이 대체로는 Regular Expression(정규 표현식)에 대한 것들이네요. C 코드 분석이나 Perl 이야기나 모두.. 몇번에 걸쳐 올리던 C 코드 분석은 현재로서는 잠정 중단입니다. 대신에 회사 업무 도중 Regexp 를 사용할 수 있는 좋은 기회가 생겼지요.

 아시다시피 제가 속한 부서는 임베디드 소프트웨어를 만들고 있습니다. C 를 이용해서 프로그램을 만들지요. 비슷한 기능들을 모아 CSC라 분류하고 각 개발자는 몇개의 CSC를 맡아서 개발 및 테스트를 진행합니다. 보통 CSC는 디렉토리별로 나뉘게 되고, 이 디렉토리 아래에 .c 파일이 위치하게 되는 형태이지요.

 프로그램을 작성하다보면 소프트웨어의 작동시 발생되는 여러 이벤트들을 외부에 알려야 하는 경우가 생깁니다. 이를 위해 event_log 라는 함수를 사용하게 되는데요, event_log 함수는 다음과 같이 사용됩니다.

event_log( EVENT_ID , "detail desc");

 개발 과정에서는 뒤의 detail description을 이용해서 보다 알기 쉽게 이벤트의 내용을 표시해주게 됩니다. 하지만 정식 릴리즈가 가까워지면 수행 시간이나 이미지 크기 등의 문제로 인해, 뒤의 "detail desc"를 삭제해 주어야 합니다. 즉 아래와 같은 형태로 바꾸어야 하지요.

event_log( EVENT_ID );

 여기서 문제가 발생합니다. event_log 함수가 전체 프로그램 내에서 무척이나 많이 사용되고 있기 때문에 "detail desc"을 삭제하는 일이 만만치 않은 작업이 되는 것이죠. 또한 정식 릴리즈 버전이 아니라 테스트를 위해서는 자세한 이벤트의 내용을 알기를 원하기 때문에 아래와 같은 형태로 변경하기로 하였습니다.

#ifdef EVENT_DEBUG
event_log( EVENT_ID, "detail desc");
#else
event_log( EVENT_ID);
#endif

즉, 컴파일 시점에서 테스트용 이미지와 정식 빌드용 이미지를 EVENT_DEBUG를 이용해서 분리해내고자 하는 것입니다. 이러한 기법은 C/C++에서는 너무도 많이 쓰이는 방법입니다.

 소스 코드 전체에 대해서 단순히 뒤의 자세한 설명만 없애는 것을 예상했을 때는 UltraEdit, AcroEdit 등 개발용 텍스트 에디터등을 이용해 정규 표현식으로 치환하려고 했습니다. 하지만 새롭게 제안된 내용은 단순한 치환으로는 안되더군요. 해당 에디터 들에서 줄바꿈을 만들어 내기가 어려웠습니다. 물론 매크로등을 이용하면 되긴 했지만, 뭔가 찜찜한 기분에 아예 변환 도구를 만들자고 생각했습니다. 

 어떤 언어로 만들까 잠시 고민을 해보았습니다. 최근에 관심을 가지고 있는 Perl을 이용해서 간단히 작업을 해보았는데 꽤나 쉽게 처리할 수 있었습니다. 이 글을 작성하는 곳이 회사가 아니라 집이다보니 해당 소스를 완벽하게 첨부를 하지는 못하지만, 대략 아래와 같은 형태입니다.

 foreach  $aLine (<fileHandle> )
 {   
   
   if( $aLine =~ /^\s*(event_log.*),.*;$/ ) {
       print "#ifdef EVENT_DEBUG\n";
       print "$aLine\n";
       print "#else\n";
       print "$1);\n";
       print "#endif\n";
    } else {
       print "$aLine";
    }
 }
 close( fileHandle );

 앞서 소개했던 Perl을 이용한 C 소스 코드 분석 에 나온 코드 형태와 유사합니다.  일단 주어진 하나의 파일에 대해 처리하는 것은 어렵지 않더군요. 하지만 코드를 작성하다보니 점점 하고 싶어지는 게 많아집니다. #ifdef 을 붙였다가 다시 떼고 싶을 땐 어떻게 하지? 이렇게 했다가 다시 이벤트 아이디만 표시하는 깔끔한 (?) 코드로 변경하고자 한다면? 디렉토리를 입력으로 주면 하위 디렉토리에 있는 모든 .c 파일에 대해서 같은 처리를 하면 편리하지 않을까? 등등등...

 그러다 보니 아직 서툰 Perl로 작성하기에는 어려움이 많더군요. 손에 익숙치 않다보니.. 그래서 일단 Java로 짜기로 했습니다. Perl이야 말로 정규 표현식을 이용한 문자열 처리의 최고봉이긴 하지만, Java도 정규 표현식을 지원하니 비슷한 형태가 가능합니다.

오늘 회사에서 관련된 툴을 작성하여 팀 내에 배포를 하였는데, 자잘한 버그들이 존재하는 바람에 두번이나 더 배포를 해야 했습니다. 아직 한번 더 배포할 것이 남아있기도 하구요. 일단 원하는 기능을 잘 동작하는 것 같습니다. 자세한 소스는 다음에 올리기로 하고, 앞으로는 오늘 작성한 소스에 대한 공개적인 리팩토링 및 이를 Perl 등의 다른 언어로 표현하는 것에 대해서 다루어볼까 합니다. Perl을 배우는 좋은 기회가 될 것 같기도 하구요.

모두 좋은 밤, 그리고 즐거운 주말 되세요.

p.s. 펄 관련 유용한 사이트 링크 하나..
http://gypark.pe.kr/wiki/Perl/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D

1 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/269 관련글 쓰기

  1. Subject: Regular Expression 을 실제 작업에 이용하기 (2)

    Tracked from Talking with Shinnara :: NaraTalk.com  삭제

    Regular Expression을 실제 작업에 이용하기에 이은 두번째 글입니다. 이번 글에서는 지난 글에서 소개하지 못했던 자바 소스에 대해서 간략히 설명을 할까 합니다. 그리고는 고수님들의 조언을 받아서 리팩토링도 하고, 또 다른 언어로 바꾸어볼까도 생각합니다. 지난 글에서 밝혔듯이 처음에 작성하고 섣불리 팀내에 배포했다가 몇가지 패턴을 인식못하고 또 잘못 바꾸어주는 덕에 테스트의 중요성을 실감한 사건이기도 했습니다. 정말이지 테스트는 무지 중요..

    2008/12/15 10:03

댓글을 달아 주세요


 Java 로 Desktop 에서 돌아가는 어플리케이션을 개발을 하다보면 자주 접하는 문제 중에 하나가 배포를 어떻게 할까입니다. 특히 Windows 에서 어떻게 하면 사용자가 편리하게 실행시킬 수 있을까를 고민하게 됩니다.
가장 기본적인 것으로는 배치 파일을 만들어서 그 파일 안에 "java MainForm" 과 같은 실행 스크립트를 적는 것이겠지요. 물론 매우 열악한 해결방법이긴 하지만요..
 
좀더 나아간다면 JNI 를 이용해서 Wrapper 를 제작하는 방법입니다. 즉 해당 플랫폼에 맞는 실행화일을 만들어서 이 실행화일이 Java 프로그램을 띄우게 되는 구조입니다. Eclipse 와 같은 프로그램들이 이와 같은 방법을 사용합니다. 이 경우, 별도의 설치 없이 파일을 특정 디렉토리에 설치하는 것만으로 실행할 수 있습니다. 하지만 프로그램이 실행되는데 필요한 조건들이 선결되어야 한다는 가정이 있어야 겠지요. 예를 들어 JRE 가 설치되어 있지 않다면 Wrapper 가 Java VM 을 부르지 못하니까요.

한단계 더 나아간다면 인스톨러를 활용하는 것입니다. 일반적인 어플리케이션의 설치과정처럼 인스톨러가 있어서 환경에 대한 체크도 해주고, 실행 화일도 만들어주는 형태입니다. 사용자에게 가장 친숙하게 다가설 수 있는 방법입니다.

 그외에도 JNLP(java network launching protocol) 를 이용한 Java Web Start 도 대안이 될 수 있을 것입니다. 이는 네트워크를 통하여 어플리케이션을 설치하고 실행시키는 방법입니다.

위의 내용과 관련된 자세한 자료는 다음을 참고하세요. 기본적인 설명과 다양한 툴을 소개하고 있습니다.

http://www.excelsior-usa.com/articles/java-to-exe.html

오늘 소개할 내용은 위의 문서에도 나오는 Launch4j 입니다.


사용자 삽입 이미지
Launch4j 는 Wrapper 를 통하여 exe 를 만드는 방법입니다. 전에는 JSmooth 라는 툴을 잘 써왔는데, 이상하게도 JSmooth 에서는 classpath 를 제대로 찾지 못하는 문제가 종종 발생해서 관련된 클래스를 모두 하나의 jar 에 묶어 embed 하여 exe 를 만들어야 했습니다. 제가 잘 못해서 그런지는 모르겠지만 여하튼 그것때문에 예전에 배포한 한 프로그램은 실행화일의 크기가 4MB 가 넘어갔습니다.  그리고 수정이 있을때마다 패키징을 다시해야하는 문제까지 생겼습니다. 그래서 다른 툴을 알아보게 되었는데, 오늘 테스트해본 툴이 바로 Launch4j 입니다.

Launch4j 의 홈페이지에 보면 2.x와 3.x 의 두가지 버전을 다운로드 할 수 있게 하고 있는데, 3.x 를 추천합니다. stable 버젼은 2.x 이지만 , 받아서 설치해보니 Classpath 와 관련된 내용을 설정할 수 없도록 되어 있더군요. 3.x 에서는 제대로 동작합니다.

제가 설치한 버젼은 3.0.0-pre2 입니다. 제일 궁금한 것이 classpath 에 있는 라이브러리를 제대로 참조하여 실행 시킬수 있는가 하는 것입니다. 그래야 Wrapper 를 한번 만들면 그 다음부터는 관련된 클래스화일만 변경해도 프로그램이 변경될 수 있으니까요.

그래서 간단한 프로그램을 작성해보았습니다. JFrame 안에 두개의 JTextField 를 위 아래에 놓고, 위에는 현재 시간을, 아래에는 이 내용을 Base64 로 인코딩한 정보를 보여주는 간단한 어플리케이션입니다. Base64 를 쓰는 이유는 별도의 라이브러리인  commons-codec을 제대로 참조하는지 테스트하기 위함입니다.

소스 파일은 두개로 나누어보았습니다.

[Main.java]

package com.naratalk.test;
import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;
import java.util.*;


public class Main extends JFrame{

    JTextField tfDate,tfEncoded;
    UserLib ul;
    public Main()
    {
        super("Main Application");
        ul = new UserLib();
        initUI();
        addUpdateTask();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
   
    private void initUI()
    {
        this.setLayout(new BorderLayout());
        tfDate = new JTextField();       
        tfDate.setBorder(new TitledBorder("Current Date & Time"));       
       
        this.add(tfDate, BorderLayout.NORTH);       
       
        tfEncoded = new JTextField();       
        tfEncoded.setBorder(new TitledBorder("Encoded"));       
       
        this.add(tfEncoded, BorderLayout.SOUTH);
    }
   
    private void addUpdateTask()
    {
        java.util.Timer timer = new java.util.Timer();
        timer.schedule( new TimerTask() {
            public void run()
            {
                tfDate.setText(ul.getSimpleDate());
                tfEncoded.setText(ul.encodeBase64(tfDate.getText()));
                addUpdateTask();
            }
        }, 1000);
    }
   
   
    public static void main(String[] args)
    {
        Main mf = new Main();
        mf.setSize(500, 500);
        mf.setVisible(true);
    }
}


[UserLib.java]

package com.naratalk.test;

import java.text.*;
import java.util.*;
import org.apache.commons.codec.binary.*;
public class UserLib {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   
    public String getSimpleDate()
    {
        return sdf.format(new Date());
    }
   
    public String encodeBase64(String str)
    {
        Base64 base64 = new Base64();
        return new String(base64.encode(str.getBytes()));       
    }
}


그리고 Ant 의 build 파일은 전에 설명한 것처럼 dist 디렉토리에 jar 로 만들어놓습니다.

[build.xml]

<project name="InstallerTest" default="main" basedir=".">
    <target name="init">
        <property name="build" value="bin" />
        <property name="home" value="." />
        <property name="src" value="src" />
        <property name="dist" value="dist" />
        <property name="jarname" value="installertest.jar" />
        <property name="mainclass" value="com.naratalk.test.Main" />
    </target>

    <target name="compile" depends="init">
        <mkdir dir="${build}" />
        <javac srcdir="${src}"  destdir="${build}" >
            <classpath>
                    <fileset dir="lib">
                        <include name="**/*.jar" />
                    </fileset>
                </classpath>
       </javac>
    </target>

    <target name="makejar" depends="compile">
        <mkdir dir="${dist}" />
        <jar destfile="${dist}/${jarname}"
             basedir="${build}" >
             <manifest>
                <attribute name="Main-Class" value="${mainclass}" />
             </manifest>
        </jar>
    </target>

    <target name="main" depends="compile" />
   
</project>

그리고 dist 디렉토리에 이번에 쓰인 라이브러리인 commons-codec-1.3.jar 를 옮겨 놓습니다. 빌드를 수행하고나면 dist 디렉토리에 installertest.jar 와 commons-codec-1.3.jar 가 있게 됩니다.

이제 Launch4j 를 사용할 차례입니다.

뭐 특별한 내용은 없습니다. 워낙 심플한 것이라서요. 이번 글에서 테스트하고 싶은게 결국 classpath 를 제대로 인식하느냐의 문제이므로 이부분에 대해 적어보겠습니다.

사용자 삽입 이미지

Classpath 탭에서 Custom classpath 를 체크하고,  MainClass 에는 폴더버튼을 눌러 main class 가 있는 jar 를 선택하고, Edit Item 에 commons-codec-1.3.jar 라고 입력하고 Accept 버튼을 누르면 Classpath 에 추가가 됩니다.


사용자 삽입 이미지

그리고 JRE 탭에서는 Minimun JRE 버젼은 1.x.x 형태로 입력하시면 됩니다. 저의 경우는 Don't user private JREs 를 선택했습니다.

사용자 삽입 이미지
이렇게 하고 톱니모양의 버튼을 누르면 exe 파일이 생성되는데, 제 경우는 24KB 정도되는 크기입니다. 이 exe 파일은 Main-Class 를 불러주는 역할만을 하므로 Main-Class 의 이름이 바뀌지 않는다면 프로그램이 변경되더라도 exe 자체를 변경할 필요가 없습니다. 물론 classpath 에 추가할 내용이 있다면 다시 해야겠지만요. 오른쪽의 이미지는 이 프로그램을 실행한 화면입니다.



Launch4j 는 Ant 를 이용해서 작업을 진행할 수도 있습니다. 이와 관련해서는 다음번에 다시 한번 글을 올리도록하겠습니다.






0 Trackback, 1 Comment

TRACKBACK :: http://naratalk.com/trackback/159 관련글 쓰기

댓글을 달아 주세요

  1. 박찬식  댓글주소  수정/삭제  댓글쓰기

    정말 감사합니다.. JSmooth로 하다가 도저히 외부 jar 클래스패스 설정이 안되서 오늘 8시간 해매다 이 포스트 보고 해결했습니다.ㅜㅜ
    정말 눈물이 앞을 가리네요...
    버그였다니.. 그것도 모르고 구글링만 몇시간을 한건지..

    2010/09/28 17:33

1 
다...... (264)
Computer/Programming (106)
Links (14)
책 읽는 즐거움 (7)
끄적임 (66)
즐거운 과학 나라 (7)
일본 (5)
Study (4)