뭐하는 놈인지는 알고 쓰자 시리즈의 세번째 주제는 CMake입니다.

해당 포스팅에서는 CMake 공식 홈페이지의 튜토리얼 내용을 번역해보았습니다. CMake 공식 github에서 제공해주는 프로젝트 폴더 구성과 소스 코드를 참조하여 좀 더 상세히 CMake의 사용법에 대해 알아보는 시간을 갖도록 하겠습니다. (튜토리얼 관련 소스 코드는 이곳에서 확인 가능합니다.)

한국어로 된 CMake 관련 설명이 필요하시다면 CMake 할때 쪼오오금 도움이 되는 문서도 굉장히 잘 정리되어있으니 참고하시기 바랍니다.


목차

  1. 1. CMake Tutorial
    1. 1.1. Introduction
    2. 1.2. Step 1: A Basic Starting Point
      1. 1.2.1. Build and Run
      2. 1.2.2. Adding a Version Number and Configured Header File
      3. 1.2.3. Specify the C++ Standard
      4. 1.2.4. Rebuild
    3. 1.3. Step 2: Adding a Library
    4. 1.4. Step 3: Adding Usage Requirements for a Library


CMake Tutorial

Introduction

CMake 튜토리얼은 일반적인 빌드 시스템 이슈를 처리하기 위한 단계별 가이드를 제공해줍니다. 예시로 제공되는 프로젝트에서 다양한 주제들이 함께 처리되는 방식을 살펴보는 것은 아주 큰 도움이 될 것입니다.

The CMake tutorial provides a step-by-step guide that covers common build system issues that CMake helps address. Seeing how various topics all work together in an example project can be very helpful.


Step 1: A Basic Starting Point

가장 기본적인 프로젝트는 소스 코드로부터 빌드되는 실행 파일입니다. 이 간단한 프로젝트에서는 CMakeLists.txt 파일에 단 3줄만이 필요합니다. 이것이 우리의 튜토리얼 시작 지점이 될 것입니다. 아래와 같이 작성된 CMakeLists.txt 파일을 Step1 디렉토리안에 생성해봅시다.

The most basic project is an executable built from source code files. For simple projects, a three line CMakeLists.txt file is all that is required. This will be the starting point for our tutorial. Create a CMakeLists.txt file in the Step1 directory that looks like:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.10)

# set the project name
project(Tutorial)

# add the executable
add_executable(Tutorial tutorial.cxx)

이 예제에서는 CMakeLists.txt 파일에 소문자로 이루어진 명령어를 사용한다는 것에 주목하기바랍니다. CMake에서는 대문자, 소문자 그리고 이 둘이 혼합된 명령어를 지원합니다. tutorial.cxx의 소스 코드는 Step1 디렉토리에서 확인가능하며 이 코드는 제곱근을 계산하기 위해 사용될 수 있습니다.

Note that this example uses lower case commands in the CMakeLists.txt file. Upper, lower, and mixed case commands are supported by CMake. The source code for tutorial.cxx is provided in the Step1 directory and can be used to compute the square root of a number.

Build and Run

이것이 필요한 모든것입니다 - 지금 당장에 우리의 프로젝트를 빌드하고 실행할 수 있습니다! 먼저, 프로젝트를 설정하기 위해 cmake 실행 파일 또는 cmake-gui 실행합니다. 그리고나서 여러분이 선택한 빌드 툴을 가지고 빌드합니다.

That’s all that is needed - we can build and run our project now! First, run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool.

예를 들어, 여러분은 커맨드 라인에서 CMake 소스 코드 트리의 Help/guide/tutorial 경로로 이동할 수 있고 아래 명령어를 입력하여 빌드 디렉토리를 생성할 수 있습니다:

For example, from the command line we could navigate to the Help/guide/tutorial directory of the CMake source code tree and create a build directory:

1
mkdir Step1_build

다음으로, 생성한 빌드 디렉토리로 이동한 뒤 프로젝트를 설정하고 네이티브 빌드 시스템을 생성하기 위해 CMake를 실행합니다:

Next, navigate to the build directory and run CMake to configure the project and generate a native build system:

1
2
cd Step1_build
cmake ../Step1

그리고나서 실질적으로 프로젝트를 컴파일하고 링크하기 위해 빌드 시스템을 호출합니다:

Then call that build system to actually compile/link the project:

1
cmake --build

마지막으로, 아래 명령어들을 통해 빌드된 Tutorial을 사용해봅시다:

Finally, try to use the newly built Tutorial with these commands:

1
2
3
Tutorial 4294967296
Tutorial 10
Tutorial

Adding a Version Number and Configured Header File

