这是B战up主Xiaobing1016的课程笔记,详细内容见 基于VSCode和CMake实现C/C++开发。在此感谢up主的无私分享和细心讲解。
1 Linux系统介绍
1.1 常用指令
pwd - Print working directory
作用:打印当前终端所在目录
用法:pwd
# 打印当前目录
pwd
ls - List
作用:列出当前目录下的所有文件/文件夹名称
用法:ls [选项] [路径]
# 当前目录下所有文件/文件夹名
ls
# 指定路径下
ls ../
ls /home
# ls 选项 路径
# -l:表示以详细列表的形式展示
# -a:显示隐藏文件/文件夹
# -h:以可读性较高的形式展示
ls -lah /home
cd - change directory
作用:切换当前目录
mkdir - make directory
作用:创建目录
# 创建目录
mkdir build
# 创建多层目录
mkdir -p ./build/bin
# 创建多个目录
mkdir include src build
touch - create file
作用:创建新文件
# 在当前目录下创建文件
touch CmakeLists.txt
# 在指定目录下创建文件
touch ../CmakeLists.txt
rm - remove files or directories
作用:删除文件/目录
# 删除当前目录下的文件
rm main.cpp
# 删除指定目录下的文件
rm ../main.cpp
# 删除当前目录下的文件夹
rm -rf build
# 删除指定目录下的文件夹
rm -rf ../build
cp - copy files or directories
作用:拷贝文件/文件夹到指定位置
# 复制文件到指定位置
cp ../CmakeLists.txt ./
# 复制文件夹到指定位置
cp -r ../build ./
mv - move(rename) files or directories
作用:移动文件/文件夹到指定位置,或重命名
# 移动当前目录下文件到指定目录下
mv CmakeLists.txt ../CmakeLists.txt
# 移动当前目录下文件夹到指定目录
mv build ../build
# 移动并重命名
mv CmakeLists.txt ../CmakeLists_1.txt
2 GCC编译器和GDB调试器
2.1 g++编译参数
-
-g 编译带调试信息的可执行文件
g++ -g test.cpp
-
-O[n] 优化源代码,使其执行速度更快
# -O 减小代码长度和执行时间 # -O0 不做优化 # -O1 默认优化,与-O一样 # -O2 在-O1优化基础上,进行额外调整 # -O3 最高级别优化 g++ -O3 test.cpp
-
-l和-L 指定库文件 | 指定库文件路径
# -l 指定要链接的库,/usr/lib和/usr/local/lib中的库可直接用-l链接 g++ -lglog test.cpp # -L 对于不在系统默认库路径里的库,就需要使用-L指定库文件路径 g++ -L/Thirdparty/Open3D/lib -lOpen3D test.cpp
-
-I 指定头文件搜索目录
# -I 添加库的头文件目录,系统默认头文件搜索路径为/usr/include和/usr/local/include g++ -I/Thirdparty/Open3D/include test.cpp
-
-std=c++11 设置C++11编译标准
-
-o <输出文件名> 指定输出文件名
-
-D 定义宏
# 常用-DDEBUG: 定义DEBUG宏,打印调试信息 g++ -DDEBUG test.cpp
2.2 gdb调试
要使用gdb调试,编译时需要添加-g参数。在终端中执行gdb [可执行文件]
,进入gdb调试程序。
## 括号内为命令的简化使用
$(gdb)help(h) # 查看命令帮助
$(gdb)run(r) # 开始运行文件
$(gdb)run argv[1] argv[2] # 调试时命令行传参
$(gdb)start # 单步执行,运动程序,停在第一行语句
$(gdb)list(l) # 查看源代码
$(gdb)set # 设置变量的值
$(gdb)next(n) # 逐过程调试,函数直接执行
$(gdb)step(s) # 逐语句调试,跳入函数内部执行
$(gdb)backtrace(bt) # 查看函数调用的堆栈
$(gdb)continue(c) # 继续
$(gdb)print(p) # 打印变量
$(gdb)display # 追踪查看变量
$(gdb)undisplay # 取消追踪查看变量
$(gdb)watch # 被设置观察点的变量发生修改时,打印显示
$(gdb)break n # 在第n行设置断点
$(gdb)info breakpoints # 查看设置的断点
$(gdb)delete breakpoints n # 删除第n个断点
$(gdb)enable breakpoints # 启用断点
$(gdb)disable breakpoints # 禁用断点
3 CMake
3.1 常用指令和变量
3.1.1 重要指令
-
cmake_minimum_required 指定CMake的最小版本要求
- 语法:cmake_minimum_required(VERSION versionNum [FATAL_ERROR])
cmake_minimum_required(VERSION 3.2.0 FATAL_ERROR)
-
project 定义工程名,并可指定工程支持的语言
- 语法:project(projectName [CXX] [C] [Java])
project(Geometry)
-
set 显示定义变量
- 语法:set(var [value])
set(SRC src/point_cloud.cpp main.cpp)
-
include_directories 向工程添加多个特定的头文件搜索路径 —>相当于g++中的-I
- 语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
include_directories(/home/Open3D/include ${PROJECT_SOURCE_DIR}/include)
-
link_directories 向工程添加多个特定的库文件搜索路径 —> 相当于g++中的-L
- 语法:link_directories(dir1 dir2 …)
link_directories(/home/Open3D/lib)
-
add_library 生成库文件
- 语法:add_library(libName [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] src1 src2 …)
# 通过变量SRC中的文件生成libhello.so动态库 add_library(hello SHARED ${SRC})
-
add_complie_options 添加编译参数
- 语法:add_compile_options( …)
# 添加C++11标准、显示警告、-O2优化 add_compile_options(-Wall -std=c++11 -O2)
-
add_executable 生成可执行文件
- 语法:add_executable(exeName src1 src2 …)
add_executable(test ${SRC})
-
target_link_libraries 为target添加需要链接的动态库 —> 相当于g++中的-l
- 语法:target_link_libraries(target lib1 lib2 …)
target_link_libraries(test hello)
-
add_subdirectory 向当前工程添加存放源文件的子目录,并可指定中间和目标二进制文件存放的位置
- 语法:add_subdirectory(sourceDir [binaryDir] [EXCLUDE_FROM_ALL])
# 添加src目录,src中需要有CMakeLists.txt add_subdirectory(src)
-
list 对列表进行操作
- 语法:list(subcommand [args…])
# 添加工程目录中的cmake文件夹到搜索路径,方便后续按照文件夹中的.cmake文件查找对应的外部库 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) ## find_package(OpenCV REQUIRED)
3.1.2 常用变量
-
CMAKE_C_FLAGS、CMAKE_CXX_FLAGS gcc、g++编译选项
set(CMAKE_CXX_FLAGS "{CMAKE_CXX_FLAGS} -std=c++11") # 添加-std=c++11
-
CMAKE_BUILD_TYPE 编译类型(Debug, Release)
# 调试时选择Debug,会自动加上-g set(CMAKE_BUILD_TYPE Debug) # 发布时选择Release,会自动加上-O3,关闭debug调试 set(CMAKE_BUILD_TYPE Release)
-
CMAKE_BINARY_DIR, PROJECT_BINARY_DIR, <projectname>_BINRARY_DIR
在out-of-source编译(新建build并在build中cmake …)中,上述三个变量都指代build目录。
-
CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, <projectname>_SOURCE_DIR
上述三个变量都指代工程根目录
-
EXECUTABLE_OUTPUT_PATH(旧), CMAKE_RUNTIME_OUTPUT_DIRECTORY 可执行文件输出的存放路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
-
LIBRARY_OUTPUT_PATH(旧), CMAKE_LIBRARY_OUTPUT_DIRECTORY 库文件输出的存放路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
-
CMAKE_CXX_STANDARD C++标准
set(CMAKE_CXX_STANDARD 11) # 添加c++11支持,和设置编译选项效果一样
3.2 使用VSCode构建CMake项目
- 设置项目目录
include/
Gun.h
Soldier.h
src/
Gun.cpp
Soldier.cpp
bin/
main.cpp
CMakeLists.txt
-
编写源文件
-
构建编译规则CmakeLists.txt
cmake_minimum_required(VERSION 3.2.0) project(SoldierFire) ## 启动调试,也可写为set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_FLAGS "{CMAKE_CXX_FLAGS} -g -Wall") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) add_executable(soldier_fire main.cpp src/Gun.cpp src/Soldier.cpp) target_include_directories(sodier_fire PUBLIC ${PROJECT_SOURCE_DIR}/include)
-
配置json文件并调试项目
- launch.json
"version": "0.2.0", "configurations": [ { "name": "(gdb) 启动", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/bin/soldier_fire", // 步骤1 修改可执行文件路径 "args": [], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "将反汇编风格设置为 Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ] "preLaunchTask": "Build", // 步骤2 配置自动化生成任务 } ]
- tasks.json 配置自动化生成任务,无需手动输入命令进行编译
{ "version": "2.0.0", "options": { "cwd": "${workspaceFolder}/build" }, "tasks": [ { "label": "cmake", "type": "shell", "command": "cmake", "args": [ ".." ] }, { "label": "make", "group": { "kind": "build", "isDefault": true }, "command": "make", "args": [] }, { "label": "Build", "dependsOrder": "sequence", //按顺序执行任务依赖项 "dependsOn":[ "cmake", "make" ] } ] }
3.3 现代CMake
3.3.1 Targets
现代CMake是基于target构建项目的,target可以是一个executable,也可以是一个static/shared/header-only lib,甚至是一个custom。
add_executable(<name> <sourcefile> ...)
add_library(<name> [SHARED|STATIC|INTERFACE] <sourcefile> ...)
add_custom_target(<name> ...) # 伪目标
其中,对于lib target,有:
add_library(<name> [SHARED|STATIC|INTERFACE] <sourcefile>...)
- no option —— creates either shared or static depending on
BUILD_SHARED_LIBS
SHARED
—— creates a shared librarySTATIC
—— creates a static libraryINTERFACE
—— creates a header only library
- no option —— creates either shared or static depending on
add_library(<name> ALIAS <original>)
给<original>取别名<name>add_library(<name> <SHARED|STATIC|MODULE|UNKNOWN> IMPORTED [GLOBAL])
allows you to define a library target for a external library (导入已经生成的库)
3.3.2 使用target_**
可以使用如下函数来控制target:
target_include_directories(<target-name> [PUBLIC|INTERFACE|PRIVATE] <include-dir>...)
添加包含目录target_compile_definitions(<target-name> [PUBLIC|INTERFACE|PRIVATE] <definition>...)
添加预处理定义(WITH_TBB)target_compile_options(<target-name> [PUBLIC|INTERFACE|PRIVATE] <option>...)
添加编译命令行选项(-Wall)target_sources(<target-name> [PUBLIC|INTERFACE|PRIVATE] <source-file>...)
添加源文件target_link_directories(<target-name> [PUBLIC|INTERFACE|PRIVATE] <other-target>...)
链接库
其中,函数中的[PUBLIC|INTERFACE|PRIVATE]表示作用域(传递),假设项目中存在如下3个targets——库hello.so,hello_world.so,可执行文件main.o。
PRIVATE
只有当前构建的hello_world需要使用hello的功能,而main只使用hello_world中的功能,即用户只能使用hello_world中定义的功能。此时,hello_world/CMakeLists.txt 中使用 PRIVATE 关键字。
INTERFACE
当前构建的hello_world不使用hello的功能,而main会使用hello的功能,即hello_world仅仅起到一个传递依赖的作用。此时hello_world/CMakeLists.txt中使用INTERFACE关键字。
PUBLIC
当前构建的hello_world和main都依赖hello,即用户可以同时使用hello_world和hello的功能。此时,hello_world/CMakeLists.txt中使用PUBLIC。
详解target_**中的PUBLIC、PRIVATE、INTERFACE
3.3.3 惯用法
调用环境变量
使用ENV{variable_name}
调用系统的环境变量。
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
主要开关选项
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # 使用c++11编译
添加target
# GLOB_RECURSE表示递归搜索,CONFIGURE_DEPENDS表示自动检测目录更新
file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS src/*.cpp include/*.h)
add_library(biology STATIC ${SRCS})
target_include_directories(biology PUBLIC include)
源码依赖
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://gitee.com/slamist/json.git
GIT_TAG v3.7.3)
FetchContent_MakeAvailable(json)
添加第三方库
find_package
命令包含两种模式:Module模式和Config模式。Module模式会查找Find<Package>.cmake
,Config模式会查找<Package>Config.cmake
。缺省时先查找Find<Package>.cmake
,再查找<Package>Config.cmake
。用法如下:
find_package(<PkgName> [version]
[EXACT] [QUIET] [REQUIRED]
[CONFIG] [MODULE]
[COMPONENTS [components...]]
)
每个Find<XX>.cmake
文件会定义如下变量:
<XX>_INCLUDE_DIRS
:库的头文件目录<XX>_LIBRARIES
:库的库文件<XX>_DEFINITIONS
:使用XX库时用到的预处理定义<XX>_FOUND
:是否找到库
对于CONFIG模式而言,直接使用find_package即可添加完整的库依赖;而对于MODULE模式,由于历史兼容性,可能需要额外做包含头文件目录的操作。
find_package(OpenCV REUIQRED)
target_link_libraries(MyTarget ${OpenCV_LIBS})
添加预处理宏
用于添加一些可选的依赖,例如使用TBB进行并行化操作:
#ifdef WITH_TBB
#include <tbb/parallel_for.h>
#endif
int main() {
#ifdef WITH_TBB
tbb::parallel_for(0, 4, [&] (int i) {
#else
for (int i = 0; i < 4; ++i) {
#endif
printf("hello, %d \n", i);
#ifdef WITH_TBB
});
#else
}
#endif
}
在CMakeLists.txt中,通过target_compile_definitions
命令传递宏给编译器:
//----CMakeLists.txt----//
find_package(TBB)
if (TBB_FOUND)
message(STATUS "TBB found at: ${TBB_DIR}")
target_link_libraries(MyTarget PUBLIC TBB::tbb)
target_compile_definitions(MyTarget PUBLIC WITH_TBB)
else()
message(WARNING "TBB not found")
endif()
定义生成目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) # so
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) # executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) # a
添加伪目标
add_custom_target
可用于创建伪目标。例如,创建一个run伪目标,执行main,从而在build时启动main.exe程序。
add_executable(main main.cc)
add_custom_target(run COMMAND $<TARGET_FILE:main>)
安装库
为了将自己写好的库能够安装到系统中,给用户使用(通过find_package调用),可以通过CMake进行安装配置。
我们定义一个库项目LearningCMake,其中包括MyLib库和可执行文件MyExe,其项目结构如下:
LearningCMake
├── cmake
│ └── MyLibConfig.cmake.in
├── CMakeLists.txt
├── MyExe
│ ├── CMakeLists.txt
│ └── src
│ └── main.cc
└── MyLib
├── CMakeLists.txt
├── include
│ └── MyLib
│ └── MyLib.h
└── src
└── MyLib.cc
1、首先在顶层CMakeLists.txt中设置好安装路径:
#---- CMakeLists.txt ----#
cmake_minimum_required(VERSION 3.6)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
project(LearningCMake VERSION 0.1.0)
message(STATUS "Project will be install to ${CMAKE_INSTALL_PREFIX}")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
# Set installation paths
if(UNIX OR CYGWIN)
include(GNUInstallDirs)
set(MyLib_INSTALL_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}")
set(MyLib_INSTALL_BIN_DIR "${CMAKE_INSTALL_BINDIR}")
set(MyLib_INSTALL_LIB_DIR "${CMAKE_INSTALL_LIBDIR}")
set(MyLib_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
else()
set(MyLib_INSTALL_INCLUDE_DIR include)
set(MyLib_INSTALL_BIN_DIR bin)
set(MyLib_INSTALL_LIB_DIR lib)
set(MyLib_INSTALL_CMAKE_DIR CMake)
endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# Allow the developer to select if Dynamic or Static libraries are built
option (BUILD_SHARED_LIBS "Build Shared Libraries" ON)
set (LIB_TYPE STATIC)
if (BUILD_SHARED_LIBS)
set (LIB_TYPE SHARED)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
add_subdirectory(MyLib)
option(BUILD_TEST "Build test." ON)
if (BUILD_TEST)
add_subdirectory(MyExe)
endif()
2、在MyLib目录中,CMakeLists.txt配置安装:
#---- MyLib/CMakeLists.txt ----#
file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS src/*.cc include/*.h)
add_library(MyLib ${LIB_TYPE} ${SRCS})
target_include_directories(MyLib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${MyLib_INSTALL_INCLUDE_DIR}>
)
# <<< Install and export targets >>>
# Install targets
install(
TARGETS MyLib
EXPORT MyLibTargets
ARCHIVE
DESTINATION ${MyLib_INSTALL_LIB_DIR}
COMPONENT lib
RUNTIME
DESTINATION ${MyLib_INSTALL_BIN_DIR}
COMPONENT bin
LIBRARY
DESTINATION ${MyLib_INSTALL_LIB_DIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${MyLib_INSTALL_INCLUDE_DIR}/MyLib
COMPONENT dev
)
# Install config files
include(CMakePackageConfigHelpers)
# find_package MyLib Version
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# find_package MyLib
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/MyLibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION ${MyLib_INSTALL_CMAKE_DIR}
)
# Install the MyLibConfig.cmake and MyLibConfigVersion.cmake
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
DESTINATION ${MyLib_INSTALL_CMAKE_DIR}
COMPONENT dev
)
# Creates export file which can be imported by other cmake projects
install(EXPORT MyLibTargets
NAMESPACE MyLib::
DESTINATION ${MyLib_INSTALL_CMAKE_DIR})
3、cmake目录中需要设置MyLibConfig.cmake.in,用于生成MyLibConfig.cmake文件,以便用户通过find_package添加MyLib库。
@PACKAGE_INIT@
if(POLICY CMP0072)
cmake_policy(SET CMP0072 @CMP0072_VALUE@)
endif()
# Declare the dependencies of MyLib
# include(CMakeFindDependencyMacro)
# find_dependency(<Dep>)
# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")