본문 바로가기

개발자/C++(Linux, Window)

CMake 총정리 및 자세한 설명

반응형

C++ 프로젝트를 위한 CMake 사용법

 

  • CMake 사용법
  • 실행 파일 및 라이브러리 만들기

 

CMake 란?

CMake  빌드 파일을 생성해주는 프로그램 입니다. 다시 말해 CMake 를 통해서 프로젝트를 빌드를 하는 것이 아니라, CMake 를 통해서 빌드 파일을 생성하면 빌드 프로그램을 통해서 프로젝트를 빌드 하는 것입니다. 예를 들어서 make 를 사용한다면 CMake 를 통해서 Makefile 을 생성할 것이고, 요즘 핫한 Ninja 를 사용한다면 CMake 를 통해서 .ninja 빌드 파일을 만들어줄 것입니다.

Ninja 빌드 시스템

아무튼, 이 부분에 대해서 많은 사람들이 혼동하고 계시는데, 다시 한 번 강조하자면 CMake 는 빌드 프로그램 아니라 빌드 파일을 생성하는 프로그램 입니다.

CMake 는 이제 대부분의 C/C++ 계열 프로젝트에서 사용되고 있습니다. 사실 요즘에는 CMake 를 안쓰는 곳을 찾아보기 힘들어졌죠. 물론 CMake 역시 make 처럼 초반에 러닝 커브가 조금 있고, CMake 역사가 꽤 긴 만큼, 올바른 사용 방식이 (Best practice) 버전에 따라 많이 바뀌어서 아직도 구시대적인 CMake 문법을 사용하는 경우가 많습니다. 마치 C++ 98 을 아직도 사용하고 있는 느낌으로 말이죠.

이 글에서는 CMake 문법과 함께 최신 CMake 에서 권장하는 작성 방식에 대해서 다루어 보고자 합니다.

CMake 개요

CMake 를 사용하는 모든 프로젝트에는 반드시 프로젝트 최상위 디렉토리에 CMakeLists.txt 파일이 있어야 합니다. 해당 파일에는 CMake 가 빌드 파일을 생성하는데 필요한 정보들이 들어 있습니다. 따라서 보통의 컴파일 과정은 다음과 같이 진행됩니다.

CMake 를 사용한 프로젝트 빌드 과정

물론 꼭 Makefile 을 만들 필요는 없고 원하는 빌드 프로그램을 선택할 수 있습니다. 이에 관해서는 아래에서 다루겠습니다.

최상위 CMakeLists.txt 에는 반드시 아래 두 개의 내용이 들어가야 합니다.

# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)

# 프로젝트 정보
project(
  ModooCode
  VERSION 0.1
  DESCRIPTION "예제 프로젝트"
  LANGUAGES CXX)

먼저 make 처럼 CMake 에서도 주석을 달기 위해서는 # 를 사용하면 됩니다.

cmake_minimum_required(VERSION 3.11)

CMakeLists.txt 최상단에는 해당 프로젝트에서 사용할 CMake 의 최소 버전을 명시해줍니다. 시스템 마다 설치된 CMake 의 버전이 다를 텐데, 저의 경우 3.19 버전이 설치되어 있습니다. 앞서 이야기 했듯이 CMake 는 버전이 바뀜에 따라 차이가 꽤 커서 (특히 2. 대 버전일 경우) 옛날 버전의 CMake 를 사용할 경우 지원하지 않는 기능이 있을 수 도 있습니다. 최소한 이 글에서 다룰 내용들은 최소 3.0 을 사용 하므로 minimum_required 를 3.0 이상으로 씁시다.

# 프로젝트 정보
project(
  ModooCode
  VERSION 0.1
  DESCRIPTION "예제 프로젝트"
  LANGUAGES CXX)

그 다음으로 프로젝트의 정보를 간단히 명시할 수 있습니다. 사실 꼭 필요한 것은 프로젝트 이름이고 (위에서 ModooCode 부분), 나머지 정보는 없어도 됩니다.

참고로 LANGUAGES 부분의 경우 C 프로젝트면 C 를, C++ 프로젝트면 CXX 를 명시하면 되고, 만일 명시하지 않았을 경우 디폴트로 C  CXX 가 설정됩니다. 그 외에도 CUDA, OBJC, OBJCXX, Fortran 등등이 가능합니다. 자세한 내용은 여기 참조하세요.