우리가 추가하게 될 첫번째 기능은 우리의 실행 파일과 프로젝트에 버전 번호를 제공하는 것입니다. 버전 번호를 소스 코드 상에 추가하는 것도 하나의 방법이지만 CMakeLists.txt의 사용은 이를 좀 더 유연하게 해줍니다.

The first feature we will add is to provide our executable and project with a version number. While we could do this exclusively in the source code, using CMakeLists.txt provides more flexibility.

먼저, 프로젝트의 이름과 버전 번호를 설정하기 위한 명령어인 project()를 사용하기 위해 CMakeLists.txt 파일을 수정합니다.

First, modify the CMakeLists.txt file to use the project() command to set the project name and version number.

1
2
3
4
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

그리고나서, 버전 번호를 소스 코드로 전달하기 위해 헤더 파일을 설정합니다.

Then, configure a header file to pass the version number to the source code:

1
configure_file(TutorialConfig.h.in TutorialConfig.h)

설정된 파일은 바이너리 트리 안에 작성될 것이기때문에 포함 파일을 찾기 위해 경로 리스트에 해당 디렉토리를 추가해주어야합니다. CMakeLists.txt 파일의 끝에 아래 라인을 추가해줍니다.

Since the configured file will be written into the binary tree, we must add that directory to the list of paths to search for include files. Add the following lines to the end of the CMakeLists.txt file:

1
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")

여러분이 선호하는 편집기를 사용하여 아래 내용을 포함하고 있는 TutorialConfig.h.in 파일을 소스 디렉토리에 생성해줍시다.

Using your favorite editor, create TutorialConfig.h.in in the source directory with the following contents:

1
2
3
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

CMake가 이 헤더 파일을 설정하는 동안 @Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 값은 대체될 것입니다.

When CMake configures this header file the values for @Tutorial_VERSION_MAJOR@ and @Tutorial_VERSION_MINOR@ will be replaced.

다음으로 설정된 헤더 파일인 TutorialConfig.h를 포함하기 위해 tutorial.cxx 파일을 수정합시다.

Next modify tutorial.cxx to include the configured header file, TutorialConfig.h.

마지막으로, 아래와 같이 tutorial.cxx 파일을 수정해서 실행 파일의 이름과 버전 번호를 출력해봅시다.

Finally, let’s print out the executable name and version number by updating tutorial.cxx as follows:

1
2
3
4
5
6
7
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

Specify the C++ Standard

다음으로 tutorial.cxx 소스 내에 atofstd::stod로 변경함으로써 C++11 기능을 우리 프로젝트에 추가해봅시다. 동시에 #include <cstdlib> 선언도 제거해줍니다.

Next let’s add some C++11 features to our project by replacing atof with std::stod in tutorial.cxx. At the same time, remove #include <cstdlib>.

우리는 올바른 플래그 값을 사용하도록 CMake 코드에 명시적으로 언급할 필요가 있을것입니다. CMake에서 특정 C++ 버전을 지원하도록 하는 가장 쉬운 방법은 CMAKE_CXX_STANDARD 변수를 사용하는 것입니다. 현재 튜토리얼에서는 CMakeLists.txt 파일의 CMAKE_CXX_STANDARD 변수를 11로 설정하고, CMAKE_CXX_STANDARD_REQUIRED 변수를 True로 설정해보겠습니다. CMAKE_CXX_STANDARDadd_executable 명령어 호출 이전에 선언되어야한다는 것을 잊지말기 바랍니다.

We will need to explicitly state in the CMake code that it should use the correct flags. The easiest way to enable support for a specific C++ standard in CMake is by using the CMAKE_CXX_STANDARD variable. For this tutorial, set the CMAKE_CXX_STANDARD variable in the CMakeLists.txt file to 11 and CMAKE_CXX_STANDARD_REQUIRED to True. Make sure to add the CMAKE_CXX_STANDARD declarations above the call to add_executable.

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

Rebuild

우리의 프로젝트를 다시 빌드해봅시다. 우리는 이미 빌드 디렉토리 생성했고 CMake를 실행했기때문에 빌드 과정으로 건너뛸 수 있습니다:

Let’s build our project again. We already created a build directory and ran CMake, so we can skip to the build step:

1
2
cd Step1_build
cmake --build .

이제 이전과 동일한 명령어를 통해 새롭게 빌드된 Tutorial을 실행해볼 수 있습니다.

Now we can try to use the newly built Tutorial with same commands as before:

1
2
3
Tutorial 4294967296
Tutorial 10
Tutorial

인자 없이 실행 파일을 구동 할때 버전 번호가 출력되는지 확인해보시길 바랍니다.

Check that the version number is now reported when running the executable without any arguments.


Step 2: Adding a Library

