[모던 CMake] 재사용 가능한 라이브러리를 위한 CMake 패키지 구성

이번 글에서는 모던 CMake를 활용하여 재사용 가능한 라이브러리를 패키지화하고, 다른 프로젝트에서 손쉽게 사용할 수 있도록 설정하는 방법을 알아보겠습니다. CMake의 패키지 관리 기능을 통해 라이브러리를 배포하고, find_package()를 이용하여 의존성을 간편하게 관리할 수 있습니다. 이 과정을 통해 프로젝트의 확장성과 유지보수성을 크게 향상시킬 수 있습니다.

CMake 패키지 구성의 필요성

재사용 가능한 라이브러리를 패키지화하면 다음과 같은 이점을 얻을 수 있습니다:

  • 의존성 관리의 단순화: find_package()를 통해 필요한 라이브러리를 손쉽게 찾고 링크할 수 있습니다.
  • 버전 관리: 특정 버전의 라이브러리를 요구하고, 호환성을 보장할 수 있습니다.
  • 배포의 용이성: 라이브러리를 다른 개발자나 프로젝트와 쉽게 공유할 수 있습니다.
  • 모듈화: 프로젝트를 여러 개의 독립적인 컴포넌트로 분리하여 관리할 수 있습니다.

패키지 구성의 기본 흐름

  1. 라이브러리 정의: CMake로 라이브러리를 정의하고, 필요한 설정을 추가합니다.
  2. 설치 규칙 설정: install() 명령어를 사용하여 라이브러리와 헤더 파일을 설치할 위치를 지정합니다.
  3. 패키지 설정 파일 생성: export() 명령어를 통해 패키지 설정 파일을 생성합니다.
  4. 패키지 찾기 설정: 다른 프로젝트에서 find_package()를 사용하여 패키지를 찾고 링크할 수 있도록 설정합니다.

예제 프로젝트 구성

디렉토리 구조

my_library/
├── CMakeLists.txt
├── include/
│   └── mylib.h
├── src/
│   └── mylib.cpp
└── tests/
    └── test.cpp

최상위 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(MyLibrary VERSION 1.0.0 LANGUAGES CXX)

# C++ 표준 설정
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 서브디렉토리 추가
add_subdirectory(src)
add_subdirectory(tests)

# 패키지 설치 설정
include(CMakePackageConfigHelpers)

write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY AnyNewerVersion
)

configure_package_config_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyLibraryConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
    INSTALL_DESTINATION lib/cmake/MyLibrary
)

install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
    DESTINATION lib/cmake/MyLibrary
)

src/CMakeLists.txt

add_library(MyLib src/mylib.cpp)

# 공개 헤더 파일 경로 설정
target_include_directories(MyLib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
    $<INSTALL_INTERFACE:include>
)

# 라이브러리 설치 규칙 설정
install(TARGETS MyLib EXPORT MyLibraryTargets
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin
    PUBLIC_HEADER DESTINATION include
)

# 헤더 파일 설치 규칙 설정
install(DIRECTORY ../include/ DESTINATION include)

# Export 설정
install(EXPORT MyLibraryTargets
    FILE MyLibraryTargets.cmake
    NAMESPACE MyLibrary::
    DESTINATION lib/cmake/MyLibrary
)

cmake/MyLibraryConfig.cmake.in

@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/MyLibraryTargets.cmake")

tests/CMakeLists.txt

enable_testing()

find_package(MyLibrary CONFIG REQUIRED)

add_executable(MyTests test.cpp)
target_link_libraries(MyTests PRIVATE MyLibrary::MyLib)

add_test(NAME MyTest COMMAND MyTests)

src/mylib.cpp 예제

#include "mylib.h"

int add(int a, int b) {
    return a + b;
}

include/mylib.h 예제

#pragma once

int add(int a, int b);

tests/test.cpp 예제

#include <gtest/gtest.h>
#include "mylib.h"

TEST(MyLibTest, AddFunction) {
    EXPECT_EQ(add(1, 2), 3);
    EXPECT_EQ(add(-1, -1), -2);
}

