CMake学习实践笔记

有些日子没写博客了,今天又要开始“创作”了,写一篇跨平台的工程构建工具CMake的学习笔记,方便以后随时查看回顾

简介

  • 背景
    CMake是kitware公司以及一些开源开发者在开发几个工具套件(VTK)过程的衍生品,最终形成体系,成为一个独立的开源项目,项目诞生于2001年,其官网是https://cmake.org/ 帮助文档是https://cmake.org/documentation/
  • 特点
  1. 开源,使用BSD许可发布
  2. 跨平台,可生成native编译配置文件,在Linux/Unix平台,生成Makefile文件;在macOS平台,生成Xcode工程;在Windows平台,生成MSVC工程文件
  3. 能够管理大型项目,例:KDE4
  4. 简化编译构建过程和编译过程,只需要 cmake + make
  5. 效率高
  6. 可扩展,可以为CMake编写特定功能的模块,扩充CMake功能

构建CMake “hello world”

1. 内部构建
  • 创建源文件和构建定义文件
#!/bin/bash
mkdir cmake_test1
cd cmake_test1
touch main.cpp 
touch CMakeLists.txt #创建CMake的构建定义文件,文件名大小写敏感
  • 编辑源代码
//main.cpp
#include <iostream>

int main() {
    
    
    std::cout << "hello world" << std::endl;
    return 0;
}
  • 编辑构建定义文件(指令是大小写无关的,参数是有关的)
#CMakeLists.txt
#指明对 CMake 最低版本的要求(对版本没有特殊要求可以省略)
cmake_minimum_required(VERSION 3.10.0)
#定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言
project(HELLO)
#显式定义变量,如果有多个源文件,可以在 main.cpp 后面追加,可以用空格 " " 或分号隔开 ";"
set(SRC_LISTS main.cpp)
#定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LISTS 中定义的源文件列表,在这里可以直接写成 add_executable(hello main.cpp)
#另外需要注意的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的
add_executable(hello ${SRC_LISTS})
  • 开始构建
#在当前目录构建对应平台的编译配置文件
cmake . 
#输出下面的信息    
-- The C compiler identification is AppleClang 12.0.0.12000032
-- The CXX compiler identification is AppleClang 12.0.0.12000032
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/liuwenlong/Downloads/code/cmake_proj/cmake_test1

生成CMakeCache.txt等中间文件,还有一个Makefile文件

  • 编译
#编译
make
#输出下面的信息   
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/main.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello

生成了可执行文件hello

  • 注意
  1. projectname_BINARY_DIR(指代编译路径)和projectname_SOURCE_DIR(指代工程路径)
cmake_minimum_required(VERSION 3.10.0)
#project 指令不仅定义工程名称,
#还隐式定义了两个 cmake 变量: <projectname>_BINARY_DIR(在这里就是HELLO_BINARY_DIR)和 <projectname>_SOURCE_DIR(在这里就是HELLO_SOURCE_DIR)
#CMake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR 变量,他们的值分别跟 <projectname>_BINARY_DIR 与 <projectname>_SOURCE_DIR 一致
project(HELLO)
#message([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)指令用于向终端输出用户定义的信息,包含了三种类型:
#1.SEND_ERROR 产生错误,生成过程被跳过
#2.SATUS 输出前缀为“--”的信息
#3.FATAL_ERROR 立即终止所有 cmake 过程
message(STATUS "HELLO_BINARY_DIR: " ${HELLO_BINARY_DIR})
message(STATUS "HELLO_SOURCE_DIR: " ${HELLO_SOURCE_DIR})
message(STATUS "PROJECT_BINARY_DIR: " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR: " ${PROJECT_SOURCE_DIR})
  1. 引用变量
cmake 使用 “${
    
    }” 引用(应用)变量,但是在 “IF” 控制语句,变量是直接使用变量名引用,而不需要 “${
    
    }” ;如果使用了 “${
    
    }” 引用变量,“IF” 会去判断名为 “${
    
    }” 所代表的变量的值,那是不存在的
  • 清理工程
#可以对构建结果进行清理,但是无法对产生的中间文件进行清理,因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟踪这些临时文件到底是哪些,所以推荐使用外部构建
make clean 
2. 外部构建
  • 因为内部构建会产生无法自动清理的中间文件,导致原始目录看起来比较乱,所以一般使用外部构建的方式构建工程
cd cmake_test1
#在之前的工程目录下创建用于放置中间文件和最终生成的文件目录(build)
mkdir build
#".."利用上一层目录的CMakeLists.txt文件构建构成
cmake ..
#构建过程中的中间文件和最终生成的文件都在一个子目录中了,不会对原始目录产生任何影响了
make

更规范的CMake “hello world”

接下来构建一个更加规范的CMake "hello world"工程

  • 在工程目录下添加一个src子目录存放源文件
  • 构建过程生成的中间文件和二进制输出文件放置在build目录下的bin文件中
  • 在工程目录下创建run.sh调用生成的二进制可执行文件
#!/bin/bash
mkdir cmake_test2
cd cmake_test2
mkdir src
mkdir build
touch run.sh
touch CMakeLists.txt
cd src
touch main.cpp
touch CMakeLists.txt
cd ../build
  • 编辑工程目录下的构建定义文件
#cmake_test2/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.0)
project(HELLO)
#add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]),该指令用于添加存放源文件的子目录( source_dir ),并可以指定中间二进制和目标二进制存放的位置( [binary_dir] 可选),EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,如工程有 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建
#下面的命令将src子目录加入工程,并指定编译输出(包含编译中间结果)路径为 bin 目录;如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在 build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,相当于在编译时将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录
add_subdirectory(src bin)
  • 编辑src子目录下的构建定义文件(如果工程存在多个目录,需要确保每个要管理的目录都存在一个 CMakeLists.txt)
set(SRC_LISTS main.cpp)
add_executable(hello ${SRC_LISTS})
  • EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
#不论 add_subdirectory 是否指定编译输出目录,我们都可以通过 set 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
#添加上面命令的原则是:在哪里 add_executable 或 add_library,如果需要改变目标存放路径,就在哪里加入上述的定义
#将上面的命令加在 cmake_test2/src/CMakeLists.txt 文件中
set(SRC_LISTS main.cpp)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello ${SRC_LISTS})
  • 编辑执行脚本
