Etc.,  Programming

CMake 명령어

CMake를 사용할 때는 CMakeLists.txt에 다양한 명령어를 함수처럼 호출해서 사용한다. 이번 글에서 각 명령어들에 대해 소개하도록 한다.

cmake_minimum_required

cmake_minimum_required (VERSION [minimum required version])

최소로 요구되는 CMake version을 명시한다.

project

project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

해당 project의 필수 정보들을 입력한다. 이 명령은 CMake 수행에 반드시 존재해야 한다.

add_executable

add_executable ([target] [source code 1] [source code 2] ...)

예를 들면 아래와 같이 main.cpp, foo.cpp 파일이 있어서 이를 executable file을 만든다고 가정하자.

// foo.cpp
#include "foo.hpp"

int foo() { return 100; }
// foo.hpp
int foo();
// main.cpp
#include <iostream>
#include "foo.hpp"
 
int main() {
    std::cout << "Hello CMake" << std::endl;
    std::cout << "foo=" << foo() << std::endl;
    return 0;
}

그리고 CMakeLists.txt를 아래와 같이 작성 후 실행해보자.

# CMakeLists.txt                       
# Minimum required CMake version       
cmake_minimum_required (VERSION 3.20)  
                                       
# Project information                  
project (                              
    ShuminProject                      
    VERSION 0.1                        
    DESCRIPTION "Toy Exmaple"          
    LANGUAGES CXX                      
       )                               

add_executable (a.out main.cpp foo.cpp)
$ make
[ 33%] Building CXX object CMakeFiles/a.out.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/a.out.dir/foo.cpp.o
[100%] Linking CXX executable a.out
[100%] Built target a.out
$ ./a.out 
Hello CMake
foo=100

위와 같이 예상하는 결과를 출력한 것을 볼 수 있다.

target_compile_options

target_compile_options([target] PUBLIC [compile opt1] [compile opt2] ...)

Build option을 추가 할 때 사용하는 명령어로, executable file 생성 시 필요한 옵션을 추가해준다. 참고로 add_executable에 명시한 executable file과 다른 경우 cmake 수행 시 error를 발생시킨다.

# CMakeLists.txt
# Minimum required CMake version
cmake_minimum_required (VERSION 3.20)

# Project information
project (
    ShuminProject
    VERSION 0.1
    DESCRIPTION "Toy Exmaple"
    LANGUAGES CXX
       )

add_executable (a.out main.cpp foo.cpp)

target_compile_options (a.out PUBLIC -Wall -Werror -save-temps)
 make
Consolidate compiler generated dependencies of target a.out
[ 33%] Building CXX object CMakeFiles/a.out.dir/main.cpp.o
x86_64-conda_cos6-linux-gnu-g++: warning: -pipe ignored because -save-temps specified
[ 66%] Building CXX object CMakeFiles/a.out.dir/foo.cpp.o
x86_64-conda_cos6-linux-gnu-g++: warning: -pipe ignored because -save-temps specified
[100%] Linking CXX executable a.out
[100%] Built target a.out
$ ls
a.out  CMakeCache.txt  CMakeFiles  cmake_install.cmake  foo.ii  foo.s  main.ii  main.s  Makefile

위 결과를 보면 -save-temps 옵션으로 인해 temporary file들이 생성된 것을 볼 수 있다.

참고로 target이란 꼭 executable만을 말하지 않고 library file이 될 수도 있다.

target_include_directories

target_include_directories([target] PUBLIC [path 1] [path 2] ...)

프로젝트 규모가 커지면 include 디렉토리를 따로 관리하는 경우가 존재한다. 이를 위한 명령어 target_include_directories를 통해서 include path를 지정 가능하다.

$ tree
.
|-- CMakeLists.txt
|-- foo.cpp
|-- include
|   `-- foo.hpp
`-- main.cpp

include 디렉토리를 생성해 header file을 내부에 저장하도록 변경해서 명령어를 통해 해당 경로를 지정하는 방식으로 CMakeLists.txt를 수정했다.

# CMakeLists.txt
# Minimum required CMake version
cmake_minimum_required (VERSION 3.20)

# Project information
project (
    ShuminProject
    VERSION 0.1
    DESCRIPTION "Toy Exmaple"
    LANGUAGES CXX
       )

add_executable (a.out main.cpp foo.cpp)

target_compile_options (a.out PUBLIC -Wall -Werror -save-temps)
target_include_directories(a.out PUBLIC ${CMAKE_SOURCE_DIR}/includes)

참고로 CMAKE_SOURCE_DIR 는 CMake에서 기본으로 제공하는 변수로 최상위 디렉토리 위치, 즉 CMakeLists.txt 파일이 있는 곳을 가리킨다.

add_library

add_library (<library> [STATIC | SHARED | MODULE ] <source 1> <source 2> ...)

만약에 중간에 static library 또는 shared library를 생성하고 싶을 때는 add_library 명령을 통해서 실행이 가능하다.

Static library는 STATIC, shared library는 SHARED, MODULE은 동적으로 링크되진 않지만 dlopen 함수와 같이 runtime에 불러올 수 있는 library를 말한다. add_library를 사용하는 예제를 다음과 같이 나타낼 수 있다.

