CMake基础学习使用与Demo-第一篇

CMake 使用笔记

前言

目前有几种主流使用方案:

  • VS使用vcxprj文件进行源代码管理,nmake编译
  • QT4,5使用QMAKE(.pro)文件进行管理
  • Keil使用uvprojx文件进行管理
  • Linux下一般使用make(makefile)进行管理

CMake即跨平台和跨方案的类似make方案,使用CMakeLists.txt进行编译内容管理。

和他对应的是国产的xmake, 更简单易用,但是不够国际化,正式项目以Cmake为主

目前,VS,QT, Clion,VSCode等各大IDE都默认支持CMake构建,所以,以后的项目如果可以使用CMake是一个比较好的开放方案。

CMake 组成

cmake是命令行,cmake-gui是windows下的GUI实现, ccmake是交互式的命令行GUI工具。

CMake是一个构建生成器,提供了强大的领域特定语言(DSL)来描述构建系统应该实现的功能。这是CMake的主要优势之一,它允许使用相同的CMake脚本集生成平台原生构建系统。CMake软件工具集,使开发人员可以完全控制给定项目的生命周期:

  • CMake是描述如何在所有主要硬件和操作系统上配置、构建和安装项目,无论是构建可执行文件、库,还是两者都要构建。
  • CTest定义测试、测试套件,并设置应该如何执行。
  • CPack为打包需求提供了DSL。
  • CDash将项目的测试结果在面板中展示。

CMake支持c/c++/java等语言的项目。

一般 CMake 的使用步骤:

  • 编写源码
  • 编写 CMakeLists.txt
  • 使用 cmake 命令生成 Makefile/project 文件
  • 使用 make/nmake 等命令编译
  • 运行生成的可执行文件

CMake 的所有的语句都写在 CMakeLists.txt 文件中,且必须要是这个文件名,CMake 命令会根据 CMakeLists.txt 中的语法构建编译规则,生成 Makefile 文件。可以在 CMakeLists.txt 中包含其他的 cmake 文件,被包含的文件名可以自定义,一般以 .cmake 结尾。

常用指令

project 关键字

可以用来指定工程名和支持的语言,默认支持所有语言,如

# 指定工程名字,并支持所有语言(建议使用)
project(hello)
# 指定工程名字,并支持 C++ 语言
project(hello CXX)
# 指定工程名字,并支持 C/C++ 语言
project(hello C CXX)

cmake_minimum_required 关键字

定义 CMake 最低支持的版本,低于次版本则会报错,如最低支持3.5以上的版本
cmake_minimum_required(VERSION 3.5)

set 关键字

用于显示指定变量,类似于变量赋值。如 set(src_list main.cc) 表示 src_list 变量包含了 main.cc 文件。多个文件使用空格分开,如

set(src_list main.cc)
set(src_list main.cc util.cc test.cc)

还可以设置其他信息,如 C++ 支持的版本

set(CMAKE_CXX_STANDARD 11)		# C++11
set(CMAKE_CXX_STANDARD 14)		# C++14

message 关键字

向终端用户输出自定义信息,主要包含 3 种信息:

  • SEND_ERROR 生产错误,生成过程被跳过;
  • STATUS 输出前缀为 – 的信息;
  • FATAL_ERROR 立即终止所有 cmake 过程。
message(STATUS "binary dir ${hello_BINARY_DIR}")
message(STATUS "source dir ${hello_SOURCE_DIR}")

# 输出内容,会有 -- 前缀
-- binary dir /home/mayw/tmp/cmake_test
-- source dir /home/mayw/tmp/cmake_test

aux_source_directory 关键字

查找目录中的所有源文件,并赋值给指定变量,如

# 查找当前目录下的所有源文件,将名称保存到 src_list 变量中,可以使用 ${src_list} 进行引用
aux_source_directory(. src_list)

这样便可以不用逐个添加源文件了,详细信息可以参考官网 aux_source_directory

include_directories 关键字

添加头文件目录,相当于 gcc/g++ 命令的 -I 参数的功能,如

