十一、CMake 简介

文章目录

 


一、CMake 简介

  • CMake是一个开源的跨平台构建系统,支持多层目录、多个可执行程序与多个库(动态库或静态库)的生成
  • ubuntu 下安装:sudo apt install cmake cmake-qt-gui
  • Demo 示例如下:
# 设置 cmake 最低版本
cmake_minimum_required(VERSION 3.15)

# 设置支持相对路径(这样工程位置发生改变的时候,可以不用修改配置文件)
cmake_policy(SET CMP0015 NEW)

# 设置项目名称
project(TestCMake)

# 设置编译版本
set(CMAKE_CXX_STANDARD 14)

# 声明头文件路径
set(INC_DIR ./include)

# 声明链接库搜索目录
set(LINK_DIR ./lib)

# 引入头文件:相当于 g++ 选项中的 -I 参数的作用,也相当于环境变量中增加路径到 CPLUS_INCLUDE_PATH 变量的作用
include_directories(${INC_DIR})

# 引入库文件:相当于 g++ 命令的 -L 选项的作用,也相当于环境变量中增加 LD_LIBRARY_PATH 路径的作用
link_directories(${LINK_DIR})

# 设置生成可执行文件
add_executable(TestCMake cluster.cpp)

# 将第三方库链接在一起
target_link_libraries(TestCMake lib_acl_cpp.a lib_acl.a lib_protocol.a pthread)
  • 在 build 中编译执行过程如下:
mkdir build    # 外部编译
cd build 

# CMAKE_INSTALL_PREFIX 默认为 /usr/local
cmake -j16 ..  # 注意此时 CMakelists.txt 要在 ../ 目录下,自动生成 Makefile
make -j16      # 编译 Makefile

./a.out        # 执行可执行文件

make clean     # 对构建结果进行清理

二、CMake 基本语法

  • CMake 的指令大小写都可以,但变量大小写是有区分的,使用${}对进行变量的引用,在 IF 等语句中,是直接使用变量名而不通过${}取值
  • CMake 指令的参数使用括弧括起,参数之间使用空格或分号分开
  • Cmake 支持正则表达式

1、变量相关指令

# 1、工程名定义:可指定工程支持的语言,也可忽略不写(默认支持所有语言)
PROJECT(projectname [CXX] [C] [Java])  

# 2、变量定义:参数之间使用空格或分号分开
SET(VAR [VALUE])
SET(SRC_LIST main.c t1.c t2.c)  # 赋多个值给变量 SRC_LIST


# 3、MESSAGE 打印指令
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
- SEND_ERROR:产生错误,生成过程被跳过
- STATUS:输出前缀为--的信息
- FATAL_ERROR:立即终止所有 cmake 过程 
MESSAGE(STATUS “This is BINARY dir” ${PROJECT_BINARY_DIR})


# 4、隐式变量
PROJECT_SOURCE_DIR         # 工程所在路径
PROJECT_BINARY_DIR         # 工程编译发生的目录,通常为${PROJECT_SOURCE_DIR}/build
CMAKE_CURRENT_SOURCE_DIR   # 当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRRENT_BINARY_DIR  # target 编译目录
EXECUTABLE_OUTPUT_PATH     # 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH        # 重新定义目标链接库文件的存放位置
PROJECT_NAME               # 返回通过 project 命令定义的项目名称
# 指定最终生成目标二进制和库的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

# 4、宏定义:若代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)  # -D 来定义,参数之间用空格分割

# 5、设置编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -march=native -O3 -frtti -fpermissive -fexceptions -pthread")  方式 1
add_compile_options(-march=native -O3 -fexceptions -pthread -fPIC)  # 方式 2
# 两者区别
- add_compile_options 命令添加的编译选项是针对所有编译器的(包括 c 和 c++ 编译器)
- set 命令设置 CMAKE_C_FLAGS 或 CMAKE_CXX_FLAGS 变量则是分别只针对 c 和 c++ 编译器的

2、可执行程序相关指令

# 1、生成可执行文件指令:hello 和工程名没有任何关系
ADD_EXECUTABLE(hello ${SRC_LIST})

