서론
이 장은 CMake를 다루는 방법에 대해 소개한다.
cmake에 대해 간단히 설명하면 Make라는 명령어로 Makefile을 처리해주는데 이때
이 Makefile을 생성해주는 프로그램 cmake이다
목차
- Example
- 명령어
- Compile option 지정
- Target, Property
- include path setting
- 다른 라이브러리를 사용하는 라이브러리
- 파일을 한꺼번에 추가
- Cmake 이외의 빌드 시스템 사용하기
Example
빌드하기 위한 예제 코드를 만든다. 파일이름은 main.cc
#include <iostream>
int main() {
std::cout << "Hello, CMake" << std::endl;
return 0;
}
아래와 같이 CMakeLists.txt를 작성한다. 꼭 필요한 것은 프로젝트이름(ModooCode)와 LANGUAGES다.
C인 경우 C로 표기하고 C++인 경우는 CXX이다.
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)
# 프로젝트 정보
project(
ModooCode
VERSION 0.1
DESCRIPTION "예제 프로젝트"
LANGUAGES CXX)
add_executable (program main.cc)
CMakeLists.txt는 작업하는 최상단 디렉토리에 있어야 한다.
또한 여러 캐시파일들이 생성됨으로 아래와 같이 build폴더를 만들어주어 해당 폴더 내에서 작업한다.

cmake ..
위 명령어를 사용하면 아래와 같이 build폴더 내에 다음과 같이 Makefile등이 생기며 prgram을 실행시킬 수 있다.