실행 파일 만들기

그렇다면 가장 간단한 실행 파일을 하나 빌드해봅시다.

 C/C++ 확대 축소
#include <iostream>

int main() {
  std::cout << "Hello, CMake" << std::endl;
  return 0;
}
입력  코드 수정  실행

위 내용을 main.cc 에 저장하고

# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)

# 프로젝트 정보
project(
  ModooCode
  VERSION 0.1
  DESCRIPTION "예제 프로젝트"
  LANGUAGES CXX)

add_executable (program main.cc)

위 내용으로 CMakeLists.txt 를 생성합니다. 이 때 두 파일은 같은 경로에 있어야 합니다.

이제 빌드 파일을 생성해봅시다. CMake 에서 권장하는 방법은 빌드 파일은 작업하는 디렉토리와 다른 디렉토리에서 만드는 것입니다. 따라서 build 라는 폴더를 하나 만듭시다.

$ tree
.
├── build
├── CMakeLists.txt
└── main.cc

현재 프로젝트 폴더의 형태는 위와 같습니다.

그 다음에 build 안으로 들어가서 cmake .. 을 실행 합니다.

$ cmake ..
-- The CXX compiler identification is GNU 9.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /build

위와 같이 CMake 에서 여러가지 설정들을 체크한 뒤에 빌드 파일을 생성한 것을 볼 수 있습니다.

CMake 를 실행 할 때에는 최상위 CMakeLists.txt 가 위치한 폴더의 경로를 입력해야 하는데, 바로 부모 디렉토리에 있으므로 .. 을 쓰는 것입니다. build 안에 여러가지 폴더와 파일들이 생성된 것을 볼 수 있습니다. 제 경우 아래와 같습니다.

$ tree
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.19.1
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   └── CompilerIdCXX
│   │   │       ├── a.out
│   │   │       ├── CMakeCXXCompilerId.cpp
│   │   │       └── tmp
│   │   ├── cmake.check_cache
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── program.dir
│   │   │   ├── build.make
│   │   │   ├── cmake_clean.cmake
│   │   │   ├── DependInfo.cmake
│   │   │   ├── depend.make
│   │   │   ├── flags.make
│   │   │   ├── link.txt
│   │   │   └── progress.make
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
└── main.cc

위 처럼 여러가지 파일들이 생성된 것을 볼 수 있습니다. 특히 Makefile 도 만들어졌네요!

주의 사항

반드시 빌드 파일 용 디렉토리를 만든 다음에 해당 디렉토리에서 CMake 를 실행합시다. CMake 는 실행시 여러가지 파일들 (캐시 용도로) 을 생성하는데 이 때문에 프로젝트 디렉토리가 난장판이 될 수 있습니다. 특히 이미 있는 파일을 덮어 씌우기야 한다면 더욱 끔찍..

그럼 이제 build 디렉토리로 들어가서 make 를 실행해봅시다.

$ make
Scanning dependencies of target program
[ 50%] Building CXX object CMakeFiles/program.dir/main.cc.o
[100%] Linking CXX executable program
[100%] Built target program

$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  program

그러면 실제로 위 처럼 program 이라는 이름의 실행 파일이 생성된 것을 볼 수 있습니다. 해당 파일을 실행해보면

실행 결과

Hello, CMake

예상대로 잘 동작합니다.

생성할 실행 파일을 추가하는 명령 add_executable

CMake 에서 실행 파일을 생성하기 위해서는 위와 같이 add_executable 을 사용하면 됩니다.

add_executable 레퍼런스 링크

add_executable (<실행 파일 이름> <소스1> <소스2> ... <소스들>)

맨 처음에 생성하고자 하는 실행 파일 이름을 적은 뒤에, 그 뒤로 해당 실행 파일을 만드는데 필요한 소스들을 쭈르륵 나열하면 됩니다.

예를 들어서 실행 파일을 생성하는데 main.cc 말고도 foo.cc 도 필요하다고 해봅시다.

각각의 파일들이 다음과 같이 구성되어 있습니다.

  • foo.h
 C/C++ 확대 축소
int foo();
  • foo.cc
 C/C++ 확대 축소
#include "foo.h"

int foo() { return 3; }
  • main.cc
 C/C++ 확대 축소
#include <iostream>

