是不是觉得Makefile很繁琐,一个cmake文件就可以解决

linux编译程序的时候,通常是使用Makefile文件来进行编译,这个是能够提高效率的,但是对于大中型的项目,每个文件夹下都需要创建Makefile,并且改变项目目录结构都需要调整Makefile文件,如果是小型项目的话,那花费的时间还是能够接受的,但是大中型项目要调整目录结构,这个工作量还是不小的。所以,我们可以通过使用cmake来解决这样的问题。

本文首先简单介绍什么是cmake,它可以用来干什么,接着给出一个简单的例子,让初学者对cmake有一个大致的了解,然后抛出cmake文件,并针对该cmake文件进行详细的解释,最后再进行总结,并分享个人的学习心得。

一、什么是cmake

cmake一款跨平台的编译工具,  它的全称是cross platform make,注意这里的make不是指linux下的make,使用它构建的工程,既可以生成linux下的makefile,也可以生成Mac系统的xcode的工程文件,还能够生成window的projects等。cmake并不会生成最终的软件程序,它只是生成标准的建构档,例如linux的Makefile文件。简单来说,cmake可以生产不同平台的建构档,然后再由建构档来生成最终的软件程序。

cmake组织档的取名都为CMakeLists.txt, 现在许多开源软件都采用cmake来组织代码,可见其用处还是很大的,学习了解cmake对于学习开源软件是有很大的帮助的。

二、入门例子

首先电脑上需要安装cmake软件,具体下载安装方法,可以网络搜索,这不是本文的主题,所以不进行说明。

linux上安装成功之后,可以执行命令cmake –version来查看当前的cmake版本

创建一个main.cpp文件,其内容如下所示,打印一句字符信息。

#include <stdio.h>

int main(int argc, char **argv)
{
    printf("this is the first cmake project.\n");
    return 0;
}

接着在同级目录下创建cmake文件 CMakeLists.txt

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息,项目名称
project (EXAMPLE_01)

# 指定生成目标
add_executable(example_01 main.cpp)

为了代码干净,同级目录下创建build目录,进入build目录,执行“cmake ../”命令来生成Makefile文件,接着执行命令make编译,最后build目录下生成二进制文件example_01,  执行程序可以输出打印信息。

build目录下生成的文件内容如下,Makefile是生成的建构档,example_01是生成的可执行二进制程序。

三、cmake代替Makefile

上面只是cmake的一个简单的入门例子,还不能明显看出cmake的强大,对于中大型的项目来看,cmake的作用就比较明显,特别是相同代码不同平台的编译。

下面给出本章节将要详细解释说明的cmake文件,可以先熟悉下整体的流程,看不明白没有关系,后面将针对该文件进行详细的解释说明。

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8.7 FATAL_ERROR)

# 项目信息,项目名称
project (EXAMPLE_02)

# CMAKE_INCLUDE_CURRENT_DIR 自动增加CMAKE_CURRENT_SOURCE_DIR和CMAKE_CURRENT_BINARY_DIR到每个目录的include路径
set(CMAKE_INCLUDE_CURRENT_DIR ON)
message("CMAKE_CURRENT_SOURCE_DIR : ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR : ${CMAKE_CURRENT_BINARY_DIR}")
message("PROJECT_SOURCE_DIR : ${PROJECT_SOURCE_DIR}")

set(CROSS_TOOLCHAIN_PREFIX "")
set(CMAKE_C_COMPILER ${CROSS_TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${CROSS_TOOLCHAIN_PREFIX}g++)
set(CROSS_OBJCOPY ${CROSS_TOOLCHAIN_PREFIX}objcopy)
set(CROSS_STRIP ${CROSS_TOOLCHAIN_PREFIX}strip)

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/linux)

# find_package()命令是用来查找依赖包
find_package(PkgConfig)

# export cmd 
message(STATUS “HOME dir: $ENV{HOME}”)
message(STATUS “LANG: $ENV{LANG}”)

