CMake
- 빌드 파일을 생성해주는 프로그램 ( = Makefile, .ninja를 자동으로 생성해주는 프로그램)
- Makefile은 간단한 프로젝트를 관리하기 좋지만, 프로젝트 크기가 커지거나 여러 플랫폼에서 배포할 때 불편한 점이 많음
- CMake를 사용 시 프로젝트 최상위 디렉토리에 CMakeLists.txt 파일이 있어야 함
- 해당 파일에는 반드시 "CMake 프로그램의 최소 버전" 과 "프로젝트 정보"가 포함되어야 함
- 주석은 '#' 으로 작성
#기본 템플릿
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)
# 프로젝트 정보
project(
ModooCode
VERSION 0.1
DESCRIPTION "예제 프로젝트"
LANGUAGES CXX)
# 라이브러리 파일은 빌드 디렉토리 안에 lib 폴더에 출력.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 실행 파일은 빌드 디렉토리 안에 bin 폴더에 출력.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(app)
add_subdirectory(lib)
add_subdirectory(tests)
MinGW를 기반으로 빌드를 한다면 아래와 같이 명령어 입력
$ cmake {CMakeLists.txt가 위치한 경로} -G "MinGW Makefiles"
mac에 cmake 설치
brew install cmake
CMakeLists.txt
실행 파일 만들기
1) /foo.h , /foo.cpp , /main.cpp
// /foo.h
int foo();
// /foo.cpp
#include "foo.h"
int foo() { return 3; }
#include <iostream>
#include<stdlib.h>
#include "foo.h"
using namespace std;
int main() {
cout << "Foo : " << foo() << "\n";
system("pause"); // for check console
return 0;
}
2) /CMakeLists.txt
- CMake에서 실행 파일을 생성하기 위해 add_executable 사용
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.5.1)
# 프로젝트 정보
project(Test)
# 생성할 실행 파일을 추가하는 명령
add_executable (program main.cpp foo.cpp)
3) /build
$ mkdir build
$ cd build
$ cmake .. -G "MinGW Makefiles"
$ make
$ start program.exe
빌드 디렉토리를 만든후 해당 디렉토리에서 CMak 실행하는 이유
CMake 는 실행시 여러가지 파일들 (캐시 용도로) 을 생성하는데 이 때문에 프로젝트 디렉토리가 난장판이 될 수 있기 때문!!
( + 이미 있는 파일들을 덮어씌우는 문제가 발생할 수 있음)
컴파일 옵션 지정하기
- target_compile_options 을 사용하여 지정
target_compile_options(program PUBLIC -Wall -Werror)
-Wall (모든 경고 표시)
-Werror (경고는 컴파일 오류로 간주)
Target 과 Property
- 타겟 (Target) : 프로그램을 구성하는 요소들 (실행파일, 라이브러리 등)
- 속성 (Property)
모든 CMake 명령은 타겟을 정의하고(add_executable 와 같이), 해당 타겟들의 속성을 지정하는 명령들(target_compile_options 처럼)로 이루어짐
include 경로 지정하기
- foo.h 가 includes/ 폴더 안에 있는 경우
- CMakeLists.txt 에 includes 디렉토리를 헤더 파일 경로 탐색 시 확인하라고 알려줘야 함
- {CMAKE_SOURCE_DIR} : CMake에서 기본으로 제공하는 변수로, 최상위 경로(=프로젝트 경로)를 의미
$ tree
├── CMakeLists.txt
├── foo.cpp
├── includes
│ └── foo.h
└── main.cpp
target_include_directories(program PUBLIC ${CMAKE_SOURCE_DIR}/includes)
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.5.1)
# 프로젝트 정보
project(Test)
# 생성할 실행 파일을 추가하는 명령
add_executable (program main.cpp foo.cpp)
# 컴파일 옵션
target_compile_options(program PUBLIC -Wall -Werror)
# includes 경로를 알려줌
target_include_directories(program PUBLIC ${CMAKE_SOURCE_DIR}/includes)
target_compile_options, target_include_directories 명령어를 사용하기 위해서는 해당 타겟이 먼저 생성되어야 함
따라서, target_compile_options와 target_include_directories 명령어는 해당 타겟이 생성된 후에 작성되어야 올바르게 동작함.
CMake에서 디렉토리의 경로를 지정할 때 왠만하면 절대 경로를 쓰지 않는 것이 좋음
CMak 의 가장 큰 장점이 "여러 플랫폼에서 사용할 수 있다"는 것인데, 절대 경로로 박아 놓으면 다른 시스템에서 사용할 수 없기 때문
라이브러리 만들기
- 라이브러리로 쪼개서 관리하면, 바뀌지 않은 부분은 컴파일 하지 않아도 되서 (링킹만 하면 됨) 개발 속도가 빠름
- 라이브러리의 각 요소들을 전체를 한꺼번에 묶어 놓았을 때 보다 테스팅 하기 용이함
make로 작업한 경우
- 외부 라이브러들이 잘 설치되어 있는지 확인
- 라이브러리1, 2 가 각각에 맞는 외부 라이브러리들을 참조할 수 있도록 설정
- 실행 파일을 만들 때 라이브러리 1과 2를 사용하도록 설정
CMake로 작업한 경우
add_library 이용하여 위 작업들을 간단하게 수행 가능
add_library (<라이브러리 이름> [STATIC | SHARED | MODULE ] <소스 1> <소스 2> ...)
STATIC : 정적 라이브러리
SHARED : 동적 라이브러리 (동적으로 링크)
MODULE : 동적으로 링크되지 않지만, dlopen과 같은 함수로 런타임시 불러올 수 있는 라이브러리
* 보통 정적으로 링크하면 실행 파일의 크기가 커지는 대신 동적 라이브러리를 사용할 때 보다 빠름
1) includes/shapes.h
- 헤더 파일은 includes 폴더 안에 정의
- 라이브러리를 사용 시 구현 부분을 참조할 필요는 없지만 헤더는 꼭 참조해야 하기 때문에 라이브러리와 헤더 소스를 분리
class Rectangle {
public:
Rectangle(int width, int height);
int GetSize() const;
private:
int width_, height_;
};
2) lib/shape.cpp
#include "shape.h"
Rectangle::Rectangle(int width, int height) : width_(width), height_(height) {}
int Rectangle::GetSize() const { // 직사각형의 넓이 반환
return width_ * height_;
}
3) lib/CMakeLists.txt
- shape 라이브러리는 /lib 안에 구현되어 있으므로 해당 위치에 CMakeLists.txt를 생성
# 정적 라이브러리 shape을 만듦
add_library(shape STATIC shape.cpp)
# 해당 라이브러리 컴파일 시 사용할 헤더파일 경로
target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)
# 해당 라이브러리를 컴파일 할 옵션
target_compile_options(shape PRIVATE -Wall -Werror)
PRIVATE vs PUBLIC
A 라이브러리가 B 라이브러리를 사용한다면 A는 B 의 컴파일 옵션들과 헤더 파일 탐색 디렉토리 목록을 물려받는데,
PUBLIC 으로 설정된 것은 물려 받고, PRIVATE 으로 설정된 것은 물려받지 않음
4) /main.cpp
#include<iostream>
#include "shape.h"
using namespace std;
int main(){
Rectangle r(4, 5);
cout << r.GetSize();
}
5) /CMakeLists.txt
프로젝트 레벨 CMakeLists.txt
# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)
# 프로젝트 정보
project(
ModooCode
VERSION 0.1
DESCRIPTION "예제 프로젝트"
LANGUAGES CXX)
# 확인할 디렉토리 추가
add_subdirectory(lib)
add_executable (program main.cpp)
# program 에 shape 를 링크
target_link_libraries(program shape)
다른 라이브러리를 사용하는 라이브러리
- ex) shape 라이브러리에서 thread 라이브러리르 사용하는 경우
- thread 라이브러리를 사용하려면 pthread 라이브러리를 링크시켜줘야 함
- 따라서 아래와 같이 shape의 CMakeLists.txt를 수정해줘야 함
#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_;
}
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)
라이브러리 A를 참조할 때,
A를 헤더 파일과 구현 내부에서 모두 사용한다면 : PUBLIC
A를 내부 구현에서만 사용하고 헤더 파일에서는 사용하지 않는다면 : PRIVATE
A를 헤더 파일에서만 사용하고 내부 구현에서는 사용하지 않는다면 : INTERFACE
위 경우 <thread> 를 내부 구현(shape.cpp)에서만 사용하고 헤더 파일(shape.h) 에서는 사용하지 않으므로 PRIVATE으로 링크해주는 것이 맞음 !!
파일들 한번에 추가하기
- add_library를 이용하는 경우, 파일들을 새로 추가할 때 마다 add_library를 수정해줘야 함
- 이러한 귀찮음을 해결하기 위해 해당 디렉토리에 있는 파일들을 모두 해당 라이브러리를 빌드하는데 사용하도록 할 수 있음
- file : CMake 에서 파일들을 관련해서 다룰 때 사용하는 명령
- GLOB_RECURSE 옵션 : 인자로 주어진 디렉토리와 해당 디렉토리 안에 있는 모든 하위 디렉토리 까지 재귀적으로 살펴봄
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/*.cc
)
add_library(shape STATIC ${SRC_FILES})
CMake에서 변수 사용, ${변수이름}
* make는 소괄호 ()로 감싸주는 반면, CMake는 중괄호 {}로 감싸줌
원하는 라이브러리를 설치하는 FetchContent
- CMake 에서 제공하는 FetchContent 를 사용하면 왠만한 외부 라이브러리들을 쉽게 불러오고 설치할 수 있음 (Python의 PIP와 같은 역할)
- FetchContent 를 사용하기 위해서는 적어도 3.11 이상 버전의 CMake 를 사용해야함
- 이전 버전의 경우 ExternalProject 모듈 사용
- ex) fmt 라이브러리를 사용하고 싶은 경우
# FetchContent 모듈을 불러옴
include(FetchContent)
# 어디에서 데이터를 불러올지 명시
FetchContent_Declare(
Fmt
GIT_REPOSITORY "https://github.com/fmtlib/fmt"
GIT_TAG "7.1.3"
)
# fmt 를 사용할 수 있도록 설정
FetchContent_MakeAvailable(Fmt)
FetchContent vs ExternalProject
FetchContent 는 CMake 를 실행하는 시점에서 외부 파일들을 불러오는 반면 ExternalProject 는 빌드 타임에 불러옴
Make 이외의 빌드 시스템 사용하기
- "Unix Makefiles" (디폴트)
- "Ninja"
- "Visual Studio 16 2019" 등등
$ cmake .. -DCMAKE_GENERATOR=Ninja
이미 빌드 시스템을 설정하였다면 바꿀 수 없음
디렉토리를 만들어서 CMake 명령을 다시 실행하거나, 기존 디렉토리 안의 파일들을 모두 지워야 함
'언어 > C, C++' 카테고리의 다른 글
[C/C++] 공백을 포함한 파일을 문자열 변수에 쓰기 (0) | 2024.05.27 |
---|---|
make, Makefile 정리 (0) | 2024.02.13 |
[C++] stringstream (0) | 2024.01.02 |
RapidJSON 정리 (0) | 2023.12.27 |
json-c 정리 (1) | 2023.12.26 |