cmake是什么
cmake是一个生成工程文件的工具,生成的工程文件可以在各个平台上用开发工具来第二次编译。也就是说cmake并不是直接的编译工具,它只能生成对应平台的工程文件,比如:Windows上的Visual studio的sln,Linux上的makefile等。作为一个中间层的工具,cmake集成了很多内置的命令和变量,这也是我们在阅读cmake源码时候应该注意的。
cmake的源码文件是CMakeLists.txt文件,在下级目录中也有需要生成成makefile的情况下,需要在每个目录中添加CMakeLists.txt文件,在上级中用include添加即可。
本文的目标
本文从实用度的角度解析libevent中CMakeLists.txt所做的内容。注解与libevent源码相关度最大的cmake代码。在【cmake源码解读】中我将会从第一句开始解读。
libevent中cmake使用
为了避免cmake中间文件污染要生成makefile的源码文件目录,我们需要新建一个build目录,之后在build目录中生成makefile和编译
cd build
cmake .. -CMAKE_BUILD_TYPE=Release|Debug
make && make install
关于cmake的使用在很多项目中都像上面一样,cmake ..
代表要解释的CMakeLists.txt在哪个目录。
cmake源码解读
-
cmake_minimum_required(VERSION 3.1.2 FATAL_ERROR)
约定使用cmake生成工程文件的最小的版本,如果没有满足,直接FATAL退出 -
set(CMAKE_BUILD_TYPE Release CACHE STRING "Set build type to Debug or Release (default Release)" FORCE)
设置默认的CMAKE_BUILD_TYPE,CMAKE_BUILD_TYPE也就是发布版和调试版
-
project(libevent C)
设置project的名字,cmake的一些命令比较特别,后面可以带多个参数 -
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
将cmake/目录下的文件名全部设置到CMAKE_MODULE_PATH中。cmake/目录下有一些宏函数和函数,在下面的include中会include进去
include(CheckFileOffsetBits)
include(Macros)
include(CheckVariableExists)
include(CheckSymbolExists)
include(CheckStructHasMember)
include(CheckCSourceCompiles)
include(CheckPrototypeDefinition)
include(CheckFunctionKeywords)
include(CheckConstExists)
include(AddCompilerFlags)
include(VersionViaGit)
这里会include本地的宏函数和函数,如果本地找不到,那就是cmake系统内置的宏函数和函数
option(EVENT__DISABLE_DEBUG_MODE "Define if libevent should build without support for a debug mode" OFF)
这种是设置某个内部变量的方式,如果cmake源码中需要根据某个内部变量来改变逻辑,就可以用这个选项,最后一个参数为OFF/ON,表示是否默认关闭开启
if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
endif()
if (NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
endif()
if (NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
endif()
设置输出文件路径
# Check if header files exist list(APPEND FILES_TO_CHECK fcntl.h
inttypes.h
memory.h
signal.h
stdarg.h
stddef.h
stdint.h
stdlib.h
string.h
errno.h
unistd.h
time.h
sys/types.h
sys/stat.h
sys/time.h
sys/param.h )
if (WIN32)
list(APPEND FILES_TO_CHECK io.h
winsock2.h
ws2tcpip.h
afunix.h )
else()
list(APPEND FILES_TO_CHECK netdb.h
dlfcn.h
arpa/inet.h
poll.h
port.h
sys/socket.h
sys/random.h
sys/un.h
sys/devpoll.h
sys/epoll.h
sys/eventfd.h
sys/event.h
sys/ioctl.h
sys/mman.h
sys/queue.h
sys/select.h
sys/sendfile.h
sys/uio.h
sys/wait.h
sys/resource.h
sys/timerfd.h
netinet/in.h
netinet/in6.h
netinet/tcp.h
ifaddrs.h )
endif()
if (NOT "${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Linux")
list(APPEND FILES_TO_CHECK sys/sysctl.h)
endif()
if (APPLE)
list(APPEND FILES_TO_CHECK mach/mach_time.h
mach/mach.h )
endif()
foreach(FILE ${FILES_TO_CHECK})
CHECK_INCLUDE_FILE_CONCAT(${FILE} "EVENT")
endforeach()
unset(FILES_TO_CHECK)
将头文件汇总到FILES_TO_CHECK
变量中,使用CHECK_INCLUDE_FILE_CONCAT
宏函数进行检测是否存在
对应CHECK_INCLUDE_FILE_CONCAT
宏函数,我们需要追踪到Macros.cmake文件中找到其定义
macro(CHECK_INCLUDE_FILE_CONCAT FILE PREFIX)
string(REGEX REPLACE "[./]" "_" FILE_UL ${FILE})
string(TOUPPER "${FILE_UL}" FILE_UL_UPPER)
if ("${PREFIX}" STREQUAL "")
set(HAVE_FILE_DEF "HAVE_${FILE_UL_UPPER}")
else()
set(HAVE_FILE_DEF "${PREFIX}__HAVE_${FILE_UL_UPPER}")
endif()
CHECK_INCLUDE_FILES("${EVENT_INCLUDES};${FILE}" ${HAVE_FILE_DEF})
if(${HAVE_FILE_DEF})
set(EVENT_INCLUDES ${EVENT_INCLUDES} ${FILE})
endif()
endmacro()
最终调用的是CHECK_INCLUDE_FILES
这个cmake内置的宏函数,这个宏函数的原理很简单,就是将头文件放到一个c/c++到源码,然后gcc判断是否能够成功,如果报错就代表该文件不存在。 上面还有个重要的地方, 这里会根据select,poll,epoll等的头文件来设置EVENT__HAVE_EPOLL的值!
# test.c
#include <netinet/tcp.h>
int main() {
return 0;
}
# gcc -o test test.c
# Check if functions exist
list(APPEND SYMBOLS_TO_CHECK getaddrinfo
getnameinfo
getprotobynumber
getservbyname
gethostbyname
inet_ntop
inet_pton
gettimeofday
signal
strtoll
splice
strlcpy
strsep
strtok_r
vasprintf
timerclear
timercmp
timerisset
timeradd
nanosleep
putenv
umask )
if (NOT EVENT__DISABLE_CLOCK_GETTIME)
list(APPEND SYMBOLS_TO_CHECK clock_gettime)
endif()
if (WIN32)
list(APPEND SYMBOLS_TO_CHECK _gmtime64_s
_gmtime64 ) else() list(APPEND SYMBOLS_TO_CHECK getifaddrs
select
epoll_create
epoll_create1
epoll_ctl
eventfd
poll
port_create
kqueue
fcntl
mmap
pipe
pipe2
sendfile
sigaction
strsignal
sysctl
accept4
arc4random
arc4random_buf
arc4random_addrandom
getrandom
getegid
geteuid
issetugid
usleep
timerfd_create
setenv
unsetenv
setrlimit
gethostbyname_r )
if (APPLE)
list(APPEND SYMBOLS_TO_CHECK mach_absolute_time)
endif()
endif()
# Add stdio.h for vasprintf set(EVENT_INCLUDES ${EVENT_INCLUDES} stdio.h) CHECK_SYMBOLS_EXIST("${SYMBOLS_TO_CHECK}" "${EVENT_INCLUDES}" "EVENT")
unset(SYMBOLS_TO_CHECK)
set(EVENT__HAVE_EPOLL ${EVENT__HAVE_EPOLL_CREATE})
if(WIN32 AND NOT CYGWIN)
set(EVENT__HAVE_WEPOLL 1)
endif()
检测函数是否存在,这个原理和上面的一样。在这里使用了CHECK_SYMBOLS_EXIST
宏函数
CHECK_TYPE_SIZE("struct sockaddr_un" EVENT__HAVE_STRUCT_SOCKADDR_UN)
CHECK_TYPE_SIZE("uint8_t" EVENT__HAVE_UINT8_T)
CHECK_TYPE_SIZE("uint16_t" EVENT__HAVE_UINT16_T)
CHECK_TYPE_SIZE("uint32_t" EVENT__HAVE_UINT32_T)
CHECK_TYPE_SIZE("uint64_t" EVENT__HAVE_UINT64_T)
CHECK_TYPE_SIZE("short" EVENT__SIZEOF_SHORT BUILTIN_TYPES_ONLY)
CHECK_TYPE_SIZE("int" EVENT__SIZEOF_INT BUILTIN_TYPES_ONLY)
CHECK_TYPE_SIZE("unsigned" EVENT__SIZEOF_UNSIGNED BUILTIN_TYPES_ONLY)
CHECK_TYPE_SIZE("unsigned int" EVENT__SIZEOF_UNSIGNED_INT BUILTIN_TYPES_ONLY)
CHECK_TYPE_SIZE("long" EVENT__SIZEOF_LONG BUILTIN_TYPES_ONLY)
CHECK_TYPE_SIZE("long long" EVENT__SIZEOF_LONG_LONG BUILTIN_TYPES_ONLY)
检测某个数据类型是否存在,使用的是CHECK_TYPE_SIZE
这个宏函数。后面还有其他检测,内容类似,方法类似。
set(HDR_PRIVATE bufferevent-internal.h
changelist-internal.h
defer-internal.h
epolltable-internal.h
evbuffer-internal.h
event-internal.h
evmap-internal.h
evrpc-internal.h
evsignal-internal.h
evthread-internal.h
ht-internal.h
http-internal.h
iocp-internal.h
ipv6-internal.h
log-internal.h
minheap-internal.h
mm-internal.h
ratelim-internal.h
strlcpy-internal.h
util-internal.h
evconfig-private.h
compat/sys/queue.h)
set(HDR_COMPAT include/evdns.h
include/evrpc.h
include/event.h
include/evhttp.h
include/evutil.h)
set(HDR_PUBLIC include/event2/buffer.h
include/event2/bufferevent.h
include/event2/bufferevent_compat.h
include/event2/bufferevent_struct.h
include/event2/buffer_compat.h
include/event2/dns.h
include/event2/dns_compat.h
include/event2/dns_struct.h
include/event2/event.h
include/event2/event_compat.h
include/event2/event_struct.h
include/event2/watch.h
include/event2/http.h
include/event2/http_compat.h
include/event2/http_struct.h
include/event2/keyvalq_struct.h
include/event2/listener.h
include/event2/rpc.h
include/event2/rpc_compat.h
include/event2/rpc_struct.h
include/event2/tag.h
include/event2/tag_compat.h
include/event2/thread.h
include/event2/util.h
include/event2/visibility.h
${PROJECT_BINARY_DIR}/include/event2/event-config.h)
设置隐藏头文件,兼容头文件和共有头文件。这就是libevent中的隐藏的使用方法,即将一些头文件不放入到打包的include中
。在发布打包后,只能在对应的.h文件中找到的方法才是可以外部使用的,可以看到log头文件都是外部不能使用的。
set(SRC_CORE buffer.c
bufferevent.c
bufferevent_filter.c
bufferevent_pair.c
bufferevent_ratelim.c
bufferevent_sock.c
event.c
evmap.c
evthread.c
evutil.c
evutil_rand.c
evutil_time.c
watch.c
listener.c
log.c
signal.c
strlcpy.c)
if(EVENT__HAVE_SELECT)
list(APPEND SRC_CORE select.c)
endif()
if(EVENT__HAVE_POLL)
list(APPEND SRC_CORE poll.c)
endif()
if(EVENT__HAVE_KQUEUE)
list(APPEND SRC_CORE kqueue.c)
endif()
if(EVENT__HAVE_DEVPOLL)
list(APPEND SRC_CORE devpoll.c)
endif()
if(EVENT__HAVE_EPOLL)
list(APPEND SRC_CORE epoll.c)
endif()
if(EVENT__HAVE_WEPOLL)
list(APPEND SRC_CORE epoll.c wepoll.c)
endif()
if(EVENT__HAVE_EVENT_PORTS)
list(APPEND SRC_CORE evport.c)
endif()
if (NOT EVENT__DISABLE_OPENSSL)
find_package(OpenSSL REQUIRED)
set(EVENT__HAVE_OPENSSL 1)
message(STATUS "OpenSSL include: ${OPENSSL_INCLUDE_DIR}")
message(STATUS "OpenSSL lib: ${OPENSSL_LIBRARIES}")
include_directories(${OPENSSL_INCLUDE_DIR})
list(APPEND SRC_OPENSSL bufferevent_openssl.c)
list(APPEND HDR_PUBLIC include/event2/bufferevent_ssl.h)
list(APPEND LIB_APPS ${OPENSSL_LIBRARIES}) endif()
设置源码文件,这里会根据是否存在EVENT__HAVE_SELECT等变量来添加对应的.c文件。至于EVENT_HAVE_SELECT这些变量怎么设置的,在前面(第8条)已经讲解过。
set(SRC_EXTRA event_tagging.c
http.c
evdns.c
evrpc.c)
设置扩展库的源文件,libevent会编译成几个库文件,这种就是通过这种方式设置的。
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/event-config.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/include/event2/event-config.h NEWLINE_STYLE UNIX)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/evconfig-private.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/include/evconfig-private.h)
根据模板,同时根据上面生成的一些变量来生成event-config.h和evconfig-private.h文件,这两个文件至关重要,是后面进行事件分发器选择的根据。
include(AddEventLibrary)
add_event_library(event_core SOURCES ${SRC_CORE})
add_event_library(event_extra INNER_LIBRARIES event_core
SOURCES ${SRC_EXTRA})
if (NOT EVENT__DISABLE_OPENSSL)
add_event_library(event_openssl INNER_LIBRARIES event_core
OUTER_INCLUDES ${OPENSSL_INCLUDE_DIR}
LIBRARIES ${OPENSSL_LIBRARIES}
SOURCES ${SRC_OPENSSL})
endif()
if (EVENT__HAVE_PTHREADS)
set(SRC_PTHREADS evthread_pthread.c)
add_event_library(event_pthreads INNER_LIBRARIES event_core
SOURCES ${SRC_PTHREADS}) endif()
生成的几个库分别为event_core, event_extra, event_openssl, event_pthreads就是在这里设置的
# Install compat headers install(FILES ${HDR_COMPAT} DESTINATION "include"
COMPONENT dev)
# Install public headers install(FILES ${HDR_PUBLIC} DESTINATION "include/event2"
COMPONENT dev)
# Install the configs. install(FILES ${PROJECT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/LibeventConfig.cmake
${PROJECT_BINARY_DIR}/LibeventConfigVersion.cmake
DESTINATION "${EVENT_INSTALL_CMAKE_DIR}"
COMPONENT dev)
cmake生成完成后,将对应的文件拷贝到对应的目录,至此输出一些信息,cmake完成