#include "foo.h"

int main() {
  std::cout << "Foo : " << foo() << std::endl;
  return 0;
}
입력  코드 수정  실행

그렇다면 컴파일에 필요한 것이 소스 파일들인 main.cc  foo.cc 이므로, add_executable 을 다음과 같이 수정해주면 됩니다.

add_executable (program main.cc foo.cc)

그럼 이제 build 디렉토리에 들어가서 make 를 실행해봅시다.

$ make
Scanning dependencies of target program
[ 50%] Linking CXX executable program
/usr/bin/ld: CMakeFiles/program.dir/main.cc.o: in function `main':
main.cc:(.text+0x24): undefined reference to `foo()'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/program.dir/build.make:103: program] Error 1
make[1]: *** [CMakeFiles/Makefile2:95: CMakeFiles/program.dir/all] Error 2
make: *** [Makefile:103: all] Error 2

만일 그냥 실행하였다면 위와 같이 오류가 발생합니다. 왜냐하면 아직 CMakeLists.txt 를 바꾸었지만, 아직 Makefile 를 바꾼 것은 아니기 때문이죠. 따라서 cmake .. 을 통해서 Makefile 을 다시 생성해야 합니다.

아무튼 CMake 를 다시 실행해서 만든 새로운 Makefile 에서 컴파일을 해보면

실행 결과

Foo : 3

잘 나옴을 알 수 있습니다.

컴파일 옵션 지정하기

만약에 컴파일 옵션을 지정하고 싶다면 어떨까요? 간단합니다. target_compile_options 명령을 사용하면 됩니다.

target_compile_options 레퍼런스 링크

target_compile_options(<실행 파일 이름> PUBLIC <컴파일 옵션1> <컴파일 옵션2> ...)

예를 들어서

target_compile_options(program PUBLIC -Wall -Werror)

위 경우 program 을 빌드할 때 컴파일 옵션으로 -Wall (모든 경고 표시) 과 -Werror (경고는 컴파일 오류로 간주) 을 준다는 의미 입니다. 중간에 PUBLIC 은 뒤에서 설명할 텐데, 실행 파일을 빌드할 때에는 그닥 중요하지 않습니다. (PUBLIC 말고도 INTERFACE 와 PRIVATE 도 있는데 사실 어느 옵션을 사용하도 큰 상관은 없습니다. 적어도 실행 파일을 만드는 단계에서는요.)

컴파일 옵션이 잘 돌아가는지 보려면 아래와 같이 main.cc 를 수정해보세요.

 C/C++ 확대 축소
#include <iostream>

#include "foo.h"

int main() {
  int i;
  std::cout << "Foo : " << foo() << std::endl;
  return 0;
}
입력  코드 수정  실행

그럼 빌드 시에 아래와 같이 unused variable 오류가 발생합니다.

컴파일 오류

/home/jaebum/teach/cmake/main.cc: In function ‘int main()’:
/home/jaebum/teach/cmake/main.cc:6:7: error: unused variable ‘i’ [-Werror=unused-variable]
    6 |   int i;
      |       ^
cc1plus: all warnings being treated as errors

즉, 컴파일 옵션이 제대로 들어간 것을 볼 수 있죠.

CMake 에서의 기본 개념 Target 과 Property

앞서 target_compile_options 명령을 통해서 program 을 빌드할 때 컴파일 옵션을 줄 수 있었습니다. 그렇다면 CMake 에서 말하는 타겟(target) 이란 무엇일까요? 쉽게 말해서 타겟이란 여러분의 프로그램을 구성하는 요소들 이라 보시면 됩니다. 예를 들어서 위 처럼 실행 파일이 될 수 도 있고, 라이브러리 파일이 될 수 도 있습니다.

CMake 의 모든 명령들은 이 타겟들을 기준으로 돌아갑니다. 그리고 각 타겟에는 속성(Property) 을 정의할 수 있는데, 위와 같이 컴파일 옵션을 주는 것도 program 이라는 타겟에 컴파일 옵션 속성을 설정하는 것이라 볼 수 있죠. 그 외에도, 어떠한 라이브러리를 링크하는 것인지, include 하는 파일은 어디서 보는지 등등의 여러가지 속성들을 정의해줄 수 있습니다.

