References
- Professional CMake : A Practical Guide
Contents
- Basic Build Types
- Common Erros
- Custom Build Types
빌드 타입build type(일부 IDE tool에서는 build configuration 또는 build scheme라고 함)은 컴파일러와 링커의 동작을 고수준으로 조작합니다. 이번 글에서는 빌드 타입에 대해서 알아보고, 다음에 컴파일러와 링커 옵션을 제어하는 구체적인 내용들을 살펴보도록 하겠습니다.
Basic Build Type
빌드 타입은 빌드에 대한 거의 모든 것에 영향을 미칠 가능성이 있습니다. 주로 컴파일러 및 링커 동작에 직접적인 영향을 미치지만 프로젝트에 사용되는 디렉토리 구조에도 영향을 미칩니다. 빌드 타입은 개발자가 어떻게 자신의 로컬 개발 환경을 구성할 지에도 영향을 미칠 수 있으므로, 빌드 타입은 꽤 많은 곳에 영향을 미칠 수 있습니다.
일반적으로 빌드는 디버그(Debug) 또는 릴리즈(Release) 중에 하나로 생각합니다. 디버그 빌드의 경우에는 컴파일러 플래그를 사용하여 디버거가 기계 명령어(machine instructions)를 소스 코드와 연결하는데 사용하는 정보를 기록하도록 할 수 있습니다. 이러한 빌드에서는 최적화가 비활성화되어서 기계 명령어를 소스코드 위치에 직접적으로 매핑시키고, 프로그램의 실행 스텝을 따라가기 쉽습니다. 반면 릴리즈 빌드에서는 일반적으로 완전한 최적화가 활성화되어 있고 디버그 정보가 생성되지 않습니다.
다음은 CMake에서 빌드 타입으로 참조하는 것들의 예입니다. 원하는 빌드 타입을 정의할 수도 있지만, 일반적으로 아래의 기본 빌드 타입으로도 충분합니다.
- Debug : 최적화를 적용하지 않고, full debug 정보를 기록합니다. 이는 보통 개발과 디버깅에서 사용됩니다.
- Release : 실행 속도를 위해 full 최적화를 적용하고, 디버깅 정보는 없습니다. 다만, 일부 플랫폼에서는 이 빌드 타입이더라도 디버깅 심볼을 생성할 수도 있습니다. 일반적으로 최종 SW 릴리즈에서 빌드할 때 사용되는 타입입니다.
- RelWithDebInfo : 이 타입은 Debug와 Release를 절충한 타입이라고 볼 수 있습니다. 릴리즈 빌드에 가까운 성능을 제공하지만 여전히 일정 수준의 디버깅을 허용합니다. 특히 속도에 대한 최적화는 일반적으로 적용되지만, 대부분의 디버그 기능 또한 활성화됩니다. 따라서 이 빌드 타입은 디버그 빌드의 성능이 디버깅하기에 적합하지 않을 때 유용합니다. RelWithDebInfo는 기본적으로 assertions을 비활성화 합니다.
- MinSizeRel : 이 빌드 타입은 일반적으로 임베디드 디바이스와 같은 제한된 리소스 환경에 사용됩니다. 코드는 속도보다 크기에 최적화되어 있으며 디버깅 정보는 생성되지 않습니다.
각 빌드 타입은 서로 다른 컴파일러 및 링커 플래그들을 구성합니다. 또한, 빌드 타입에 따라 컴파일할 소스 파일이나 링크할 라이브러리를 변경하도록 동작을 설정할 수 있습니다. 이와 관련된 내용은 다음 글에서 더 자세히 알아보고, 이번 시간에는 빌드 타입을 선택하는 방법과 몇 가지 일반적인 문제들을 피하는 방법에 대해서 알아보겠습니다.
Single Configuration Generators
CMake는 위와 같은 다양한 타입의 project generator를 설정할 수 있습니다. Makefiles나 Ninja와 같은 일부 타입은 빌드 디렉토리당 하나의 빌드 타입만 지원합니다. 이런 generator의 경우에는 CMAKE_BUILD_TYPE 캐시 변수를 설정하여 빌드 유형을 선택해야 합니다. 예를 들어, Ninja로 프로젝트를 구성하고 빌드하려면 다음과 같은 커맨드를 사용할 수 있습니다.
CMAKE_BUILD_TYPE 캐시 변수는 커맨드 대신 CMake GUI 프로그램에서도 변경할 수 있지만, 결과는 동일합니다.
서로 다른 빌드 타입이 필요할 때, 서로 다른 빌드 타입을 스위칭하는 것말고 다른 방법은 각 빌드 타입에 대해 별도의 빌드 디렉토리를 설정하는 것입니다. 모두 동일한 소스를 사용하는데, 이러한 디렉토리 구조는 다음처럼 구성될 수 있습니다.
빌드 타입이 자주 변경된다면, 위와 같은 빌드 디렉토리 구성으로 컴파일을 다시 하는 것을 피할 수 있습니다. 또한 single configuration generator에서 multi configuration generator처럼 활용할 수 있고, Qt Creator와 같은 IDE 환경에서 Xcode나 Visual Studio에서 빌드 configuration을 쉽게 변경하는 것처럼 쉽게 빌드 디렉토리를 전환하도록 지원합니다.
Multi Configuration Generator
Xcode 및 Visual Studio와 같은 일부 generator는 단일 빌드 디렉토리에서 여러 configuration을 지원합니다. 이러한 generator는 CMAKE_BUILD_TYPE 캐시 변수를 무시하고 대신 개발자가 IDE 내에서 또는 빌드 시 커맨드 옵션을 통해 빌드 타입을 선택할 수 있습니다.
Xcode IDE 내에서 빌드할 때, 빌드 타입은 build scheme에 의해서 제어되는 반면, Visual Studio IDE 내에서는 현재 솔루션 configuration이 빌드 타입을 제어합니다. 두 환경 모두 다른 빌드 타입에 대해 별도의 디렉토리를 유지하므로 빌드 간에 전환해도 지속적인 rebuild가 발생하지 않습니다.
Common Errors
single configuration generator의 경우 빌드 타입은 configure time에 결정되는 반면, multi configuration generator의 경우에는 build time에 빌드 타입이 결정됩니다. CMake가 프로젝트의 CMakeLists.txt 파일을 처리할 때 빌드 타입을 항상 알 수 있는 것은 아니기 때문에 이 차이점은 치명적입니다.
다음은 잘못된 CMakeLists.txt 작성 예시입니다.
위 내용은 Makefile 기반 generator 및 Ninja에서는 제대로 동작하지만 Xcode 또는 Visual Studio에서는 동작하지 않습니다. 실제로 오직 single configuration generator만 사용되는 프로젝트가 아니라면, CMAKE_BUILD_TYPE이 사용되는 로직은 의심스러운 로직입니다. multi configuration generator에서는 CMAKE_BUILD_TYPE이 비어 있을 수 있지만, 비어 있지 않더라도 빌드 시에 무시되므로 이 값을 신뢰할 수 없습니다. 따라서 CMakeLists.txt 파일에서 CMAKE_BUILD_TYPE을 참조하는 것이 아닌 $<CONFIG:...>과 같은 Generator Expression을 사용해야 합니다.
single configuration generator에 대해 CMAKE_BUILD_TYPE을 명시적으로 설정하지 않는 것이 일반적이지는 않지만, 명시적으로 설정하지 않는 것이 의도한 것은 아닐 것입니다. 만약 CMAKE_BUILD_TYPE이 설정되지 않고 비어있다면, single configuration generator는 특정하게 동작되는데, 이런 경우 configuration별로 설정된 컴파일러나 링커 플래그가 사용되지 않으며 최소한의 플래그가 사용됩니다. 종종 디버그 빌드와 유사할 수 있지만, 디버그 빌드라고 보장할 수는 없습니다.
Custom Build Types
프로젝트에서 기본 제공되는 빌드 타입의 하위 집합으로 제한하거나 특별한 컴파일러나 링커 플래그를 사용하여 커스텀 빌드 타입을 추가할 수 있습니다. (후자의 경우에는 프로파일링 또는 코드 커버리지를 위해 빌드 타입을 추가하는 것이 있을 수 있습니다.)
Xcode 및 Visual Studio와 같은 multi configuration generator를 사용할 때는 IDE 환경에서 configuration을 드롭다운 목록과 같은 것으로 확인할 수 있습니다.
Makefiles 또는 Ninja와 같은 single configuration generator의 경우에는 CMAKE_BUILD_TYPE 캐시 변수에 빌드 타입이 직접 입력되지만 CMake GUI 프로그램에서는 콤보 상자를 표시하도록 만들 수 있습니다.
위 두 경우의 메커니즘은 다르므로, 별도로 처리되어야 합니다.
multi configuration generator에서 빌드 타입은 CMAKE_CONFIGURATION_TYPES 캐시 변수에 의해서 컨트롤되는데, 정확하게는 최상단 CMakeLists.txt 파일의 처리가 끝날 때 이 변수 값에 의해 제어됩니다. CMakeLists.txt에서 처음 호출되는 project() 커맨드는 이 캐시 변수가 아직 정의되지 않은 경우 기본 리스트로 채우는데, project() 커맨드 이후에 같은 이름의 일반 변수를 설정하여 수정할 수 있습니다. 커스텀 빌드 타입은 CMAKE_CONFIGURATION_TYPES에 추가하여 정의할 수 있으며, 원하지 않는 빌드 타입은 해당 리스트에서 제거할 수 있습니다.
그러나, CMAKE_CONFIGURATION_TYPES가 정의되지 않은 경우, 이 변수를 설정하지 않도록 주의해야 합니다. CMake 3.9 버전 이전에는 multi configuration generator가 사용 중인지를 확인하기 위해서 CMAKE_CONFIGURATION_TYPES가 비어있는지 확인하는 것이 일반적이었습니다. 심지어 CMake 내부에서도 3.11 이전 버전에는 이러한 방식이 사용되었습니다. 이 방법은 일반적으로 정확하지만, single configuration generator를 사용하더라도 이 변수가 설정될 수 있으므로, generator에 관련하여 처리가 잘못될 수 있습니다. 이 문제를 해결하기 위해서 CMake 3.9에서는 multi configuration generator를 사용할 때 TRUE로 설정되는 새로운 GENERATOR_IS_MULTI_CONFIG라는 새로운 Global Property를 추가하여 확실한 방법을 제공합니다. 다만, CMAKE_CONFIGURATION_TYPES를 체크하는 방법은 많이 사용되는 방법이므로 해당 변수가 존재하는 경우에만 수정하도록 해야합니다.
또한, CMake 3.11 이전 버전에서는 CMAKE_CONFIGURATION_TYPES에 커스텀 빌드 타입을 추가하는 것이 기술적으로 안정적이지 않다는 점에 유의해야합니다. 따라서, 커스텀 빌드 타입을 정의하려는 경우에는 최소한 CMake 3.11을 사용하는 것을 권장합니다.
CMAKE_CONFIGURATION_TYPES에 의해 발생할 수 있는 다른 문제는 이 캐시 변수에 다른 타입을 추가하거나 제거할 수 있는 점입니다. 따라서, 어떤 configuration 타입이 정의되었는지 아닌지 가정을 해서는 안됩니다. 다음은 이 문제를 고려하여 multi configuration generator에 커스텀 빌드 타입을 추가하는 방법을 보여주고 있습니다.
single configuration generator의 경우에는 빌드 타입이 하나만 있으며, 문자열인 CMAKE_BUILD_TYPE 캐시 변수로 지정됩니다. 그리고 캐시 변수는 유효한 값을 가질 수 있도록 STRINGS 속성을 가질 수 있습니다.
속성은 CMakeLists.txt 파일 내에서만 변경할 수 있으므로 위처럼 STRINGS 속성을 안전하게 설정할 수 있습니다. 그러나 캐시 변수의 STRINGS 속성을 설정한다고 하더라도 캐시 변수가 정의된 값 중 하나를 유지한다는 보장은 없으므로, 변수가 정의된 값 중 하나를 가지도록 엄격하게 제한하려면 다음의 테스트를 자체적으로 수행해야합니다.
CMAKE_BUILD_TYPE의 기본값은 빈 문자열이므로 위의 경우 개발자가 명시적으로 설정하지 않는 한 single 또는 multi configuration generator 모두에서 FATAL_ERROR 가 발생합니다. 이는 특히 CMAKE_BUILD_TYPE 변수 값을 사용하지 않는 multi configuration generator의 경우에는 바람직하지 않습니다.
이러한 경우에는 CMAKE_BUILD_TYPE이 설정되지 않은 경우 프로젝트에서 기본값을 설정하도록 처리하여 해결할 수 있습니다.
위의 문제들을 고려하여 다음과 같이 작성할 수 있습니다.
다음 두 종류의 변수를 사용하면 CMAKE_<LANG>_FLAGS, CMAKE_<TARGETTYPE>_LINKER_FLAGS 변수에 추가로 컴파일러 및 링커 플래그를 설정할 수 있습니다.
예를 들어, 커스텀 빌드 타입에 대한 플래그는 다음과 같이 정의할 수 있습니다.
보여주기 위해서 위 예시는 플래그를 간단하게 설정하였습니다. 하지만 다른 기본 플래그들을 사용해야하기 때문에 이를 위해 다른 빌드 타입의 컴파일러 및 링커 플래그를 기반으로 필요한 플래그를 추가하는 방법이 있습니다. 실제로 프로파일링의 경우, RelWithDebInfo 빌드 타입은 디버깅과 대부분의 최적화를 모두 가능하게 하므로 추가하는 것이 좋습니다.
커스텀 빌드 타입에 대해 정의될 수 있는 다른 변수는 CMAKE_<CONFIG>_POSTFIX 입니다. 이는 각 라이브러리 타켓의 <CONFIG>_POSTFIX 속성을 초기화하는데 사용되며, 해당 값은 각 타겟의 파일 이름에 지정된 configuration이 붙습니다.
CMAKE_DEBUG_POSTFIX는 특히 디버그 및 비-디버그 빌드에서 서로 다른 런타임 DLL을 사용해야하는 Visual Studio에서 d 또는 _debug와 같은 값으로 설정되므로 패키지에 두 빌드 유형에 대한 라이브러리가 포함될 수 있습니다.
위의 PROFILE 커스텀 빌드 타입 예시에 대해 다음과 같이 설정할 수 있습니다.
여러 빌드 타입이 포함된 패키지를 만드는 경우 각 빌드 유형에 대해 CMAKE_<CONFIG>_POSTFIX를 설정하는 것이 좋습니다. 규칙에 따라 릴리즈 빌드의 접미사는 일반적으로 비어 있으며, <CONFIG>_POSTFIX 타겟 속성은 Apple 플랫폼에서는 무시됩니다.
여러 이유로 target_link_libraries() 커맨드로 전달된 디버그나 최적화 키워드가 접두사로 붙은 항목은 디버그 또는 비-디버그 빌드에서만 각각 링크되어야할 수 있습니다. 만약 Global 속성으로 DEBUG_CONFIGURATIONS가 리스트에 있으면 빌드 유형은 디버그로 간주되고, 그렇지 않다면 최적화된 것으로 간주됩니다. 커스텀 빌드 타입에서는 이러한 상황에서 디버그 빌드로 처리되어야 하는 경우 Global 속성에 DEBUG_CONFIGURATIONS를 추가해야 합니다.
프로젝트에서 StrictChecker라는 커스텀 빌드 타입을 정의하고 해당 빌드 타입을 최적화되지 않은 디버그 빌드 타입으로 간주해야하는 프로젝트는 다음과 같이 명확하게 처리할 수 있습니다.
(디버그 및 최적화 키워드 대신 Generator Expression 사용을 더 권장하긴 합니다.)
'CMake' 카테고리의 다른 글
[CMake] Language Requirements (0) | 2021.11.09 |
---|---|
[CMake] Compiler and Linker 설정 (0) | 2021.11.06 |
[CMake] Policy (0) | 2021.11.04 |
[CMake] Modules (6) | 2021.11.04 |
[CMake] Generator Expressions (0) | 2021.11.03 |
댓글