부록

이재홍 http://www.pyrasis.com 2007.10.27 ~ 2008.04.20

목차

날짜를 버전으로 사용하기

프로젝트에 따라서 1.0.0.1과 같은 버전이 아닌 2008.10.3.1과 같이 날짜를 버전으로 사용해야 할 때도 있습니다(2008.10.3.1에서 마지막 1은 그날 빌드한 횟수). 이번에는 날짜를 버전으로 사용하는 방법에 대해 알아보겠습니다.

rcversion-date.ps1

# rcversion-date.ps1 <Project name> <Resource file Path>

if ($args.count -eq 0)
{
    "Usage: version.ps1 <Project name> <Resourc file Path>"
    exit
}

$project = $args[0]
$rc_file_path = $args[1]
$ccnet_dir = "C:\Program Files\CruiseControl.NET\server\"

# 오늘 날짜를 8.12.31(2008년 12월 31일) 형식으로 저장한다.
$date = (get-date -uformat %Y)+"."+(get-date -format %M.%d)

# 프로젝트 state 파일을 XML 형식으로 읽어온다.
[xml]$state_file = get-content -path $ccnet_dir$project.state

# state 파일의 Label과 오늘 날짜가 같으면 그날 빌드한 횟수($next_version) 증가
# 다르면 1로 설정
if ($state_file.IntegrationResult.Label -match $date)
{
    $full_date = $date+"."

    [int]$next_version = $state_file.IntegrationResult.Label -replace $full_date, ""
    if ($state_file.IntegrationResult.Status -match "Success")
    {
        $next_version++
    }
}
else
{
    $next_version = "1"
}

# 리소스 파일의 내용을 읽어온다.
$rc_file = get-content -path $rc_file_path
$rc_length = $rc_file.Length

# 년, 월, 일, 빌드 횟수를 조합하여 버전 문자열을 생성한다.
$year = get-date -uformat %Y
$month = get-date -format %M
$day = get-date -format %d
$version_string = $year+", "+$month+", "+$day+", "+$next_version

# 리소스 파일에서 버전 부분을 찾아 조합한 버전 문자열로 업데이트 한다.
for ($i = 0; $i -lt $rc_length; $i++)
{
    if ($rc_file[$i] -match " FILEVERSION ")
    {
        $rc_file[$i] = " FILEVERSION "+$version_string
        echo "File Version Updated"
    }

    if ($rc_file[$i] -match "            VALUE `"FileVersion`", ")
    {
        $rc_file[$i] = "            VALUE `"FileVersion`", `""+$version_string+"`""
        echo "File Version Updated"
    }

    if ($rc_file[$i] -match " PRODUCTVERSION ")
    {
        $rc_file[$i] = " PRODUCTVERSION "+$version_string
        echo "Product Version Updated"
    }

    if ($rc_file[$i] -match "            VALUE `"ProductVersion`", ")
    {
        $rc_file[$i] = "            VALUE `"ProductVersion`", `""+$version_string+"`""
        echo "Product Version Updated"
    }

    if ($rc_file[$i] -match "#define FILEVER ")
    {
        $rc_file[$i] = "#define FILEVER "+$version_string
        echo "File Version Updated"
    }

    if ($rc_file[$i] -match "#define STRFILEVER ")
    {
        $rc_file[$i] = "#define STRFILEVER `""+$version_string+"\0"+"`""
        echo "File Version Updated"
    }

    if ($rc_file[$i] -match "#define PRODUCTVER ")
    {
        $rc_file[$i] = "#define PRODUCTVER "+$version_string
        echo "Product Version Updated"
    }

    if ($rc_file[$i] -match "#define STRPRODUCTVER ")
    {
        $rc_file[$i] = "#define STRPRODUCTVER `""+$version_string+"\0"+"`""
        echo "Product Version Updated"
    }
}

set-content -path $rc_file_path $rc_file

Trac에 날짜 형식의 버전을 등록하려면 아래 스크립트를 사용해야 합니다.

tracversion-date.ps1

$trac_env = "C:\trac\" + $args[0]
$project = $args[1]
$ccnet_dir = "C:\Program Files\CruiseControl.NET\server\"

$date = (get-date -uformat %Y)+"."+(get-date -format %M.%d)
[xml]$state_file = get-content -path $ccnet_dir$project.state

if ($state_file.IntegrationResult.Label -match $date)
{
    $full_date = $date+"."

    [int]$next_version = $state_file.IntegrationResult.Label -replace $full_date, ""
    if ($state_file.IntegrationResult.Status -match "Success")
    {
        $next_version++
    }
}
else
{
    $next_version = "1"
}

$version_string = $date+"."+$next_version

C:\Python25\Scripts\trac-admin.exe $trac_env version add $version_string

이 두 파일을 C:\tools로 복사합니다.

이제 빌드 스크립트에 rcversion-date.ps1과 tracversion-date.ps1 설정을 추가합니다.

  • rcversion-date.ps1 “빌드 스크립트 상의 프로젝트 이름” “RC 파일 경로”
  • tracversion-date.ps1 “trac 프로젝트 이름” “빌드 스크립트 상의 프로젝트 이름”

위와 같은 형태로 사용합니다.

C:\Program Files\CruiseControl.NET\server\ccnet.config

    <project name="hello.exe">
        <!-- labeller는 날짜를 버전으로 사용할 수 있도록 dateLabeller로 지정 -->
        <labeller type="dateLabeller" />

        <!-- Subversion 저장소 주소 및 작업 디렉터리 설정 -->
        <sourcecontrol type="svn">
            <trunkUrl>http://192.168.1.100/svn/hello/trunk</trunkUrl>
            <workingDirectory>c:\build\hello</workingDirectory>
        </sourcecontrol>

        <!-- 매일 20:00에 자동 빌드 하도록 설정 -->
        <triggers>
            <scheduleTrigger time="20:00" buildCondition="ForceBuild" name="Scheduled" />
        </triggers>

        <tasks>
            <!-- 자동 버전 업데이트 스크립트, 날짜를 버전으로 사용 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\rcversion-date.ps1 "hello.exe" "C:\build\hello\hello\hello.rc"</buildArgs>
            </exec>

            <!-- Visual Studio 2005 빌드 설정 -->
            <devenv>
                <solutionfile>C:\build\hello\hello.sln</solutionfile>
                <configuration>Release</configuration>
            </devenv>

            <!-- 수정 된 소스 코드를 원래대로 되돌림 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\revert.ps1 "C:\build\hello"</buildArgs>
            </exec>

            <!-- trac에 버전을 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\tracversion-date.ps1 "hello" "hello.exe"</buildArgs>
            </exec>

            <!-- PDB 파일에 Subversion 저장소 정보를 인덱싱 하고, 압축 한 뒤 심볼 서버에 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\symbols.ps1 C:\build\hello C:\build\hello\release hello.exe</buildArgs>
                <buildTimeoutSeconds>10</buildTimeoutSeconds>
            </exec>

            <!-- 빌드된 결과물을 Releases 디렉터리 아래 각 프로젝트 디렉터리로 복사 -->
            <buildpublisher>
                <sourceDir>C:\build\hello\release</sourceDir>
                <publishDir>C:\Releases\hello</publishDir>
                <useLabelSubDirectory>true</useLabelSubDirectory>
            </buildpublisher>

            <!-- 빌드된 결과물을 Release 저장소로 커밋 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\release.ps1 C:\Releases\hello C:\build\hello</buildArgs>
            </exec>
        </tasks>
    </project>

다국어 리소스에서 자동 버전 업데이트 스크립트 사용

리소스 파일(.rc)이 한글로만 되어있으면 자동 버전 업데이트 스크립트(rcversion.ps1, rcversion-date.ps1)를 사용하는데 아무 문제가 없습니다. 하지만 리소스 파일에 한글, 일본어, 중국어, 영어 등이 섞여 있으면 한글, 영어를 제외한 다른 언어는 모두 깨져서 정상적으로 표시되지 않을 수 있습니다. 따라서 버전 부분을 따로 빼내서 처리하는 방법을 사용해야 합니다.

문자열이 깨지는 현상은 빌드하는 운영체제에 따라 달라질 수 있는데, 일본어 운영체제에서 빌드한다면 일본어는 정상적으로 나오겠지만 한글, 중국어 등이 깨져서 표시됩니다.

이유는 간단합니다. 리소스 파일이 PowerShell을 한번 거치고 나면 글자들의 인코딩이 해당 운영체제의 인코딩으로 바뀌어 버리기 때문에, 프로젝트를 컴파일 하여 실행해 보면 글자가 깨져서 나오게 되는 것입니다. 그래서 버전 부분을 따로 빼내면 리소스 파일의 인코딩이 바뀌는 것을 방지할 수 있습니다.

프로젝트에 다음과 같은 내용으로 version.h라는 파일을 만들고 추가합니다. STRFILEVER와 STRPRODUCTVER 부분은 꼭 “(따옴표)로 묶어주어야 합니다.

C:\build\hello\hello\version.h

