일상의 정리

CMake 플랫폼 체크하기

프로그래밍

1개의 소스로 서로 다른 운영 체제에서 컴파일 및 실행되는 소프트웨어를 생성하길 원한다면, 각각의 플랫폼(OS)간의 특별한 속성을 파악해야 한다.

서로 다른 플랫폼(운영 체제) 간에는 미묘한 차이가 있다.

예를 들어 FreeBSD에서는 malloc.h를 사용하지 않아야 하고, 반면에 Linux에서 사용할 수 있다.

이러한 차이는 통상 config.h 라고 불리는 각 플랫폼의 특성을 구분짓는 여러개의 define문을 포함하는 헤더파일을 통해 처리된다.

#define HAVE_MALLOC_H 1 /* #undef HAVE_SYS_MNTTAB_H 1 */ /* #undef HAVE_SYS_MNTENT_H 1 */ #define HAVE_SYS_MOUNT_H 1

이 헤더파일은 아래 예제 처럼 소스의 맨 처음에 include 되어 처리 된다.

foo.c: #include "config.h" #ifdef HAVE_MALLOC_H #include <malloc.h> #else #include <stdlib.h> #endif void do_something() { void *buf=malloc(1024); ... }

config.h의 내용은 소스가 컴파일 되는 플랫폼에 따라 달라야 하므로, 실제로 컴파일 되기 전에 헤더 파일이 생성되어야 한다.

Autotools 기반 소프트웨를 사용중이라면, make 전 단계에서 실행되는 ./configure 단계를 알고 있을 것이다.

./configure 스크립트는 시스템 내부 검사를 수행하고 수집 된 정보로부터 config.h 헤더 파일을 생성한다. 

여기서는 CMake서도 ./configure를 사용하는 것과 똑같이 할 수 있는 방법에 대해 설명할 것이다.

또한 내장 명령에 있어서도, CMake는 module이라고 불리는 스크립트 파일을 사용해서 더 많은 명령을 지원한다.

이 파일들은 cmake 모듈 디렉토리에 있으며, UNIX 시스템의 경우 디폴트로 /usr/local/share/CMake/Modules 디렉토리 이다.

이러한 모듈의 명령을 사용하려면 먼저 CMakeLists.txt 파일 안에 include 를 해 줘야 한다.

CMake에는 시스템을 체크하기 위한 몇 가지 모듈이 포함되어 있다.

예를 들면 CHECK_INCLUDE_FILES :

INCLUDE (CheckIncludeFiles) # usage: CHECK_INCLUDE_FILES (<header> <RESULT_VARIABLE> ) CHECK_INCLUDE_FILES (malloc.h HAVE_MALLOC_H) CHECK_INCLUDE_FILES ("sys/param.h;sys/mount.h" HAVE_SYS_MOUNT_H) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)

CMake 모듈 CheckIncludeFiles는 CHECK_INCLUDE_FILES() 명령을 사용할 수 있게 해 준다.

이 명령의 첫 번째 인수는 체크하려고 하는 헤더파일이다. 두 번째 인수는 결과를 돌려받을 변수이다.

주어진 헤더가 발견되면 두번째 인수의 변수가 1로 설정되고, 그렇지 않으면 빈 상태로 된다.

헤더를 사용하기 위해 다른 헤더가 필요한 경우 위의 예에서 보듯이 헤더들을 세미콜론으로 구분해서 나열해야 한다.

더 정확한 CHECK_INCLUDE_FILES()의 구현에 대해서는 /usr/local/share/CMake/Modules/CheckIncludeFiles.cmake 을 참고하면 된다.

지정된 헤더 파일에 대한 테스트 결과는 CMakeCache.txt 파일에 아래 예와 같이 저장되므로, 나중에 테스트가 성공했는지 여부를 확인하려는 경우

CMakeCache.txt 파일을 살펴보면 된다.

//Have include HAVE_MALLOC_H HAVE_MALLOC_H:INTERNAL=1

결과가 캐시에 있으면 테스트가 다시 실행되지 않으므로, 다시 실행하려면 CMakeCache.txt 파일을 삭제하거나 다시 테스트 하려는 변수 항목을 삭제해야만 테스트가 다시 실행될 수 있다.

테스트가 실패한 경우 이유를 찾고 싶다면 CMakeFiles/CMakeError.log 파일을 열어서 헤더 이름 (또는 함수 등)을 찾으면 거기에 실패한 코드가 표시되어 있다.

이제 우리는 예를 들어 malloc.h가 존재하는 지 테스트 하고 cmake 변수 HAVE_MALLOC_H에 결과를 얻었으므로, 이제 헤더 config.h를 만들어야 한다.

이를 위해 cmake 명령 CONFIGURE_FILE()을 사용해야 한다.

이것은 소스 파일을 대상 파일로 복사하면서 원하는 부분을 수정해 주는 역할을 한다.

