References
- Professional CMake : A Practical Guide
Contents
- Setting The Language Standard Directly
- Setting The Language Standard By Feature Requirements
C나 C++이 계속해서 업데이트되면서 코드에서 사용하는 C/C++ 버전을 지원하려면 컴파일러 및 링커 플래그를 이해해야합니다. 컴파일러마다 다른 플래그를 사용하지만 동일한 컴파일러와 링커를 사용하는 경우에도 플래그를 사용하여 표준 라이브러리의 다른 버전을 선택할 수 있습니다.
C++11에 대한 서포트가 시작한지 얼마되지 않았을 때는 CMake에서 사용할 표준을 선택하는 직접적인 지원이 없었으므로 프로젝트에서 필요한 플래그를 스스로 해결해야 했습니다. 그러나 CMake 3.1에서 C 및 C++ 표준을 일관되고 편리한 방식으로 선택할 수 있는 기능이 도입되었습니다. 이러한 서포트는 후속 버전에서 확장되었고, CMake 3.6부터는 대부분의 컴파일러를 커버합니다. (CMake 3.2에서 대부분의 컴파일러 서포트를 추가, 3.6에서 Intel 컴파일러 추가)
CMake에서는 언어 요구사항을 구체화하기 위해서 두 가지 주요 방법을 제공합니다. 첫 번째는 language standard를 직접 설정하는 것이고, 두 번째는 프로젝트가 필요한 언어를 지정하고 CMake가 적절한 standard를 선택하도록 하는 것입니다. 이러한 기능은 주로 C/C++ 언어에 의해 주도되었지만, CUDA와 같은 다른 언어도 지원합니다.
Setting The Language Standard Directly
프로젝트에서 빌드에서 사용하는 Language Standard를 제어하는 가장 간단한 방법은 직접 설정하는 것입니다. 이 접근 방식을 사용하면, 개발자는 코드에서 사용할 개별 언어를 알거나 지정할 필요가 없고 코드가 지원되는 표준을 나타내는 숫자만 설정하면 됩니다. 이는 이해하고 사용하기 쉬울뿐만 아니라 간단하게 프로젝트 전체에서 동일한 표준이 사용되도록 합니다. 또한 링크 단계에서 링크된 모든 라이브러리나 오브젝트 파일에 걸쳐 일관된 표준 라이브러리를 사용할 때 특히 중요합니다.
CMake의 일반적인 패턴과 마찬가지로 타겟(Target) 속성은 해당 타겟의 소스를 빌드할 때와 최종 실행 파일 또는 공유 라이브러리를 링크할 때 사용할 표준을 제어합니다. 주어진 언어에서 표준을 지정하기 위한 세 가지 타겟 속성이 있습니다.
- <LANG>_STANDARD : 프로젝트가 지정된 타겟에서 사용할 language standard를 지정합니다. 이 속성을 지원하는 첫 번째 CMake 버전에서 C_STANDARD의 유효한 값은 90, 99, 또는 11이고, CXX_STANDARD의 경우에는 98, 11, 14입니다. CMake 3.8에서는 17과 20도 사용할 수 있습니다. 또한, CMake 3.8은 CUDA_STANDARD도 지원합니다. 타겟이 생성될 때 이 속성의 초기 값은 CMAKE_<LANG>_STANDARD 변수에서 가져옵니다.
- <LANG>_STANDARD_REQUIRED : <LANG>_STANDARD 속성은 프로젝트가 원하는 language standard를 지정하는 반면, <LANG>_STANDARD_REQUIRED는 해당 언어 표준이 최소 요구사항으로 처리되는지 아니면 "가능하면 사용"으로 처리되는지를 결정합니다. 일반적으로 <LANG>_STANDARD가 기본 요구사항이 될 것이라고 예상할 수 있지만, <LANG>_STANDARD_REQUIRED 속성은 기본적으로 OFF입니다. OOF일 때는 요청된 표준이 컴파일러에서 지원되지 않으면 CMake는 에러를 발생시키지 않고 이전 표준으로 설정합니다. 이러한 동작은 예기치 못한 동작이 일어날 수 있고 혼동될 수 있습니다. 따라서, 대부분의 프로젝트에서 <LANG>_STANDARD 속성을 지정할 때는 <LANG>_STANDARD_REQUIRED 속성은 요청된 특정 표준이 확실하게 처리되도록 항상 TRUE로 설정해야 합니다. 타겟이 생성될 때 이 속성의 초기값은 CMAKE_<LANG>_STANDARD_REQUIRED 변수에서 가져옵니다.
- <LANG>_EXTENSIONS : 많은 컴파일러는 language standard에 대한 자체적인 확장을 지원하고 이 확장을 활성화하거나 비활성화하기 위해서 컴파일러/링커 플래그를 제공합니다. <LANG>_EXTENSIONS 타겟 속성은 해당 확장이 해당 타겟에 대해 활성화되는지 여부를 결정합니다. 일부 컴파일러/링커의 경우 이 설정은 타겟이 연결된 표준 라이브러리를 변경할 수 있습니다(아래 예시 참조). 대부분의 컴파일러/링커의 경우 동일한 플래그가 language standard와 확장 사용 여부를 제어하는데 사용됩니다. 이 속성을 설정하는 경우 <LANG>_STANDARD도 설정해야 하며, 그렇지 않으면 <LANG>_EXTENSIONS이 무시될 수 있습니다. 타겟이 생성될 때 이 속성의 초기값은 CMAKE_<LANG>_EXTENSIONS 변수에서 가져옵니다.
일반적으로 프로젝트에서는 타겟 속성을 직접 설정하는 것보다 이 속성들에 대한 기본값을 제공하는 변수들을 설정합니다. 이렇게 하면 프로젝트의 모든 타겟이 호환가능한 설정을 사용하여 일관된 방식으로 빌드됩니다. 또한 프로젝트에서 이 3가지 속성/변수를 모두 설정하는 것이 좋습니다.
GCC 또는 Clang을 사용할 때 위의 내용은 일반적으로 '-std=c++11' 플래그를 추가합니다. 플랫폼에 따라 '-stdlib=libc++'와 같은 링커 플래그를 추가할 수도 있습니다. 비주얼 스튜디오 15 업데이트 3 이전의 비주얼 스튜디오 컴파일러의 경우, 컴파일러가 기본적으로 C++11을 지원하거나 C++11을 전혀 지원하지 않는다면 플래그가 추가되지 않습니다. 또한 그 이후 버전의 컴파일러는 C++표준 지정을 지원하지만, C++14 이상에만 적용됩니다. (기본 세팅은 C++14입니다.)
위 내용은 C++14로 설정하고 컴파일러 확장을 활성화하여 '-std=gnu++14'와 같은 GCC/Clang 컴파일러 플래그를 생성합니다. 비주얼 스튜디오 컴파일러는 컴파일러 버전에 따라 기본적으로 요청된 표준을 다시 지원하거나 지원하지 않을 수 있습니다. 사용중인 컴파일러가 C++표준을 지원하지 않는 경우 CMake는 지원하는 최신 C++표준을 사용하도록 컴파일러를 구성합니다.
C의 경우에도 매우 유사한데, 아래 내용은 특정 타겟의 속성을 직접 변경하는 방법으로 C 표준을 설정하는 방법을 보여주고 있습니다.
Setting The Language Standard By Feature Requirements
타겟 또는 전체 프로젝트에 대한 language standard를 직접 설정하는 것이 표준 요구사항을 관리하는 가장 간단한 방법입니다. 이는 프로젝트의 개발자가 프로젝트 코드에서 사용되는 언어 버전을 알고 있을 때 가장 적합한 접근 방법입니다. 이는 각 기능을 명시적으로 지정할 필요가 없기 때문에 많은 언어를 사용할 때 특히 편리합니다. 그러나 어떤 경우에는 개발자가 코드에서 사용하는 언어 기능을 명시하고 적절한 language standard를 선택하도록 하는 것을 더 선호할 수 있습니다. 이는 표준을 직접 지정하는 것과는 달리 컴파일 기능 요구사항이 타겟 인터페이스의 일부가 될 수 있으므로 이를 다른 타겟에 적용할 수 있다는 장점이 있습니다.
컴파일 기능 요구사항은 COMPILE_FEATURES와 INTERFACE_COMPILE_FEATURES에 의해 제어되지만, 이러한 속성은 일반적으로 직접 조작하는 대신 target_compile_features() 명령을 사용하여 적용합니다. 이 명령은 다른 target_...() 명령과 유사합니다.
PRIVATE, PUBLIC, INTERFACE 키워드는 나열된 feature들을 적용하는 방법을 제어합니다. PRIVATE는 타겟 자체에 적용되는 COMPILE_FEATURES 속성을 채웁니다. INTERFACE 키워드로 지정되면 targetName에 연결되는 모든 타겟에 적용되는 INTERFACE_COMPILE_FEATURES 속성을 채웁니다.
PUBLIC으로 지정된 feature들은 두 속성에 모두 추가되며 타겟 자체와 이 타겟에 연결된 모든 타겟에 적용됩니다.
각 feature들은 기본 컴파일러에서 지원하는 기능 중의 하나이어야 합니다. CMake는 두 리스트를 지원하는데, 하나는 CMAKE_<LANG>_KNOWN_FEATURES이고, 이는 해당 언어에 알려진 모든 feature들이 포함되어 있고, CMAKE_<LANG>_COMPILE_FEATURES에 포함된 feature들 중의 컴파일러에서 지원하는 feature만 포함되어 있습니다. 요청된 feature가 컴파일러에서 지원되지 않는 경우, 에러가 발생합니다.
CMAKE_<LANG>_KNOWN_FEATURES의 공식 문서는 꽤 유용합니다. 특정 버전의서의 기능을 나열하고, 각 기능과 관련된 표준 문서의 참조도 제공합니다.
CMAKE_<LANG>_KNOWN_FEATURES, CMAKE_<LANG>_COMPILE_FEATURES 공식 문서
- CMAKE_C_KNOWN_FEATURES, CMAKE_C_COMPILE_FEATURES
- CMAKE_CUDA_KNOWN_FEATURES, CMAKE_CUDA_COMPILE_FEATURES
- CMAKE_CXX_KNOWN_FEATURES, CMAKE_CXX_COMPILE_FEATURES
CMake 3.8부터 언어별 meta-feature를 사용하여 특정 컴파일 기능이 아닌 특정 language standard을 지정할 수 있습니다. 이러한 meta-feature는 <lang>_std_<value> 형식을 취하고 compile feature로 추가되면 CMake는 해당 표준을 활성화하는 컴파일러 플래그가 사용되도록 합니다. 예를 들어, 타겟과 타겟에 연결된 모든 것에 C++14 지원이 활성화되도록 다음과 같이 사용할 수 있습니다.
CMake 3.8 이전 버전을 사용해야하는 경우에는 이 기능을 사용할 수 없습니다. 이러한 경우 각 컴파일 feature를 개별적으로 나열해야 하므로 불편할 수 있습니다.
타겟에 <LANG>_STANDARD 속성이 설정되어 있고, 컴파일 feature가 지정되어 있는 경우, CMake는 더 강력한 표준 요구사항을 적용합니다. 아래의 예제에서, foo는 C++14로, bar는 C++17로, guff는 C++14로 빌드됩니다.
Detection And Use of Optional Language Features
CMake에는 특정 언어 feature의 지원 여부를 관리하는 기능이 있습니다. 예를 들어, fallback implementation을 제공하거나 컴파일러에서 지원하는 경우에 특정 함수 오버로드만 정의할 수 있습니다. 또한 키워드와 같은 일부 컴파일러 기능을 선택적으로 지원할 수도 있습니다. C++의 final 또는 override와 같은 키워드가 일반적인 예입니다.
CMake는 방금 설명한 지원 기능들을 처리하기 위해 여러가지 방법들을 제공하는데, 한 가지 접근 방식은 특정 컴파일러의 가용성에 따라 컴파일러 define 또는 include를 조건부로 설정하기 위해 generator expression을 사용하는 것입니다. 이는 상당한 유연성과 정확한 관리가 가능하도록 지원합니다.
위의 코드를 사용하면 override 키워드 지원 여부와 관계없이 모든 C++ 컴파일러에 대해 다음과 같은 코드를 컴파일 할 수 있습니다.
override 키워드 이외의 많은 다른 featuer에도 거의 동일한 방식으로 사용되는 조건부로 정의된 심볼이 있을 수 있습니다. final, constexpr, noexcept 등과 같은 C++ 키워드가 사용된 코드에서 사용할 수 있는 경우에 사용하도록 하고, 컴파일러에서 지원하지 않더라도 올바른 코드로 동작하도록 생략시킬 수 있습니다. nullptr이나 static_assert와 같은 다른 키워드에는 컴파일러에서 지원하지 않는 경우에 사용할 수 있는 대체 구현이 있습니다. 지원되는 경우와 지원되지 않는 경우를 커버하기 위해 각 featrue에 대해서 generator expression을 지정하는 것은 잠재적으로 취약할 수는 있습니다. 따라서, CMake에서는 모듈(Module)을 통해 조금 더 편리한 메커니즘을 제공합니다. WriteCompilerDetectionHeader 모듈은 이러한 처리를 자동화하는 write_compiler_detection_header()라는 함수를 제공합니다. 이 함수는 프로젝트 소스가 적절하게 define된 컴파일러 정의를 선택하기 위해 #include할 수 있는 헤더 파일을 생성합니다.
아래 문법은 write_compiler_detection_header()에서 필수적으로 필요한 인수들만 표시했습니다.
이 함수는 C/C++ 헤더를 지정된 fileName에 작성하며, 그 내용에는 나열된 feature에 대해 적절한 매크로가 정의됩니다. 모든 feature들이 prefix_COMPLIER_UPPERCASEFEATRUE의 형태의 매크로를 가지고, 이 값은 사용중인 컴파일러의 지원 여부에 따라 1 또는 0으로 설정됩니다. 일부 feature에는 prefix_UPPERCASEFEATURE 형식의 매크로가 있을 수 있습니다. 이 매크로는 해당하는 컴파일러의 다른 버전을 포함하여 명명된 컴파일러에 대해 해당 기능에 가장 적절한 feature를 제공합니다.
이는 아래의 예시에서 잘 보여줍니다.
이 코드는 C++ 프로젝트에서 컴파일러에서 사용한 경우 override, final, nullptr 키워드를 사용할 수 있고, 해당 컴파일러가 지원하는 플랫폼에서 GNU, Clang, Visual Studio, Intel 컴파일러는 지원하는 것을 목표로 합니다. 또한 컴파일러가 rvalue reference를 지원하는 경우 프로젝트에서 이동 생성자를 정의합니다. 이렇게 코드를 작성하면 빌드 디렉토리에 foo_compile_detection.h라는 단일 헤더를 생성하고, 'foo_' 문자열로 시작하는 매크로가 생성됩니다.
위에서 정의한 매크로를 사용하는 C++ 예제 코드는 다음과 같습니다.
위의 소스 파일을 사용하는 타겟은 적절한 language standard를 선택해야 하지만, 이 경우에는 대체 구현을 사용할 수 있으므로 원하는 표준을 필수적으로 지정해야하는 것은 아닙니다.
CMake는 WriteCompileDetectionHeader 모듈 문서에 설명되어 있는 몇 가지 feature에 대한 대체 구현을 제공합니다. write_compile_detection_header() 함수는 생성된 헤더 파일의 구조와 위치를 제어하고, 생성된 헤더의 시작과 끝에 임의의 내용을 추가할 수 있도록 여러 인수들을 제공하는데 여기서 다루지는 않았습니다. 자세한 내용은 공식 문서를 참조바랍니다.
WriteCompileDetectionHeader 모듈을 사용하기 전에 이 모듈을 사용할 가치가 있는지 신중하게 고려해야합니다. 프로젝트에서 지원할 수 있는 컴파일러의 범위를 확장하기 위한 훌륭한 도구가 될 수 있습니다. 하지만 소스 코드의 가독성이 떨어지게 되고, 모든 소스 파일이 표준 언어 키워드 대신 대체 키워드를 사용하도록 강제하는 것도 어려울 수 있습니다.
'CMake' 카테고리의 다른 글
[CMake] Target Types (0) | 2021.11.14 |
---|---|
[CMake] Compiler and Linker 설정 (0) | 2021.11.06 |
[CMake] Build Type / Custom Build Type (0) | 2021.11.05 |
[CMake] Policy (0) | 2021.11.04 |
[CMake] Modules (6) | 2021.11.04 |
댓글