아무튼 모든 CMake 명령은 그냥 타겟을 정의하고 (add_executable 와 같이), 해당 타겟들의 속성을 지정하는 명령들 (target_compile_options 처럼) 로 이루어진 것이라 보시면 됩니다.

include 경로 지정하기

CMake 에서는 컴파일 시에 헤더 파일들을 찾을 경로의 위치를 지정할 수 있습니다. 보통 컴파일러는 #include <> 의 형태로 include 되는 헤더 파일들은 시스템 경로에서 찾고, #include "" 의 형태로 include 된 헤더 파일의 경우는 따로 지정하지 않는 이상 현재 코드의 위치를 기준으로 찾습니다.

하지만 경우에 따라서 (특히 나중에 라이브러리 만들 시에 더욱) 헤더 파일들을 다른 곳에 위치시키는 경우가 있는데 컴파일러가 해당 파일들을 찾기 위해서는 컴파일 시에 따로 경로를 지정해줘야 합니다.

예를 들어서 foo.h  includes 라는 폴더 안에 있다고 해봅시다. 즉 현재 프로젝트 구조는

$ tree
├── CMakeLists.txt
├── foo.cc
├── includes
│   └── foo.h
└── main.cc

와 같이 생겼죠. 만일 그냥 컴파일 한다면

컴파일 오류

/home/jaebum/teach/cmake/main.cc:3:10: fatal error: foo.h: No such file or directory
    3 | #include "foo.h"
      |          ^~~~~~~
compilation terminated.

위 처럼 foo.h 를 찾을 수 없다는 오류가 발생합니다.

따라서 CMakeLists.txt  includes 디렉토리를 헤더 파일 경로 탐색 시 확인해라 라고 알려줘야 합니다.

target_include_directories(program PUBLIC ${CMAKE_SOURCE_DIR}/includes)

사용 방법은 아래와 같습니다.

target_include_directories 레퍼런스 링크

target_include_directories(<실행 파일 이름> PUBLIC <경로 1> <경로 2> ...)

위 경우 ${CMAKE_SOURCE_DIR}/includes 를 헤더 파일 탐색 경로에 추가하고 있습니다. 한 가지 중요한 점은 CMake 에서 디렉토리의 경로를 지정할 때 왠만하면 절대 경로를 쓰지 않는 것이 좋습니다. 왜냐하면 CMake 의 가장 큰 장점으로 여러 플랫폼에서 사용할 수 있다 인데, 절대 경로로 박아 놓으면 다른 시스템에서 사용할 수 없기 때문이죠.

${CMAKE_SOURCE_DIR} 은 CMake 에서 기본으로 제공하는 변수로, 최상위 CMakeLists.txt,  cmake .. 할 때 읽어들이는 CMakeLists.txt 의 경로를 의미합니다. 다시 말해 프로젝트의 경로라고 볼 수 있죠. 따라서 ${CMAKE_SOURCE_DIR}/includes 는 현재 프로젝트 경로 안에 includes 디렉토리라 보시면 됩니다.

아무튼 이렇게 헤더 파일 탐색 경로를 추가하고 나면

실행 결과

Foo : 3

위와 같이 컴파일이 잘 됨을 확인할 수 있습니다.

라이브러리 만들기

보통 어느 정도 규모가 큰 C++ 프로젝트의 경우 아래와 같은 구조를 가집니다.

일반적인 C++ 프로젝트의 구성

보통 라이브러리 라고 하면 특정 역할을 수행하는 코드를 모아놓은 것이라 보면 됩니다. 예를 들어서 C++ 에 파일 시스템 관련 작업을 하는 것을 모아놓은 <filesystem> 라이브러리가 있는 것 처럼 말이죠.

우리가 C++ 로 개발을 하게 되면 프로그램의 여러가지 요소들을 그냥 하나의 거대한 라이브러리로 만드는 것 보다 각각의 요소들로 쪼개서 만드는 경우가 많습니다. 그 이유는 여러가지가 있는데

  1. 라이브러리로 쪼개지 않고 하나의 거대한 실행 파일로 관리하게 되면, 코드가 바뀔 때 마다 전체를 다시 컴파일 해야 한다. 반면의 라이브러리의 경우 바뀌지 않은 부분은 컴파일 하지 않아도 되서 (링킹만 하면 됨) 개발 속도가 빠르다.
  2. 라이브러리의 각 요소들을 전체를 한꺼번에 묶어 놓았을 때 보다 테스팅 하기 용이하다.

