cmake实战
最近在重新编译luvit时,发现luvit的源码文件过于分散,于是准备重写makefile,但是luvit对各个平台都做了兼容,如果直接重写会遇到很多平台兼容性的if,最后想想还是用cmake,写CMakeLists.txt来简化写makefile的过程,本篇博客主要介绍cmake一些基本语法和luvit中的libuv重写cmake的过程。
cmake的特点
- 原生支持 C/C++/Fortran/Java 的相依性的自动分析功能,免除了程序员对代码依赖的调整;
- 支持跨平台编译;
- CMake需要用户用CMake规范的语法编写CMake脚本,该语法简单易用,入门极其顺手;
- 简化构建和编译过程,只需要cmake+make就可以构建编译工程;
cmake的简单使用
demo测试代码,文件有CMakeLists.txt, main.cpp, test.cpp, test.h,其中CMakeList.txt的写法:
project(TestProject) // project(projectname [CXX] [C] [Java]) 指定工程的名称,并且制定工程指定的语言
cmake_minimum_required(VERSION2.8) // 要求cmake的最少版本为2.8
// 根据参数SHARED还是STATIC来决定生成动态库还是静态库(SHARED动态库)
add_library(test SHARED test.cpp)
// target_link_libraries(target library1 <debug | optimized> library2 ...)
// target 添加需要链接的共享库,其中library1是需要链接的库,其中如下test是目标库,
// m表示链接库,相当于传给gcc的-lxx(lm表示math库)
target_link_libraries(test m)
// STATIC静态库
add_library(tests STATIC test.cpp)
// 链接math库
target_link_libraries(tests m)
// set_target_properties修改构建目标的一些属性,
// set_target_properties(target1 target2 ...
// PROPERTIES prop1 value1
// prop2 value2 ...)
// 这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和API版本
set_target_properties(tests PROPERTIES OUTPUT_NAME test)
// 产生可执行文件test_main,依赖main.cpp
add_executable(test_main main.cpp)
// 添加链接库
target_link_libraries(test_main test)
set(TEST_HEADER test.h)
// install(TARGETS targets...
// [[ARCHIVE|LIBRARY|RUNTIME]
// [DESTINATION <dir>]
// [PERMISSIONS permissions...]
// [CONFIGURATIONS
// [Debug|Release|...]]
// [COMPONENT <component>]
// [OPTIONAL]
// ] [...])
// TARGETS表示add_executable,add_library的目标文件,
// ARCHIVE指静态库,LIBRARY指动态库,RUNTIME指可执行目标二进制
// DESTINATION定义安装的路径
install(TARGETS test_main test tests
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
// FILES表示普通的文件,${TEST_HEADER}是test.h,表示将test.h移入到include文件夹中
install(FILES ${TEST_HEADER} DESTINATION include)
cmake的常用变量
1.cmake中存在两个隐式变量
2.CMAKE_INCLUDE_PATH 环境变量,非cmake变量
3.CMAKE_LIBRARY_PATH 环境变量
4.CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
5.CMAKE_CURRENT_BINARY_DIR target编译目录,使用ADD_SURDIRECTORY(src bin)可以更改此变量的值,SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对此变量有影响,只是改变了最终目标文件的存储路径
6.CMAKE_CURRENT_LIST_FILE 输出调用这个变量的CMakeLists.txt的完整路径
7.CMAKE_CURRENT_LIST_LINE 输出这个变量所在的行
8.EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
9.LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
10.PROJECT_NAME 返回通过PROJECT指令定义的项目名称
11.CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 用来控制IF ELSE语句的书写方式
12.CMAKE_MAJOR_VERSION cmake主版本号,如2.8.6中的2
13.CMAKE_MINOR_VERSION cmake次版本号,如2.8.6中的8
14.CMAKE_PATCH_VERSION cmake补丁等级,如2.8.6中的6
15.CMAKE_SYSTEM 系统名称,例如Linux-2.6.22
16.CAMKE_SYSTEM_NAME 不包含版本的系统名,如Linux
17.CMAKE_SYSTEM_VERSION 系统版本,如2.6.22
18.CMAKE_SYSTEM_PROCESSOR 处理器名称,如i686
19.UNIX 在所有的类UNIX平台为TRUE,包括OS X和cygwin
20.WIN32 在所有的win32平台为TRUE,包括cygwin
21.CMAKE_C_FLAGS 设置C编译选项,CMAKE_CXX_FLAGS 设置C++编译选项
cmake的控制语句和API
控制语句
1.if指令
if(expression)
...
else(expression)
...
endif()
如果expression变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或 _NOTFOUND 时,表达式为真
2.while循环
while(expression)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
endwhile()
3.foreach
foreach可以分为几种使用方式:列表,范围,步长
// 列表,参数以空格区分
foreach(loop_var arg1 arg2 ...)
...
endforeach()
// 范围,默认步长是1,从0开始一直到total
foreach(loop_var RANGE total)
...
endforeach()
// 步长,从start开始到stop结束,步长是step
foreach(loop_var RANGE start stop [step])
...
endforeach(loop_var)
API
1.cmake_minimum_required(VERSION) 要求cmake的版本;
2.project(projectname [CXX] [C] [Java]) 指定工程的名称,并且制定工程指定的语言;
3.set(var value) 设置变量的名称;
4.message([SEND_ERROR | STATUS | FATAL_ERROR] “message to display” …) 打印输出信息;
5.add_executable(target main.cpp) 产生可执行文件;
6.cmake最简单的语法规则是:(1),变量使用${}方式取值,但是在IF控制语句中是直接使用变量名;(2),指令(参数1 参数2…);参数使用括弧括起,参数之间使用空格或分号分开;
7.add_library(target STATIC|SHARED test.cpp) 生成动态库或静态库;
8.set_target_properties(tests PROPERTIES OUTPUT_NAME test);
9.include_dictory([AFTER|BEFORE] [SYSTEM] dir1 dir2 …) 头文件搜索,通过AFTER或者BEFORE 参数,也可以控制是追加还是置前;
10.foreach(loop_var arg1 arg2 …)
command1(ARGS …)
command2(ARGS …)
…
endforeach(loop_var)
可以结合使用aux_source_directory的栗子
aux_source_directory(. SRC_LIST)
foreach(F ${SRC_LIST})
MESSAGE(${F})
endforeach(F)
11.aux_source_directory(dir VARIABLE) 发现一个目录下所有的源代码文件并将列表存储在一个变量中;
12.add_definitions 向 C/C++编译器添加-D 定义,比如:add_definitions(-DENABLE_DEBUG -DABC),参数之间用空格分割,如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效;
13.find_path( name1 path1 path2 …) 用来在指定路径中搜索文件名,其中VAR表示搜索的路径;
14.find_library( name1 path1 path2 …) 用来在指定路径中搜索库文件,其中VAR表示搜索的路径;
15.target_link_libraries(test_main test) 为指定的的可执行文件添加链接库,其中test为链接库;
16.execute_process(COMMAND
17.function (argument_tester arg) … endfunction() argument_tester表示函数名,arg表示参数,其中在函数体内使用${ARGN}获取参数;
18.get_filename_component( FileName PATH|ABSOLUTE|NAME|EXT|NAME_WE|REALPATH [CACHE]) 获取文件名的指定部分;
重写libuv的CMakeLists.txt
libuv的目录结构:
include(包含头文件等信息),
src(.c的源代码),
CMakeLists.txt(cmake的编译文件),其中CMakeLists.txt的写法如下:
cmake_minimum_required(VERSION 2.8)
project(LIBUV)
set(PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src)
set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS
"${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -Wno-unused-function"
)
set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS
"${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -Wno-unused-function"
)
set(LIBUV_COMMON_SRC ${PROJECT_SOURCE_DIR}/fs-poll.c ${PROJECT_SOURCE_DIR}/heap-inl.h ${PROJECT_SOURCE_DIR}/inet.c ${PROJECT_SOURCE_DIR}/queue.h ${PROJECT_SOURCE_DIR}/threadpool.c ${PROJECT_SOURCE_DIR}/uv-common.c ${PROJECT_SOURCE_DIR}/uv-common.h ${PROJECT_SOURCE_DIR}/version.c)
message(STATUS "PROJECT_BINARY_DIR dir : " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR dir : " ${PROJECT_SOURCE_DIR})
message(STATUS "PROJECT_INCLUDE_DIR dir : " ${PROJECT_INCLUDE_DIR})
include_directories(${PROJECT_INCLUDE_DIR})
include_directories(${PROJECT_SOURCE_DIR})
if(WIN32)
message(STATUS "System name : windows")
set(WIN_DIR "")
foreach(F win/async.c win/atomicops-inl.h win/core.c win/detect-wakeup.c win/dl.c win/error.c win/fs-event.c win/fs.c win/getaddrinfo.c win/getnameinfo.c win/handle.c win/handle-inl.h win/internal.h win/loop-watcher.c win/pipe.c win/poll.c win/process-stdio.c win/process.c win/req.c win/req-inl.h win/signal.c win/stream.c win/stream-inl.h win/tcp.c win/thread.c win/timer.c win/tty.c win/udp.c win/util.c win/winapi.c win/winapi.h win/winsock.c win/winsock.h)
list(APPEND WIN_DIR ${PROJECT_SOURCE_DIR}/${F})
endforeach()
set(LIBUV_SRC ${WIN_DIR})
include_directories(${PROJECT_SOURCE_DIR}/win)
else()
message(STATUS "System name : " ${CMAKE_SYSTEM_NAME})
set(UNIX_DIR "")
foreach(F unix/async.c unix/atomic-ops.h unix/core.c unix/dl.c unix/fs.c unix/getaddrinfo.c unix/getnameinfo.c unix/internal.h unix/loop-watcher.c unix/loop.c unix/pipe.c unix/poll.c unix/process.c unix/signal.c unix/spinlock.h unix/stream.c unix/tcp.c unix/thread.c unix/timer.c unix/tty.c unix/udp.c)
list(APPEND UNIX_DIR ${PROJECT_SOURCE_DIR}/${F})
endforeach()
set(LIBUV_SRC ${UNIX_DIR})
endif()
set(UNIX_EXT_NAME ${PROJECT_SOURCE_DIR}/unix)
if(APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DARWIN_USE_64_BIT_INODE=1 -D_DARWIN_UNLIMITED_SELECT=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_DARWIN_USE_64_BIT_INODE=1 -D_DARWIN_UNLIMITED_SELECT=1")
set(LIBUV_EXT_SRC ${UNIX_EXT_NAME}/bsd-ifaddrs.c ${UNIX_EXT_NAME}/darwin.c ${UNIX_EXT_NAME}/darwin-proctitle.c ${UNIX_EXT_NAME}/fsevents.c ${UNIX_EXT_NAME}/kqueue.c ${UNIX_EXT_NAME}/proctitle.c ${UNIX_EXT_NAME}/pthread-barrier.c)
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(LIBUV_EXT_SRC ${UNIX_EXT_NAME}/linux-core.c ${UNIX_EXT_NAME}/linux-inotify.c ${UNIX_EXT_NAME}/linux-syscalls.c ${UNIX_EXT_NAME}/proctitle.c ${UNIX_EXT_NAME}/linux-syscalls.h)
elseif(ANDROID)
set(LIBUV_EXT_SRC ${UNIX_EXT_NAME}/android-ifaddrs.c ${UNIX_EXT_NAME}/pthread-fixes.c ${UNIX_EXT_NAME}/pthread-barrier.c)
else(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
set(LIBUV_EXT_SRC ${UNIX_EXT_NAME}/bsd-ifaddrs.c ${UNIX_EXT_NAME}/freebsd.c ${UNIX_EXT_NAME}/kqueue.c ${UNIX_EXT_NAME}/posix-hrtime.c)
endif()
add_library(libuv SHARED ${LIBUV_COMMON_SRC} ${LIBUV_SRC} ${LIBUV_EXT_SRC})