패키지 설치 및 사용

라이브러리 빌드 및 설치

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
cmake --build .
cmake --install .
  • CMAKE_INSTALL_PREFIX: 라이브러리가 설치될 경로를 지정합니다. 기본값은 /usr/local입니다.

다른 프로젝트에서 패키지 사용

디렉토리 구조

external_project/
├── CMakeLists.txt
└── src/
    └── main.cpp

external_project/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(ExternalProject LANGUAGES CXX)

find_package(MyLibrary CONFIG REQUIRED)

add_executable(ExternalApp src/main.cpp)
target_link_libraries(ExternalApp PRIVATE MyLibrary::MyLib)

external_project/src/main.cpp 예제

#include <iostream>
#include "mylib.h"

int main() {
    std::cout << "1 + 2 = " << add(1, 2) << std::endl;
    return 0;
}

빌드 및 실행

mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/usr/local ..
cmake --build .
./ExternalApp
  • CMAKE_PREFIX_PATH: find_package()가 패키지 설정 파일을 찾을 경로를 지정합니다. 라이브러리가 설치된 경로를 포함해야 합니다.

패키지 버전 관리

패키지 버전 관리는 프로젝트 간 호환성을 유지하는 데 중요합니다. CMake는 패키지 버전을 명시하고, 요구되는 최소 버전을 설정할 수 있습니다.

find_package()에서 버전 요구

find_package(MyLibrary 1.0.0 CONFIG REQUIRED)
  • 1.0.0: 최소 요구 버전을 지정합니다.
  • REQUIRED: 패키지를 찾지 못하면 빌드를 중단합니다.

패키지 설정 파일에서 버전 정보 사용

MyLibraryConfig.cmake.in 파일에 다음 내용을 추가하여 버전 정보를 포함시킬 수 있습니다.

include(CMakeFindDependencyMacro)

find_dependency(Boost REQUIRED COMPONENTS system)

# 패키지 타겟 설정
add_library(MyLibrary::MyLib ALIAS MyLib)

패키지 재배포

패키지를 재배포하려면, 설정한 설치 규칙에 따라 패키지를 생성하고 배포할 수 있습니다. CPack을 사용하여 다양한 형식의 패키지를 생성할 수 있습니다.

CPack 설정 추가

최상위 CMakeLists.txt에 다음을 추가합니다.

include(CPack)

set(CPACK_PACKAGE_NAME "MyLibrary")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_CONTACT "support@mycompany.com")
set(CPACK_GENERATOR "TGZ;DEB;RPM;ZIP")
include(CPack)

패키지 생성

cmake --build . --target package
  • CPACK_GENERATOR에 지정된 형식으로 패키지가 생성됩니다.

베스트 프랙티스

  • 타겟 기반 설정 사용: target_include_directories(), target_compile_definitions(), target_link_libraries() 등 타겟 기반 설정을 사용하여 설정을 명확하게 관리합니다.
  • 인터페이스 라이브러리 활용: 공통 설정이나 기능을 인터페이스 라이브러리로 분리하여 재사용성을 높입니다.
  • 버전 고정: 패키지의 버전을 명시하고, 의존성의 최소 요구 버전을 설정하여 호환성을 유지합니다.
  • 명확한 설치 규칙: 헤더 파일과 라이브러리 파일의 설치 경로를 명확하게 지정하여 패키지를 일관되게 관리합니다.
  • 패키지 설정 파일의 일관성 유지: EXPORT와 find_package()가 올바르게 동작하도록 설정 파일을 일관되게 유지합니다.

참고 자료

이상으로, 모던 CMake를 활용하여 재사용 가능한 라이브러리를 패키지화하고 다른 프로젝트에서 쉽게 사용할 수 있도록 설정하는 방법을 살펴보았습니다. 이러한 패키지 관리 전략을 통해 프로젝트의 확장성과 유지보수성을 크게 향상시킬 수 있습니다.

반응형