등등이 있습니다.

위 그림에서 나온 프로젝트의 경우 실행 파일을 만들기 위해 두 개의 라이브러리들이 사용됩니다. 그리고 각각의 라이브러리는 또 다른 외부 라이브러리들을 참조하고 있음을 알 수 있죠.

만약에 이와 같은 구성을 그냥 make 로 작업하였으면 꽤나 골치 아팠을 것입니다. 일단 위 프로젝트를 성공적으로 빌드 하기 위해서는

  1. 외부 라이브러리들이 잘 설치되어 있는지 확인해야 하고
  2. 라이브러리 1 과 라이브러리 2 가 각각에 맞는 외부 라이브러리들을 참조할 수 할 수 있도록 설정해야 하고
  3. 실행파일을 만들 때 라이브러리 1 과 2 를 사용하도록 해야 합니다.

하지만 CMake 에서는 위와 같은 작업들을 아주 간단하게 할 수 있습니다.

예를 들어서 아주 간단한 라이브러리를 만들어보겠습니다. 먼저 우리가 사용할 라이브러리는 프로젝트의 lib 폴더 안에 만들도록 하겠습니다.

  • shape.cc
 C/C++ 확대 축소
#include "shape.h"

Rectangle::Rectangle(int width, int height) : width_(width), height_(height) {}

int Rectangle::GetSize() const {
  // 직사각형의 넓이를 리턴한다.
  return width_ * height_;
}

그리고 헤더 파일은 includes 폴더 안에 정의하겠습니다. C++ 라이브러리의 경우 헤더와 소스를 따로 분리하는데, 그 이유는 라이브러리를 사용할 경우 라이브러리의 구현 부분을 참조할 필요는 없지만 헤더는 꼭 참조해야 하기 때문입니다. 따라서 구현 부분을 lib 안에, 헤더 파일은 includes 에 따로 뺍니다.

아무튼 includes 안에는 아래와 같이 shapes.h 헤더 파일을 작성합니다.

  • shape.h
 C/C++ 확대 축소
class Rectangle {
 public:
  Rectangle(int width, int height);

  int GetSize() const;

 private:
  int width_, height_;
};

그렇다면 이제 해당 라이브러리를 어떻게 빌드 해야 할지 알려주는 CMakeLists.txt 를 작성해야 합니다. 우리의 shape 라이브러리는 /lib 안에 구현되어 있으니까 해당 위치에 CMakeLists.txt 를 새로 만들어줍시다.

# /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 의 사용법은 간단합니다.

add_library 레퍼런스 링크

add_library (<라이브러리 이름> [STATIC | SHARED | MODULE ] <소스 1> <소스 2> ...)

중간에 어떠한 형태의 라이브러리를 만들지 설정할 수 있는데 STATIC 으로 명시하면 정적 라이브러리를, SHARED 로 설정하면 동적으로 링크되는 동적 라이브러리를, MODULE 로 명시하면, 동적으로 링크되지는 않지만, dlopen 과 같은 함수로 런타임 시에 불러올 수 있는 라이브러리를 생성합니다.

정적 라이브러리와 동적 라이브러리의 차이는 여기 에 정리해놓았으니 참조하시면 됩니다. 간단히 말하자면 정적 라이브러리는 프로그램 실행 파일에 라이브러리 코드가 전부 들어 있는 것이도, 동적 라이브러리는 프로그램 실행 파일에 라이브러리가 포함되어 있는 것이 아니라 메모리에 라이브러리가 따로 올라가는데 이를 참조하는 형태 입니다. 보통 정적으로 링크하면 실행 파일의 크기가 커지는 대신 동적 라이브러리를 사용할 때 보다 더 빠릅니다.

우리의 경우 간단한 라이브러리이므로 STATIC 으로 설정해서 정적 라이브러리를 만들도록 하겠습니다.

# 해당 라이브러리 컴파일 시 사용할 헤더파일 경로
target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)

# 해당 라이브러리를 컴파일 할 옵션
target_compile_options(shape PRIVATE -Wall -Werror)

나머지 부분은 아까와 같습니다. 여기서 한 가지 중요한 점은 PUBLIC  PRIVATE 의 차이 입니다.