# 2、支持添加相对路径:https://cmake.org/cmake/help/v3.0/policy/CMP0015.html
cmake_policy(SET CMP0015 NEW)

# 3、添加子目录:并在该目录下自动寻找 CMakeLists.txt 运行
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- source_dir:源文件路径
- binary_dir:生成的二进制文件路径
- EXCLUDE_FROM_ALL:将这个目录从编译过程中排除
# 包含的子目录 src,src 中也必须存在 CMakeLists.txt
# 该语句会在执行完当前文件夹 CMakeLists.txt 之后执行 src 子目录下的 CMakeLists.txt
ADD_SUBDIRECTORY(src) 

# 4、引入头文件搜索路径
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
- [AFTER|BEFORE]:控制 dir2 是追加在 dir1 后面还是前面
- dir1 dir2:添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来
SET(INC_DIR ../../test/include ../../include/public)
INCLUDE_DIRECTORIES(${INC_DIR})  # 引入相对路径下多个头文件的搜索路径

# 5、引入库文件搜索路径
LINK_DIRECTORIES(directory1 directory2 ...)
SET(LINK_DIR ../../common/libso ../../common/liba)
LINK_DIRECTORIES(${LINK_DIR})  # 引入相对路径下多个库文件的搜索路径

# 6、为 target(可执行二进制文件或库) 添加需要链接的共享库
TARGET_LINK_LIBRARIES(target library1 library2 ...)
# 以下写法均可:
TARGET_LINK_LIBRARIES(myProject libhello.a)   # 显示指定链接静态库
TARGET_LINK_LIBRARIES(myProject libhello.so)  # 显示指定链接动态库
TARGET_LINK_LIBRARIES(myProject hello)  # 亦可不要前后缀,cmake 默认先找相应的动态库链接,找不到再找相应的静态库链接

# 7、添加 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)

# 8、在 CMakeLists.txt 处理过程中在指定的目录运行某个程序
EXEC_PROGRAM(Executable [directory in which to run]  # 通过 ARGS 添加参数,通过 OUTPUT_VARIABLE 和 RETURN_VALUE 
                 [ARGS <arguments to executable>]    # 参数定义变量来获取输出和返回值
                 [OUTPUT_VARIABLE <var>]
                 [RETURN_VALUE <var>])
# 示例如下:
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
IF(not LS_RVALUE)  # LS_RVALUE = 0 说明程序执行成功
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)

3、静态库和动态库相关指令

# 1、动态库与静态库的构建
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
 [EXCLUDE_FROM_ALL]
 source1 source2 ... sourceN
)
- libname:不需要写全 libname.so/libname.a,只需填写 name 即可,系统会自动添加
- EXCLUDE_FROM_ALL:这个库不会被默认构建,除非有其他的组件依赖或者手工构建

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})         # 生成动态库,可以添加多个源文件
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})  # 生成静态库(默认),可以添加多个源文件

# 2、设置输出的名称,对于动态库,还可以用来指定动态库和 API 版本
SET_TARGET_PROPERTIES(target1 target2 ...
 PROPERTIES prop1 value1
 prop2 value2 ...
)

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")  # 重新设置静态库输出的名称,保证 libhello.so/libhello.a 共存
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)  # 设置动态库版本:VERSION 指代动态库版本,SOVERSION 指代 API 版本
# eg:
libhello.so.1.2  # 动态库
libhello.so.1->libhello.so.1.2  # 软连接
libhello.so ->libhello.so.1     # 软连接

4、文件查找及搜索相关指令

# 1、查找一个目录(dir)下所有的源代码文件(.c/.cpp)并将列表存储在一个变量中(VARIABLE),用来自动构建源文件列表
AUX_SOURCE_DIRECTORY(dir VARIABLE)
AUX_SOURCE_DIRECTORY(. SRC_LIST) # 搜索当前目录下的所有源代码文件并赋值给变量 SRC_LIST