#define FILEVER 0,0,0,0
#define STRFILEVER "0, 0, 0, 0"
#define PRODUCTVER 0,0,0,0
#define STRPRODUCTVER "0, 0, 0, 0"
TIP
맨 아래 빈 줄이 있어야합니다. 그렇지 않으면 컴파일할 때 에러가 발생합니다. (fatal error RC1004: unexpected end of file found)

프로젝트의 리소스 파일(C:\build\hello\hello\hello.rc)을 열고 다음과 같이 수정합니다.

  • 1. VS_VERSION_INFO VERSIONINFO 바로 위에 #include “version.h”를 추가합니다.

  • 2. 실제 버전 부분을 version.h에서 정의한 FILEVER, PRODUCTVER, STRFILEVER, STRPRODUCTVER로 수정합니다.

수정 전

FILEVERSION 1,0,0,1 
PRODUCTVERSION 1,0,0,1
VALUE "FileVersion", "1, 0, 0, 1"
VALUE "ProductVersion", "1, 0, 0, 1"

수정 후

FILEVERSION FILEVER
PRODUCTVERSION PRODUCTVER
VALUE "FileVersion", STRFILEVER
VALUE "ProductVersion", STRPRODUCTVER

다음은 수정이 끝난 리소스 파일의 내용입니다.

C:\build\hello\hello\hello.rc

#include "version.h"
VS_VERSION_INFO VERSIONINFO
 FILEVERSION FILEVER
 PRODUCTVERSION PRODUCTVER
 FILEFLAGSMASK 0x17L
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "041204b0"
        BEGIN
            VALUE "FileDescription", "hello Application"
            VALUE "FileVersion", STRFILEVER
            VALUE "InternalName", "hello"
            VALUE "LegalCopyright", "Copyright (C) 2008"
            VALUE "OriginalFilename", "hello.exe"
            VALUE "ProductName", "hello Application"
            VALUE "ProductVersion", STRPRODUCTVER
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x412, 1200
    END
END

그리고 CruiseControl.NET 스크립트 파일도 수정해주어야 합니다. 버전 업데이트 부분에서 rcversion.ps1 매개변수 부분에 리소스 파일(.rc)이 아닌 version.h를 지정합니다.

C:\Program Files\CruiseControl.NET\server\ccnet.config

            <!-- 자동 버전 업데이트 스크립트, 헤더 파일의 버전을 업데이트 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\rcversion.ps1 "hello.exe" "C:\build\hello\hello\version.h"</buildArgs>
            </exec>

CC.NET에서 Subversion Indexing을 사용할 때 Exception 문제 해결

CruiseControl.NET에서 symbols.ps1 스크립트를 사용하여 심볼 파일(PDB)에 Subversion Indexing을 하면 다음과 같이 Exception이 발생합니다.


그림 A-1 빌드 후 Exception 발생

이러한 Exception이 나오지 않게 하려면 C:\Program Files\Debugging Tools for Windows\sdk\srcsrv에서 svn.pm 파일을 열고 DisplayVariableInfo 부분을 찾은 ::status_message 앞에 #을 추가합니다.

C:\Program Files\Debugging Tools for Windows\sdk\srcsrv\svn.pm

# -----------------------------------------------------------------------------
# Display module internal option state.
# -----------------------------------------------------------------------------
sub DisplayVariableInfo {
    my $self = shift;

    #::status_message("%-15s: %s\n",
    #                 "SVN Executable",
    #                 $$self{'SVNCMD'});

    #::status_message("%-15s: %s\n",
    #                 "SVN Revision",
    #                 $$self{'SVNREV'} ? $$self{'SVNREV'} : "<N/A>");

    #::status_message("%-15s: %s\n",
    #                 "SVN Username",
    #                 $$self{'SVNUSER'} ? $$self{'SVNUSER'} : "<N/A>");

    #::status_message("%-15s: %s\n",
    #                 "SVN Password",
    #                 $$self{'SVNPASS'} ? $$self{'SVNPASS'} : "<N/A>");

}

Exception이 나오는 이유는 위 코드에서 <N/A> 부분 때문입니다. 따라서 ::status_message로 메시지를 출력하는 부분을 모두 #으로 주석 처리하여 <N/A>를 출력하지 않으면 됩니다.

이렇게 하면 CruiseControl.NET에서 Exception이 발생하지 않습니다.

VisualSVN Server로 authz 설정하기

authz 파일은 Subversion 저장소에 접근할 수 있는 권한을 설정하는 파일입니다. 지금까지 이 파일을 텍스트 에디터로 직접 수정하여 사용해 왔습니다. 이번에 소개해드리는 VisualSVN Server를 사용하면 authz 파일을 손쉽게 설정할 수 있습니다.

VisualSVN Server 설치

VisualSVN Server는 Apache를 내장하고 있어서 Subversion 저장소를 http, https로 운영할 수 있도록 해주는 프로그램입니다. 더불어 사용자 관리와 권한 관리도 GUI로 쉽게 할 수 있도록 구성되어 있습니다. VisualSVN-Server-1.0.1.msi를 설치합니다.

  1. VisualSVN Server 설치 마법사가 실행되면, Next를 눌러 넘어갑니다.


    그림 A-2 VisualSVN Server 설치 마법사

  2. I accept the terms in the License Agreement를 체크하고 Next를 누릅니다.


    그림 A-3 라이센스 동의 화면

  3. Repositories 부분을 Browse… 버튼을 눌러 C:\Repos로 지정합니다. 다른 부분은 설정할 필요가 없습니다. 우리는 이미 서버를 구축하였기 때문에 VisualSVN에서 제공해주는 서버는 사용하지 않을 것입니다.


    그림 A-4 VisualSVN Server 설치 경로, Subversion 저장소 경로, 서버 이름, 포트 설정

  4. Install을 눌러 설치를 시작합니다.


    그림 A-5 VisualSVN Server 설치 준비

  5. VisualSVN Server 파일 복사 화면이 나타납니다.


    그림 A-6 VisualSVN Server 파일 복사 화면

  6. Finish를 눌러 설치를 완료합니다. Start VisualSVN Server Manager를 체크하면 Finish를 누른 뒤 VisualSVN Server Manager가 실행됩니다.


    그림 A-7 VisualSVN Server 설치 완료

VisualSVN Server 사용 방법

시작 → VisualSVN → VisualSVN Server Manager를 실행합니다. 서비스 및 응용 프로그램 아래에 VisualSVN Server가 있습니다. Repositories에는 C:\Repos에 위치한 각 프로젝트의 저장소들이 표시됩니다. 저장소의 세부 경로와 파일까지 볼 수 있습니다.


그림 A-8 VisualSVN Server로 example 저장소 보기

저장소의 세부 경로마다 권한을 지정할 수 있습니다. 디렉터리 뿐만 아니라 파일도 권한 지정이 가능합니다.

hello의 trunk 디렉터리의 권한을 설정하는 화면입니다 현재 developer 그룹은 읽기, 쓰기를 할 수 있고 모든 사용자는 접근 불가로 되어 있는 것을 볼 수 있습니다.


그림 A-9 권한 설정

Add… 버튼으로 그룹 및 사용자를 추가할 수 있고 Remove 버튼으로 그룹 및 사용자를 삭제합니다. Permissions에서 각 권한을 지정할 수 있습니다.


그림 A-10 사용자 추가, 삭제 및 권한 설정 화면

Users에서는 C:\Repos\htpasswd 파일에 등록된 사용자들을 표시해 주며, 사용자를 추가하고 삭제 할 수 있습니다. 그리고 각 사용자의 비밀번호를 바꿀 수도 있습니다.


그림 A-11 사용자 목록 표시, 사용자 추가 방법

Groups에서는 C:\Repos\authz 파일에 등록된 그룹을 표시해 주며, 그룹을 추가 하고 삭제할 수 있습니다.


그림 A-12 그룹 목록 표시, 그룹 추가 방법

developer 그룹에 소속된 사용자들을 보여주고 있습니다. Add… 버튼을 누르면 오른쪽 화면과 같이 목록에서 사용자를 선택하여 추가할 수 있습니다.


그림 A-13 그룹 편집 화면

TIP
C:\Repos 아래에 authz와 htpasswd라는 파일이 꼭 있어야 하며 Apahce에도 이 파일들로 설정되어 있어야 합니다. 그리고 authz, htpasswd라는 이름으로 사용해야 합니다. 다른 파일명은 VisualSVN Server Manager에서 인식하지 하지 못합니다. 지금까지 설명은 authz와 htpasswd로 해왔기 때문에 설명대로 했다면 아무 문제가 없을 것입니다.

우리는 이미 Trac과 Subversion 서버를 구성하여 사용하고 있기 때문에 VisualSVN Server에서 제공해 주는 서버는 사용하지 않아도 됩니다. 따라서 VisualSVN 서비스를 시작하지 않도록 설정합니다.

제어판 → 관리 도구 → 서비스를 실행합니다.

VisualSVN Server가 등록되어 있는 것을 볼 수 있습니다. VisualSVN Server에서 마우스 오른쪽 버튼을 누르고 속성을 선택합니다.


그림 A-14 관리 도구 → 서비스의 VisualSVN Server

