CMake 빌드 시스템 만들기

Younghyun Jo
6 min readFeb 1, 2019

--

빌드하기

타겟

CMake 빌드 시스템은 논리적인 타겟(target)의 집합으로 구성된다. 타겟은 실행파일, 라이브러리, 사용자 명령어 등을 표현하고 있다.

타겟간 의존관계에 따라 빌드 순서와 수정 사항에 대해 재빌드 할 타겟이 결정된다.

실행 프로그램 빌드하기

add_executable(mytest mytest.cpp)
target_link_libraries(mytest c-json)

add_executable()은 실행 프로그램 타겟을 만들고, 타겟에 필요한 소스 파일을 나열한다.

target_link_libraries()는 빌드에 필요한 타겟을 나타낸다. 필요한 타겟은 라이브러리, 외부 프로젝트, 실행 프로그램 등이 있다.

라이브러리 빌드하기

  • STATIC 라이브러리
add_library(archive archive.cpp zip.cpp lzma.cpp) add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)
  • SHARED 라이브러리
add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)

add_library()는 라이브러리를 위한 타겟을 정의하고, 타겟에 필요한 소스 파일을 명시한다.

라이브러리는 STATIC, SHARED, MODULE 타입을 가진다. STATIC는 정적 라이브러리, SHARED는 동적 라이브러리, MODULE은 plugin처럼 런타임에 동적으로 로딩되는 라이브러리다. 타입이 없으면 정적 라이브러리 타입을 사용한다.

SHARED, MODULE 타입은 타겟 속성 POSITION_INDEPENDENT_CODE을 TRUE로 변경한다.

속성(Properties)

타겟의 속성을 지정하여 컴파일 옵션, 해더파일 디렉토리 위치 등의 빌드명세*를 설정할 수 있다.

* 빌드 명세(Build Specification) : 오브젝트 파일의 생성, 변경에 대한 정보를 기록한 명령어 집합

타겟의 속성을 지정하는 함수에는 target_compile_definitions(), target_link_libraries()등이 있다.

set(srcs archive.cpp zip.cpp)
if (LZMA_FOUND)
list(APPEND srcs lzma.cpp)
endif()
add_library(archive SHARED ${srcs})
if (LZMA_FOUND)
# The archive library sources are compiled with -DBUILDING_WITH_LZMA
target_compile_definitions(archive PRIVATE BUILDING_WITH_LZMA)
endif()
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)
add_executable(consumer)
# Link consumer to archive and consume its usage requirements. The consumer
# executable sources are compiled with -DUSING_ARCHIVE_LIB.
target_link_libraries(consumer archive)

PRIVATE / INTERFACE / PUBLIC

타겟 속성을 지정할 때 모드를 설정할 수 있다. 모드에 따라 속성의 적용 범위(Scope)가 결정이 된다.

  • PRIVATE
    타겟 A의 속성을 PRIVATE로 지정하면, 타겟 A에게만 적용된다.(Link Dependency)
  • INTERFACE
    타겟 A의 속성을 INTERFACE로 지정하면, 타겟 A를 사용하는 제3의 타겟에게만 적용된다. (Link Interface)
  • PUBLIC
    타겟 A의 속성을 PUBLIC으로 지정하면, 타겟 A와 타겟 A를 사용하는 제3의 타겟에게 적용된다.(Link Dependency and Link Interface)

예를 보자. 총 3개의 타겟 C, B, A가 있고, 다음과 같은 포함관계가 있다고 생각하자.

C⊃B⊃A

C는 B를 사용하고, B는 A를 사용한다.

target_link_libraries(B PRIVATE A)
target_link_libraries(C PRIVATE B)

B가 A를 링크할 때 속성은 PRIVATE이다. 즉, 타겟 B를 빌드할 때 A에 대한 의존성을 설정하지만, 타겟 C를 빌드할 때엔 A에 대한 의존성은 설정하지 않는다.

target_link_libraries(B PUBLIC A)
target_link_libraries(C PRIVATE B)

B가 A를 링크할 때 속성은 PUBLIC이다.타겟 B를 빌드할 때 A에 대한 의존성을 설정하고, 타겟 C를 빌드 할 때에도 A에 대한 의존성을 설정한다.

이처럼 타겟 B가 A에게 의존할 때를 Link Dependency, 타겟 B가 A에게 의존한다는 정보를 타겟C에게 노출하는 것을 Link Interface라고 한다.

PRIVATE @ STATIC 라이브러리

add_library(C STATIC c.cpp)
add_library(B STATIC b.cpp)
add_library(A STATIC a.cpp)
target_link_libraries(B PRIVATE A)
target_link_libraries(C PRIVATE B)

STATIC 라이브러리 B는 PRIVATE으로 A를 링크하기 때문에, C를 빌드할 때 A에 대한 의존성이 없어야 한다. 하지만 STATIC 라이브러리에는 의존성이 있는 라이브러리에 대한 정보가 없다. 따라서, C가 B를 링크한다면, B만 링크를 하고, A는 링크가 되지 않는다. 즉, 링크 에러가 발생한다. CMake는 이러한 상황을 방지 위해 STATIC 라 STATIC 라이브러리를 링크할 경우엔 PRIVATE을 PUBLIC으로 자동으로 변경한다. 단, target_link_libraries()에 대해서만 변경을 하고 나머지(compiler options/flags and include search path)등은 변경하지 않는다.

타겟 속성(Target Properties)

타겟 속성에는 컴파일시 전처리 변수 정의(COMPILE_DEFINITIONS), 해더파일 검색 위치(INCLUDE_DIRECTORIES), 컴파일 옵션(COMPILE_OPTIONS) 등이 있다.

전처리 변수 정의는 컴파일러의 -D 옵션과 동일하다. 아래 명령어로 타겟에 전처리 변수를 지정할 수 있다.

# -DUSING_DEBUG_MODE
target_compile_definitions(TARGETNAME PUBLIC USING_DEBUG_MODE)

해더파일 검색 위치 지정은 컴파일러의 -I 옵션과 동일하다. 아래 명령어로 해더파일 검색 위치를 지정할 수 있다.

# -I../include
target_include_directories(TARGET_NAME PRIVATE ../include)

컴파일 옵션은 나머지 컴파일 옵션을 지정하는데 쓰인다. 아래 명령어를 사용한다.

target_compile_options(TARGET_NAME INTERFACE -Wall)

--

--