이제 우리의 프로젝트에 라이브러리를 추가해볼 것입니다. 이 라이브러리는 제곱근 계산을 직접 구현한 내용이 포함되어 있을 것입니다. 해당 라이브러리를 추가한 이후에는 실행 파일이 컴파일러가 제공하는 일반적인 제곱근 함수 대신에 이 라이브러리를 사용할 수 있게됩니다.

Now we will add a library to our project. This library will contain our own implementation for computing the square root of a number. The executable can then use this library instead of the standard square root function provided by the compiler.

이번 튜토리얼에서는 해당 라이브러리를 MathFunctions이라는 하위 디렉토리에 포함시킬 것입니다. 이 디렉토리에는 헤더 파일인 MathFunctions.h와 소스 파일인 mysqrt.cxx가 포함되어 있습니다. 소스 파일에는 컴파일러의 sqrt 함수와 유사한 기능을 제공하는 mysqrt라는 함수가 존재합니다.

For this tutorial we will put the library into a subdirectory called MathFunctions. This directory already contains a header file, MathFunctions.h, and a source file mysqrt.cxx. The source file has one function called mysqrt that provides similar functionality to the compiler’s sqrt function.

MathFunctions 디렉토리의 CMakeLists.txt 파일에 아래와 같이 한 줄을 추가합니다.

Add the following one line CMakeLists.txt file to the MathFunctions directory:

1
add_library(MathFunctions mysqrt.cxx)

새롭게 추가한 라이브러리를 사용하기 위해 ①최상위 CMakeLists.txt 파일에 add_subdirectory() 호출을 추가할 것이며, 이 호출을 통해 라이브러리가 빌드될 것 입니다. ②실행 파일에 새로운 라이브러리를 추가하고 mysqrt.h ③헤더 파일을 참조할 수 있도록 MathFunctions를 포함 디렉토리로 추가합니다. 이제 최상위 CMakeLists.txt 파일의 마지막 몇 개의 라인은 아래와 같아야 합니다:

To make use of the new library we will add an add_subdirectory() call in the top-level CMakeLists.txt file so that the library will get built. We add the new library to the executable, and add MathFunctions as an include directory so that the mysqrt.h header file can be found. The last few lines of the top-level CMakeLists.txt file should now look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ① add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(Tutorial tutorial.cxx)

# ② add the new library(= MathFUnction) to the executable
target_link_libraries(Tutorial PUBLIC MathFunctions)

# ③ add the binary tree to the search path for include files so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)

이번엔 MathFunctions 라이브러리를 옵션화해봅시다. 이번 튜토리얼을 진행하는데 있어서 이 작업이 반드시 필요한 것은 아니지만, 규모가 더 큰 프로젝트들에서 이러한 옵션화 작업은 일반적입니다. 가장 먼저 최상위 CMakeLists.txt 파일에 option을 추가합니다.

Now let us make the MathFunctions library optional. While for the tutorial there really isn’t any need to do so, for larger projects this is a common occurrence. The first step is to add an option to the top-level CMakeLists.txt file.

1
2
3
4
5
option(USE_MYMATH "Use tutorial provided math implementation" ON)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)

이 옵션은 cmake-guiccmake에서 기본 값인 ON으로 보여지게 될 것이며 이 값은 사용자가 변경할 수 있습니다. 이 설정은 캐시에 저장될 것이기 때문에 사용자는 빌드 디렉토리에서 CMake를 실행할 때마다 이 값을 설정할 필요가 없습니다.

This option will be displayed in the cmake-gui and ccmake with a default value of ON that can be changed by the user. This setting will be stored in the cache so that the user does not need to set the value each time they run CMake on a build directory.

다음 변화는 MathFunctions 라이브러리를 조건에 따라 빌드하고 링킹하도록 만드는 것입니다. 이를 위해 옵션 값을 체크하는 if 구문을 생성할 것입니다. if 블록 내부에 라이브러리를 링크하기 위해 필요한 정보를 저장하고 하위 디렉토리를 튜토리얼 타겟의 포함 디렉토리로 추가하기 위한 list 명령어와 함께 add_subdirectory() 명령어를 추가합시다.

The next change is to make building and linking the MathFunctions library conditional. To do this, we will create an if statement which checks the value of the option. Inside the if block, put the add_subdirectory() command from above with some additional list commands to store information needed to link to the library and add the subdirectory as an include directory in the Tutorial target. The end of the top-level CMakeLists.txt file will now look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()

# add the executable
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)

나중에 실행파일에 링크되는 옵션화된 라이브러리들을 모으기 위해 EXTRA_LIBS 라는 변수를 사용하는 것을 주목하기 바랍니다. EXTRA_INCLUDES 변수는 옵션화된 헤더 파일을 대상으로 이와 유사하게 사용됩니다. 이것은 많은 옵션 컴포넌트들을 처리하기 위한 고전적인 방식이며, 다음 단계에서 현대적인 접근 방식에 대해 살펴볼 것입니다.