시작 유형(E)는 사용 안 함으로 선택하고, 중지(T) 버튼을 눌러 서비스를 종료합니다.


그림 A-15 VisualSVN Server 서비스를 사용 안 함으로 설정

ccnet.config를 Subversion으로 관리하기

ccnet.config는 CruiseControl.NET의 빌드 스크립트 파일입니다. 이 파일은 빌드 서버에 위치하고 있어서 새로운 프로젝트의 빌드 스크립트를 추가하거나, 수정해야 할 때 매번 빌드 서버에서 작업을 해야 한다는 문제가 있습니다. 이것을 Subversion 저장소에 넣어 놓고 빌드 스크립트를 담당하는 개발자가 ccnet.config를 체크아웃 하여 수정한 뒤 커밋을 하면 빌드 서버에도 자동으로 적용되도록 할 수 있습니다. CruiseControl.NET은 기본적으로 이러한 기능을 제공해주고 있습니다.

  • 1. ccnet.config 파일을 저장할 Subversion 저장소를 만듭니다. 만들어진 저장소의 경로는 http://192.168.1.100/svn/buildserver/trunk라고 하겠습니다.

  • 2. 빌드 서버의 C:\Program Files\CruiseControl.NET\server\config에 http://192.168.1.100/svn/buildserver/trunk를 체크아웃 합니다.

  • 3. C:\Program Files\CruiseControl.NET\server에 있는 ccnet.config 파일을 체크아웃 한 config 디렉터리에 넣고 추가한 뒤 커밋합니다.


    그림 A-16 config 디렉터리에 체크아웃한 화면

  • 4. 우리는 서비스로 CruiseControl.NET을 실행하고 있으므로 C:\Program Files\CruiseControl.NET\server의 ccservice.exe.config를 수정합니다.

<appSettings> 부분을 찾아 <add key="ccnet.config" value="config\ccnet.config"/>와 같이 설정합니다.

    <appSettings>
        <!-- Without this appSetting ccservice will look for ccnet.config in its own directory. -->
        <add key="ccnet.config" value="config\ccnet.config"/>
        <add key="service.name" value="CCService"/>
        <add key="remoting" value="on"/>
        <add key="ServerLogFilePath" value="ccnet.log"/>
        <!-- Used by the WebDashboard ServerLog plugin to locate the log file produced by the LogFileTraceListener (above) -->
        <add key="ServerLogFileLines" value="100"/>
        <!-- Used by the WebDashboard ServerLog plugin to determine how many lines from the log file should be read -->
        <add key="WatchConfigFile" value="true"/>
        <!-- Turns on or off the file watcher used to monitor the ccnet.config file -->
    </appSettings>
TIP
서비스가 아닌 ccnet.exe를 사용하려면 ccnet.exe -config:config\ccnet.config 처럼 실행하면 됩니다.
  • 5. ccnet.config에 다음 내용을 추가하고 커밋합니다.
    <project name="cc-config">
        <!-- 30초에 한번씩 아래 <sourcecontrol>을 실행하도록 설정 -->
        <triggers>
            <intervalTrigger seconds="30" />
        </triggers>

        <!-- Subversion 저장소 주소 및 작업 디렉터리 설정 -->
        <sourcecontrol type="svn">
            <trunkUrl>http://192.168.1.100/svn/buildserver/trunk</trunkUrl>
            <workingDirectory>C:\Program Files\CruiseControl.NET\server\config</workingDirectory>
        </sourcecontrol>
    </project>

http://192.168.1.100/svn/buildserver/trunk를 30초에 한번씩 검사하여 ccnet.config가 수정되었다면 자동으로 업데이트를 할 수 있도록 설정하는 것입니다. 이제 개발자 PC에서 ccnet.config를 수정하여 커밋하면 자동으로 빌드 서버에 적용 될 것입니다.

Visual Studio 6.0을 사용하려면

현재 Visual Studio 2005가 나온지 오래 되었지만 아직까지도 Visual Studio 6.0을 쓰는 개발자들이 많습니다. 앞의 예제들에서는 Visual Studio 2005를 기준으로 설명하였는데, Visual Studio 6.0을 사용하려면 어떻게 해야 하는지 설명하겠습니다.

Visual C++ 6.0으로 콘솔 프로젝트를 만듭니다. Visual C++ 6.0을 실행하고 File → New…를 선택합니다. Win32 Console Application을 선택하고 Project name:에는 hello, Location에는 C:\project 디렉터리를 지정합니다.


그림 A-17 Visual C++ 6.0에서 새 프로젝트 생성

설정이 완료되었으면 OK 버튼을 눌러 다음 단계로 진행합니다.

A “Hello, World!” application.을 선택하고 Finish를 눌러 프로젝트를 생성합니다.


그림 A-18 프로젝트 형식 선택

리소스 파일이 없으므로 리소스 파일을 추가해야 합니다. Insert → Resource…을 선택합니다.


그림 A-19 리소스 추가

Version을 선택하고 New 버튼을 누르면 리소스가 추가됩니다. 저장 버튼을 눌러 모든 내용을 저장합니다. 이때 Script1.rc 파일을 어디에 저장할지 선택하게 되는데, C:\project\hello에 디렉터리에 저장합니다.


그림 A-20 리소스 선택

Resource Files에서 마우스 오른쪽 버튼을 누르면 팝업 메뉴가 나옵니다. Add Files to Folder…를 선택하고 C:\project\hello 디렉터리의 Script1.rc 파일을 추가합니다.


그림 A-21 Script1.rc 파일 추가

Project → Settings…에서 Win32 Release를 선택하고 General 탭으로 이동합니다.


그림 A-22 출력 디렉터리 설정

Intermediate files:에는 Release-temp를 입력합니다. VS6.0은 실행 파일과 임시 파일들이 같은 디렉터리에 생성되기 때문에 CruiseControl.NET과 연동하기가 까다롭습니다. 따라서 임시 파일들은 Release-temp 디렉터리로 모으고, 빌드된 실행파일은 Release 디렉터리에 생성되도록 해야 합니다.

C++탭으로 이동하고 Category:는 General입니다.


그림 A-23 Debug Info 설정

Debug Info:에 Program Database를 선택합니다. Link 탭으로 이동하고 Category:는 General입니다.


그림 A-24 Generate debug info 설정

Generate debug info에 체크합니다. VS6.0 프로젝트를 저장소에 추가할 때에는 .ncb, .opt, .plg 파일은 제외합니다.


그림 A-25 추가할 파일 선택

VS6.0 프로젝트를 위한 빌드 스크립트는 다음과 같습니다.

    <project name="hello.exe">
        <!-- 기본 레이블로 지정 -->
        <labeller type="defaultlabeller">
            <prefix>1.0.0.</prefix>
        </labeller>

        <!-- Subversion 저장소 주소 및 작업 디렉터리 설정 -->
        <sourcecontrol type="svn">
            <trunkUrl>http://192.168.1.100/svn/hello/trunk</trunkUrl>
            <workingDirectory>c:\build\hello</workingDirectory>
        </sourcecontrol>

        <!-- 매일 20:00에 자동 빌드 하도록 설정 -->
        <triggers>
            <scheduleTrigger time="20:00" buildCondition="ForceBuild" name="Scheduled" />
        </triggers>

        <tasks>
            <!-- 자동 버전 업데이트 스크립트, Script1.rc 파일로 지정 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\rcversion.ps1 "hello.exe" "C:\build\hello\Script1.rc"</buildArgs>
            </exec>

            <!-- Visual Studio 6.0 빌드 설정 -->
            <exec>
                <executable>"C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin\MSDEV.COM"</executable>
                <baseDirectory>C:\build\hello</baseDirectory>
                <buildArgs>c:\build\hello\hello.dsw /MAKE "hello - Win32 Release" /REBUILD</buildArgs>
            </exec>

            <!-- 수정 된 소스 코드를 원래대로 되돌림 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\revert.ps1 "C:\build\hello"</buildArgs>
            </exec>

            <!-- trac에 버전을 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\tracversion.ps1 "hello" "hello.exe"</buildArgs>
            </exec>

            <!-- PDB 파일에 Subversion 저장소 정보를 인덱싱 하고, 압축 한 뒤 심볼 서버에 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\symbols.ps1 C:\build\hello C:\build\hello\release hello.exe</buildArgs>
                <buildTimeoutSeconds>10</buildTimeoutSeconds>
            </exec>

            <!-- 빌드된 결과물을 Releases 디렉터리 아래 각 프로젝트 디렉터리로 복사 -->
            <buildpublisher>
                <sourceDir>C:\build\hello\release</sourceDir>
                <publishDir>C:\Releases\hello</publishDir>
                <useLabelSubDirectory>true</useLabelSubDirectory>
            </buildpublisher>

            <!-- 빌드된 결과물을 Release 저장소로 커밋 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\release.ps1 C:\Releases\hello C:\build\hello</buildArgs>
            </exec>
        </tasks>
    </project>

Visual Studio 2008을 사용하려면

Visual Studio 2008이 출시되었습니다. CruiseControl.NET 1.3 버전에서는 아직 공식적으로 Visual Studio 2008을 지원하지 않고 있습니다. 그렇지만 <devenv>를 이용하면 Visual Studio 2008을 사용하여 빌드할 수 있습니다.