#!/bin/bash
cd build/bin/
./hello

构建静态库和动态库

  • 创建源文件和构建定义文件
#!/bin/bash
mkdir cmake_test3
cd cmake_test3
touch CMakeLists.txt
mkdir lib
cd lib
touch hello.h
touch hello.cpp
touch CMakeLists.txt
mkdir -p ../build
  • 编辑源文件
//hello.h
#ifndef __HELLO__
#define __HELLO__
#include <iostream>
void printFunc();
#endif //__HELLO__
//hello.cpp
#include "hello.h"
void printFunc() {
    
    
    std::cout << "hello world" << std::endl;
}
  • 编辑构建定义文件
#/cmake_test3/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.0)
project(HELLOLIB)
add_subdirectory(lib)
#/cmake_test3/lib/CMakeLists.txt

#构建动态库
set(SRC_LISTS hello.cpp)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/shared)
#add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN) 
#libname 不需要写全 libhello.so,只需要填写 hello 即可,cmake 系统会自动为你生成 libhello.X
#类型有三种:
#SHARED,动态库
#STATIC,静态库
#MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
#EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建
add_library(hello SHARED ${SRC_LISTS})

#构建静态库
set(SRC_LISTS hello.cpp)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/static)
add_library(hello STATIC ${SRC_LISTS})

#动态库和静态库同时构建
set(SRC_LISTS hello.cpp)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/library)
#因为如果库的名字相同会创建失败,这里先创建两个不同名字的库
add_library(hello_static STATIC ${SRC_LISTS})
add_library(hello_shared SHARED ${SRC_LISTS})
#set_target_properties(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...) 指令设置 target 属性
#可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
set_target_properties(hello_shared PROPERTIES OUTPUT_NAME "hello")
  • 构建和编译
cmake ..
make

引用外部共享库和头文件

  • 创建源文件和构建定义文件
#!/bin/bash
mkdir cmake_test4
cd cmake_test4
touch CMakeLists.txt
mkdir build
mkdir lib
mkdir src
cd src
touch main.cpp
touch CMakeLists.txt
  • 将之前在 cmake_test3 中生成的动态库和静态库以及对应的头文件拷贝到 lib 目录下
  • 编辑源文件
//main.cpp
#include "hello.h"
int main() {
    
    
    printFunc();
    return 0;
}
  • 编辑构建定义文件
#cmake_test4/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.0)
project(LINKLIB)
add_subdirectory(src)
#cmake_test4/src/CMakeLists.txt

#默认链接动态库
set(SRC_LISTS main.cpp)
#include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...) 用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面
#可以通过两种方式来进行控制搜索路径添加的方式:
#1.CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 set 使这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面
#2.通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前
include_directories(../lib)
#link_directories(directory1 directory2 ...) 添加非标准的共享库搜索路径
link_directories(../lib)
add_executable(main ${SRC_LISTS})
#target_link_libraries(target library1 <debug | optimized> library2 ...) 可以用来为 target 添加需要链接的共享库
#直接写共享库的名字默认链接的使动态库
target_link_libraries(main hello)

#查看编译依赖情况
ldd main
	linux-vdso.so.1 (0x00007ffe09558000)
	libhello.so => /home/vim/longlong/cmake_test4/src/../lib/libhello.so (0x00007fa94f17a000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa94edf1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa94ea00000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa94e662000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa94f57e000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa94e44a000)

#指定要链接的动态库
set(SRC_LISTS main.cpp)
include_directories(../lib)
link_directories(../lib)
add_executable(main ${SRC_LISTS})
#指定要链接的动态库
target_link_libraries(main libhello.so)

#Linux下查看编译依赖情况(macOS下:otool -L main)
ldd main
	linux-vdso.so.1 (0x00007ffd644fc000)
	libhello.so => /home/vim/longlong/cmake_test4/src/../lib/libhello.so (0x00007f7735891000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7735508000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7735117000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7734d79000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f7735c95000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7734b61000)

#指定要链接的静态库
set(SRC_LISTS main.cpp)
include_directories(../lib)
link_directories(../lib)
add_executable(main ${SRC_LISTS})
#指定要链接的静态库
target_link_libraries(main libhello.a)

#查看编译依赖情况
ldd main
	linux-vdso.so.1 (0x00007ffd5d197000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f6fbb7e0000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6fbb3ef000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6fbb051000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6fbbd6b000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f6fbae39000)
  • 构建编译
cmake ..
make

参考文章:
[1]. 《Cmake 实践》

如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢

猜你喜欢

转载自blog.csdn.net/xiao_ma_nong_last/article/details/109687728