본문 바로가기
CMake

[CMake] Generator Expressions

by 별준 2021. 11. 3.

References

  • Professional CMake : A Practical Guide

Contents

  • Generator Expressions
  • Target Details
  • General Information

CMake를 실행하면서 프로젝트의 CMakeLists.txt 파일을 읽고 프로젝트 파일들을 생성하는 것을 하나의 단일 단계로 생각할 수 있습니다. 그러나 CMakeLists.txt를 읽는 것과 빌드 파일을 생성하는 것은 전혀 다른 단계입니다.

CMake가 실행되면, 먼저 소스 트리의 최상단에 존재하는 CMakeLists.txt를 읽고 처리합니다. 그리고 프로젝트의 internal representation들은 CMakeLists.txt 안의 커맨드와 함수들이 실행되면서 만들어집니다.

이 과정을 Configuration Step이라고 합니다. CMake가 실행되면서 콘솔에 출력되는 대부분의 로그(message() 커맨드를 포함)는 Configuration 단계에서 출력되는 것입니다.

이 Configuration 단계가 끝나면, 'Configuring done' 이라는 메세지가 출력됩니다.

 

CMake가 CMakeLists.txt 파일을 다 읽으면, 다음으로 Generation Step이 수행됩니다. Generation Step은 Configuration에서 빌드된 internal representation들을 사용하여 빌드 툴의 프로젝트 파일들을 생성되는 단계입니다. 대부분의 경우, 이 Generation Step을 무시하는 경향이 있는데, 콘솔 로그를 보더라도 Configuration 단계가 완료되면 거의 즉지 'Generating done' 이라는 메세지가 나타나기 때문에 이런 경향을 보일 수 있습니다.

그러나 이 두 단계를 분리해서 이해하는 것이 중요할 수 있습니다.

 

Visual Studio나 Xcode와 같은 multi configuration CMake Generator로 처리되는 프로젝트를 예로 들어보겠습니다. CMakeLists.txt 파일을 읽을 때, CMake는 빌드되는 Target이 어떤 configuration으로 빌드되는지 알지 못합니다.

(multi configuration setup이므로, 여러 선택지-debug or release-가 존재합니다.)

이런 경우 개발자는 CMake가 완료된 후 빌드 시간에 이 configuration을 선택합니다.

그렇다면, 최종 실행 파일이 존재하는 디렉토리의 위치는 빌드가 수행될 때의 configuration에 따라 다르기 때문에 CMakeLists.txt 파일이 주어진 Target에 대한 최종 실행 파일과 동일한 디렉토리에 복사하는 것은 문제가 있어보입니다. 따라서, CMake에게 어떤 configuration으로 빌드가 되든, 동일한 최종 실행 파일의 디렉토리를 사용하라는 일종의 placeholder가 필요합니다.

 

이 placeholder가 바로 Generator Expression가 제공하는 기능 중의 대표적인 예입니다.

Generator Expression은 configuration 단계에서 평가되지 않는 일부 로직(some logic which is not evaluated at configure time)을 인코딩하는 방법을 제공합니다. 대신 Evaluation은 프로젝트 파일이 생성되는 Generation 단계까지 딜레이됩니다. (즉, configure 단계에서는 Generator Expression이 처리되지 않습니다.)

Generator Expression은 조건부 로직을 수행하고, 디렉토리나 어떤 것들의 이름, 또는 플랫폼의 세부 정보를 제공하는 output 문자열을 사용할 수 있습니다. 또한, 빌드 또는 install이 수행되는지 여부에 따라 다른 content를 제공하는데 사용될 수 있습니다.

간단히 아래의 내용의 CMakeLists.txt를 실행해보세요.
cmake_minimum_required(VERSION 3.10)
project(generator_expression)

message("$<1:foo>")
message("$<0:foo>")
그 결과, generator expression이 제대로 적용되지 않은 것을 볼 수 있습니다. 이는, generator expression은 generating time에 적용되는데, message()로 출력하는 것은 configuring time이기 때문입니다.

 