Note the use of the variable EXTRA_LIBS to collect up any optional libraries to later be linked into the executable. The variable EXTRA_INCLUDES is used similarly for optional header files. This is a classic approach when dealing with many optional components, we will cover the modern approach in the next step.

위 작업에 따른 소스 코드 상의 변경점은 꽤 간단합니다. 먼저, 필요 시에 tutorial.cxx에서 MathFunctions.h를 포함시켜줍니다.

The corresponding changes to the source code are fairly straightforward. First, in tutorial.cxx, include the MathFunctions.h header if we need it:

1
2
3
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif

그리고 나서, USE_MYMATH를 제곱근 함수가 사용되는 조건으로 만들어줍니다.

Then, in the same file, make USE_MYMATH control which square root function is used:

1
2
3
4
5
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif

이제 소스 코드에서 USE_MYMATH 변수를 요구하고 있기때문에 이 값을 TutorialConfig.h.in 파일에 아래와 같이 추가할 수 있습니다:

Since the source code now requires USE_MYMATH we can add it to TutorialConfig.h.in with the following line:

1
#cmakedefine USE_MYMATH

프로젝트를 설정(configure)하기 위해 cmake 실행 파일 또는 cmake-gui를 실행시키고 여러분의 빌드 도구에 맞게 빌드합니다. 그리고 빌드된 Tutorial 실행 파일을 실행합니다.

Run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool. Then run the built Tutorial executable.

이제 USE_MYMATH 값을 업데이트 해봅시다. 가장 쉬운 방법은 cmake-gui를 사용하거나 터미널 환경이라면 ccmake를 사용하는 것입니다. 또는 command-line의 옵션을 변경하기를 원한다면 아래와 같이 시도해보시기 바랍니다:

Now let’s update the value of USE_MYMATH. The easiest way is to use the cmake-gui or ccmake if you’re in the terminal. Or, alternatively, if you want to change the option from the command-line, try:

1
cmake ../Step2 -DUSE_MYMATH=OFF

Step 3: Adding Usage Requirements for a Library

사용 요구 사항을 통해 라이브러리 또는 실행 파일의 링크 및 포함 라인을 훨씬 더 잘 제어할 수 있으며 CMake 내 대상의 전이 속성을 더 많이 제어할 수 있습니다. 사용 요구 사항을 활용하는 기본 명령은 다음과 같습니다:

Usage requirements allow for far better control over a library or executable’s link and include line while also giving more control over the transitive property of targets inside CMake. The primary commands that leverage usage requirements are:

  • target_compile_definitions()
  • target_compile_options()
  • target_include_directories()
  • target_link_libraries()

사용 요구 사항의 현대적인 CMake 접근 방식을 사용하기 위해 앞선 챕터였던 Adding a Library에서 사용한 소스 코드를 리팩토링 해보겠습니다. 먼저 MathFunctions 라이브러리를 링킹하고자 한다면 현재 소스 디렉토리를 포함해야하지만 MathFunctions 자체는 포함하지 않습니다. 따라서 이것인 INTERFACE 사용 요구사항이 될 수 있습니다.

Let’s refactor our code from Adding a Library to use the modern CMake approach of usage requirements. We first state that anybody linking to MathFunctions needs to include the current source directory, while MathFunctions itself doesn’t. So this can become an INTERFACE usage requirement.

INTERFACE는 소비자가 필요로 하지만 생산자는 그렇지 않다는 것을 기억하기 바랍니다. MathFunctions/CMakeLists.txt 파일의 끝에 아래 라인을 추가합니다.

Remember INTERFACE means things that consumers require but the producer doesn’t. Add the following lines to the end of MathFunctions/CMakeLists.txt:

1
2
3
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)

이제 MathFunctions에 대한 사용 요구 사항을 명시했으므로 최상위 CMakeLists.txt에서 EXTRA_INCLUDES변수 사용을 안전하게 제거할 수 있습니다:

Now that we’ve specified usage requirements for MathFunctions we can safely remove our uses of the EXTRA_INCLUDES variable from the top-level CMakeLists.txt, here:

1
2
3
4
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()

And here:

1
2
3
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)

위와 같이 수정을 완료하였다면 프로젝트를 설정(configure)하기 위해 cmake 실행 파일 또는 cmake-gui를 실행시키고 여러분의 빌드 도구에 맞게 빌드하거나 빌드 디렉토리에서 cmake --build를 사용하여 빌드합니다.

Once this is done, run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool or by using cmake --build . from the build directory.



해당 게시글에서 발생한 오탈자나 잘못된 내용에 대한 정정 댓글 격하게 환영합니다😎

reference