最近在重新编译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中存在两个隐式变量 _SOURCE_DIR 及_BINARY_DIR,前者是工程的根目录,后者是运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build
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 [args1…]]) 指定执行多个命令;
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})