CMake 프로젝트의 유지보수성을 높이기 위해서는 코드의 구조화와 관리 방식을 신중하게 설계해야 한다. 이는 프로젝트가 확장되고 복잡해짐에 따라 특히 중요해진다. 다음은 CMake 프로젝트의 유지보수성을 높이기 위한 몇 가지 구조화 방법이다.

모듈화 및 파일 분할

CMakeLists.txt 파일의 분할

대규모 프로젝트에서는 단일 CMakeLists.txt 파일에 모든 빌드 구성을 넣는 것은 유지보수에 큰 부담이 된다. 이를 해결하기 위해 각 서브디렉토리나 모듈별로 CMakeLists.txt 파일을 분할하는 것이 좋다. 각 서브디렉토리에서 하위 CMakeLists.txt 파일을 정의하고, 상위 디렉토리의 CMakeLists.txt 파일에서 add_subdirectory() 명령을 사용해 하위 디렉토리를 포함시킬 수 있다.

예를 들어:

add_subdirectory(src)
add_subdirectory(tests)

이와 같이 CMakeLists.txt 파일을 분할하면 코드의 가독성이 높아지고, 특정 모듈이나 컴포넌트에 대한 빌드 설정을 독립적으로 관리할 수 있다.

공통 설정의 분리

프로젝트 전반에 걸쳐 공통적으로 사용되는 설정이나 매크로는 별도의 파일로 분리하여 관리하는 것이 좋다. 예를 들어, 공통 변수나 함수, 유틸리티 매크로 등을 cmake/ 디렉토리 아래에 별도로 정의한 후 include() 명령을 통해 필요한 곳에서 포함시킬 수 있다.

include(cmake/common.cmake)

이를 통해 중복된 코드 작성이 줄어들고, 유지보수가 쉬워진다.

변수와 옵션 관리

프로젝트-전역 변수 사용의 최소화

CMake는 전역 변수를 지원하지만, 유지보수성을 위해 전역 변수의 사용을 최소화하는 것이 좋다. 가능한 경우, 변수의 스코프를 명확하게 지정하고, 필요할 때만 전역 변수를 사용하는 것이 좋다. 예를 들어, 특정 타겟에만 필요한 변수를 target_compile_definitions()와 같은 명령을 통해 타겟 스코프로 제한할 수 있다.

옵션 설정의 중앙 집중화

프로젝트에서 사용되는 다양한 빌드 옵션(CMAKE_CXX_STANDARD, CMAKE_BUILD_TYPE 등)은 중앙에서 관리하는 것이 좋다. 이를 위해 option() 명령을 사용해 프로젝트에서 사용할 수 있는 빌드 옵션을 정의하고, CMakeLists.txt 파일의 상단부나 별도의 설정 파일에 이를 모아두는 것이 좋다.

option(BUILD_TESTS "빌드 시 테스트를 포함할지 여부" ON)

이와 같이 옵션을 중앙 집중화하면, 프로젝트의 빌드 설정을 일관되게 유지할 수 있다.

타겟 기반 설정

타겟 별 설정 사용

CMake는 타겟 기반의 설정을 지원하며, 이를 통해 각 타겟(실행 파일, 라이브러리 등)에 대해 별도의 빌드 구성을 적용할 수 있다. target_compile_options(), target_include_directories(), target_link_libraries() 등의 명령어를 사용해 각 타겟의 빌드 설정을 독립적으로 관리하는 것이 좋다.

예를 들어, 특정 라이브러리만 디버그 빌드에서 추가적으로 컴파일 옵션을 적용하고자 할 때:

target_compile_options(my_library PRIVATE
    $<$<CONFIG:Debug>:-DDEBUG>
)

이와 같은 설정은 타겟별로 필요한 빌드 구성을 명확히 하고, 불필요한 전역 설정을 줄여준다.

프로젝트 구조와 디렉토리 관리

논리적인 디렉토리 구조 설계

프로젝트의 소스 코드와 빌드 파일이 논리적으로 조직화되도록 디렉토리 구조를 설계하는 것이 중요하다. 예를 들어, 소스 파일은 src/, 헤더 파일은 include/, 빌드 관련 스크립트는 cmake/ 디렉토리에 배치하는 방식으로 구성할 수 있다. 이는 코드의 탐색과 관리에 용이하며, 프로젝트의 확장성에도 기여한다.

project_root/
├── CMakeLists.txt
├── cmake/
│   ├── common.cmake
├── src/
│   ├── main.cpp
│   ├── CMakeLists.txt
├── include/
│   ├── my_header.h
└── tests/
    ├── test_main.cpp
    ├── CMakeLists.txt

서브 프로젝트 및 외부 종속성 관리

프로젝트가 여러 서브 프로젝트로 구성되어 있다면, 각 서브 프로젝트를 독립된 CMakeLists.txt 파일로 관리하고, 상위 프로젝트에서 이를 통합하는 것이 좋다. 외부 라이브러리나 서브모듈을 포함해야 할 경우에는 ExternalProject 모듈을 사용해 외부 종속성을 관리하거나, FetchContent 모듈을 사용해 필요한 서브 프로젝트를 동적으로 가져올 수 있다.

include(ExternalProject)
ExternalProject_Add(
    googletest
    PREFIX ${CMAKE_BINARY_DIR}/external
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG release-1.10.0
)

유지보수성을 위한 테스트 자동화

테스트 통합

CMake는 테스트 자동화를 위한 CTest 모듈을 제공한다. 프로젝트의 각 구성 요소에 대해 테스트를 작성하고, 이를 CTest를 통해 통합하면 유지보수 과정에서 발생할 수 있는 오류를 조기에 발견할 수 있다. add_test() 명령을 사용해 테스트를 정의하고, enable_testing()으로 CTest를 활성화하여 빌드 시 자동으로 테스트가 실행되도록 설정할 수 있다.

enable_testing()
add_executable(my_test test_main.cpp)
add_test(NAME MyTest COMMAND my_test)

이를 통해 코드 변경 시 테스트 자동화를 통해 문제를 신속하게 감지할 수 있다.

문서화와 주석 관리

CMakeLists.txt 문서화

CMakeLists.txt 파일에는 코드의 역할과 주요 설정에 대한 주석을 충분히 달아두는 것이 좋다. 특히, 복잡한 설정이나 비표준적인 구성이 있을 경우, 해당 부분에 대해 명확한 설명을 추가하는 것이 중요하다. 이는 프로젝트를 인수받은 다른 개발자나 시간이 지나 본인 스스로가 다시 프로젝트를 유지보수할 때 큰 도움이 된다.

사용자 정의 모듈 문서화

사용자 정의 CMake 모듈이나 함수가 많아질 경우, 이를 별도의 문서화 파일로 관리하거나, 각 모듈의 상단에 명확한 설명을 추가해두는 것이 좋다. 또한, 사용 예제를 함께 포함해 다른 개발자가 쉽게 이해하고 사용할 수 있도록 돕는 것이 바람직한다.


관련 자료: