본문 바로가기
CMake

[CMake] Target Types

by 별준 2021. 11. 14.

References

  • Professional CMake : A Practical Guide

Contents

  • Executables
  • Libraries
  • Promoting Imported Targets

CMake는 단순한 실행파일이나 라이브러리뿐만 아니라 다양한 타겟 타입을 지원합니다. 또한, 자체적으로 빌드되지 않고 다른 entity에 대한 레퍼런스 역할을 하는 타겟 타입을 정의할 수도 있습니다. 실제 자체 바이너리를 생성하지 않고 전이(transitive) 속성이나 의존성들을 읽는데 사용될 수도 있고, 또는 기존의 static 또는 shared 라이브러리가 아닌 단순히 object 파일들을 모아두는데 사용할 수도 있습니다. 오늘 포스팅에서는 다양한 타겟 타입들과 그 용도에 대해서 알아보겠습니다.

 


Excutables

add_excutable() 명령은 아래처럼 사용될 수 있습니다. 

첫 번째는 많이 보셨겠지만, 단순하게 타겟을 만드는데 사용되는 형식입니다.

 

두 번째 IMPORTED 를 사용하면, 프로젝트에서 빌드된 것이 아닌 존재하는 실행 파일에 대한 CMake 타겟을 생성할 수 있습니다. 이미 존재하는 실행 파일을 타겟으로 생성함으로써, 프로젝트 내에서는 이 타겟을 프로젝트가 자체적으로 빌드한 다른 실행 타겟과 동일하게 처리할 수 있습니다(일부 제한은 있습니다). 일반 타겟과의 차이점으로 이렇게 생성한 타겟은 INSTALL 할 수 없다는 것이 있습니다.

IMPORTED 실행 타겟을 정의할 때, 특정 타겟 속성을 설정해야 유용하게 사용할 수 있습니다. 관련된 속성으로는 IMPORTED로 시작되는 속성들이 있는데, 실행 파일의 경우에는 IMPORTED_LOCATIONIMPORTED_LOCATION_<CONFIG>가 가장 중요합니다. IMPORTED 실행 타겟의 위치가 필요할 때, CMake는 먼저 configuration의 특정된 속성에서 찾습니다. 여기서 설정되지 않은 경우에만, IMPORTED_LOCATION에서 위치를 찾게 됩니다. 하지만, 일반적으로 이 위치는 configuration에서 특정될 필요가 없기 때문에 IMPORT_LOCATION을 설정하는 것이 일반적입니다.

IMPORTED에 GLOBAL 키워드 없이 명령을 실행하면 IMPORTED 타겟은 현재 디렉토리와 그 하위 스코프에서 사용될 수 있지만, GLOBAL 키워드를 추가하면 이 타겟은 어디에서나 사용될 수 있습니다. IMPORTED가 아닌 일반적으로 프로젝트 내에서 생성된 타겟은 항상 GLOBAL 스코프입니다. 

 

세 번째 ALIAS 타겟은 CMake 내에서 다른 타겟을 참조하는 읽기 전용 방법입니다. 이는 새로운 타겟을 생성하는 것이 아닌 별칭을 사용하여 기존의 타겟의 별명을 만드는 것입니다. 이 별칭은 오직 실제 타겟만을 가리킬 수 있는데, 즉, 별칭의 별칭은 가리킬 수 없습니다. 이렇게 별칭으로 생성된 타겟은 INSTALL하거나 EXPORTED될 수 없습니다.

CMake 3.11 이전에는 IMPORTED 타겟에 별칭을 지정할 수 없었는데, CMake 3.11부터는 IMPORTED 타겟에도 별칭을 지정할 수 있도록 제한이 풀렸지만, GLOBAL 스코프를 가지는 IMPORTED 타겟에 대해서만 별칭을 지정할 수 있습니다.

 


Libraries

add_library() 명령으로 라이브러리를 생성할 수 있는데, 이 명령은 다양한 형식으로 사용할 수 있습니다.

먼저 기본 형식은 아래처럼 사용할 수 있으며, 아마도 가장 친숙한 방법일 것입니다. 이 방법을 통해서 일반적인 유형의 라이브러리를 정의할 수 있습니다.

이전 포스팅에서 라이브러리 추가에 대해서 간단하게 살펴봤지만, 이전 포스팅에서 언급하지 않은 OBJECT 키워드가 있습니다. (2021.10.28 - [CMake] - [CMake] Tutorial (2) - Library 추가)

이는 single archive나 shared library로 묶이기 않은 object 파일들의 모임인 object 라이브러리로 정의하는데 사용할 수 있습니다. CMake 3.12 이전에 object 라이브러리들은 다른 유형처럼 링크할 수 없었습니다. 즉, target_link_libraries()와 함께 사용할 수 없었습니다. 따라서 object 라이브러리는 다른 유형의 라이브러리에 링크하기 위해서 소스 코드의 일부로 generator expression을 사용하여 '$<TARGET_OBJECTS:objLib>'와 같은 형식으로 사용해야 했습니다. 링크를 할 수 없기 때문에 해당 유형의 전이 의존성을 제공하지 않습니다. 따라서 헤더 검색 경로나 컴파일러 정의 등을 추가되는 타겟에 수작업으로 전달해야하므로 다른 라이브러리 유형에 비해서 매우 불편합니다.

 

CMake 3.12부터는 object 라이브러리가 다른 유형의 라이브러리처럼 동작할 수 있는 기능이 추가되었지만, 몇 가지 주의사항은 있습니다. target_link_libraries() 명령에 object 라이브러리가 사용될 수 있지만, 실제 라이브러리가 아닌 object 파일들을 추가하는 것이기 때문에, 이 object 파일들이 여러번 추가되는 것을 방지하기 위해서 transitive nature이 더욱 제한됩니다. 간단하게 말하면, object 파일들은 object 라이브러리에 직접 링크되는 타겟에만 추가되고, 그 이상으로는 전이되지 않는다는 것입니다. 하지만 usage requirements는 다른 일반 라이브러리와 똑같이 전이됩니다.

 

CMake 프로젝트가 아닌 곳에서 source나 object 파일 기반으로 정의된 타겟인 경우에 object 라이브러리를 사용할 수 있으나, 선택의 여지가 있는 경우에는 그냥 static 라이브러리를 사용하는 것이 더 편리할 수 있습니다. 따라서 사용하기 전에 static 라이브러리를 사용하는 것이 더 편리한지 고려해보는 것을 추천합니다.

 

실행 파일과 마찬가지로 라이브러리도 IMPORTED 타겟으로 정의할 수 있습니다.

이들은 보통 프로젝트에서 빌드할 라이브러리로 정의하지 않고 외부에서 제공되는 라이브러리에 대한 참조 역할을 합니다. 예를 들면, CMake 프로젝트 외부의 일부 프로세스에 의해 빌드되거나 config 파일의 일부의 패키지에서 제공되는 시스템에 이미 존재하는 라이브러리를 참조할 때 사용합니다.

Example

 

라이브러리 타입은 targetName 바로 뒤에 따라오는데, 참조할 라이브러리의 유형을 알고 있는 경우에는 그대로 지정하면 됩니다. 그러면 CMake가 Imported 타겟을 다양한 상황에서 명시된 유형의 일반 라이브러리처럼 처리할 수 있습니다. OBJECT 타입은 Cmake 3.9 버전 이상에서만 사용할 수 있습니다. UNKNOWN 타입은 라이브러리의 유형을 알 수 없는 경우에 사용합니다. 이 경우에는 CMake는 추가적인 interpretation없이 전체 경로를 링커 커맨드 라인과 같은 곳에 사용합니다.

UNKNOWN 타입 example

 

(imported excutable의 경우와 동일하게) object 라이브러리를 제외하고 나머지 라이브러리 유형에서는 imported 타겟이 나타내는 경로(파일시스템에서의)를 IMPORTED_LOCATION과 IMPORTED_LOCATION_<CONFIG> 속성으로 지정해주어야 합니다. 윈도우 플랫폼인 경우에 두 속성은 다음과 같이 설정되어야 합니다. IMPORTED_LOCATION은 DLL의 위치를 나타내야 하고, IMPORTED_IMPLIB은 연관된 import 라이브러리의 위치(일반적으로 .lib 확장자)를 나타내야 합니다. object 라이브러리는 위의 위치 속성 대신에 IMPORTED_OBJECTS 속성에 imported 타겟이 나타내는 object 파일의 목록들을 설정해주어야 합니다.

Imported 라이브러리는 또한 다른 타겟 속성들을 지원하며, 대부분은 일반적으로 그대로 두거나 CMake에 의해서 자동으로 설정됩니다. config 패키지를 수작업으로 작성하는 경우에는 IMPORTED_... 으로 시작되는 타겟 속성을 이해하고 문서를 참조해야 하는데, 대부분의 프로젝트에서 CMake가 이러한 파일을 생성하므로 신경쓰지 않아도 되는 부분입니다.

 