# 类似 gcc/g++ 中的 -I 选项
include_directories(../include)

也可以添加一个 CPLUS_INCLUDE_PATH 环境变量,并将目录追加到环境变量中,但 CMake 一般不这样用。

include 关键字

包含其他的 cmake 文件,被包含的文件一般以 cmake结尾,如
include(../cmake/common.cmake)

add_definitions 关键字

添加定义信息,类似于 gcc/g++ 中的 CFLAGS 和 宏定义,如

add_definitions(-g -Wall -O3)

# -D开表示宏定义
if(MSVC)
  add_definitions(-D_WIN32_WINNT=0x600)
endif()

link_directories 关键字

添加库文件目录,相当于 gcc/g++ 命令的 -L 参数的功能,如

# 类似 gcc/g++ 中的 -L 选项
include_directories(../lib ../lib64)

也可以添加一个 LD_LIBRARY_PATH 环境变量,并将目录追加到环境变量中。Linux 中一般出现找不到库文件的问题,就会追加路径到次环境变量中。

target_link_libraries 关键字

链接库文件到程序中,相当于 gcc/g++ 命令的 -l 参数的功能,如

# 类似 gcc/g++ 中的 -l 选项
target_link_libraries(hello mylib)

一般都是与 add_executable 配合使用,紧跟其后。

add_executable 关键字

指定生成的可执行文件,如

set(src_list main.cc)
add_executable(hello ${src_list})
# 生成可执行文件 hello,源文件读取变量 src_list 里的内容,也就是 main.cc,等价add_executable(hello main.cc)
# 若要链接 libmylib.so,可以添加
target_link_libraries(hello mylib)

注意:工程名 hello 和生成的可执行文件 hello 没有任何关系,可以相同,也可以不相同。

add_library 关键字

add_executable类似,指定生成的库文件。

set(lib_src add.cpp)
add_library(add STATIC ${lib_src})
  • add :库文件名称,Linux 上生成库文件会自动加上前后缀,如当前的静态库文件名称为 libadd.a;
  • STATIC :静态库,动态库为 SHARED;
  • ${lib_src} :构造库文件所需的源码文件。

add_subdirectory 关键字

add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
添加一个子目录并构建该子目录。

CMake 语法的基本原则

  • 变量使用 ${} 方式取值,但是在 if 控制语句中是直接使用变量名;
  • 指令参数使用括号,参数之间使用空格或分号分开,如 add_executable(hello main.cc util.cc)
  • 指令是大小写无关的,即 add_executableADD_EXECUTABLE 是一样的,但 参数和变量是大小写相关的。google 规范中全部使用小写指令和变量,可视项目情况而定,与项目保持一致就行。

语法注意事项

  • set(src_list main.cc) 可以写成 set(src_list "main.cc") ,这两种写法是一样的,但如果文件名中有空格、中文或其他特殊字符,则必须要加双引号;
  • add_executable(hello main.cc) 后缀名可以不写,CMake 会自动去找 .c 和 .cpp 的源文件文件,但最好明确指定源文件的后缀名。

Demo

# 设置最低版本3.5,否则发出致命错误
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
# 工程名称project01, 语言CXX代表C++
project(project01 CXX)
# 添加可执行文件hello-world, 源代码main.cpp
# 源代码可以多个,使用空格或者;分割
add_executable(hello-world main.cpp)

message(STATUS "==> cmake file end <==")

NOTE: CMake中,C++是默认的编程语言。不过,我们还是建议使用LANGUAGES选项在project命令中显式地声明项目的语言。

构建:

$ mkdir -p build
$ cd build
$ cmake ..
-- Building for: Visual Studio 17 2022
-- Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.19044.
-- The CXX compiler identification is MSVC 19.34.31937.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- ==> cmake file end <==
-- Configuring done
-- Generating done
-- Build files have been written to: C:/xxxxx/build