기본적으로 CMake 에서 만약에 A 라이브러리가 B 라이브러리를 사용한다면 A 는 B 의 컴파일 옵션들과 헤더 파일 탐색 디렉토리 목록을 물려받게 됩니다. 정확히 말하면 PUBLIC 으로 설정된 것은 물려 받고, PRIVATE 으로 설정된 것은 물려받지 않습니다. (아래 부분에서 좀 더 정확히 다루겠습니다만 일단 이정도로 알고 계셔도 됩니다.)

따라서

target_include_directories(shape PUBLIC ${CMAKE_SOURCE_DIR}/includes)

위 문장의 의미는 다음과 같습니다.

  1. shape 를 컴파일 할 때 헤더 파일 검색 경로에 ${CMAKE_SOURCE_DIR}/includes 를 추가해주세요. 그리고
  2. shape 를 참조 하는 타겟의 헤더 파일 검색 경로에 ${CMAKE_SOURCE_DIR}/includes 를 추가해주세요

가 됩니다. 따라서 예를 들어서 아까 위에서 보았던 program  shape 를 사용한다면, program 을 컴파일 할 때 파일 검색 경로에 ${CMAKE_SOURCE_DIR}/includes 가 자동으로 추가됩니다.

반면에 라이브러리를 컴파일 하는 옵션은

target_compile_options(shape PRIVATE -Wall -Werror)

PRIVATE 으로 설정되어 있습니다. 그 이유는 shape 를 빌드할 때에는 -Wall  -Werror 옵션을 사용하고 싶지만, shape 를 사용하는 애들에게까지 이 옵션을 강제하고는 싶지 않기 때문이죠.

자 그렇다면 이 shape 를 한 번 program 에 링크해서 사용해봅시다.

shape 사용하는 간단한 프로젝트

참고를 위해 그림으로 나타내보자면 현재 전체적인 구성은 위와 같습니다.

프로젝트 레벨 CMakeLists.txt 를 다음과 같이 수정합니다.

# CMake 프로그램의 최소 버전
cmake_minimum_required(VERSION 3.11)

# 프로젝트 정보
project(
  ModooCode
  VERSION 0.1
  DESCRIPTION "예제 프로젝트"
  LANGUAGES CXX)

# 확인할 디렉토리 추가
add_subdirectory(lib)

add_executable (program main.cc)

# program 에 shape 를 링크
target_link_libraries(program shape)

먼저 add_subdirectory 명령을 통해서 CMake 가 추가로 확인해야 할 디렉토리의 경로를 지정해줍니다. 그러면 CMake 실행 시에, 해당 디렉토리로 들어가서 그 안에 있는 CMakeLists.txt 도 실행할 것입니다.

target_link_libraries 레퍼런스 링크

# program 에 shape 를 링크
target_link_libraries(program PUBLIC shape)

그리고 위와 같이 program 을 빌드 할 때 shape 라이브러리를 링크 시켜 줍니다. 참고로 실행 파일은 PUBLIC 이냐 PRIVATE 이냐의 여부가 크게 중요하지는 않습니다. 왜냐하면 실행 파일을 다른 타겟이 참조할 수 는 없기 때문이죠.

그래서 그냥

target_link_libraries(program shape)

로 써도 됩니다.

아무튼 전체적인 프로젝트의 구조는 다음과 같습니다.

$ tree
├── CMakeLists.txt
├── includes
│   └── shape.h
├── lib
│   ├── CMakeLists.txt
│   └── shape.cc
└── main.cc

그리고 한 번 실행해보면

실행 결과

Get size : 30

잘 나옴을 알 수 있습니다. 실제로 build 디렉토리를 확인해보면 libshape.a 파일이 생긴 것을 볼 수 있습니다. 참고로 CMake 는 라이브러리를 만들게 되면 앞에 lib 을 붙인 라이브러리 파일을 생성합니다.

주의 사항

옛날 버전의 CMake 에서는 앞에 *target_ 이 빠진 include_directories, link_directories 와 같은 명령들이 사용되었는데 이는 최신의 CMake 에서는 사용이 권장되지 않는 명령들 입니다. 현재의 CMake 패러다임은 타겟들을 기준으로 돌아가기 때문에 꼭 target_* 형태의 명령을 사용하도록 합시다.

 

출처 : https://modoocode.com/332

반응형