VS2008에서 프로젝트를 만드는 방법은 VS2005와 똑같기 때문에 따로 설명하지 않겠습니다.

먼저 ccnet.config 파일에서 VS2005는 아래와 같이 사용했습니다.

<!-- Visual Studio 2005 빌드 설정 -->
<devenv>
    <solutionfile>C:\build\hello\hello.sln</solutionfile>
    <configuration>Release</configuration>
</devenv>

VS2008은 아래와 같이 사용하면 됩니다.

<!-- Visual Studio 2008 빌드 설정 -->
<devenv>
    <solutionfile>C:\build\hello\hello.sln</solutionfile>
    <configuration>Release</configuration>
    <executable>"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com"
</executable>
</devenv>

<executable>로 VS2008의 “C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com”을 직접 실행하도록 설정하였습니다.

<solutionfile>에는 프로젝트의 솔루션 파일의 경로를 설정합니다. <configuration>에는 빌드할 방식을 설정합니다. 릴리스 모드로 빌드하고자 한다면 Release로 설정합니다.

CruiseControl.NET으로 윈도우 드라이버 빌드하기

윈도우 기반 개발에는 응용프로그램 이외에 드라이버 개발 분야도 있습니다. 이번에는 CruiseControl.NET에서 윈도우 드라이버를 빌드하는 방법에 대해서 알아보겠습니다.

Windows Driver Kit(WDK)를 설치합니다. WDK를 설치하는 것은 따로 설명하지 않겠습니다. CruiseControl.NET에서 드라이버를 빌드하려면 ddkbuild.bat이 필요합니다.

  • 다운로드 주소 : http://www.hollistech.com/Resources/ddkbuild/ddkbuild.htm
  • 부록 CD 경로 : Applications/DDK Build/

시스템 환경 변수의 시스템 변수에 다음과 WNETBASE를 추가합니다. 변수 값에는 WDK(DDK)가 설치된 경로를 지정합니다.


그림 A-26 환경 변수의 시스템 변수에 WNETBASE 추가

아래 그림과 같이 world라는 드라이버 소스가 있습니다. ddkbuild.bat도 소스와 같은 디렉터리에 있습니다.


그림 A-27 드라이버 소스 파일

우선 드라이버를 빌드할 때 사용하는 sources 파일을 편집합니다.

sources

TARGETNAME=world
TARGETTYPE=DRIVER
TARGETPATH=.\

SOURCES=world.c world.rc

드라이버 파일과 심볼 파일이 C:\build\world\i386에 생성될 수 있도록 TARGETPATH=.\로 설정합니다.

응용프로그램과 마찬가지로 드라이버도 버전을 가지고 있습니다. 이 드라이버 파일에 버전을 표시하려면 리소스 파일이 필요합니다. 리소스 파일은 Visual Studio 방식도 사용할 수 있고 드라이버 방식도 사용할 수 있습니다. 아래 리소스 파일은 드라이버 방식입니다.

world.rc

#include <windows.h>

#define VER_DEBUG                   0
#define VER_PRERELEASE              0
#define VER_FILEFLAGSMASK           VS_FFI_FILEFLAGSMASK
#define VER_FILEOS                  VOS_NT_WINDOWS32
#define VER_FILEFLAGS               (VER_PRERELEASE|VER_DEBUG)

#define VER_FILETYPE    VFT_DRV
#define VER_FILESUBTYPE VFT2_DRV_SYSTEM

#define VER_COMPANYNAME_STR         "EXAMPLE"
#define VER_PRODUCTNAME_STR         "WORLD Driver"
#define VER_LEGALCOPYRIGHT_YEARS    "2008"
#define VER_LEGALCOPYRIGHT_STR      "Copyright (C) EXAMPLE " VER_LEGALCOPYRIGHT_YEARS
#define VER_LEGALTRADEMARKS_STR     "Copyright (C) 2008 EXAMPLE"

#define VER_PRODUCTVERSION          1,0,0,1
#define VER_PRODUCTVERSION_STR      "1.0.0.1"
#define VER_PRODUCTVERSION_W        (0x0434)
#define VER_PRODUCTVERSION_DW       (0x0434)
#define VER_FILEDESCRIPTION_STR     "WORLD Driver"
#define VER_INTERNALNAME_STR        "world.sys"
#define VER_ORIGINALFILENAME_STR    "world.sys"

#include "common.ver"