먼저 config.h.in이라는 이름의 소스 파일을 작성해야 한다. (이름은 아무래도 좋지만 autotools에서는 통상 config.h.in으로 사용한다) :

#cmakedefine HAVE_MALLOC_H 1 #cmakedefine HAVE_SYS_MOUNT_H

cmake가 실행되면 위 구문 중 #cmakedefine이 대체된다. HAVE_MALLOC_H 및 HAVE_SYS_MOUNT_H가 true이면 아래와 같은 config.h가 생성된다.

#define HAVE_MALLOC_H 1 #define HAVE_SYS_MOUNT_H

만약 2개 변수 모두 false 이면 아래와 같은 파일이 생성된다.

/* #undef HAVE_MALLOC_H */ /* #undef HAVE_SYS_MOUNT_H */

이 헤더를 소스 파일에 포함 시키고 #ifdef를 사용해서 속성을 확인할 수 있다.

이러한 체크를 최상위 CMakeLists.txt 만이 아니라 프로젝트의 모든 CMakeLists.txt에 추가할 수 있다.

구성된 헤더가 여러 개인 경우에는 그것들을 모두 config.h라고 부르면 include에 문제가 생길 수 있다.

각 서브 디렉트리에 따라 config.h, foo/ 디렉토리는 config-foo.h, bar/ 디렉토리는 config-bar.h 와 같은 이름을 지정하는 편이 좋다

시스템 검사를 수행하는 cmake의 다른 명령들은 다음과 같다.

Module: INCLUDE (CheckIncludeFiles) Usage: CHECK_INCLUDE_FILES(headers variable) Example: CHECK_INCLUDE_FILES(strings.h HAVE_STRINGS_H)

지정된 헤더파일이 존재하는 지 여부를 판정.

Module: INCLUDE (CheckFunctionExists) Usage: CHECK_FUNCTION_EXISTS(function variable) Example: CHECK_FUNCTION_EXISTS(madvise HAVE_MADVISE)

지정된 함수가 존재하는 지 여부를 판정.

Module: INCLUDE (CheckSymbolExists) Usage: CHECK_SYMBOL_EXISTS(symbol headers variable) Example: CHECK_SYMBOL_EXISTS((LC_MESSAGES "locale.h" HAVE_LC_MESSAGES)

지정된 헤더가 존재하는 경우에, 지정된 기호가 존재하는 지 여부를 판정 "Symbol"은 Preprocessor 매크로 또는 주소를 사용할 수 있으며, Enumerate 형은 사용 될 수 없음

Module: INCLUDE (CheckLibraryExists) Usage: CHECK_LIBRARY_EXISTS(library function location variable) Example: CHECK_LIBRARY_EXISTS(volmgt volmgt_running "" HAVE_VOLMGT)

지정된 라이브러리가 존재하고 제공된 함수를 포함하는지 여부를 확인. 이것은 함수를 사용하는 작은 프로그램과 라이브러리에 대한 링크를 통해서 수행. 필요한 경우 추가 링크 디렉토리 위치 매개 변수(-Ldir)를 지정할 수 있다.

Module: INCLUDE (CheckTypeSize) Usage: SET(CMAKE_EXTRA_INCLUDE_FILES header) CHECK_TYPE_SIZE(type variable) SET(CMAKE_EXTRA_INCLUDE_FILES) Example: SET(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h) CHECK_TYPE_SIZE("struct ucred" STRUCT_UCRED) SET(CMAKE_EXTRA_INCLUDE_FILES)

지정된 타입이 존재하는 지 여부를 판정 해, 그 타입의 크기를 돌려줌. 변수 안에는 타입의 크기가 설정되며, 타입이 존재한다면 HAVE_STRUCT_UCRED는 true로 설정됨. 타입의 크기에는 관심이 없고, 단순히 타입이 존재하는 지 여부만 알고 싶다면 STRUCT_UCRED 를 직접 사용할 수 있다. 타입이 없을 경우 이 값은 FALSE로 평가된다.(HAVE_STRUCT_UCRED).

Module: INCLUDE (CheckPrototypeExists) Usage: CHECK_PROTOTYPE_EXISTS(function headers variable) Example: CHECK_PROTOTYPE_EXISTS(mkstemps "stdlib.h;unistd.h" HAVE_MKSTEMPS_PROTO)

헤더가 주어진 것에 대한 선언을 제공하는지 여부를 확인. 함수가 실제로 정의되었는 지 여부는 확인하지 않음.

Module: INCLUDE (CheckCXXSourceCompiles) INCLUDE (CheckCSourceCompiles) Usage: CHECK_CXX_SOURCE_COMPILES(source variable) CHECK_C_SOURCE_COMPILES(source variable)

소스 코드가 컴파일되고 링크되는지 여부를 확인. CMAKE_REQUIRED_LIBRARIES, CMAKE_REQUIRED_FLAGS 및 CMAKE_REQUIRE 를 설정할 수 있음