실전! 지속적인 통합 9편: 빌드 자동화의 핵심 – MSBuild

빌드 자동화의 핵심 ? MSBuild

 

지속적인 통합은 단순히 생각하면 빌드 자동화, 컴파일 자동화에 불과하다. 이 말은 지난달 칼럼의 첫 문장이다. 물론 정말로 지속적인 통합의 가치가 그 정도뿐이라 폄하하자는 건 아니고, 자동화를 이루고 개발자 테스트를 도입하면 더 나은 품질을 기대할 수 있다는 점을 보여주려는 의도에서 한 말이다. 하지만 자동화는 말로는 쉬워도 막상 해보면 만만치 않다. 소스 코드를 자동으로 빌드하고 데이터베이스를 생성하고 단위 테스트를 돌리며, 그 결과를 누구나 알기 쉽게 보여주는 단일 소프트웨어란 존재하지 않기 때문이다. 그러나 MSBuild를 사용하면 좀더 편리하게 자동화를 도모할 수 있다.

 

[필자 소개] 최재훈 http://andromedarabbit.net, [email protected] SK 아이미디어의 게임 서버 팀에서 일한다. 요즘은 스크립트 엔진을 개발하는 데 전념하며, 새로운 도전을 즐긴다. 직업 외적인 측면에선 배철수의 음악 캠프를 15년째 즐겨 듣고, U2가 최고의 밴드라 생각한다.

 

 

 

, 여름, 2분기 동안 우리는 무엇을 공부했는가? 처음엔 버전 관리 시스템인 서브버전(Subversion)을 도입했으며, 이어서 지속적인 컴파일에 대해 알아봤다. 비주얼 스튜디오와 빌드 서버인 CruiseControl .NET을 이용해 기초적인 컴파일 자동화를 꾀했다. 여기까지 해보니 어느새 봄을 지나 여름이 왔다. 여름엔 C++용 단위 테스트 라이브러리인 UnitTest++를 이용해 단위 테스트를 구성했다. 그리고 가을이 왔다.

 

이젠 기초를 갖추었으니 좀더 그럴듯한 자동화를 꾀해보자. 여태껏 컴파일 자동화, 단위 테스트 자동화를 따로따로 도입했다면, 이제부턴 이 모든 것을 한데 엮어 하나의 완성품으로 승화시켜보자. 그러려면 무엇보다 모든 조각을 묶어줄 뭔가가 필요한데, 바로 빌드 스크립트가 필요한 시점이다.

 

빌드 스크립트의 의의

 

그런데 도대체 빌드 스크립트가 무엇이길래 이런 중요한 역할을 맡는단 말인가?

 

이렇게 생각해보자. 소스 코드를 빌드하고 데이터베이스를 적절히 갖추고 단위 테스트를 돌린 다음 우리가 개발한 게임 서버를 실행시켜 자동화 못 시킨 부분을 사람이 직접 돌려볼 수 있게 하려 한다. 물론 이 모든 과정을 사람이 직접 해도 되지만, 한창 개발 중인 시점이라 하루에도 똑같은 과정을 수십 번씩 반복하는 상황이라면 과연 어떨까? 과거엔 이렇게 귀찮고 짜증나는 일은 신입 개발자의 몫이었다. 핵심 개발자들이 신나게 소스 코드를 만지작거리며 무언가 멋진 기능을 완성하는 즐거움을 누릴 때, 부푼 꿈을 안고 회사에 들어온 신입 개발자들은 소스 코드를 구경조차 못하고 잡다한 일을 처리하는 심부름꾼으로 전락하곤 했다.

 

이런 불합리함을 과거엔 배우는 과정이라며 합리화시켰지만, 사람 구하기 힘들다는 요즘엔 이래선 곤란하다. 그래서 우리는 자동화를 꾀한다. 그런데 이것이 말처럼 쉽지 않다. 테스트를 할 땐 DEBUG 빌드를 하고 출시 전엔 RELEASE 빌드를 해야 한다(실제론 상황에 따라 다르다). 64비트로 컴파일해야 하는 애플리케이션이 있는가 하면 32비트 컴파일만 가능한 애플리케이션도 있다. 이렇게 설정 값이 다양한데 컴파일만 하는 것도 아니다. 테스트를 돌리고 데이터베이스에 접속해 필요한 스키마와 데이터를 생성해야 한다.

 

빌드 자동화라는 말 한마디에 속으면 안 된다. 실제 프로젝트에선 다양한 빌드가 존재한다. 평소 땐 /RTC(Run-Time Error Checks) 옵션과 함께 DEBUG 빌드를 돌려서 좀더 미묘한 오류를 잡아내고, 하루에 한번 정도는 시간이 오래 걸리는 RELEASE 빌드를 돌린다. 출시일이 다가오면 빌드와 더불어 패키징 작업까지 해본다. 이런 식으로 여러 형태의 빌드를 전부 다루고, 앞서 언급한 것처럼 여러 형태의 기술(데이터베이스, 프로그래밍 언어 등)을 쉽게 조작할 수 있어야 한다.

 

이때 MSBuildNAnt 같은 빌드 스크립트가 도움이 된다. 일반적으로 빌드 스크립트엔 빌드시 일상적으로 쓰이는 기술(데이터베이스나 레지스트리 조작 등)이 내장된다. 또한 익히기도 비교적 쉬워서 파이썬 같은 범용 스크립트 언어로 빌드 자동화를 꾀하는 것보다 생산성이 높다. 더군다나 MSBuild 같은 유명한 빌드 스크립트는 CruiseControl .NET 같은 다른 빌드 도구와도 연동된다는 장점까지 있다. 그래서 팀 내에 적어도 한 명은 빌드 스크립트를 익혀야 한다.

 

빌드 스크립트를 써야 하는 이유를 장황하게 설명했지만 요약하면 아주 간단하다.

 

  1. 빌드 때 자주 쓰는 기능이 내장되어 있다.
  2. 범용 스크립트 언어보다 익히기 쉽고 생산성 측면에서 이점이 있다.
  3. 다른 빌드 도구(. CruiseControl .NET)과 연동하기 쉽다.

 

MSBuild인가?

 

빌드 스크립트는 참 종류가 많다. 그런데 굳이 MSBuild를 써야 할 이유가 있을까? 우선 이 칼럼은 윈도우 플랫폼에서 x64 애플리케이션을 개발하는 경우를 상정한다. 그러니 Java용으로 개발된 Ant 같은 빌드 스크립트는 제외하자. C++ 세계에선 오래 전부터 빌드 자동화의 할아버지 make를 주로 써왔다. 물론 아직도 make의 위력은 건재하다. 하지만 신입 개발자일 적을 돌아보면 make는 너무 어려웠다. 복잡한 암호를 보는 듯한 기분이랄까? @$ 같은 기호가 난무하기 때문이었던 것 같다. 어쨌거나 make는 익히기 쉽지 않을뿐더러 최근에 개발된 XML형 빌드 스크립트와 비교하면 생산성이 떨어지는 문제도 있다. MSBuildNAnt는 다른 빌드 도구와 쉽게 연동이 된다. 무엇보다 빌드 결과를 XML 형태로 출력하거나 다른 도구가 뱉어낸 XML 출력 결과를 읽기 쉬워야 하는데, make는 이러한 부분이 부족하다.

 

[목록 1] make 예제(출처. Wikipedia)

  1. helloworld: helloworld.o

  2. cc -o [email protected] $<

  3. helloworld.o: helloworld.c

  4. cc -c -o [email protected] $<

  5. .PHONY: clean

  6. clean:

  7. rm -f helloworld helloworld.o

 