기본적으로 imported 라이브러리는 로컬 타겟으로 정의됩니다. 즉, 현재 디렉토리나 그 하위 스코프에서만 접근 가능합니다. 마찬가지로 GLOBAL 키워드를 사용하면 다른 일반 타겟처럼 전역에서 참조가 가능합니다. 또한 처음에는 GLOBAL 키워드없이 로컬 타겟으로 생성될 수 있지만, 나중에 전역으로 승격할 수 도 있습니다.

(승격에 관한 내용은 아래 Promoting Imported Target에서 다시 언급하도록 하겠습니다.)

object 라이브러리 example

 

add_library() 명령의 또 다른 형식으로 INTERFACE를 사용하여 인터페이스 라이브러리를 정의할 수 있습니다.

인터페이스 라이브러리는 일반적으로 물리적인 라이브러리가 아닌 해당 라이브러리에 링크되는 모든 것에 적용할 수 있는 usage requirement나 종속성을 수집하는데 사용됩니다. 

모든 target_...() 명령은 INTERFACE 키워드와 사용되어 인터페이스 라이브러리가 수행하는 usage requirements를 정의할 수 있습니다. set_property() 또는 set_target_properties()를 사용하면 관련된 INTERFACE_... 속성들을 직접적으로 설정할 수 있지만, target_...() 명령이 조금 더 안전하고 사용하기 쉽습니다.

위의 예에서 myApp 타겟은 myHeaderOnlyToolKit 인터페이스 라이브러리에 링크됩니다. myApp 소스가 컴파일되면 헤더 검색 경로로 '/some/path/include'가 추가되고, 컴파일러 커맨드에는 컴파일러 정의인 'COOL_FEATURE=1'이 추가될 것입니다. 만약 myApp 타겟이 C++11 지원이 활성화된 상태로 빌드되는 경우에는 HAVE_CXX11 심볼도 정의될 것입니다.

 

인터페이스 라이브러리의 또 다른 용도는 더 큰 라이브러리 세트에 링크하기 위한 편의성을 제공해주는 것입니다. 

위의 예시를 살펴보면, CMake 옵션 변수인 ENABLE_ALGO_BETA가 true인 경우 링크할 라이브러리 목록에 algo_beta가 포함됩니다. 다른 타겟들은 단순히 algo_all에 링크되고 algo_beta의 조건부 링크만 라이브러리 인터페이스에 의해서 처리됩니다. 이 예시는 실제 링크되거나 정의되는 등의 세부 사항을 추상화하여 이 세부 정보들을 스스로 수행할 필요가 없도록 해주는 것을 보여줍니다. 

 

INTERFACE IMPORTED 라이브러리는 혼동을 일으킬 수 있는데, 이 키워드 조합은 인터페이스 라이브러리를 프로젝트 외부에서 사용하기 위해 내보내거나 INSTALL할 때 사용합니다. 이 라이브러리는 다른 프로젝트에서 사용될 때 INTERFACE 라이브러리의 목적도 수행하지만, 이 라이브러리가 다른 곳에서 왔다는 것을 나타내기 위해서 IMPORTED가 추가되었습니다. 이 키워드의 효과는 라이브러리의 기본 스코프를 현재 디렉토리로 제한하는 것입니다. 한 가지 예외를 제외하고, INTERFACE IMPORTED GLOBAL 키워드 조합은 INTERFACE만 사용할 때와 차이가 거의 없는 라이브러리가 됩니다. INTERFACE IMPORTED 라이브러리는 IMPORTED_LOCALTION을 설정하지 않고, 실제로 설정하는 것이 금지되어 있습니다.

 

CMake 3.11 이전에는 target_...() 명령을 사용하여 모든 종류의 IMPORTED 라이브러리에서의 INTERFACE... 속성을 설정할 수 없었습니다. 대신 이러한 속성은 set_property()나 set_target_properies() 명령으로 설정할 수 있었습니다. CMake 3.11 부터는 target_...() 명령을 사용하여 INTERFACE_... 속성을 설정할 수 있도록 제한이 사라졌고, 따라서, INTERFACE IMPORTED는 일반적인 IMPORTED 라이브러리와 유사해졌습니다.

아래의 표는 다양한 키워드 조합에서 서포트하는 것을 보여줍니다.

* target_...() 명령으로 INTERFACE_... 속성을 설정하는 것은 오직 CMake 3.11 이후부터 가능합니다.

 

add_library() 명령에서의 마지막 형식은 바로 ALIAS 키워드입니다.

