C++ 개발자 중 CMake를 사용해 빌드환경을 구성하는 방식이 익숙하시다면 CMakeLists.txt에도 익숙하실 것 입니다. 항상 잘 되기만 하면 좋겠지만 많은 경우 작은 문제를 해결하는데 CMakeLists.txt에서 시간을 많이 소모하곤 합니다.
그래도 CMake는 여러므로 C++ 프로젝트를 구성하고 빌드하는 환경을 구성하는데 꽤 도움이 되는 편 입니다. 여러 라이브러리를 사용하는 복잡한 프로젝트일 수록 그 장점이 유용하게 다가옵니다.
CMake의 유용한 점 중 하나는 빌드환경을 구성하는 전처리 과정에서 변수를 사용하여 시스템을 더 유연하게 만드는 것이 가능하다는 점 입니다. 이는 여러 경우에 유용하지만, 저는 이 기능을 빌드환경의 최상위 루트(root) 디렉토리 경로를 자동으로 찾고 이를 C++ 파일에서 활용할 수 있도록 사용하고 있습니다.
이번 포스팅에서는 CMake 경로를 예로 CMakeLists.txt 내에 변수를 이용해 이를 컴파일 과정에서 이용할 수 있는 방법에 대해 소개합니다.
추가로, C++과 Python 프로그램간 통신하는 방법에 대한 포스팅도 있으니 필요하시면 함께 참고하셔도 좋을 것 같습니다.
Table of Contents
CMake 프로젝트 디렉토리
CMake는 프로젝트를 빌드하기 전 시스템을 구성하는 generation 단계에서 프로젝트 디렉토리 절대경로를 가져올 수 있습니다. CMake가 정의하는 경로는 몇가지 있는데, 그 중에서 가장 많이 사용되는 프로젝트 경로는 root와 source 디렉토리 입니다.
Root 디렉토리
루트 경로는 CMake 프로젝트의 최상위 디렉토리를 가리킵니다. 이 경로는 CMakeLists.txt
파일이 위치한 디렉토리로, 프로젝트의 전체 구조와 빌드 과정이 시작되는 곳입니다. 루트 디렉토리는 프로젝트의 기본 설정, 라이브러리 링크, 포함할 하위 프로젝트 등에 대한 정보를 포함하고 있습니다. 루트 경로를 기준으로 CMake는 전체 프로젝트의 구성 요소를 파악하고, 필요한 빌드 스크립트를 생성합니다. CMAKE_SOURCE_DIR
또는 PROJECT_SOURCE_DIR
변수를 사용하여 루트 경로에 접근할 수 있습니다.
Source 디렉토리
소스 경로는 프로젝트의 소스 코드가 포함된 디렉토리를 의미합니다. 이 경로는 소스 파일, 헤더 파일, 기타 리소스 파일 등 프로젝트에 필요한 실제 코드 파일들이 저장되는 곳입니다. 소스 경로는 루트 경로 내부에 있을 수도 있고, 별도의 디렉토리에 위치할 수도 있습니다. CMake에서 소스 경로는 CMAKE_CURRENT_SOURCE_DIR
변수를 통해 참조됩니다. 이 경로는 현재 처리 중인 CMakeLists.txt
파일이 있는 디렉토리를 가리키며, 프로젝트의 여러 하위 컴포넌트들이 각자의 소스 경로를 가질 수 있습니다.
두 경로의 차이와 선택
루트 경로와 소스 경로는 서로 밀접하게 연관되어 있으면서도 구별됩니다. 루트 경로는 프로젝트의 전반적인 구조와 설정을 정의하는 반면, 소스 경로는 구체적인 코드 파일과 리소스를 관리합니다. 이 두 경로는 프로젝트의 빌드 과정에 중요한 역할을 하며, 각각의 경로에 대한 명확한 이해는 효율적인 빌드 시스템 구축에 필수적입니다.
CMake 절대경로 자동으로 가져오기
지금부터는 CMakeLists.txt를 이용해 자동으로 CMake 경로를 찾아 가져오는 방법에 대해 소개하겠습니다. 여러 경로 중 Source 디렉토리를 가져오는 방법을 적어보겠습니다.
이를 구현하는 방식은 아래와 같습니다.
- CMakeLists.txt에서 사용되는 변수 내용을 컴파일 전 generation 단계에서 정해진 파일에 작성하도록 합니다.
- 그 뒤, 컴파일 단계에서 이 내용을 string으로 읽어 컴파일 타임에 사용하도록 합니다.
변수 내용을 담을 파일 생성
CMake가 파일에 정보를 담으려면 우선 내용을 담을 파일을 만들어야 합니다. 첫 1회만 생성하면 그 뒤로는 generation 시 자동으로 덮어쓰게 됩니다.
이름에 제약은 없지만, convention에 따라 파일 이름은 config.h.in으로 정하겠습니다. 이 파일을 CMakeLists.txt와 동일한 경로에 생성합니다.
// kongineer.com
#ifndef CONFIG_H_IN
#define CONFIG_H_IN
#define PROJECT_NAME @PROJECT_NAME@ //project name will be written here
#define STR(x) #x //for stringizing
#define CMAKE_SOURCE_DIR STR(@CMAKE_CURRENT_SOURCE_DIR@) //cmake source dir will be written here
#endif // CONFIG_H_IN
CMakeLists.txt 내용 수정
다음으로, CMakeLists.txt에 아래 내용을 추가합니다. 추가할 위치는 크게 중요하지 않지만, 가급적 상단에 적어주는 것이 깔끔할 것 같습니다.
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h @ONLY)
동작 확인
위 두가지 내용을 적용하고 CMake를 Generate하면 다음과 같이 원래 없던 config.h 파일이 생성된 것을 확인할 수 있습니다.
C++ 파일에서 사용하기
파일에 경로 정보가 담기는 것을 확인하였으니 이제 C++ 파일에서 이를 사용하는 방법을 소개드리겠습니다. 사실 이미 헤더파일에 string으로의 변환까지 준비되어 있어 매우 간단히 가져다 사용할 수 있습니다. 아래 라인이 CMake에서 파일에 적은 내용을 메크로 변수를 string으로 변환시켜주는 기능을 하는데, 이를 stringizing 이라고 합니다. 이에 대한 자세한 내용은 이곳에서 확인할 수 있습니다.
#define STR(x) #x //stringizing
아래 코드와 같이 config.h 헤더파일을 부르고 string으로 변환하여 사용하면 됩니다.
// Example for parsing cmake source directory at cmake generation
// https://phd.korean-engineer.com
//
#include <iostream>
#include "config.h"
using namespace std;
int main()
{
string cmake_src_dir = std::string(CMAKE_SOURCE_DIR);
cout << "CMake Directory: " << cmake_src_dir << endl;
return 0;
}
실행하면 현재 프로젝트의 절대경로가 출력되는 것을 확인할 수 있습니다.
마무리
이번 포스팅에서는 CMake 환경에서 source directory를 generation 단계에서 매크로로 추출하고, 이를 빌드 단계에서 문자열 데이터로 활용하는 방법에 대해 알아보았습니다. 이는 CMake를 사용하는 개발자들에게 매우 중요한 주제로, 효율적인 빌드 과정과 프로젝트 관리를 위해 알아두면 도움이 될 수 있는 기능입니다.