닷넷 프레임워크가 출시되고 나서 윈도우 세계에선 NAnt(http://nant.sourceforge.net/)MSBuild가 차세대 빌드 스크립트로 떠올랐다. NAnt는 더 오래됐고 그만큼 다양한 기능을 제공한다. 하지만 NAnt는 사실상 개발이 중지되거나 개발 속도가 느리다. 200712월에 0.86 베타 1이 출시된 게 고작이니 말이다.

 

그에 비해 MSBuild는 출시 이후 꾸준하지만 빠른 성장을 보였다. “추천 도구 절에서 소개할 MSBuildTasks 같은 MSBuild 확장 라이브러리도 나와있어 기본적으로 제공되지 않는 기능을 쉽게 추가할 수 있으며, 무엇보다 마이크로소프트 사가 직접 개발하는 빌드 도구인 만큼 비주얼 스튜디오 같은 개발 도구와의 연동도 비교적 쉽다.

 

이러한 이유 때문에 이 칼럼에선 MSBuild를 주 빌드 스크립트로 활용할 것이며, 부족한 부분은 MSDOS 배치 파일 등을 통해 보충할 생각이다.

 

 

MSBuild로 비주얼 스튜디오 솔루션(.sln)을 빌드하기

MSBuild 바이너리는 닷넷 프레임워크와 함께 배포되며 보통 C:WINDOWSMicrosoft.NETFrameworkv2.0.50727msbuild.exe 같은 경로를 취한다. 이때 해당 소스 코드가 참조하는 닷넷 프레임워크 버전에 맞춰 경로를 잡아야 한다.64비트 컴파일을 할 거라면Framework가 아닌 Framework64를 선택해야 한다.

 

 

64비트 빌드엔 MSBuild가 필수다

윈도우 프로젝트 필수 유틸리티란 책에는 다음과 같은 대목이 있다.

 

<devenv> 를 사용하면 Release 모드와 Debug 모드를 구분해서 빌드할 수 있지만, 32, 64비트 빌드를 구분해서 할 수는 없습니다. <devenv>에서 64비트 빌드를 하려면 프로젝트 설정에서 Win32 플랫폼은 삭제하고 x64만 남겨두어야 합니다. VCBuildRelease 모드와 Debug 모드를 구분해서 빌드할 수 있고, 32, 64비트 빌드를 구분하여 할 수 있습니다.

 

여기서 말하는 devenv 엘레멘트는 CruiseControl .NET의 설정 파일인 ccnet.config에서 사용된다. 우선 책에서 말한 내용이 사실인지 검증부터 해보자.

 

[목록 2] CCNet.config의 비주얼 스튜디오 태스크

  1. <devenv>

  2. <solutionfile>srcMyProject.sln</solutionfile>

  3. <configuration>Debug</configuration>

  4. <buildtype>Build</buildtype>

  5. <project>MyProject</project>

  6. <executable>c:program filesMicrosoft Visual Studio .NETCommon7IDEdevenv.com</executable>

  7. <buildTimeoutSeconds>600</buildTimeoutSeconds>

  8. <version>VS2002</version>

  9. </devenv>

 

CruiseControl .NET 홈페이지에서 문서의 “Visual Studio Task”라는 항목을 확인하면, [목록 2]과 같은 전체 예제가 있다. 예제를 보면 <devenv> 엘레멘트 아래에 7가지 엘레멘트가 쓰인다. 솔루션 파일을 지정하고(solutionfile), DEBUG 빌드인지 RELEASE 빌드인지 구분하고(configuration), Rebuild, Build, Clean 중 어떤 빌드 유형을 선택할 건지 고른다(buildtype). 그러나 어디에도 플랫폼, 그러니까 32비트 빌드인지 64비트 빌드인지 구분하는 XML 엘레멘트(Element)가 보이지 않는다. 그러니 책에서 말한 대로 <devenv> 엘레멘트를 써선 안 된다.

 

다행스럽게도 이 문제는 MSBuild를 쓰면 쉽게 해결된다. MSBuild 스크립트에서 윈도우 솔루션 파일을 빌드하고, ccnet.config에선 이 스크립트를 실행시키면 된다., 몇 가지 주의할 점이 있다.

 

 

C# 프로젝트는 쉽다

C# 같은 순수 닷넷 프로젝트라면 빌드 자동화가 쉽다. 지속적인 통합 5편에서 언급한 바 있듯, C# 프로젝트 파일(.csproj) 및 솔루션 파일(.sln)은 그 자체가 MSBuild 스크립트에 불과하다. 그래서 다음과 같이 ccnet.config를 구성해도 된다.

 

  1.  <tasks>

  2. <msbuild>

  3. <executable>C:WINDOWSMicrosoft.NETFrameworkv2.0.50727MSBuild.exe</executable>

  4. <workingDirectory>C:srcMyProject -mainbuildtrunkserversrcConsoleApplication </workingDirectory>

  5. <projectFile>ConsoleApplication .sln</projectFile>

  6. <buildArgs>/noconsolelogger /p:Configuration=Debug;Platform=Win32 /v: minimal</buildArgs>

  7. <logger>C:Program Files (x86)CruiseControl.NETserverThoughtWorks.CruiseControl.MSBuild.dll</logger>

  8. </msbuild>

  9. </tasks>

 

 

C++ 프로젝트는 특별하다

안타깝게도 MSBuild 바이너리는C++ 프로젝트는 빌드하지 못한다. 단 별도의MSBuild 스크립트를 만들면 방법이 있다. MSBuild<msbuild>란 태스크가 있는데, 여기에 솔루션 파일 이름을 적어주면 대부분 빌드가 된다. 한데 msbuild 태스크는 비주얼 스튜디오 바이너리(devenv.exe)를 호출하지 않고, VCBuild 바이너리를 호출한다. 이는 IDE 종속성을 제거한다는 측면에서 바람직하지만, 안타깝게도 devenvVCBuild가 항상 똑같은 방식으로 작동하는 건 아니다. 특히 C++/CLI 기술을 쓰는 경우엔 비주얼 스튜디오로는 빌드가 되던 솔루션을MSBuild로 빌드하려면 실패하는 일이 잦다(참고. http://andromedarabbit.net/wp/cplusplus_cli_and_x64_and_msbuild/).

 

이땐, MSBuild<Exec> 태스크를 사용하여 Devenv를 직접 호출하면 된다. 비주얼 스튜디오 바이너리는 설정(Configuration)값과 플랫폼(Platform)값을 매개변수로 받기 때문에 앞서 윈도우 프로젝트 필수 유틸리티의 한 대목에서 소개한 문제도 발생하지 않는다.

 

CCNET에는 MSBuild용 로거가 있다. 이 로거는 MSBuild 스크립트에서 <msbuild> 태스크를 사용하여 솔루션 파일을 빌드할 때 가장 잘 작동한다. 하지만 다행스럽게도 <Exec> 태스크를 사용해도 이 로거는 작동한다. 단지, 로그 상세 정도를 지정하는 옵션(Verbose)은 제대로 동작하지 않기 때문에, 웹 대시보드에서 표시되는 로그가 상당히 길어지기 마련이다. 하지만 빌드가 깨졌을 때만 웹 대시보드에 들리게 되니 마우스 스크롤의 불편함 정도는 감수할만하다.

 

64비트로 빌드하기

이제 MSBuild를 활용해 64비트 Visual C++ 프로젝트를 빌드해보자. x64 빌드를 잘 다룰 수 있다면 WIN32 빌드는 쉽다. 사실 우리는 32비트 환경에 익숙하기 때문에 32비트 빌드 자동화는 크게 문제되지 않는다. 문제는 x64 빌드인데, 익숙하지 않은 환경일뿐더러 64비트 WOW 환경에선 32비트 에뮬레이션이 가능하므로 헷갈릴만한 요소가 많다. 그러니 64비트 빌드 자동화부터 익숙해져 보자.

 

64비트 MSBuild인가?

64비트 운영체제엔 두 개의 MSBuild가 있다. C:WINDOWSMicrosoft.NETFrameworkv2.0.50727msbuild.exeC:WINDOWSMicrosoft.NETFramework64v2.0.50727msbuild.exe인데, 자세히 보면 64란 숫자가 붙어있다. 한 마디로 말해 32비트용MSBuild이냐, 64비트용 MSBuild이냐라는 차이가 있다.

 

논의를 좀더 진행하기에 앞서 비주얼 스튜디오 2005를 잠시 살펴보자. Visual Studio 2005 x64란 키워드로 구글링해봐야 64비트용 비주얼 스튜디오를 찾을 수는 없다. 32비트용 비주얼 스튜디오만 있는데, 32비트용이라고 해도 32비트와 64비트 컴파일 모두 지원한다.

 

이와 마찬가지로 32비트용 MSBuild를 사용해도64비트 VC++ 프로젝트를 빌드하는 데 아무런 문제도 없다. 그렇다면 왜 64비트용을 굳이 써야 하는가?

 

32비트용 MSBuild64비트 VC 프로젝트와 64비트 .NET 프로젝트를 함께 빌드하려고 하면 문제가 발생한다. VC 프로젝트는 빌드가 되지만, 닷넷 프로젝트는 빌드가 안 된다. 곰곰이 생각해보면 뭔가 이상하다. 32비트용인 비주얼 스튜디오로 64비트 닷넷 프로젝트를 빌드할 수 있는데, MSBuild만 왜 난리를 치는가? 고민해봤지만 결국 원인을 파악하지 못했다. 환경 설정을 바꿔가며 테스트해봤지만 결과는 같았다.

 

결론1. 64비트 빌드를 하려면 64비트 MSBuild를 사용하라.

 

 

64비트 MSBuild에 문제는 없는가?

64비트용 MSBuildVC 프로젝트를 빌드하다가 괴상한 오류와 마주쳤다.

 

  1. C2259: “cannot instantiate abstract class”

 

이 오류는 Visual Studio 2005의 버그였는데1, 서비스 팩 1에서 고쳐졌다. 하지만 64비트용 vcbuild.exe는 깜박 잊고 손보지 않았는지, 이 오류 메시지가 출력됐다. 역시나 환경 설정을 잘못했나 살펴봤지만, 그다지 이상한 점이 눈에 띄지는 않았다.

 

다행히 이 오류는 컴파일러 파서가 삽질할 때 나기 때문에 원래 소스 코드에 의미상 아무런 차이도 없는 캐스팅을 붙여줌으로써 문제를 해결했다. 물론 32비트 빌드할 때와 64비트 빌드할 때, 서로 다른 버전의 컴파일러를 쓴다는 점이 마음에 걸리긴 한다.

 

결론 2. 32비트 vcbuild.exe에 적용된 핫픽스가 64비트 vcbuild에는 적용 안 됐다.

 

환경 설정 불러내기

비주얼 스튜디오 2005가 깔려 있다고 가정하고 이야기를 진행하자. 보통 32비트 빌드를 할 때는 이런 식으로 배치 파일을 만든다.

 

[목록 3] msbuild-win32.bat

  1. call “&#xVS;80COMNTOOLS%”vsvars32.bat

  2. msbuild buildall.msbuild /t:Build

 

64비트 빌드할 때는 이렇게 하면 된다.

 

[목록 3] msbuild-x64.bat

  1. @echo off

  2. call “C:Program Files (x86)Microsoft Visual Studio 8VCvcvarsall.bat” x64

  3. call “C:Program FilesMicrosoft.NETSDKv2.0 64bitBinsdkvars.bat”

  4. “C:WINDOWSMicrosoft.NETFramework64v2.0.50727msbuild.exe” /t:Build

 

CruiseControl .NET 연동하기

[목록 4] CCNet.configMSBuild 태스크

  1. <msbuild>

  2. <executable>C:WINDOWSMicrosoft.NETFramework64v2.0.50727msbuild.exe</executable>

  3. <workingDirectory>C:srcsrc</workingDirectory>

  4. <projectFile>build.msbuild</projectFile>

  5. <buildArgs>/noconsolelogger /p:Configuration=Debug /v:m</buildArgs>

  6. <targets>Build</targets>

  7. <timeout>12000</timeout>

  8. <logger>C:Program Files (x86)CruiseControl.NETserverThoughtWorks.CruiseControl.MSBuild.dll</logger>

  9. </msbuild>

 

일반적인 CCNet.config 설정은 위와 같다. 하지만 환경 설정 값을 불러내야 하기 때문에 곧바로 msbuild.exe를 호출해선 안 된다. 우선 MsBuild_x64_ForCI.bat 란 배치 파일을 만든다.

 

[목록 5] MsBuild_x64_ForCI.bat

  1. @echo off

  2. call “C:Program Files (x86)Microsoft Visual Studio 8VCvcvarsall.bat” x64

  3. call “C:Program FilesMicrosoft.NETSDKv2.0 64bitBinsdkvars.bat” >>

  4. “C:WINDOWSMicrosoft.NETFramework64v2.0.50727msbuild.exe” %*

 

그러고 나서 CCNet.config 파일을 수정해주면 된다.

 

[목록 6] 올바른 CCNet.config 설정

  1. <msbuild>

  2. <executable>MsBuild_x64_ForCI.bat</executable>

  3. <workingDirectory>C:srcsrc</workingDirectory>

  4. <projectFile>build.msbuild</projectFile>

  5. <buildArgs>/noconsolelogger /p:Configuration=Debug /v:m</buildArgs>

  6. <targets>Build</targets>

  7. <timeout>12000</timeout>

  8. <logger>C:Program Files (x86)CruiseControl.NETserverThoughtWorks.CruiseControl.MSBuild.dll</logger>

  9. </msbuild>

 

개선하기

여기서 모든 게 끝났다고 생각하면 오산이다. CruiseControl .NET 빌드 파일을 보면 뭐가 문제인지 알 수 있다.

 

  1. <cruisecontrol>

  2. <msbuild startTime=”10/30/2007 16:43:39″ elapsedTime=”00:00:12″ success=”true”>

  3. <project name=”Build” file=”C:srcbuild.msbuild” success=”true”>

  4. <project name=”Rebuild” file=”C:srcProjects.sln” success=”true”>

  5. <message level=”high”><![CDATA[MockClientForMaster -> C:srccuckoo-x64-releaseserversrcTestServicesMockClientForMasterbinx64ReleaseMockClientForMaster.exe]]></message>

  6. </project>

  7. </project>

  8. </msbuild>Setting environment for using Microsoft Visual Studio 2005 x64 tools.

  9. Setting environment to use Microsoft .NET Framework v2.0 SDK tools.

  10. For a list of SDK tools, see the ‘StartTools.htm’ file in the bin folder.

  11. </cruisecontrol>

 

환경 설정을 불러내면서 콘솔에 메시지가 출력되는 바람에 CCNet 로그가 엉망이 되어 버렸다. 이 문제는MsBuild_x64_ForCI.bat 배치 파일을 조금만 손보면 해결할 수 있다.

 

  1. @echo off

  2. call “C:Program Files (x86)Microsoft Visual Studio 8VCvcvarsall.bat” x64

  3. > _temp.txt

  4. call “C:Program FilesMicrosoft.NETSDKv2.0 64bitBinsdkvars.bat” >> _temp.txt

  5. “C:WINDOWSMicrosoft.NETFramework64v2.0.50727msbuild.exe” %*

 

콘솔 출력을 텍스트 파일로 리다이렉트시켰다. 이로써 모든 게 끝났다.

 

추천 문서

 

 

추천 도구

MSBuildTasks

MSBuildTasks(http://msbuildtasks.tigris.org/)MSBuild의 확장팩이라고 생각하면 된다. 스타크래프트 브루드워 같은 것이랄까? 주요 기능을 살펴보면 간략하게 이렇다.

 

  • 레지스트리 조작 (RegistryRead, RegistryWrite)
  • C# 스크립트 실행 (Script)
  • 파일 업로드 (FileUpdate, FtpUpload)
  • SQL 실행 (ExecuteDDL)
  • 이메일 전송 (Mail)
  • NDoc 및 NUnit 지원
  • 정규표현식 (RegexMatch, RegexReplace)
  • 서브버전 및 Visual SourceSafe 제어 (SvnCheckout, SvnClient, SvnCommit 등)
  • 파일 압축 (Unzip, Zip)
  • XML 읽고 쓰기 (XmlRead, XmlWrite 등)

 

레지스트리 조작과 C# 스크립트 실행 기능은 ODBC DSN을 등록할 때 써보았고, ExecuteDDL은 단위 테스트 전에 데이터베이스 스키마를 생성할 때 썼다. 서브버전 제어 기능을 이용해 특정 소스 코드가 변했을 때만 다시 빌드(Rebuild)를 하는 논리를 구성하기도 했다. 기회가 닿는 대로 이 모든 사례에 대해 알아보도록 하겠다.

 

MSBuildShellExtension

MSBuildShellExtension(http://www.codeplex.com/msbuildshellex)Visual Studio에서 곧바로 MSBuild 스크립트를 실행시키게 해준다. 스크린 샷을 보면 어떤 도구인지 한눈에 알 수 있다.

 MSBuildShellExtension.png

Visual Studio가 제공하는 빌드 이벤트 같은 것에 의존하지 않으려면 이것도 좋다.

 

끝마치는 말

이번 시간엔 64비트 빌드 자동화의 기본을 익혔다. 이제부턴 응용만 남았다. 응용은 어려워 보여도 실은 기초가 갖춰지면 응용은 아무것도 아니다. 하지만 , 이런 것도 가능하구나라는 가능성을 보면 빌드 스크립트의 도입을 설득하고 추진하기 쉬워질 것이다.

 

 

 

이 글은 스프링노트에서 작성되었습니다.

Advertisements

최 재훈

블로그, 페이스북, 트위터 고성능 서버 엔진, 데이터베이스, 지속적인 통합 등 다양한 주제에 관심이 많다.
Close Menu