리소스 파일을 사용할 때 드라이버 방식을 사용했다면 rcversion.ps1, rcversion-date.ps1에 다음 부분을 추가해 주어야 버전을 자동으로 업데이트 할 수 있습니다.

    if ($rc_file[$i] -match "#define VER_PRODUCTVERSION ")
    {
        $rc_file[$i] = "#define VER_PRODUCTVERSION "+$version_string
        echo "Product Version Updated"
    }

    if ($rc_file[$i] -match "#define VER_PRODUCTVERSION_STR ")
    {
        $rc_file[$i] = "#define VER_PRODUCTVERSION_STR `""+$version_string+"\0"+"`""
        echo "Product Version Updated"
    }

빌드 스크립트는 다음과 같습니다. 응용프로그램 빌드 하는 것과 큰 차이가 없습니다. 단 <devenv> 대신 <exec>ddkbuild.bat -WNETXP free . 을 실행한 다는 것이 다릅니다.

    <project name="world.sys">
        <!-- 기본 레이블로 지정 -->
        <labeller type="defaultlabeller">
            <prefix>1.0.0.</prefix>
        </labeller>

        <!-- Subversion 저장소 주소 및 작업 디렉터리 설정 -->
        <sourcecontrol type="svn">
            <trunkUrl>http://192.168.1.100/svn/world/trunk</trunkUrl>
            <workingDirectory>c:\build\world</workingDirectory>
        </sourcecontrol>

        <!-- 매일 20:00에 자동 빌드 하도록 설정 -->
        <triggers>
            <scheduleTrigger time="20:00" buildCondition="ForceBuild" name="Scheduled" />
        </triggers>

        <tasks>
            <!-- 자동 버전 업데이트 스크립트 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\rcversion.ps1 "world.sys" "C:\build\world\world.rc"</buildArgs>
            </exec>

            <!-- WDK(DDK) 빌드 설정 -->
            <exec>
                <executable>ddkbuild.bat</executable>
                <baseDirectory>C:\build\world</baseDirectory>
                <buildArgs>-WNETXP free .</buildArgs>
            </exec>

            <!-- 수정 된 소스 코드를 원래대로 되돌림 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\revert.ps1 "C:\build\world"</buildArgs>
            </exec>

            <!-- trac에 버전을 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\tracversion.ps1 "world" "world.sys"</buildArgs>
            </exec>

            <!-- PDB 파일에 Subversion 저장소 정보를 인덱싱 하고, 압축 한 뒤 심볼 서버에 등록 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\symbols.ps1 C:\build\world C:\build\world\objfre_wxp_x86\i386 world.sys</buildArgs>
                <buildTimeoutSeconds>10</buildTimeoutSeconds>
            </exec>

            <!-- 빌드된 결과물을 Releases 디렉터리 아래 각 프로젝트 디렉터리로 복사 -->
            <buildpublisher>
                <sourceDir>C:\build\world\objfre_wxp_x86\i386</sourceDir>
                <publishDir>C:\Releases\world</publishDir>
                <useLabelSubDirectory>true</useLabelSubDirectory>
            </buildpublisher>

            <!-- 빌드된 결과물을 Release 저장소로 커밋 -->
            <exec>
                <executable>powershell.exe</executable>
                <baseDirectory>C:\tools</baseDirectory>
                <buildArgs>C:\tools\release.ps1 C:\Releases\world C:\build\world</buildArgs>
            </exec>
        </tasks>
    </project>

Trac 인덱스 페이지 꾸미기

Trac 인덱스 페이지는 http://192.168.1.100/trac에 접속했을 때 나오는 기본 페이지를 말합니다. 내용은 아무것도 없고 단지 프로젝트의 목록만 나열하고 있습니다. 이 기본 인덱스 페이지를 좀더 실용적으로 꾸며보겠습니다. C:\Python25\Lib\site-packages\trac\templates 디렉터리로 가서 index.html을 다음과 같이 수정합니다.

C:\Python25\Lib\site-packages\trac\templates\index.html

<?python from genshi import HTML ?>
<!DOCTYPE html
    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <link rel="stylesheet" type="text/css" media="screen" href="/trac/example/chrome/common/css/trac.css" />
    <title>Available Projects</title>    
  </head>    

  <body>

    <img src="/trac/example/chrome/common/trac_banner.png" />
    <p />

    <table class='wiki'>
      <tr>
        <td class='wiki' width='100'>&nbsp;Projects</td>
        <td class='wiki' width='800'>&nbsp;Descriptions</td>
      </tr>
      <tr py:for="project in projects" py:choose="">
        <td class='wiki'>&nbsp;
          <a py:when="'href' in project" href="$project.href" 
            title="$project.description">$project.name</a>
          <py:otherwise>
            <small>$project.name: <em>Error</em> <br /> ($project.description)</small>
          </py:otherwise>
        </td>
        <td class='wiki'>${HTML(project.description)}</td>
      </tr>
    </table>
  </body>
</html>

맨 첫줄에 <?python from genshi import HTML ?>가 추가되었습니다. 그리고 trac.css와 trac_banner.png를 사용하도록 수정하였고, 프로젝트 이름과 프로젝트 설명을 테이블로 표시하도록 수정하였습니다.

index.html을 저장하고 http://192.168.1.100/trac에 접속하면 다음 화면과 같이, Projects에 프로젝트 이름이 나열되고, Descriptions에 프로젝트의 설명이 표시 됩니다.


그림 A-28 꾸미기가 완료된 Trac 인덱스 페이지

프로젝트의 설명은 Admin 메뉴의 Basic Settings에서 설정할 수 있습니다. 줄바꿈을 하려면 <br />을 사용하면 됩니다.


그림 A-29 줄바꿈 방법

자동 빌드하지 않는 방법

CruiseControl.NET은 요일별, 한달, 일주일, 하루에 한번 빌드를 하도록 설정할 수 있습니다. 또한 기간에 상관없이 아예 자동 빌드를 하지 않도록 설정할 수도 있습니다. 이렇게 설정하면 CCTray로 ForceBuild 명령을 내릴 때만 빌드가 됩니다. 즉 일일빌드, 주간빌드까지는 필요없고, 원하는 시간에만 빌드하면 되는 프로젝트에 이러한 설정을 사용하면 됩니다.

설정방법은 약간의 주의사항이 있지만 아주 간단합니다.

<cruisecontrol>
    <project name="example" queue="buildserver">
        <!-- example 프로젝트의 Web Dashboard 주소 -->
        <webURL>http://192.168.1.100:8000/ccnet/server/local/project/example/ViewProjectReport.aspx</webURL>

        <!-- 기본 레이블로 지정 -->
        <labeller type="defaultlabeller">
            <prefix>1.0.0.</prefix>
        </labeller>

        <!-- Subversion 저장소 주소 및 작업 디렉터리 설정 -->
        <sourcecontrol type="svn">
            <trunkUrl>http://192.168.1.100/svn/example/trunk</trunkUrl>
            <workingDirectory>c:\build\example</workingDirectory>
        </sourcecontrol>

        <!-- 자동 빌드를 하지 않도록 설정 -->
        <triggers />

        <tasks>
            생략...
        </tasks>
    </project>
</cruisecontrol>

자동 빌드를 하지 않으려면 <project> 태그 아래에 <triggers />로 설정해주면 됩니다. 이렇게 설정하면 빌드 시간에 대한 아무런 정보가 없기 때문에, 사용자가 명령을 내리기 전까지는 빌드를 하지 않게 됩니다. 주의해야 할 점은 <triggers /> 마저도 지워버리면 <project> 태그의 기본설정을 따르게 됩니다. 즉 60초에 한 번씩 소스에 변경사항이 있으면(IfModificationExists) 자동 빌드하게 됩니다.

Subversion 저장소 합치기

프로젝트를 진행하다 보면 여러개로 분산된 프로젝트를 하나의 저장소로 관리하고 싶을 때가 있습니다. 또는 같은 내용을 가진 저장소이지만 여러가지 이유로 인해 저장소가 분리되어 관리 될 때가 있습니다. 이런 저장소도 하나의 저장소로 관리하고자 할 때 이 장의 내용이 유용할 것입니다.

내용이 다른 프로젝트

내용이 다른 두개의 프로젝트가 있습니다. 물론 저장소도 두개로 구성되어 있습니다. 이 두개의 저장소를 하나로 합치는 방법에 대해서 알아보겠습니다.

먼저 svndumptool을 설치합니다.

svndumptool-0.4.0.tar.gz를 C:\temp에 압축을 해제한 뒤 다음과 같이 실행합니다.

C:\temp\svndumptool-0.4.0>setup.py install

easy_install.exe을 이용하여 바로 설치하려면 아래와 같이 합니다.

C:\Python25\Scripts\easy_install.exe http://repos.borg.ch/svn/svndumptool/trunk/

venus와 mars라는 프로젝트가 있고 각 프로젝트의 구성은 다음과 같습니다.


그림 A-30 venus 저장소


그림 A-31 mars 저장소

C:\Repos 디렉터리로 이동하여 svnadmin dump 명령을 사용하여 저장소 덤프 파일을 생성합니다.

C:\Repos>svnadmin dump venus > venus.dump
C:\Repos>svnadmin dump mars > mars.dump

svndumptool.py를 사용하여 두개의 저장소를 합칩니다.

C:\Repos>C:\Python25\Scripts\svndumptool.py merge -i venus.dump -r trunk trunk/venus -r tags tags/venus -r branches branches/venus -i mars.dump -r trunk trunk/mars -r tags tags/mars -r branches branches/mars -o venusandmars.dump -d trunk -d tags -d branches

여기서 -i 옵션에는 합치고자 하는 덤프 파일을 지정합니다. 그리고 -r 옵션에는 합치기 전의 경로와 합쳐진 뒤의 경로를 지정합니다. venus와 mars 모두 trunk, tags, branches 디렉터리가 있기 때문에 두 저장소의 내용을 구분해주어야 합니다. 따라서 저장소 이름으로 하위 디렉터리를 만들어 주는 것입니다. -r 옵션으로 경로를 지정해 주지 않으면, 아무리 덤프 파일을 합쳐봐야 저장소로 복구할 때 오류가 발생하게 됩니다. -o 옵션에는 최종적으로 합쳐진 덤프 파일의 이름을 지정합니다. -d 옵션에는 합쳐진 뒤 생성할 디렉터리를 지정합니다.

이제 venusandmars.dump 파일이 만들어졌습니다. 새로운 저장소를 만들고 덤프 파일을 저장소에 복구합니다.

C:\Repos>svnadmin create venusandmars
C:\Repos>svnadmin load venusandmars < venusandmars.dump

다음은 venus와 mars를 합친 venusandmars의 모습입니다. 여기서 리비전은 각 저장소의 리비전이 커밋된 시간순으로 다시 매겨집니다.


그림 A-32 venusandmars 저장소 1

각 프로젝트를 저장소의 최상위 디렉터리에 위치시키고 싶다면 아래와 같이 실행합니다.

C:\Repos>c:\Python25\Scripts\svndumptool.py merge -i venus.dump -r trunk venus/trunk -r branches venus/branches -r tags venus/tags -i mars.dump -r trunk mars/trunk -r branches mars/branches -r tags mars/tags -o venusandmars.dump -d venus -d mars

이번에는 각 프로젝트의 trunk, branches, tags 디렉터리를 venus/trunk, venus/branches, venus/tags 처럼 각 프로젝트 디렉터리 아래로 지정하여, 각 프로젝트의 디렉터리가 저장소의 최상위 디렉터리가 되도록 하였습니다. 그리고 합쳐진 뒤 venus, mars 디렉터리를 생성하도록 설정하였습니다. venus와 mars 프로젝트의 디렉터리가 저장소의 최상위에 위치한 것을 확인할 수 있습니다.


그림 A-33 venusandmars 저장소 2

내용이 이어지는 프로젝트

이번 사례는 필자에게 메일로 문의가 왔던 문제입니다(복구 아이디어를 제공해 주신 임진혁(zwei22@gmail.com)님께 감사드립니다).

  1. Subversion으로 저장소를 만들고 여러명이서 프로젝트를 진행하던 중 하드디스크의 고장이 났다.

  2. 하드디스크를 데이터 복구 업체에 맏겼고, 복구 기간은 1주일이 걸린다.

  3. 긴급한 프로젝트를 진행하고 있었고, Subversion 또한 사용하고 싶었다. 그래서 임시 저장소를 만들고 마지막 체크아웃한 소스를 임포트 하여 리비전 1부터 다시 시작했다. 그리고 작업을 쭉 진행해왔다.

  4. 하드디스크 복구가 성공적으로 완료되었고, 이전 저장소의 데이터도 얻을 수 있었다.

  5. 이전 저장소의 내용에 임시 저장소의 내용을 합쳐 깔끔하게 관리하고 싶다.

예를 들어 kairos라는 프로젝트가 있고 리비전 4까지 작업이 되어 있습니다. 그런데 여기서 하드디스크가 고장이 났습니다.


그림 A-34 리비전 4까지 작업된 kairos 프로젝트

그래서 리비전 4(hello 출력.)의 소스를 임시 저장소인 kairos-temp에 임포트 하였고, 리비전 1부터 다시 시작하게 됩니다.


그림 A-35 임시 저장소 kairos-temp에서 다시 시작

그리고 임시 저장소 kairos-temp에서 작업을 계속 진행했습니다.


그림 A-36 kairos-temp에서 작업을 계속 진행

이제 kairos 저장소가 복구되었고 이 kairos에 임시 저장소 kairos-temp의 내용을 합쳐보겠습니다. 여기서 중요한 것은 kairos의 저장소의 kairos-temp의 내용이 이어져야 한다는 것입니다. 현재 kairos 리비전 4의 내용과 kairos-temp 리비전 1의 내용은 완전히 똑같습니다. 따라서 kairos-temp 리비전 2의 내용부터 시작해야 합니다. 임시 저장소 kairos-temp의 리비전 2부터 4까지 덤프 파일로 만듭니다.

C:\Repos>svnadmin dump kairos-temp -r 2:4 --incremental > kairos-temp-r2-4.dump 

-r 2:4는 리비전 2에서 4까지라는 뜻입니다. -r2:100이라고 하면 리비전 2에서 100의 내용까지 덤프 파일이 생성됩니다. 여기서 –incremental 옵션이 중요합니다. 이 옵션을 설정하지 않으면, kairos 저장소에 복구할 수 없게 됩니다. 덤프 파일을 kairos 저장소에 이어 붙입니다.

C:\Repos>svnadmin load kairos < kairos-temp-r2-4.dump

다음과 같이 출력되면 정상적으로 kairos 저장소에 덤프 파일이 복구된 것입니다.


그림 A-37 svnadmin으로 kairos 저장소를 복구

이제 kairos 저장소를 체크아웃한 디렉터리로 가서 업데이트를 하면 kairos-temp의 내용이 완벽히 이어진 것을 확인할 수 있습니다. kairos-temp 리비전 2, 3, 4가 kairos 리비전 5, 6, 7이 되었습니다.


그림 A-38 완전히 복구된 모습

Subversion 저장소 나누기

이번에는 저장소를 나누는 방법을 알아보겠습니다. 실제로 프로젝트를 진행하다 보면 하나의 저장소 안에 여러개의 프로젝트가 들어가 있게 됩니다. 이때 프로젝트 하나를 꺼내서 새로운 저장소로 만들고 싶을 때 이 방법을 사용하면 됩니다.

trunk 디렉터리 아래에 각 프로젝트의 디렉터리가 위치할 때

아래와 같이 earth라는 저장소가 있습니다. 이 저장소 안에는 크게 continent와 ocean 이라는 두 프로젝트가 있습니다.


그림 A-39 earth 저장소

여기서 ocean 프로젝트를 꺼내서 새로운 저장소로 만들겠습니다. C:\Repos로 이동한 뒤 earth 저장소의 덤프 파일을 만듭니다.

C:\Repos>svnadmin dump earth > earth.dump

svndumpfilter로 ocean 부분만 따로 저장합니다. 현재 ocean 디렉터리는 trunk 디렉터리 아래에 있으므로 trunk/ocean이 됩니다.

C:\Repos>type earth.dump | svndumpfilter include trunk/ocean --drop-empty-revs --renumber-revs > ocean.dump

–drop-empty-revs는 빈 리비전은 삭제하는 옵션이고, –renumber-revs는 ocean 디렉터리 부분만 리비전을 다시 매기는 옵션입니다.

ocean 저장소를 만들고 trunk 디렉터리를 생성합니다.

C:\Repos>svnadmin create ocean
C:\Repos>svn mkdir file:///c:/repos/ocean/trunk -m "trunk 디렉터리 생성"
TIP
여기서 ocean 저장소에 trunk 디렉터리를 만들어 주지 않으면 ocean.dump 파일을 사용할 수 없게됩니다. trunk 디렉터리가 없으면 trunk 디렉터리 아래에 ocean 디렉터리를 만들 수 없게 되므로 아래 화면과 같이 에러 메시지가 출력되게 됩니다.

![](/assets/images/WindowsProjectEssentialUtilityAppendix/40.png) 그림 A-40 trunk 디렉터리가 없어서 에러 발생

ocean 저장소를 ocean.dump 파일로 복구합니다.

C:\Repos>svnadmin load ocean < ocean.dump

아래 화면과 같이 나오면 정상적으로 복구가 되는 것입니다.


그림 A-41 svnadmin으로 ocean 저장소 복구

이제 ocean 프로젝트가 완벽히 나누어진 것을 확인할 수 있습니다. 리비전도 새로 매겨진 것을 볼 수 있습니다.


그림 A-42 ocean 저장소


그림 A-43 ocean 저장소의 로그 메시지

각 프로젝트의 디렉터리 저장소의 최상위 디렉터리로 위치할 때

이번에는 앞의 예제와 디렉터리 구조가 약간 다른 예제를 준비했습니다. 앞의 예제에서는 저장소 최상위 디렉터리가 trunk, branches, tags 였고 trunk 디렉터리 아래에 continent, ocean 프로젝트가 있었습니다. 하지만 이번에는 최상위 디렉터리 부터 continent, ocean 프로젝트의 디렉터리이고 각자 trunk, branches, tags 디렉터리를 가지고 있습니다. 그리고 trunk 디렉터리에 소스 파일이 들어있습니다.


그림 A-44 earth 저장소

C:\Repos로 이동한 뒤 earth 저장소의 덤프 파일을 만듭니다.

C:\Repos>svnadmin dump earth > earth.dump

svndumpfilter로 ocean 부분만 따로 저장합니다. 현재 ocean이 최상위 디렉터리이므로 ocean만 지정하면 됩니다.

C:\Repos>type earth.dump | svndumpfilter include ocean --drop-empty-revs --renumber-revs > ocean.dump

–drop-empty-revs는 빈 리비전은 삭제하는 옵션이고, –renumber-revs는 ocean 디렉터리 부분만 리비전을 다시 매기는 옵션입니다.

ocean 저장소를 만듭니다. 이번에는 ocean이 저장소의 최상위 디렉터리이므로 따로 다른 디렉터리를 만들어줄 필요가 없습니다.

C:\Repos>svnadmin create ocean

ocean 저장소를 ocean.dump 파일로 복구합니다.

C:\Repos>svnadmin load ocean < ocean.dump

이제 ocean 프로젝트가 완벽히 나누어진 것을 확인할 수 있습니다. 리비전도 새로 매겨진 것을 볼 수 있습니다.


그림 A-45 ocean 저장소


그림 A-46 ocean 저장소의 로그 메시지

Subversion에서 외부 저장소 연결하기

프로젝트를 진행하다 보면 따로 떨어져 있는 저장소의 내용을 현재 프로젝트의 디렉터리 안에 넣고 싶을 때가 있습니다. 따로 떨어져 있는 저장소의 소스 코드를 현재 프로젝트의 디렉터리로 복사해서 사용하면 되지만, 따로 떨어져 있는 저장소의 소스 코드가 수정될 때 마다 수정된 내용을 가져와서 커밋해주어야 하는 불편함이 있습니다.

이럴 때에 사용할 수 있는 것이 svn:externals 속성(property)입니다. 프로젝트의 작업 디렉터리에 svn:externals 속성을 지정하여 외부 저장소의 내용을 가져올 수 있습니다. 외부 저장소의 내용이 수정되더라도 Subversion의 업데이트 명령을 사용하면 언제나 최신 상태로 유지할 수 있습니다. 또한 외부 저장소를 연결하더라도 저장소의 공간은 늘어나지 않으므로 매우 효율적입니다.

외부 저장소를 연결하고자 하는 작업 디렉터리로 이동하여 TortoiseSVN → 속성을 실행합니다.


그림 A-47 TortoiseSVN 속성 실행 방법

추가(A)… 버튼을 누릅니다.


그림 A-48 속성 편집 및 추가, 삭제

속성 이름에 svn:externals를 선택하고 속성 값에 외부 저장소의 내용을 받아올 디렉터리 이름과 저장소 URL을 입력합니다.


그림 A-49 속성 추가

svn://192.168.1.100/world의 내용을 world라는 디렉터리에 표시하겠다는 뜻입니다. 디렉터리 이름은 연결하고자 하는 외부 저장소의 이름과 일치하지 않아도 됩니다. 저장소 URL은 svn 뿐만 아니라 http, https도 사용할 수 있습니다. svn:externals 속성이 추가되었습니다.


그림 A-50 속성 편집 및 추가, 삭제

SVN 업데이트를(U) 실행하여 외부 저장소의 내용을 가져옵니다.


그림 A-51 SVN 업데이트(U) 실행 방법

SVN 업데이트를 실행하면 외부 저장소의 내용을 가져오는데 이때에는 아래와 같이 “외부”라고 표시됩니다. svn://192.168.1.100/world의 world.c를 가져온 것을 확인할 수 있습니다.


그림 A-52 SVN 업데이트 실행

svn://192.168.1.100/world를 연결한 world 디렉터리가 생성되었습니다. 아직 끝난 것이 아닙니다. 속성을 수정하였으면 커밋을 꼭 해주어야 합니다.


그림 A-53 SVN 커밋 방법

속성이 수정하거나 추가하면 아래와 같이 변경된 파일 부분에 .이 표시됩니다. .은 현재 디렉터리를 뜻합니다.


그림 A-54 속성을 추가한 뒤 커밋

이후 업데이트를 하면 현재 작업 디렉터리의 내용과 함께 외부 저장소의 내용도 업데이트 하게 됩니다.

MS 오피스 파일과 TortoiseSVN

Subversion 저장소에는 소스 코드와 같은 일반 텍스트 형식의 파일을 저장하고 관리하는 것이 일반적입니다. TortoiseSVN을 사용하면 바이너리 포맷인 마이크로소프트 오피스 파일들을 텍스트 파일의 비교(Diff) 명령처럼 수정 되었을 때 차이점을 확인 할 수 있습니다.

프로젝트를 진행하다 보면 워드, 파워포인트, 엑셀을 자주 사용하게 됩니다. 이러한 문서 파일들도 Subversion으로 관리하면 아주 편리합니다. 앞서 소개한 것 처럼 차이점도 비교할 수 있기 때문에 변경 사항을 추적하기가 쉽습니다.

오피스 파일을 Subversion 저장소에 추가하고 관리하는 것은 일반 텍스트 파일과 똑같습니다. 그리고 차이점 비교할 때에도 똑같이 TortoiseSVN → 비교(Diff) 메뉴를 사용합니다.

아래는 워드 파일을 저장소에 추가하고 커밋한 뒤 일부 내용을 수정하고, 비교 메뉴를 사용하여 차이점을 비교한 화면입니다.


그림 A-55 Microsoft Word에서 차이점 비교

삭제된 부분은 어떤 부분이 삭제되었는지 표시해주고, 추가된 부분은 밑줄이 쳐지고 글자색은 붉은색으로 표시됩니다. “마이크로소프트 Word입니다” 처럼 삭제 된 부분과 추가된 부분을 동시에 표시하는 방식으로 수정된 부분을 보여주고 있습니다.

아래는 엑셀 파일을 비교한 화면입니다.


그림 A-56 Microsoft Excel에서 차이점 비교

엑셀은 차이점을 비교했을 때 이전 내용은 아래에 새롭게 수정된 내용은 위에 표시해줍니다. 그리고 수정되거나 추가, 삭제된 부분은 붉은색으로 표시해줍니다.

아래는 파워포인트 파일을 비교한 화면입니다.


그림 A-57 Microsoft PowerPoint에서 차이점 비교

파워포인트는 오른쪽에 위치한 수정 내용 창의 슬라이드 변경 내용에 수정 내용을 표시해줍니다. 슬라이드 변경 내용의 각 항목을 클릭하면 수정되거나 추가, 삭제된 부분을 확인할 수 있습니다. “삽입된”, “삭제된” 앞의 체크박스를 클릭할 때 마다 해당 삭제 및 추가(삽입)된 내용이 슬라이드에 표시됩니다. 괄호안의 experinece는 윈도우를 설치할 때 입력한 컴퓨터 이름입니다.

User Mode Process Dumper 사용하기

프로그램의 덤프 파일을 생성할 때 흔히 닥터 왓슨(drwtsn32.exe)을 사용합니다. 이번에 소개해드리는 User Mode Process Dumper는 닥터 왓슨보다 훨씬 강력한 기능을 가지고 있습니다. 예외가 발생했을 때 덤프 파일을 생성하는 것은 기본이고, 무한루프에 빠진 프로세스나 특정 부분에서 계속 멈춰있는 프로세스처럼 실행 중인 프로세스의 덤프 파일도 생성할 수 있습니다. 또한 예외가 발생하면 무조건 덤프 파일을 생성하지 않고, 특정 예외가 발생 했을 때 덤프 파일이 생성되도록 설정할 수도 있습니다. 단축키를 설정하여 원하는 시점의 덤프 파일도 생성할 수 있습니다.

사용할 수 있는 운영체제는 Windows 2000, Windows XP, Windows Server 2003입니다.

User Mode Process Dumper는 아래 URL에서 받을 수 있으며 부록 CD에도 포함되어 있습니다.

  • 다운로드 주소 : http://www.microsoft.com/downloads/details.aspx?FamilyID=E089CA41-6A87-40C8-BF69-28AC08570B7E&displaylang=en&displaylang=en
  1. UserModeProcessDumper8_1_2929_5.exe를 실행합니다. Yes를 누릅니다.


    그림 A-58 라이센스 동의 화면

  2. 설치할 경로를 지정하는 화면입니다. 기본설정대로 설치합니다. Unzip 버튼을 누르면 설치를 시작합니다.


    그림 A-59 설치 디렉터리 지정

  3. 자동 실행되지 않도록 설정 되었으니 직접 설치하라는 말이 나옵니다. 확인을 눌러 넘어갑니다. User Mode Process Dumper를 사용하려면 자체적인 설치과정을 거쳐야 합니다.


    그림 A-60 직접 설치 경고 메시지

  4. C:\kktools\userdump8.1\x86 디렉터리로 이동하여 setup.exe를 실행합니다. (64비트 환경이라면 C:\kktools\userdump8.1\x64)


    그림 A-61 User Mode Process Dumper 설치 시작

  5. User Mode Process Dumper를 설치합니다. 다음을 눌러 넘어갑니다. 처음 설치하는 것이라면 Install 부분만 활성화 되어 있을 것입니다. 설치 후 삭제하려면 다시 setup.exe를 실행하여 Remove User Mode Process Dumper from this computer를 선택하면 됩니다.


    그림 A-62 프로세스 종료 시 덤프 생성 설정

  6. 프로세스가 종료 되었을 때 덤프를 생성하는 기능의 사용 여부를 설정하는 화면입니다. Enable을 선택하여 이 기능을 사용합니다. 마침을 누르면 설치를 시작합니다.


    그림 A-63 설치 준비

  7. 설치가 성공적으로 끝났습니다. 제어판에 Process Dumper 항목이 추가됩니다. “예”를 누르면 제어판의 Process Dumper 항목을 실행합니다.


    그림 A-64 설치 완료 메시지

  8. 제어판의 Process Dumper를 실행하면 아래와 같은 화면이 나옵니다.


    그림 A-65 User Mode Process Dumper 등록 정보

  9. User Mode Process Dumper는 프로세스 하나를 지정하여 세부 설정을 하도록 되어 있습니다. 즉 프로세스 마다 설정값을 다르게 할 수 있습니다. New… 버튼을 누르면 실행 파일의 이름을 입력하는 창이 나옵니다. 여기에 덤프를 생성할 실행 파일의 이름을 입력합니다.


    그림 A-66 덤프를 생성할 실행 파일 추가

  10. hello.exe가 추가되었습니다. 처음 추가하면 기본 설정이 적용됩니다.


    그림 A-67 hello.exe가 추가됨

  11. Rules… 버튼을 누르면 해당 프로세스의 설정을 바꿀 수 있습니다.


    그림 A-68 상세 설정

  12. Use custom rules를 선택하면 모든 부분이 활성화 되면서 설정을 바꿀 수 있게 됩니다.

설정 항목 설정내용
Dump file folder 덤프 파일이 생성될 경로입니다.
Exception Codes 특정 예외에서만 덤프가 발생하도록 하거나, 특정 예외를 무시하도록 설정할 수 있습니다. All Exceptions 체크 박스를 체크하면 모든 예외가 선택됩니다. Add… 버튼으로 예외 코드를 추가할 수 있습니다.
Bugcheck after dumping 덤프를 생성하고 Bugcheck가 실행됩니다. 이 Bugcheck는 우리가 흔히 보는 블루스크린을 뜻합니다. Bugcheck가 실행되면 블루스크린이 나오고 커널 모드 덤프를 생성한 뒤 컴퓨터는 재부팅 됩니다.
Ignore exceptions that occur inside Kernel32.dll Kernel32.dll에서 발생하는 예외는 무시합니다.
Kill Process after dumping 덤프 파일을 생성 한 뒤 해당 프로세스를 종료합니다.
MiniDump Type 덤프 파일에 기록될 내용을 조절합니다. Complete와 Small에 따라 덤프 파일을 생성하는 함수에 넘겨주는 플래그가 달라집니다.
Complete는 다음과 같은 플래그가 지정됩니다.
   MiniDumpWithFullMemory
   MiniDumpWithHandleData
   MiniDumpWithUnloadedModules
   MiniDumpWithProcessThreadData
   MiniDumpWithFullMemoryInfo
   MiniDumpWithThreadInfo
Small은 다음과 같은 플래그가 지정됩니다.
   MiniDumpWithHandleData
   MiniDumpWithUnloadedModules
   MiniDumpWithProcessThreadData
   MiniDumpWithFullMemoryInfo
   MiniDumpWithThreadInfo
   MiniDumpScanMemory
   MiniDumpWithIndirectlyReferencedMemory
Save Mode 덤프 파일을 저장하는 방식입니다. Overwrite는 말그대로 덮어쓰는 방식이고 Cyclic saving(5 times)은 덤프 파일에 숫자를 붙여서 저장합니다. 덤프 파일 이름은 프로세스 이름 + 프로세스 ID으로 정해집니다.
hello2375.dmp, hello2708#0.dmp
Monitor Process Exit CRT(C Run-Time Library)에서 프로세스 종료가 종료되는 부분까지 덤프를 생성합니다. MiniDump Type은 위에서 설명한 것과 같습니다. 이 부분을 설정하면 hello2704.dmp, hello_END2504.dmp 처럼 파일이 생성되는데 hello_END2504.dmp 파일이 CRT 부분입니다.
Switch the Dumper to 사용자가 지정한 프로그램으로 덤프를 생성합니다. 이 부분을 지정하면 User Mode Process Dumper에서는 덤프 파일을 생성하지 않습니다. 예를 들면 닥터 왓슨을 지정한다고 할 때 닥터 왓슨의 경로와 옵션 그리고 %ld를 꼭 추가해주어야 합니다. %ld는 프로세스 ID입니다.
“C:\WINDOWS\system32\drwtsn32.exe” -p %ld Wait Time(msec)은 지연 시간입니다. 여기에 지정한 시간이 지난 뒤 지정된 프로그램이 실행됩니다. 10000은 10초입니다.

표 A-1 상세 설정의 각 부분 설명

이렇게 설정한 뒤 예외가 발생하는 hello.exe를 실행하면 지정된 디렉터리에 덤프 파일이 생성됩니다. 기본 설정 디렉터리는 C:\WINDOWS 입니다.

덤프 파일이 생성될 때에는 화면의 오른쪽 위에 아래와 같은 작은 창이 표시되면서, 어떤 프로세스의 덤프를 생성하고 있는지 알려줍니다.


그림 A-69 덤프를 생성할 때 나오는 화면

이번에는 단축키를 지정하고 사용하는 방법을 알아보겠습니다. 제어판의 Process Dumper를 실행하고 Hot Keys 탭으로 이동합니다. New… 버튼을 누르면 새로운 단축키 설정을 추가 할 수 있습니다.


그림 A-70 단축키 설정 화면

Keystroke 0 부터 Z까지 지정할 수 있습니다. 이 설정이 해당 프로세스의 덤프 파일을 생성하는 단축키가 됩니다.
Folder where dump files should be placed 덤프 파일이 생성될 경로입니다.
Dump all Win32 processes (including csrss.exe) 모든 프로세스의 덤프 파일을 생성합니다.
Dump Win32 GUI applications that appears hung GUI 프로그램 중 응답이 없는 프로그램만 덤프 파일을 생성합니다.
Specify target applications 특정 프로그램만 덤프 파일을 생성합니다.
Kill process after dumping 덤프 파일을 생성 한 뒤 해당 프로세스를 종료합니다.
Bugcheck after dumping 덤프를 생성하고 Bugcheck가 실행됩니다. 이 Bugcheck는 우리가 흔히 보는 블루스크린을 뜻합니다. Bugcheck가 실행되면 블루스크린이 나오고 커널 모드 덤프를 생성한 뒤 컴퓨터는 재부팅 됩니다.

표 A-2 단축키 설정 화면 설명

단축키를 실행하는 방법은 다음과 같습니다.

  1. Shift, Ctrl, Alt 키를 동시에 누릅니다.
  2. 위 3가지 키를 누른 채로 D, U, M, P 키를 차례대로 입력합니다.
  3. 누르고 있는 Shift, Ctrl, Alt 키에서 손을 뗍니다.
  4. 위에서 지정한 단축키를 누르면 덤프 파일이 생성됩니다(예제에서는 0을 지정했습니다. 그러므로 0을 누르면 덤프 파일이 생성됩니다).

명령 프롬프트에서 직접 userdump.exe를 이용하여 덤프 파일을 생성하는 방법을 알아보겠습니다.

시작 → 모든 프로그램 → 보조 프로그램 → 명령 프롬프트를 실행합니다. 그리고 c:\kktools\userdump8.1\x86 디렉터리로 이동합니다. userdump.exe는 userdump.exe 프로세스 ID 형식으로 사용합니다.

cd c:\kktools\userdump8.1\x86
userdump.exe 2304


그림 A-71 notepad.exe의 덤프 생성

이렇게 실행하면 cd c:\kktools\userdump8.1\x86 디렉터리에 프로세스 ID가 2304인 프로세스의 덤프 파일이 생성됩니다.

TIP
프로세스 ID를 알아내는 방법

Windows 작업 관리자를 실행하고 보기(V) → 열 선택(S)...으로 이동합니다.
그림 A-72 프로세스 ID 알아내기

PID(프로세스 식별자)(P)를 체크하면 오른쪽 화면과 같이 각 프로세스에 해당하는 프로세스 ID(PID)가 표시됩니다.

CVS에서 Subversion으로 이전하기

지금까지 CVS를 사용해왔던 분들이 많이 있을 것입니다. 이번에는 기존에 사용하고 있던 CVS 저장소를 Subversion으로 이전하는 방법에 대해서 알아보겠습니다. cvs2svn이라는 파이썬 스크립트를 사용하여 CVS 저장소를 Subversion 저장소로 변환합니다.

cvs2svn의 웹사이트 : http://cvs2svn.tigris.org

2008년 4월 13일 현재 cvs2svn 웹사이트에 릴리스 된 cvs2svn-2.1.0.tar.gz은 버그가 있어 윈도우에서 정상적으로 실행되지 않습니다. 해당 버그는 cvs2svn 저장소의 소스 코드에 이미 수정이 된 상태입니다. 따라서 부록 CD에는 2008년 4월 12일자 버전의 cvs2svn을 담았습니다. 부록 CD에 포함된 cvs2svn-r4503.zip을 사용하거나 아래, cvs2svn 저장소 URL에서 최신 버전의 소스 코드를 체크아웃 하여 사용하도록 합니다.

바탕 화면에 TortoiseSVN을 이용하여 cvs2svn을 체크아웃 합니다.


그림 A-73 cvs2svn 체크아웃

시작 → 모든 프로그램 → 보조 프로그램 → 명령 프롬프트를 실행하고 체크아웃한 cvs2svn 디렉터리로 이동한 후 아래와 같이 python.exe로 setup.py를 실행시킵니다(부록 CD에 포함 된 cvs2svn-r4503.zip 파일도 설치 방법은 똑같습니다).

python.exe setup.py install


그림 A-74 cvs2svn 설치 시작

TIP
python.exe의 경로를 환경 변수의 Path에 추가하지 않았다면 python.exe가 실행되지 않을 것입니다. 이런 경우에는 아래와 같이 python.exe의 전체 경로를 지정하여 실행해주면 됩니다.
C:\Python25\python.exe setup.py install

cvs2svn은 GNU sort(UnxUtils.zip)가 필요합니다. 이 파일은 부록 CD에 포함되어 있고 아래 URL에서 받을 수 있습니다.

UnxUtils.zip 파일의 압축을 해제하면 bin, usr 두개의 디렉터리가 나옵니다. 여기서 usr → local → wbin 디렉터리로 이동하여 sort.exe 파일을 C:\tools 로 복사합니다.


그림 A-75 GNU Sort의 위치

아래와 같이 기존에 사용하던 CVS 저장소가 있습니다. 이 CVS 저장소는 CVSROOT 모듈과 sample 모듈을 포함하고 있습니다. Subversion으로 변환하게 되면 trunk 디렉터리 아래에 CVSROOT, sample 디렉터리가 위치하게 됩니다.


그림 A-76 기존의 CVS 저장소

시작 → 모든 프로그램 → 보조 프로그램 → 명령 프롬프트를 실행합니다. 이제 cvs2svn을 사용하여 CVS 저장소를 Subversion 저장소로 변환하겠습니다. cvs2svn 옵션 -s “Subversion 저장소 경로” “CVS 저장소 경로” 형식으로 사용합니다.

python.exe C:\Python25\Scripts\cvs2svn --sort=C:\tools\sort.exe --encoding=mbcs -s C:\Repos\svnproject C:\cvs\cvsproject

python.exe로 C:\Python25\Scripts\cvs2svn을 실행시킵니다. –sort 옵션에는 C:\tools 디렉터리에 복사한 sort.exe를 지정합니다. 사용하던 CVS 저장소의 소스 코드나 커밋 로그에 한글이 있다면 –encoding=mbcs를 지정하여 한글이 깨지지 않도록 합니다. -s 옵션에는 Subversion 저장소의 경로인 C:\Repos\svnproject를 지정합니다. 마지막에는 CVS 저장소 경로인 C:\cvs\cvsproject를 지정합니다.

아래와 같은 화면이 나오면 Subversion 저장소로 변환이 성공적으로 완료된 것입니다.


그림 A-77 Subversion으로 변환 완료

변환된 C:\Repos\svnproject를 TortoiseSVN의 저장소 브라우저로 살펴보면 아래와 같이 trunk 디렉터리 아래에 CVSROOT와 sample 디렉터리가 위치하고 있는 것을 확인할 수 있습니다.


그림 A-78 변환 후 디렉터리 구조

로그 메시지 또한 정상적으로 변환 된 것을 확인할 수 있습니다.


그림 A-79 변환 후 로그 메시지

CVS는 파일별로 리비전이 매겨집니다. 하지만 Subversion은 저장소 전체 리비전이 매겨지는데, 저장소를 CVS에서 Subversion 변환 할 때 기준이 되는 것은 커밋한 시간입니다. 즉 CVS에 파일별로 매겨진 리비전은 무시하고, 여러 파일들 중에서 커밋한 시간 순으로 Subversion 리비전을 정하게 되는 것입니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.