CMake를 이용한 C/C++ 프로젝트 빌드

Makefile이라는 것을 한 번 쯤은 다들 들어보셨을 것이라 생각합니다. 


Visual Studio IDE 도구를 이용하면 Makefile을 오히려 보기 힘들 수 있겠지만, Unix, Linux에서 C, C++ 프로그래밍을 하셨다거나 크로스 플랫폼으로 프로그래밍을 하신 분들이라면, 한 번 쯤은 다들 접해보셨을 것입니다.


Makefile

이 포스트는 CMake에 대한 포스트이지만, 그 전에 Makefile이란 것에 대해서 몇 가지 적어보고자 합니다.


혹시 저의 블로그에서 OpenCV 설치와 관련된 포스트를 보신 분이라면, 아마 이런 내용을 보셨을 것입니다.


$ make -j $(nproc)
$ sudo make install

여기서 중점적인 커맨드는 make 명령어입니다. 사실 make 명령어가 들어간 .sh 파일을 실행하셔서 직접 수동 설치하는 프로그램도 있습니다만. 대부분 모태는 make 명령어를 기반으로 처리합니다.


Windows에서도 Mingw나 Cygwin을 사용해서 GNU Make for Windows 프로그램을 이용해 make 명령어를 사용하여 수동으로 설치하실 수도 있기 때문에, 혹시 Windows 사용자라면, 이 부분을 참고해보셔서 한 번 실습해보시는 것도 나쁘지 않을 것 같습니다.


본론으로 넘어가서, Makefile이 무엇인지 알아보겠습니다. Makefile은 여러분들이 C, C++ 프로그래밍을 할 때, 미리 빌드 환경을 조성해놓는 일종의 파일입니다. 그림으로 한 번 보도록 하죠.


그저 프로젝트 하나 작성했을 뿐인데, Makefile에 엄청난 내용을 도배해버린 CLion....


위 사진은 우리가 C, C++ 코드를 작성한 후, 빌드를 하게 되면, 컴파일러가 하게 되는 가장 1번째 과정입니다. 바로 object 파일을 생성하는 것이죠. Visual Studio나 CLion 등의 IDE 프로그램을 사용하면 키보드 단축키 혹은 마우스 클릭으로 이 모든 것이 자동으로 되지만, vim 에디터를 이용하거나 할 경우에는 직접 커맨드를 사용해야 합니다.


작성된 목적 코드는 한 곳으로 링크되어 최종적으로 바이너리 실행 파일이 되는 것입니다.


그럼 우리가 이를 커맨드로 직접 작성한다면, 어떻게 작성해야할까요?


$ g++ -c -o main.o main.cc
$ g++ -c -o histogram.o histogram.cc

사진의 소스 코드 파일은 C++ 코드이므로, g++ 커맨드를 사용하겠습니다.
(물론 확장자가 cc가 되었든, cpp가 되었든, c가 되었든 상관없이 안에 어떤 헤더 파일이나 코드를 썼다는 것에 따라 다르지만) 


위 두 과정은 각각의 코드를 컴파일만 하는 과정으로 최종적인 바이너리 파일은 생성되지 않습니다. 그럼 최종적으로 바이너리 파일을 만들기 위해서는 커맨드 한 개를 더 써야하는 것이 되겠죠?


$ g++ -o run main.o histogram.o

대충 이런 식입니다. 본래 main.cc 소스 코드가 한 개만 있었을 때는 수동 컴파일 과정도 굉장히 간단하지만, 위와 같이 소스 코드가 여러 묶음으로 되어 있는 경우에는 이야기가 다릅니다. (위에서도 2개가 끝이지만, 그것보다 더 많아진다면?, 뭐 그 때는 쉘 프로그래밍을 해야겠죠....;;)



Makefile의 중요성

대부분의 개발자들이 IDE 프로그램에만 익숙해져 있다 보니 이런 중요한 부분을 가끔 놓치기 마련입니다. 하지만, 임베디드프로그래밍을 한다 거나 GUI 환경을 사용할 수 없다거나, 이런 극한의 상황이 오면 어떻게 해야 할까요?


정석대로라면, 위 커맨드를 일일이 다 때려서 입력해야 합니다. 하지만 얼마나 번거로운가요... (이렇게나 슬플수가.)

아니면, 쉘 스크립트를 짜보도록 할까요?


#!/bin/sh
g++ -c -o main.o main.cc
g++ -c -o histogram.o histogram.cc
g++ -o run main.o histogram.o

음 이렇게 작성해도 나쁘지 않겠군요. 그런데, 왜 IDE 도구는 쉘 스크립트가 아닌 Makefile을 쓰는 것일까요? 


Makefile에는 쉘 스크립트가 가지고 있지 않은 Incremental build 기능이 있는 것입니다. 이 기능은 내 프로젝트의 수 많은 소스 코드 중 바뀐 부분만을 골라내서 재빌드하는 기능으로 서버 프로그래밍을 하거나 게임 프로그래밍과 같은 대규모 프로젝트의 프로그래밍에서는 굉장히 중요한 기능이기도 합니다.


기본적인 Makefile은 아래와 같이 작성됩니다.


CC=g++
CFLAGS=-g -Wall
OBJS=main.o histogram.o
TARGET=run
$(TARGET): $(OBJS)
    $(CC) -o $@ $(OBJS)