# find_program查找可执行程序
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
    message("found ccache")
    set_property(GLOBAL PROPERTH RULE_LAUNCH_COMPILE ccache)
    set_property(GLOBAL PROPERTH RULE_LAUNCH_LINK ccache)	
else()
    message("no found ccache")	
endif(CCACHE_FOUND)

if($ENV{DEBUG} MATCHES 1)
    message("debug build")
    set(CMAKE_BUILD_TYPE Debug)
else()
    message("release build")
    set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# 头文件的搜索路径
function(include_sub_directories_recursively root_dir)
    if (IS_DIRECTORY ${root_dir})
        include_directories(${root_dir})
    endif()

    file (GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*)
    foreach(sub ${ALL_SUB})
        if (IS_DIRECTORY ${root_dir}/${sub} AND (NOT ("${sub}" STREQUAL ".svn")))
            include_sub_directories_recursively(${root_dir}/${sub}) 
        endif()
    endforeach()	
endfunction()	

include_directories(
    ${CMAKE_CURRENT_BINARY_DIR}
    ${PROJECT_SOURCE_DIR}
)

set(CMAKE_CXX_FLAGS_MY "-pipe -march=armv7-a -mfpu=neon -DLINUX=1 -DEGL_API_FB=1 -mfloat-abi=hard -O2 -std=c++11 -Wall -W -D_REENTRANT -fPIC -Wformat -Werror")
set(CMAKE_C_FLAGS "-pipe -march=armv7-a -mfpu=neon -DLINUX=1 -DEGL_API_FB=1 -mfloat-abi=hard -O2 -Wall -W -D_REENTRANT -fPIE -Werror")
set(CMAKE_CXX_FLAGS "")

# 指定查找库的目录
function(link_sub_directories_recursively root_dir)
    if (IS_DIRECTORY ${root_dir})
        link_directories(${root_dir})
    endif()

    file (GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*)
    foreach(sub ${ALL_SUB})
        if (IS_DIRECTORY ${root_dir}/${sub} AND (NOT ("${sub}" STREQUAL ".svn")))
            link_sub_directories_recursively(${root_dir}/${sub}) 
        endif()
    endforeach()	
endfunction()