$ cmake --build .
MSBuild version 17.4.1+9a89d02ff for .NET Framework
  Checking Build System
  Building Custom Rule C:/xxxxx/c1/CMakeLists.txt
  main.cpp
  hello-world.vcxproj -> C:\xxxxx\c1\build\Debug\hello-world.exe
  Building Custom Rule C:/XinDoc/codedb/testPrj/c1/CMakeLists.txt

这个构建命令等价于cmake -H . -B build,-H表示当前目录中搜索根CMakeLists.txt文件。-B build告诉CMake在一个名为build的目录中生成所有的文件。

-G Ninja 用来切换生成器,也可以通过GUI设定,完整列表如下:

The following generators are available on this platform (* marks default):
* Visual Studio 17 2022        = Generates Visual Studio 2022 project files.
                                 Use -A option to specify architecture.
  Visual Studio 16 2019        = Generates Visual Studio 2019 project files.
                                 Use -A option to specify architecture.
  Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 11 2012 [arch] = Deprecated.  Generates Visual Studio 2012
                                 project files.  Optional [arch] can be
                                 "Win64" or "ARM".
  Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Borland Makefiles            = Generates Borland makefiles.
  NMake Makefiles              = Generates NMake makefiles.
  NMake Makefiles JOM          = Generates JOM makefiles.
  MSYS Makefiles               = Generates MSYS makefiles.
  MinGW Makefiles              = Generates a make file for use with
                                 mingw32-make.
  Green Hills MULTI            = Generates Green Hills MULTI files
                                 (experimental, work-in-progress).
  Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Ninja Multi-Config           = Generates build-<Config>.ninja files.
  Watcom WMake                 = Generates Watcom WMake makefiles.
  CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles JOM
                               = Generates CodeBlocks project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - MinGW Makefiles   = Generates CodeLite project files.
  CodeLite - NMake Makefiles   = Generates CodeLite project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Eclipse CDT4 - NMake Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - MinGW Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
  Kate - MinGW Makefiles       = Generates Kate project files.
  Kate - NMake Makefiles       = Generates Kate project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Sublime Text 2 - MinGW Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - NMake Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.

-A <platform-name>提供target,对VS可选内容为Win32/x64/ARM/ARM64

Win32, Release版本的指令:

cd <root>
cmake -S . -B build -A Win32    # 在build目录,生成32位的Debug;Release;MinSizeRel;RelWithDebInfo 配置版本
cmake --build build --config Release  # 使用build目录里的配置信息,编译选项为Release的程序版本

一个综合例子

假设有一个项目,tool1目录提供了一个static库的源代码,tool2提供了一个DLL/so的源代码,app提供了主程序的源代码,comm提供了一些公用库和文件,大概结构如下:

proj02
   comm   # 公用代码
      comm.h
      comm.cpp
      version.h
      version.c
   tool1  # static library
      tool1.h
      tool1.cpp
      CMakeList.txt
   tool2  # dll/so library
      tool2.h
      tool2.cpp
      CMakeLists.txt
   main.h
   main.cpp
   CMakeLists.txt

proj02 对应 CMakeLists.txt

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(proj02 C CXX)

message(STATUS "prepare path")

# 自定义lib和exe输出的路径
# set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/Output)
# set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/Output)

message(STATUS "begin build")

# tool1 static lib
add_subdirectory(tool1)  # call tool1 CMakeLists.txt

# tool2 dll lib
add_subdirectory(tool2)  # call tool2 CMakeLists.txt

# app build
message(STATUS "begin ${PROJECT_NAME}")

# app code
aux_source_directory(. app_src)

# 手动追加源代码
list(APPEND app_src ../comm/comm.cpp ../comm/version.c)

message(STATUS "${PROJECT_NAME} code list ${app_src}")

# exe
add_executable(${
    
    PROJECT_NAME} ${
    
    app_src})

# header
target_include_directories(${
    
    PROJECT_NAME} PUBLIC ./comm ./tool1 ./tool2)

# link
# target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/Output/Release)
target_link_libraries(${
    
    PROJECT_NAME} tool1 tool2)