# 2、文件操作相关指令
FILE(GLOB  variable [RELATIVE path] [globbing expressions]...)  # 支持正则操作
FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expressions]...)  # 支持正则递归操作
FILE(REMOVE [directory])
FILE(REMOVE_RECURSE [directory])
FILE(MAKE_DIRECTORY [directory])
FILE(WRITE filename "message to write")  # 会覆盖原有文件内容
FILE(APPEND filename "message to write")
FILE(READ filename variable)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
# 文件操作示例
FILE(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # 将当前文件夹下所有 .cpp 文件的文件名加入到 MAIN_SRC 中
FILE(GLOB_RECURSE MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)  #  递归搜索该文件夹,将文件夹下(包含子目录)符合类型的文件添加到文件列表


# 3、列表操作相关指令
LIST(LENGTH <LIST> <output variable>) 
LIST(GET <LIST> <element index> [<element index> ...] <output variable>)
LIST(APPEND <LIST> [<element> ...])
LIST(FIND <LIST> <value> <output variable>)
LIST(INSERT <LIST> <element_index> <element> [<element> ...])
LIST(REMOVE_ITEM <LIST> <value> [<value> ...])
LIST(REMOVE_AT <LIST> <index> [<index> ...])
LIST(REMOVE_DUPLICATES <LIST>)
LIST(REVERSE <LIST>)
LIST(SORT <LIST>)
# 列表操作示例
FILE(GLOB MAIN_HDR ${CMAKE_CURRENT_SOURCE_DIR}/*.h)  # 搜索当前目录
FILE(GLOB_RECURSE MAIN_HDR_ELSE  ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h)  # 递归搜索当前目录下src子目录
LIST(APPEND MAIN_HDR ${MAIN_HDR_ELSE})  # 将 MAIN_HDR_ELSE 中的值添加到 MAIN_HDR 

# 4、查找相关指令
FIND_FILE(<VAR> name1 path1 path2 ...) 
FIND_LIBRARY(<VAR> name1 path1 path2 ...) 
FIND_PATH(<VAR> name1 path1 path2 ...) 
FIND_PROGRAM(<VAR> name1 path1 path2 ...) 
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])
# 查找相关示例:若不指定路径,默认在 CMAKE_LIBRARY_PATH 和 CMAKE_INCLUDE_PATH 中搜索
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
export CMAKE_LIBRARY_PATH=/tmp/lib  # cmake 默认搜索头文件和库文件的环境变量(需事先加入 .bashrc 中)
export CMAKE_INCLUDE_PATH=/tmp/include

# 模块查找示例:对于系统预定义的 Find<name>.cmake 模块, 会定义以下几个变量:
# <name>_FOUND(模块被找到则为真) 、<name>_INCLUDE_DIR、<name>_LIBRARY
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
   INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
   TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
     MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)

# Note:
- 系统中提供了其他各种模块,一般情况需要使用 INCLUDE 指令显式的调用
- FIND_PACKAGE 指令是一个特例,可以直接调用预定义的模块

5、控制相关指令

# 1、IF 指令:凡是出现 IF 的地方一定要有对应的 ENDIF,出现 ELSEIF 的地方,ENDIF 是可选的
IF(expression)
          # THEN section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ELSE(expression)
          # ELSE section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
ENDIF(expression)
# IF 示例:当给定的变量或者字符串能够匹配正则表达式 regex 时为真
IF(variable MATCHES regex)、IF(string MATCHES regex)
IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")

# 2、WHILE 指令
WHILE(condition)
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
ENDWHILE(condition)

# 3、FOREACH 指令
FOREACH(loop_var arg1 arg2 ...)
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
ENDFOREACH(loop_var)
# FOREACH 示例
AUX_SOURCE_DIRECTORY(. SRC_LIST)  # 将当前文件夹(相对 CMakelists.txt)的文件添加到变量SRC_LIST
FOREACH(VAR ${SRC_LIST}) 
    MESSAGE(${VAR}) 
ENDFOREACH(VAR)

6、安装指令

# 1、目标文件的安装
# - DESTINATION 定义了安装的路径,如果路径以 / 开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 就无效
# - 如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,那么安装后的路径就是
# - ${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
INSTALL(TARGETS myrun mylib mystaticlib
        RUNTIME DESTINATION bin  # 可执行二进制 myrun 安装到 ${CMAKE_INSTALL_PREFIX}/bin 目录
        LIBRARY DESTINATION lib  # 动态库 mylib 安装到 ${CMAKE_INSTALL_PREFIX}/lib 目录
        ARCHIVE DESTINATION libstatic   # 静态库 mystaticlib 安装到 ${CMAKE_INSTALL_PREFIX}/libstatic 目录
)
INSTALL(TARGETS hello RUNTIME DESTINATION bin)

# 2、普通文件的安装
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)

# 3、非目标文件的可执行程序安装(比如脚本之类)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)

# 4、目录的安装:doc 后面加反斜杠表示要安装 doc 目录中的内容
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)

# 5、构建并安装
cmake -j16 -DCMAKE_INSTALL_PREFIX=/tmp/usr ..
make -j16
make install -j16

三、CMake 在 Clion 中的配置

1、Ubuntu 下 Clion 的安装

  • clion 及相关工具安装
    # clion 所用到的工具链:gcc(C),g++(C++),make(连接),cmake(跨平台建构系统), gdb(debug)
    sudo apt install gcc
    sudo apt install g++
    sudo apt install make
    sudo apt install cmake
    sudo apt install gdb
    
    # Install using the Toolbox App or Standalone installation
    sudo tar -xzf jetbrains-toolbox* -C /opt
    
    # Standalone installation
    sudo tar xvzf CLion-*.tar.gz -C /opt/
    sh /opt/clion-*/bin/clion.sh
    #  create a desktop entry, do one of the following:
    - On the Welcome screen, click Configure | Create Desktop Entry
    - From the main menu, click Tools | Create Desktop Entry
    

在这里插入图片描述

  • 如果没有桌面快捷方式,尝试用如下方法解决:

    sudo vim /usr/share/applications/clion.desktop
    
    # 插入如下内容,保存退出即可,在 search 里面就可以找到 clion 了
    [Desktop Entry]
    Encoding=UTF-8
    Name=CLion
    Comment=clion-2020.1.2
    Exec=/opt/clion-2020.1.2/bin/clion.sh
    Icon=/opt/clion-2020.1.2/bin/clion.svg
    Categories=Application;Development;Java;IDE
    Version=2020.1.2
    Type=Application
    #Terminal=1
    

2、如何在 clion 运行多个 cpp 文件 ?

  • 直接修改 CMakeLists.txt 即可
    • 新建 CPP 文件时注意:把默认勾选的 Add to targerts 去掉(如下图);在项目处右击,选择 Reload CMake Project
    • 在重新加载完之后可以看到运行框列表有了对应的运行选项
    cmake_minimum_required(VERSION 3.16)
    project(cpp14)
    
    set(CMAKE_CXX_STANDARD 14)
    
    
    # 递归遍历项目当前目录下所有的 .cpp 文件
    file (GLOB_RECURSE files *.cpp)
    foreach (file ${files})
        string(REGEX REPLACE ".+/(.+)\\..*" "\\1" exe ${file})  # 正则匹配,取出文件名前缀
        add_executable (${exe} ${file})  # exe 文件名, file 为文件绝对路径
        message("src file name is: " ${exe})
        message (\ \ \ \ --\ "src file path is: " ${file})
    endforeach ()
    

在这里插入图片描述


四、参考资料

1、CMake 如何入门?
2、CMake-Practice
3、Modern CMake 简介
4、CMake Cookbook
5、https://cmake.org/cmake/help/latest/guide/tutorial/index.html
6、CMake学习笔记(一)基本概念介绍、入门教程及CLion安装配置
7、如何在 clion 运行多个 cpp 文件 ?
8、https://cmake.org/cmake/help/latest/command/string.html#regex-replace
9、https://www.jetbrains.com/help/clion/getting-help.html
10、https://www.jetbrains.com/zh-cn/clion/features/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值