main.o: histogram.cc main.cc
histogram.o: histogram.cc

간단히 설명드리자면, CC는 개발자가 사용할 컴파일러 명령어을 변수로, FLAGS는 뒤에 들어가는 인자 값을 변수로, OBJS는 오브젝트 파일명을 변수로 나타낸 것입니다. 스크립트 언어랑 유사하기 떄문에 변수 선언에도 자료형과 무관하게 그냥 선언을 해주고, 사용할 때는 $를 붙여서 사용하면 됩니다.


그리고, 위 코드에서 사용한 변수는 make 프로그램에 내장되어 있는 내장 변수이므로, 참고 바랍니다.



CMake

그럼 CMake는 무엇일까요? 보다시피 간단한 Makefile의 작성은 쉬웠습니다. 하지만 라이브러리의 연동 등이나 파일이 커지게 될 경우, Makefile의 코드 수도 그만큼 증가하게 되며 프로그래머가 직접 관리하기가 너무 어려워지게 됩니다.


그래서 우리는 위 사진처럼 줄여서 쓰기로 했습니다. (헐... 이렇게 간단할수가)

JetBrains CLion 프로그램은 CMake를 사용합니다.



CMake 기본 사용법

먼저 CMake 코드를 작성해보도록 하겠습니다.

add_executable(run main.o histogram.o)

딱 이 한 줄만 작성해봅니다. 그리고 CMakeLists.txt 파일로 저장한 후, 아래 명령어를 입력하면...

$ cmake CMakeLists.txt

그럼 성공적으로 Makefile을 만들어줍니다.

그런데, 놀랍게도 Makefile 안에는 우리가 썼던 Add_Executable 외에 다른 여러가지가 자동으로 작성된 것을 보실 수 있습니다.


그래서 CLion 등의 IDE 프로그램에서도 CMakeLists.txt 파일만 한 번 수정하게 되면, 자동으로 Makefile이 바뀌게 되기 때문에 사실상 우리가 이렇게 복잡한 Makefile을 고치거나 바꾸는 일이 없이 쉽게 C, C++ 프로그래밍이 가능한 것입니다.



CMake 응용

그럼 CMake를 응용해서 라이브러리의 추가 등을 한 번 알아보겠습니다.

예를 들어, 우리가 C++에서 Boost 라이브러리를 사용하려면, 아래의 커맨드를 사용해야 합니다.

g++ main.cc -o run -l boost_thread -l boost_system

소스 코드가 main.cc 파일이라고 할 때, Boost 라이브러리에서 Thread와 system을 사용하고자 한다면, l 인자 값을 사용해 라이브러리를 링크해줘야 합니다. 


cmake_minimum_required(VERSION 3.6)

# 공통 라이브러리, 패키지
find_package(Boost 1.60 COMPONENTS system filesystem thread REQUIRED)
include_directories($(Boost_INCLUDE_DIRS))

target_link_libraries(hoGarden ${SOURCE_FILES})

덧붙여서 filesystem까지 사용하려고 한다면, find_package에 filesystem을 넣어주면 됩니다. 

그 외에 컴파일 옵션을 공통으로 주거나 프로젝트 버전을 명시하는 등 다양한 코드를 작성하실 수도 있습니다.


cmake_minimum_required(VERSION 3.6)

# 프로젝트 정보.
project(hoGarden)
set(PROJECT_VERSION_MAJOR 0)
set(PROJECT_VERSION_MINOR 1)

# CMake C++11
set(CMAKE_CXX_STANDARD 11)

# 컴파일러
set(CMAKE_C_COMPILER "g++")

# 링크 라이브러리
LINK_LIBRARIES(boost_thread boost_asio)

# 컴파일 옵션
ADD_COMPILE_OPTIONS(-g -Wall)
set(CMAKE_EXE_LINKER_FLAGS "-static -Wl, --gc-sections")

# 공통 헤더 파일, 패키지
find_package(Boost 1.60 COMPONENTS system filesystem thread REQUIRED)
include_directories($(Boost_INCLUDE_DIRS))

set(SOURCE_FILES main.cpp)
add_executable(hoGarden ${SOURCE_FILES})

target_link_libraries(hoGarden ${SOURCE_FILES})


마치며...

IDE 프로그램만을 사용하다가 make를 사용하게 되니 이런저런 헷갈리고, 어려운 구석이 많아 블로그에 정리하게 되었습니다.


아무래도 바이너리 파일을 사용해서 쉽고 간단하게 설치 패키지가 제공된다면 더할 없이 좋겠지만, 오픈 소스 프로그램을 직접 짜보고, 수정하고, 테스트할 때 (특히 C, C++ 언어)만큼은 IDE 프로그램을 사용하기 보다는 직접 make 명령어를 사용해 테스팅 환경을 구축 해야하는 경우가 빈번히 생기게 됩니다.


무엇보다 이런 프로젝트의 설정 방법을 알아두면, 나중에 일일이 찾아 컴파일하지 않아도 한 번 작성해놓으면 여러모로 편리해지니 알아두는 것도 나쁘지 않다 생각해봅니다.


TAGS. ,
comments powered by Disqus

Tistory Comments 0