# post-build action
# copy tool2.dll to exe folder
add_custom_command(TARGET ${
    
    PROJECT_NAME} POST_BUILD 
               COMMAND ${
    
    CMAKE_COMMAND} -E copy_if_different
               $<TARGET_FILE:tool2>              
               $<TARGET_FILE_DIR:proj02>)

message(STATUS "build end")

tool1 对应 CMakeLists.txt

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(tool1 C CXX)

message(STATUS "try build ${PROJECT_NAME}")

# add_definitions(-D_DLL_EXPORT=1) # dll export

# tool1 code
aux_source_directory(. tool1_src)

# 手动追加源代码
list(APPEND tool1_src ../comm/comm.cpp ../comm/version.c)

message(STATUS "${PROJECT_NAME} code list ${tool1_src}")

# static lib
add_library(${
    
    PROJECT_NAME} STATIC ${
    
    tool1_src})

# header info
# include_directories(../comm)
target_include_directories(${
    
    PROJECT_NAME} PUBLIC ../comm)

tool2 对应 CMakeLists.txt

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(tool2 C CXX)

message(STATUS "try build ${PROJECT_NAME}")

# tool2 code
aux_source_directory(. tool2_src)

# 手动追加源代码
list(APPEND tool2_src ../comm/comm.cpp ../comm/version.c)

message(STATUS "${PROJECT_NAME} code list ${tool2_src}")

# windows 下必须开启才有lib文件生成
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

# dll
add_library(${
    
    PROJECT_NAME} SHARED ${
    
    tool2_src})

# header info
# include_directories(../comm)
target_include_directories(${
    
    PROJECT_NAME} PUBLIC ../comm)

编译指令如下:

cmake -S . -B build -A Win32
cmake --build build --config Release
# 输出内容在 ./build/Release里面,包含了exe和dll. 
# tool1和tool2的库输出文件在对应文件夹下的Release文件夹里面。

代码内容(参考):

// main.h
#include <cstdlib>
#include <iostream>

void app_test();

// main.cpp
#include "main.h"
#include "tool1.h"
#include "tool2.h"
#include "comm.h"

int main() {
    
    
  app_test();

  tool1_test();
  tool2_test();
  comm_test();

  int a = 0;
  std::cin >> a;
  return EXIT_SUCCESS;
}

void app_test(){
    
    
  std::cout << "Hello, CMake world!" << std::endl;
}


// comm/comm.h
#ifndef DLL_EXPORT
	#define API __declspec(dllexport)
#else 
	#define API __declspec(dllimport)
#endif

void comm_test();

// comm/comm.cpp
#include <iostream>
#include "comm.h"

void comm_test(){
    
    
    std::cout << "Hello, comm test!" << std::endl;
}

// comm/version.h
#define APP_VERSION 1.0

// comm/version.c
#include "version.h"


// tool1/tool1.h
#include "comm.h"

extern "C" API void tool1_test();

// tool1/tool1.cpp
#include <iostream>
#include "tool1.h"

void tool1_test(){
    
    
    std::cout << "Hello, tool1 test!" << std::endl;
}


// tool2/tool2.h
#include "comm.h"

extern "C" API void tool2_test();

// tool2/tool2.cpp
#include <iostream>
#include "tool2.h"

void tool2_test(){
    
    
    std::cout << "Hello, tool2 test!" << std::endl;
}

这个是一个基础使用的exe依赖static lib和shared lib的综合例子,虽然短小但是已经涵盖了一般使用的大部分需求,更复杂和深入的后续探索。

更多技巧

add_custom_command
find
find_package
...

后续继续学习(qt, boost, 嵌入式)

Reference

  • Keil环境如何使用CMake https://segmentfault.com/a/1190000019934297
  • Clion/CMake开发STM32 https://www.freesion.com/article/7529133907/ https://ost.51cto.com/posts/13464
  • CMake别人的Note https://blog.csdn.net/myw31415926/article/details/127852177
  • cmake系列教程 https://blog.csdn.net/myw31415926/category_12119774.html

猜你喜欢

转载自blog.csdn.net/bbdxf/article/details/128917077