Generator Expression은 아무대나 사용할 수는 없지만, 그래도 많은 곳에서 지원됩니다. CMake 레퍼런스 문서에서는 만약 특정 커맨드나 속성이 Generator Expression을 지원하면, 이를 언급하고 있습니다. 

(CMake 버전이 올라갈수록 Generator Exrpession을 지원하는 속성이나 커맨드가 확장되었는데, 따라서 사용하려는 속성이나 커맨드가 Generator Exrpession을 사용하기 전에 필요한 최소한의 CMake 버전을 확인해야 합니다.)

 

공식 문서에는 다음과 같이 설명하고 있습니다.
Generator Expressions은 각 빌드 구성을 위한 특정 정보를 생성하기 위해 빌드 시스템 생성 중에 평가(evaluated)됩니다. Generator Expression은 LINK_LIBRARIES, INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS와 같은 Target 속성에서 허용되고, target_link_libraries(), target_include_directories(), target_compile_definitions() 등과 같은 속성을 설정하는 커맨드에서도 사용될 수 있습니다. 이는 컴파일할 때, 조건부 링크나 조건부 정의를 가능하게 하고, 조건부 including도 가능하게 합니다. 이때 사용되는 조건은 빌드 구성, Target 속성, 플랫폼 정보 등 쿼리 가능한 어떠한 정보를 기반으로 할 수 있습니다. 

Simple Boolean Logic

Generator Expression은 '$<...>' 이라는 구문으로 사용할 수 있습니다. 괄호 안의 내용은 여러 가지 형식으로 사용할 수 있는데, 기본적인 사용법은 다음과 같습니다.

'$<1:...>'의 경우 이 표현식의 결과는 '...' 부분이 되는 반면, '$<0:...>'은 '...' 부분은 무시되고 결과는 빈 문자열이 됩니다.

'$<BOOL:...>' 표현식은 BOOL 자리에 오는 값이 CMake에서 false로 인식되는 것이라면 0과 동일하게 처리되고, true로 인식되면 1과 동일하게 처리됩니다.

이처럼 Generator Expression은 content를 선택적으로 포함할 수 있는 간단한 방법을 제공합니다.

 

Logical operation 또한 가능합니다.

각 expr은 1 또는 0으로 평가될 수 있습니다. AND 및 OR 표현식은 쉼표로 구분된 여러 argument를 취하고 해당 논리 결과를 취할 수 있는 반면, NOT은 단일 표현식만 허용됩니다. AND, OR, NOT의 경우 argument로 입력되는 표현식들이 0 또는 1로만 평가되어야 하기 때문에 이 표현식들을 $<BOOL:...>으로 래핑하여 true 또는 false로 간주되도록 강제하는 것이 좋습니다.

공식 문서의 Logical Operators

 

CMake 3.8 버전 이후부터는 if-then-else 로직 또한 제공됩니다.

일반적으로 expr는 1 또는 0으로 간주됩니다. 만약 expr이 1로 간주된다면 결과는 val1 이고, 0이라면 결과는 val0입니다. 

CMake 3.8 이전 버전이라면 if-then-else 문법 대신 아래처럼 사용하여 똑같은 결과를 얻을 수 있습니다.

 

Generator Expression은 중첩이 가능한데, 이로 인해 복잡한 표현식을 구성할 수 있습니다.

 

if() 커맨드와 마찬가지로 CMake는 Generator Expression에서 문자열, 숫자, 버전을 평가할 수 있도록 지원합니다. 다음 예시는 모두 해당 조건이 충족되면 결과는 1이 되고 그렇지 않으면 0이 됩니다.

 

빌드 타입을 체크하기 위한 유용한 조건부 표현식은 다음과 같습니다.