# 遍历匹配目录的所有子目录并匹配文件
file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/*.cpp)

# 指定生成目标
add_executable(example_02 ${SRC_FILES})

# 标示链接的库
target_link_libraries(example_02
    dl
    pthread
    m)

if (CMAKE_BUILD_TYPE MATCHES Rel)
    add_custom_command(TARGET example_02
        POST_BUILD
        COMMAND mkdir -p ${EXECUTABLE_OUTPUT_PATH}/release	
        )
else()
    add_custom_command(TARGET example_02
        POST_BUILD
        COMMAND mkdir -p ${EXECUTABLE_OUTPUT_PATH}/debug	
        )
endif()

cmake_minimum_required指定cmake最低版本号要求,FATAL_ERROR 表示当发生警告时,用错误方式提示

make_minimum_required(VERSION <min>[...<max>] [FATAL_ERROR])

CMAKE_INCLUDE_CURRENT_DIR 自动增加CMAKE_CURRENT_SOURCE_DIR和CMAKE_CURRENT_BINARY_DIR到每个目录的include路径。CMAKE_INCLUDE_CURRENT_DIR默认是关闭的。

当前测试工程的目录结构如下:

02_exapmle
    |-- build
    |-- src
        |-- CMakeLists.txt
        |-- main.cpp

message可以打印输出变量信息, CMAKE_CURRENT_SOURCE_DIR、CMAKE_CURRENT_BINARY_DIR、PROJECT_SOURCE_DIR是cmake内置变量

message("CMAKE_CURRENT_SOURCE_DIR : ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR : ${CMAKE_CURRENT_BINARY_DIR}")
message("PROJECT_SOURCE_DIR : ${PROJECT_SOURCE_DIR}")

运行后输出打印的信息, CMAKE_CURRENT_SOURCE_DIR表示当前正在处理的源代码目录,CMAKE_CURRENT_BINARY_DIR表示当前正在处理的二进制目录,PROJECT_SOURCE_DIR表示当前工程的顶层源代码目录

CMAKE_CURRENT_SOURCE_DIR : .../02_example/src
CMAKE_CURRENT_BINARY_DIR : .../02_example/build
PROJECT_SOURCE_DIR : .../02_example/src

EXECUTABLE_OUTPUT_PATH表示可执行文件输出路径

find_package()命令是用来查找依赖包, Pkg-Config维护它依赖库路径、头文件路径、编译选项、链接选项等信息。

关键字ENV查看的是当前环境变量,linux上的环境变量可以通过export命令来查看。

message(STATUS “LANG: $ENV{LANG}”)对应输出的内容为:

-- “LANG:zh_CN.UTF-8”

find_program查找可执行程序。一个名为<VAR>的cache条目会被创建用来存储该命令的结果。如果该程序被找到了,结果会存储在该变量CCACHE_FOUND

find_program(<VAR> name [path1 path2 ...])

set_property给定范围内设置一个命名属性,GLOBAL域是唯一的,PROPERTY后面紧跟着要设置的属性的名字。

set_property(<GLOBAL                      |
              DIRECTORY [<dir>]           |
              TARGET    [<target1> ...]   |
              SOURCE    [<src1> ...]      |
              INSTALL   [<file1> ...]     |
              TEST      [<test1> ...]     |
              CACHE     [<entry1> ...]    >
             [APPEND] [APPEND_STRING]
             PROPERTY <name> [value1 ...])

function,定义函数name, 并且参数为<arg1> … , 函数只有在调用的时候才起作用。

function(<name> [<arg1> ...])
 <commands>
endfunction()

include_directories包含头文件的搜索路径。

link_directories指定查找库的目录。

target_link_libraries标示链接的库。<target>必须时 add_executable() or add_library() 命令创建。<item>则是链接的库

target_link_libraries(<target> ... <item>... ...)

file产生一个匹配 <globbing-expressions> 的文件列表并将它存储到变量 <variable> 中,果 RELATIVE 标志位被设定,将返回指定路径的相对路径。file的第一个参数设置为GLOB_RECURSE,则表示遍历匹配目录的所有子目录并匹配文件。

file(GLOB <variable>
[LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]
[<globbing-expressions>...])

add_custom_command,定义一个跟指定目标target关联的新的命令,命令何时执行取决于PRE_BUILD | PRE_LINK | POST_BUILD这三个参数。

add_custom_command(TARGET target
                   PRE_BUILD | PRE_LINK | POST_BUILD
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment] [VERBATIM])
PRE_BUILD - 所有其他依赖项之前运行
PRE_LINK - 其他依赖项之后运行
POST_BUILD - 目标建立之后运行

四、总结

创建cmake文件的过程,首先当然是先创建CMakeLists.txt文件,接着声明cmake的版本要求,然后设置项目信息,再根据具体场景设置相关属性以及生成的可执行目标。

上面讲解的例子中,主要涉及到cmake的几个知识点。具体如下:

  1. cmake的变量:内置变量、环境变量以及自定义变量。
  2. 查找命令:find_package、find_program
  3. 定义函数:function
  4. 查找文件:file
  5. 搜索路径:include_directories、link_directories、target_link_libraries
  6. 自定义命令:add_custom_command
  7. 设置目标: add_executable

五、学习心得

学习cmake也有一段时间,网络上也搜索了很多信息,但是总感觉说的不够明白和全面。最后发现要想全面的了解cmake,  最有效的方法就是直接查看cmake的官方文档。虽然是英文,但是只要耐心认真阅读,就会发现里面讲的很全面。然后再结合具体的例子进行消化理解就可以。而对于cmake的变量的含义,除了查看cmake官方文档之后,还可以通过message直接打印出变量信息来加深理解其含义。