라이브러리의 별칭은 실행 파일의 별칭과 유사합니다. 이는 다른 라이브러리를 참조하는 읽기 전용으로 동작하고 새로운 빌드 타겟을 생성하지 않습니다. 마찬가지로 INSTALL할 수 없으며 다른 별칭의 별칭으로 사용할 수도 없습니다. CMake 3.11 이전에는 IMPORTED 타겟에 대해서 ALIAS 라이브러리를 생성할 수 없었지만 CMake 3.11 부터는 가능합니다.

CMake 3.0에 도입된 중요한 기능 중의 하나가 라이브러리 별칭과 관련이 있는데, install되거나 패키징되는 각 라이브러리는 projNamespace::originalTargetName의 형태의 별칭을 만든다는 것입니다. 프로젝트 내에서 이러한 별칭은 일반적으로 동일한 projNamespace를 공유합니다. 예를 들면,

위의 경우처럼 find_package를 통해 설치된 패키지를 찾는 대신 BagOfBeans 프로젝트를 자체 빌드에 통합하려는 경우에도 링크 관계를 변경할 필요없이 위와 동일한 네임스페이스에 연결되어 사용할 수 있습니다.

 

이중 콜론(::)이 가지는 중요한 특징 중의 하나는 CMake가 항상 이를 Alias 또는 Imported 타겟의 이름으로 취급한다는 것입니다. 다른 유형의 타입에서 이러한 이름으로 사용하려고 시도하면 CMake는 Error를 발생시킵니다. 따라서 타겟 이름이 target_link_library() 명령에서 사용될 때 CMake가 해당 이름의 타겟을 알지 못하는 경우 generation time에 에러를 발생시키도록 할 수 있습니다. 아래 예시를 한 번 살펴보세요. CMake가 타겟의 이름을 모르는 경우와 시스템에서 제공하는 것으로 가정하는 라이브러리로 취급하는 경우를 비교한 것입니다.

첫 번째 target_link_libaries에서는 bart가 링크 타입에 시스템에 의해서 제공될 수 있다고 판단하여 build time에 에러가 발생하지만, 두 번째는 오직 CMake target에서만 namespace가 사용되기 때문에 generation time에서 에러가 발생합니다.

따라서 namespace가 사용한 곳에서는 namespace 이름에 연결하는 것이 더 강력합니다. 프로젝트에서는 적어도 install/package 하려는 모든 타겟에 네임스페이스 별칭을 정의하는 것이 좋습니다.

 


Promoting Imported Targets

GLOBAL 키워드없이 정의될 때, Imported 타겟은 오직 이 타겟이 생성된 디렉토리 스코프나 그 하위 스코프에서만 참조될 수 있습니다. 이 동작은 Find 모듈이나 패키지 config fil의 일부로 사용되는 주요 용도에서 비롯됩니다. Find 모듈 또는 package config file에 의해 정의된 모든 것들은 보통 로컬 스코프를 가질 것으로 예상되므로 일반적으로 GLOBAL 스코프의 타겟을 추가하면 안됩니다. 이를 통해 프로젝트 계층 구조의 다른 부분에서 설정이 다른 동일한 패키지와 모듈을 가져올 수 있고, 이들은 서로 간섭하지 않습니다.

 

그럼에도 불구하고 특정 패키지의 동일한 버전이나 인스턴스가 전체 프로젝트에서 일관되게 사용하도록 하는 것처럼 Imported 타겟을 GLOBAL 스코프로 만들어야하는 상황도 있습니다. GLOBAL 키워드를 추가하여 Imported 라이브러리를 추가하면 이 목적을 달성할 수 있지만, 프로젝트는 생성을 수행하는 커맨드를 제어하지 못할 수 있습니다. 이 상황을 해결하기 위해서 CMake 3.11에서는 대상의 IMPORTED_GLOBAL 속성을 true로 설정하여 Imported 타겟의 스코프를 GLOBAL로 승격시키는 기능을 도입했습니다. 이는 단방향 전환이어서 GLOBAL 타겟을 다시 로컬 스코프로 강등시킬 수는 없습니다.

Imported 타겟의 승격은 동일한 스코프에서 정의된 경우에만 승격할 수 있다는 점을 기억해야 합니다. 상위 또는 하위 스코프에서 가져온 타겟은 승격될 수 없습니다. include() 명령은 새로운 디렉토리 스코프를 생성하지 않고 find_package() 명령도 동일하므로, 이런 식으로 정의된 imported 타겟에 대해서 승격될 수 있습니다. 그리고 GLOBAL 스코프로 승격되면 해당 타겟을 참조하는 별칭 생성을 지원할 수 있습니다.

 

 

'CMake' 카테고리의 다른 글

[CMake] Language Requirements  (0) 2021.11.09
[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

댓글