arg가 실제로 빌드되는 빌드 유형에 해당하면 결과가 1로 평가되고 다른 모든 빌드 유형에 대해서는 0으로 평가됩니다. 이 용법의 일반적인 용도는 디버그 빌드에 대해서만 컴파일러 플래그를 제공하거나 다른 빌드 타입에 대해 다른 구현을 선택할 수 있도록 하는 것입니다.

예를 들면, 다음과 같이 작성할 수 있습니다.

두 target_link_libraries는 동일한 동작이며, 아래의 target_link_libraries는 CMake 3.8 버전 이상에서만 사용할 수 있습니다. 위 코드는 실행 파일을 디버그 빌드의 경우 checkedAlgo 라이브러리에 링크하고 다른 모든 빌드 타입의 경우에는 fastAlgo 라이브러리에 링크합니다. '$<CONFIG:...>' 표현식은 Visual Studio나 Xcode와 같은 multi configuration generator를 포함하여 모든 CMake 프로젝트 generator를 위해 위의 예시와 같은 기능을 제공하는 유일한 방법입니다.

 

이외에도 CMake는 플랫폼 및 컴파일러 세부사항을 조건부로 적용하기 위한 더 많은 기능을 제공합니다.

 


Target Details

Generator Expression는 일반적으로 Target에 대한 정보를 제공할 때 사용됩니다. Target의 속성은 일반적으로 아래의 두 가지 방법 중의 하나로 얻을 수 있습니다.

첫 번째 형식은 지정된 target으로부터 property라는 이름의 값을 제공합니다.

반면, 두 번째 형식은 Generator Expression이 사용되는 target에서 property를 검색합니다.

 

TARGET_PROPERTY는 정말 유연한 표현식 타입이지만, 항상 target의 정보를 얻을 수 있는 가장 베스트의 방법은 아닙니다. 예를 들어, CMake는 Target의 빌드된 바이너리 이름이나 디렉토리에 대한 세부 정보를 가져오는 다른 표현식을 제공합니다. 

 

다른 일반적인 옵션으로는 TARGET_FILE 옵션이 있습니다.

  • TARGET_FILE : target 바이너리의 full 경로를 쿼리합니다. 파일의 suffix 또한 포함합니다(ex, .exe, .dylib).
  • TARGET_FILE_NAME : target 바이너리의 name을 쿼리합니다. TARGET_FILE에서 경로를 제외한 파일 이름입니다.
  • TARGET_FILE_DIR : TARGET_FILE에서 name없이 바이너리 파일이 존재하는 경로를 쿼리합니다.

위의 세 TARGET_FILE 표현식은 빌드 후(post build)에 파일을 복사하기 위한 custom build rule을 정의할 때 유용합니다. TARGET_FILE 이외에도 CMake는 유사한 기능의 라이브러리별 Generator Expression을 제공합니다. 이 표현식들은 TARGET_LINKER_FILE 또는 TARGET_SONAME_FILE라는 이름을 가집니다.

 

Windows 플랫폼을 지원하는 프로젝트는 지정된 Target에 대한 PDB 파일에 대한 세부 정보도 얻을 수 있습니다. TARGET_PDB_FILE을 사용하는 표현식은 TARGET_PROPERTY와 유사한 패턴을 따르며, 해당 Target에 사용되는 PDB 파일에 대한 경로 및 이름을 쿼리합니다.

 

TARGET_OBJECT는 특별히 언급할만한 가치가 있습니다.

CMake에서는 library target을 object library로 정의할 수 있습니다. 즉, 일반적인 의미의 라이브러리가 아닌 나중에 CMake가 target에 링크해야되는 object 파일(실제로 최종 라이브러리 파일이 생성되지 않는)의 모음이라고 볼 수 있습니다. object library는 object files이기 때문에, 링크될 수 없습니다. 그 대신, 소스가 target에 추가되는 것처럼 동일한 방법으로 target에 추가되어야 합니다. 그런 다음 CMake는 해당 target의 소스를 컴파일하여 생성된 object file과 마찬가지로 링크 단계에서 해당 object file을 include하게 됩니다.