명령어
생성할 실행 파일을 추가하는 명령 : add_executable
add_executable (<실행 파일 이름> <소스1> <소스2> ... <소스들>)
Compile option 지정
target_compile_options명령을 사용하면 컴파일 옵션을 지정할 수 있다.
target_compile_options(<실행 파일 이름> PUBLIC <컴파일 옵션1> <컴파일 옵션2> ...)
아래 예시를 봐보자
target_compile_options(program PUBLIC -Wall -Werror)
program 을 빌드할 때 컴파일 옵션으로 -Wall (모든 경고 표시) 과 -Werror (경고는 컴파일 오류로 간주) 을 준다는 의미다.
Target, Property
target: 프로그램을 구성하는 요소들
CMake 명령은 그냥 타겟을 정의하고 (add_executable와 같이), 해당 타겟들의 속성을 지정하는 명령들 (target_compile_options처럼) 로 이루어진 것.
include path setting
CMakeLists.txt에 includes디렉토리를 헤더 파일 경로 탐색 시 확인해라 라고 알려줘야 한다.
${CMAKE_SOURCE_DIR}/includes를 헤더 파일 탐색 경로에 추가한다.
❗ CMake 에서 디렉토리의 경로를 지정할 때 왠만하면 절대 경로를 쓰지 않는 것이 좋다.
target_include_directories(program PUBLIC ${CMAKE_SOURCE_DIR}/includes)
${CMAKE_SOURCE_DIR}은 CMake 에서 기본으로 제공하는 변수로, 최상위 CMakeLists.txt,
즉 cmake ..할 때 읽어들이는 CMakeLists.txt의 경로를 의미한다.(프로젝트 경로)
${CMAKE_SOURCE_DIR}/includes는 현재 프로젝트 경로 안에 includes디렉토리다.
target_include_directories(<실행 파일 이름> PUBLIC <경로 1> <경로 2> ...)
Example
C++ 라이브러리의 경우 헤더와 소스를 따로 분리한다.
그 이유는 라이브러리를 사용할 경우 라이브러리의 구현 부분을 참조할 필요는 없지만 헤더는 꼭 참조해야 하기 때문이다. 따라서 구현 부분을 lib안에, 헤더 파일은 includes에 따로 뺀다.
lib/
shape.cc
#include "shape.h"
Rectangle::Rectangle(int width, int height) : width_(width), height_(height) {}
int Rectangle::GetSize() const {
// 직사각형의 넓이를 리턴한다.
return width_ * height_;
}
includes/
shape.h
class Rectangle {
public:
Rectangle(int width, int height);
int GetSize() const;
private:
int width_, height_;
};
lib 폴더 내의 CMakeLists.txt
# 정적 라이브러리 shape을 만든다
add_library(shape STATIC shape.cc)
# 해당 라이브러리 컴파일 시 사용할 헤더파일 경로
target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)
# 해당 라이브러리를 컴파일 할 옵션
target_compile_options(shape PRIVATE -Wall -Werror)
먼저 add_library명령을 통해서 만들어낼 라이브러리 파일을 추가한다. add_library의 사용법은 간단하다.
중간에 어떠한 형태의 라이브러리를 만들지 설정할 수 있다.
STATIC: 정적 라이브러리SHARED: 동적 라이브러리MODULE: 동적으로 링크되지는 않지만,dlopen과 같은 함수로 런타임 시에 불러올 수 있는 라이브러리를 생성
add_library (<라이브러리 이름> [STATIC | SHARED | MODULE ] <소스 1> <소스 2> ...)
아래 명령어는 다음을 의미한다.
shape를 컴파일 할 때 헤더 파일 검색 경로에 ${CMAKE_SOURCE_DIR}/includes를 추가하라.shape를 참조 하는 타겟의 헤더 파일 검색 경로에 ${CMAKE_SOURCE_DIR}/includes를 추가하라.
target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)
라이브러리를 컴파일 하는 옵션은 아래와 같다. 옵션에 PRIVATE으로 설정되어 있는걸 볼 수 있는데, 그 이유는 shape를 빌드할 때에는 -Wall과 -Werror옵션을 사용하고 싶지만, shape를 사용하는 애들에게까지 이 옵션을 강제하고는 싶지 않기 때문이다.
target_compile_options(shape PRIVATE -Wall -Werror)
프로젝트 레벨 CMakeLists.txt
add_subdirectory명령을 통해서 CMake 가 추가로 확인해야 할 디렉토리의 경로를 지정한다.
그러면 CMake 실행 시에, 해당 디렉토리로 들어가서 그 안에 있는 CMakeLists.txt실행한다.
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)
# 프로젝트 정보
project(
ModooCode
VERSION 0.1
DESCRIPTION "예제 프로젝트"
LANGUAGES CXX)
# 확인할 디렉토리 추가
add_subdirectroy(lib)
add_executable (program main.cc)
# program에 shape를 링크
target_link_libraries(program shape)
그리고 아래 명령어는 program을 빌드 할 때 shape라이브러리를 링크 시킨다.
실행 파일은 PUBLIC이냐 PRIVATE이냐의 여부가 크게 중요하지는 않다.
왜냐하면 실행 파일을 다른 타겟이 참조할 수 는 없기 때문이다.
target_link_libraries(program shape)
그래서 다음과 같이 사용가능하다.
target_link_libraries(program shape)
그리고 main.cc는 아래와 같이 만든다
#include <iostream>
#include "shape.h"
int main(){
Rectangle r(2, 15);
std::cout << "Get Size : " << r.GetSize() << std::endl;
return 0;
}
따라서 최종 디렉토리는 아래와 같다.
$ tree
├── CMakeLists.txt
├── includes
│ └── shape.h
├── lib
│ ├── CMakeLists.txt
│ └── shape.cc
└── main.cc
실행하면 build디렉토리를 확인해보면 libshape.a파일이 생긴 것을 볼 수 있다. 라이브러리를 만들게 되면 CMake는 앞에 lib을 붙인 라이브러리 파일을 생성한다.
다른 라이브러리를 사용하는 라이브러리
예를 들어서 우리의 Shape라이브러리에서 thread라이브러리를 사용한다고 하자.
#include <iostream>
#include <thread>
#include "shape.h"
Rectangle::Rectangle(int width, int height) : width_(width), height_(height) {}
int Rectangle::GetSize() const {
std::thread t([this]() { std::cout << "Calulate .." << std::endl; });
t.join();
// 직사각형의 넓이를 리턴한다.
return width_ * height_;
}
리눅스의 경우 보통 thread라이브러리를 사용하려면 pthread라이브러리를 링크시켜줘야 한다.
따라서 아래와 같이 shape의 CMakeLists.txt를 수정해줘야 한다.
add_library(shape STATIC shape.cc)
target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)
target_compile_options(shape PRIVATE -Wall -Werror)
# 추가 명령어
# pthread 라이브러리를 링크
target_link_libraries(shape PRIVATE pthread)
target_link_libraries 를 통해서 shape 에 pthread 라이브러리를 추가해준다.
target_link_libraries 로 의존 라이브러리(Dependency) 를 추가할 때 추가하는 방식이 세 가지가 있다. 다음과 같은 가이드라인을 따르면 좋다.
만일 어떤 라이브러리 A 를 참조한다고 할 때
A를 헤더 파일과 내부 구현에서 모두 사용한다면 :PUBLICA를 내부 구현에서만 사용하고 헤더 파일에서는 사용하지 않는다면 :PRIVATEA를 헤더 파일에서만 사용하고 내부 구현에서는 사용하지 않는다면 :INTERFACE
위 경우 <thread> 를 내부 구현 (shape.cc) 에서만 사용하고 헤더 파일 (shape.h) 에서는 사용하고 있지 않는다. 따라서 이 경우 pthread 를 PRIVATE 으로 링크해주는 것이 맞습니다. 이를 통해서 shape 를 사용하는 다른 라이브러리가 불필요하게 pthread 를 링크해주는 일을 막을 수 가 있다.
파일을 한꺼번에 추가
cmake에서 타겟을 빌드하는데 필요한 소스파일을 명시하려면 아래와 같았다.
아래 명령어는 파일들이 새로 추가할 때 마다 위 add_library를 수정해줘야 한다.
add_library(shape STATIC shape.cc color.cc circle.cc)
이 디렉토리에 있는 파일들을 모두 이 라이브러리를 빌드하는데 사용해줘!라고 명령할 수 있는 방법을 제공한다. file명령의 GLOB_RECURSE옵션은, 인자로 주어진 디렉토리와 해당 디렉토리 안에 있는 모든 하위 디렉토리 까지 재귀적으로 살펴본다는 의미다.
${CMAKE_CURRENT_SOURCE_DIR}: CMake에서 기본으로 제공하는 변수로 현재 CMakeLists.txt가 위치한 디렉토리, 즉 현재 디렉토리를 의미한다.
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/*.cc
)
add_library(shape STATIC ${SRC_FILES})
위 명령은 현재 디렉토리 안에 있는 모든 .cc 로 끝나는 파일들 (하위 디렉토리 포함)을 나타낸다.
그리고 해당 파일들을 모두 모아서 SRC_FILES라는 변수를 구성하라는 의미다.
(하위 디렉토리를 포함하고 싶지 않다면 GLOB_RECURSE대신에 GLOB을 주면 된다.)
CONFIGURE_DEPENDS: 만약에 GLOB으로 불러오는 파일 목록이 이전과 다를 경우 (예를 들어서 파일을 추가하거나 지웠을 때) CMake 를 다시 실행해서 빌드 파일을 재생성 하라는 의미가 된다.
따라서 만약에 디렉토리 안에 파일이 추가 되더라도, cmake ..을 다시 실행할 필요 없이 그냥 make만 실행해도 CMake 가 다시 실행되면서 빌드 파일을 재작성 한다.
그러면 SRC_FILES 변수 안에 파일들의 목록이 쭈르륵 들어가 있으므로 아래 명령어를 입력하면
shape 를 빌드하는데 필요한 파일들을 모두 지정할 수 있다.
add_library(shape STATIC ${SRC_FILES})
Cmake 이외의 빌드 시스템 사용하기
요새 많이 사용되는 Ninja를 사용하고 싶을 수 있고, 아니면 비주얼 스튜디오를 사용할 경우 비주얼 스튜디오용 빌드 파일을 생성해야 한다.
아래와 같은 명령어를 사용하면 된다.
$ cmake .. -DCMAKE_GENERATOR=Ninja
가지 중요한 점은 이미 빌드 시스템을 설정하였다면 바꿀 수 없다는 것 새 디렉토리를 만들어서 CMake 명령을 다시 실행하거나, 기존 디렉토리 안의 파일들을 모두 지워야 한다.
Comment