$ tree
.
|-- CMakeLists.txt
|-- foo.cpp
|-- includes
|   |-- bar.hpp
|   `-- foo.hpp
|-- lib
|   |-- bar.cpp
|   `-- CMakeLists.txt
`-- main.cpp
# lib/CMakeLists.txt

# Make static library bar
add_library(bar STATIC bar.cpp)

# Header file path to compile library
target_include_directories(bar PUBLIC ${CMAKE_SOURCE_DIR}/includes)

# Option to compile the library
target_compile_options(bar PRIVATE -Wall -Werror)
// bar.cpp
#include "bar.hpp"
#include <iostream>

void bar() { std::cout << "Bar() Function" << std::endl; }
// bar.hpp
void bar();

우선 lib 안에 bar.cpp를 넣고 내부에 CMakeLists.txt를 새로 작성해준다. CMakeLists.txt를 새로 작성 할 때는 우선 add_library(bar STATIC bar.cpp)를 통해 static library를 생성 할 것을 명시해주며 include path를 명시해준다. (bar.cpp에서 bar.hpp를 참조하기 때문에)

그리고 컴파일 옵션을 지정해줄 때는 PRIVATE을 명시해주는데 이는 bar를 build 할 때는 위 옵션을 사용하고 싶지만, bar를 사용하는 곳에서까지 위 옵션을 사용하고 싶지 않기 때문이다.

최종적으로 최상위 CMakeLists.txt 파일은 다음과 같다.

# CMakeLists.txt
# Minimum required CMake version
cmake_minimum_required (VERSION 3.20)

# Project information
project (
    ShuminProject
    VERSION 0.1
    DESCRIPTION "Toy Exmaple"
    LANGUAGES CXX
       )

# Add sub directory
add_subdirectory (lib)

add_executable (a.out main.cpp foo.cpp)

target_compile_options (a.out PUBLIC -Wall -Werror -save-temps)

target_link_libraries (a.out bar)
$ ./a.out
Hello CMake
foo=100
Bar() Function

target_link_libraries

target_link_libraries는 linking 할 때 특정 library를 추가 할 때 사용하는 command다. 이 때 add_library와의 차이점이 무엇인지 궁금할 수 있는데, add_library는 target을 library file로 지정할 수 있는 반면에, target_link_libraries는 불가능하다.

만약 app 이라는 binary를 target으로 생성하고 싶을 때, app.cpp에서 libA.a library를 사용한다고 했을 때 다음과 같이 명령어를 통해 linking이 가능하다.

add_executable (app app.cpp)
add_library (a_lib STATIC a.cpp)
target_link_libraries (app a_lib)

추가로 add_library 명령어를 통해서는 dependencies, compile definitions, compile flags 등을 지정할 수 있다고 한다. (참고)

file (Include All Source Files)

CMake에서 target을 build 하는데 만약 다수의 source file이 필요하다고 하면 각각을 명시하지 않고도 추가 할 수 있도록 명령을 제공한다.

file (GLOB_RECURSE [variable name] [options] [source files])
# CMakeLists.txt
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
  ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)

CMakeLists.txt 파일을 분석해보면 현 디렉토리에서 재귀적으로 *.cpp 확장자 파일들을 모두 포함해 SRC_FILES 변수에 추가하는 명령이다.

GLOB_RECURSE 옵션은 인자로 주어진 해당 디렉토리 및 하위폴더를 포함해서 전체를 재귀적으로 보도록 하는 옵션이다. 만약 재귀적으로 포함하고 싶지 않는 경우는 GLOB을 주면 된다.

CONFIGURE_DEPENDS 옵션은 만약 파일 목록이 전과 다를 경우 CMake를 다시 실행해서 build file들을 재생성 하라는 옵션이다. 따라서 만약 파일이 추가가 되더라도 cmake ..을 수행할 필요 없이 그냥 make만 실행해도 cmake가 호출된다.

CMAKE_CURRENT_SOURCE_DIR 변수는 현재의 CMakeLists.txt의 위치를 나타낸다.

참고로 CMake는 변수를 {}로 감싸며, Make에서는 ()로 감싸는 규칙이 존재한다.

# lib/CMakeLists.txt

# Include all source files (*.cpp)
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
  ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)

# Make static library bar
add_library(bar STATIC ${SRC_FILES})

# Header file path to compile library
target_include_directories(bar PUBLIC ${CMAKE_SOURCE_DIR}/includes)

# Option to compile the library
target_compile_options(bar PRIVATE -Wall -Werror)

add_dependencies

add_dependencies(<target> [<target-dependency>]...)

Target간 의존성을 정의하고 싶을 때 사용한다. Target은 add_executable, add_library, add_custom_target으로 정의한 것들을 말한다.

Reference

  1. https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html
  2. https://modoocode.com/332
  3. https://gist.github.com/luncliff/6e2d4eb7ca29a0afd5b592f72b80cb5c
  4. https://cgold.readthedocs.io/en/latest/index.html
  5. https://www.tuwlab.com/ece/27260

Leave a Reply

Your email address will not be published. Required fields are marked *