이 과정이 아래처럼 $<TARGET_OBJECT:...> 라는 expression을 사용하여 수행될 수 있습니다.

이 예시에서 objLib에 대해 별도의 라이브러리가 생성되지 않았지만, src1.cpp와 src2.cpp는 각각의 파일이 다른 실행 파일에 include되어 각각 컴파일될 것으로 보이지만, 한 번만 컴파일될 것입니다.

이는 정적 라이브러리를 생성하는 빌드 시간 cost나 동적 라이브러리를 읽어들이는 런타임 cost를 피할 수 있을 뿐만 아니라, 동일한 소스에 대해서 여러 번 컴파일해야 하는 것을 피할 수 있어서 유용하게 사용될 수 있습니다.

 


 

Generator Expression를 사용하면 Target 이상의 정보를 제공할 수 있습니다. 사용 중인 컴파일러, Target이 빌드되는 플랫폼, 빌드 구성 이름 등에 대한 정보를 얻을 수 있습니다. 이러한 종류의 표현식은 커스텀 컴파일러를 다루거나 특정 컴파일러 또는 툴체인과 관련된 문제를 해결해야하는 (advanced) 상황에서 사용되는 경향이 있습니다. 

또한, 이러한 Generator Expression은 오용을 유발할 수 있으므로, 사용할 때 신중해야 합니다.

 

Generator Expression에 대해서 알아봤는데, 아직 크게 와닿지는 않는 것 같습니다.

(역시 이해하기 위해서는 실전이 중요한 것 같습니다 ㅠ.ㅠ)

이번 글은 몇 가지 유용한 Generator Expression을 안내하고 여기서 마무리 하도록 하겠습니다 !

아래의 표현식은 공식 문서 link에서 확인할 수 있습니다.

  • $<CONFIG> : 빌드 타입을 쿼리합니다. Visual Studio나 Xcode와 같은 multi configuration 프로젝트에서는 CMAKE_BUILD_TYPE 변수가 사용되지 않으므로, 이 표현식을 우선적으로 사용해야합니다. CMake 3.0 이전에는 $<CONFIGURATION>으로 사용되었습니다.
  • $<PLATFORM_ID> : target이 빌드되는 플랫폼을 식별합니다. 이 표현식은 특히 빌드가 여러 플랫폼을 지원할 수 있는 크로스 컴파일 상황에서 유용하게 사용될 수 있습니다. 이 표현식은 CMAKE_SYSTEM_NAME 변수와 밀접하게 관련되어 있으며 프로젝트는 특정 상황에서 이 변수를 사용하는 것이 더 간단할 수 있습니다.
  • $<C_COMPILER_VERSION>, $<CXX_COMPILER_VERSION> : 컴파일러 버전을 식별합니다. 컴파일러 버전이 특정 버전보다 이전 버전이거나 최신 버전인 경우에만 추가되어야하는 것이 있을 때 유용하게 사용될 수 있습니다. 다만, 특정 상황에서는 유용할 수 있지만, 이러한 표현식이 많이 사용되는 경우 프로젝트의 이식성이 많이 감소될 수 있습니다.

  • $<LOWER_CASE:...>, $<UPPER_CASE:...> : '...'을 모두 소문자나 대문자로 변환할 수 있습니다. 이는 아래 예시처럼 문자열 비교를 수행하기 전 단계에 유용하게 사용될 수 있습니다.

 

'CMake' 카테고리의 다른 글

[CMake] Policy  (0) 2021.11.04
[CMake] Modules  (6) 2021.11.04
[CMake] Properties  (0) 2021.11.02
[CMake] Functions and Macros  (0) 2021.11.01
[CMake] add_subdirectory() 와 변수 Scope  (0) 2021.10.31

댓글