VSCode进行CMake开发

这是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++编译参数

  1. -g 编译带调试信息的可执行文件

    g++ -g test.cpp
    
  2. -O[n] 优化源代码,使其执行速度更快

    # -O 减小代码长度和执行时间
    # -O0 不做优化
    # -O1 默认优化,与-O一样
    # -O2 在-O1优化基础上,进行额外调整
    # -O3 最高级别优化
    g++ -O3 test.cpp
    
  3. -l和-L 指定库文件 | 指定库文件路径

    # -l 指定要链接的库,/usr/lib和/usr/local/lib中的库可直接用-l链接
    g++ -lglog test.cpp
    
    # -L 对于不在系统默认库路径里的库,就需要使用-L指定库文件路径
    g++ -L/Thirdparty/Open3D/lib -lOpen3D test.cpp
    
  4. -I 指定头文件搜索目录

    # -I 添加库的头文件目录,系统默认头文件搜索路径为/usr/include和/usr/local/include
    g++ -I/Thirdparty/Open3D/include test.cpp
    
  5. -std=c++11 设置C++11编译标准

  6. -o <输出文件名> 指定输出文件名

  7. -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, _BINRARY_DIR

    在out-of-source编译(新建build并在build中cmake …)中,上述三个变量都指代build目录。

  • CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, _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项目

  1. 设置项目目录
include/
	Gun.h
	Soldier.h
src/
	Gun.cpp
	Soldier.cpp
bin/
main.cpp
CMakeLists.txt
  1. 编写源文件

  2. 构建编译规则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)
    
  3. 配置json文件并调试项目

    1. 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 配置自动化生成任务
            }
    
        ]
    
    1. 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(  ...)
add_library( [SHARED|STATIC|INTERFACE]  ...)
add_custom_target( ...) # 伪目标

其中,对于lib target,有:

  • add_library( [SHARED|STATIC|INTERFACE] ...)
    • no option —— creates either shared or static depending on BUILD_SHARED_LIBS
    • SHARED —— creates a shared library
    • STATIC —— creates a static library
    • INTERFACE —— creates a header only library
  • add_library( ALIAS )
    取别名
  • add_library( IMPORTED [GLOBAL])
    allows you to define a library target for a external library (导入已经生成的库)

3.3.2 使用target_**

可以使用如下函数来控制target:

  • target_include_directories( [PUBLIC|INTERFACE|PRIVATE] ...)
    添加包含目录
  • target_compile_definitions( [PUBLIC|INTERFACE|PRIVATE] ...)
    添加预处理定义(WITH_TBB)
  • target_compile_options( [PUBLIC|INTERFACE|PRIVATE]
    添加编译命令行选项(-Wall)
  • target_sources( [PUBLIC|INTERFACE|PRIVATE] ...)
    添加源文件
  • target_link_directories( [PUBLIC|INTERFACE|PRIVATE] ...)
    链接库

其中,函数中的[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 关键字。
PRIVATE
hello.so
hello_world.so
main.o
  • INTERFACE 当前构建的hello_world不使用hello的功能,而main会使用hello的功能,即hello_world仅仅起到一个传递依赖的作用。此时hello_world/CMakeLists.txt中使用INTERFACE关键字。
INTERFACE
hello.so
hello_world.so
main.o
  • PUBLIC 当前构建的hello_world和main都依赖hello,即用户可以同时使用hello_world和hello的功能。此时,hello_world/CMakeLists.txt中使用PUBLIC。
PUBLIC
hello.so
hello_world.so
main.o

详解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.cmake,Config模式会查找Config.cmake。缺省时先查找Find.cmake,再查找Config.cmake。用法如下:

find_package( [version] 
  [EXACT] [QUIET] [REQUIRED] 
  [CONFIG] [MODULE]
  [COMPONENTS [components...]] 
)

每个Find.cmake文件会定义如下变量:

  • _INCLUDE_DIRS:库的头文件目录
  • _LIBRARIES:库的库文件
  • _DEFINITIONS:使用XX库时用到的预处理定义
  • _FOUND:是否找到库

对于CONFIG模式而言,直接使用find_package即可添加完整的库依赖;而对于MODULE模式,由于历史兼容性,可能需要额外做包含头文件目录的操作。

find_package(OpenCV REUIQRED)
target_link_libraries(MyTarget ${OpenCV_LIBS})

添加预处理宏
用于添加一些可选的依赖,例如使用TBB进行并行化操作:

#ifdef WITH_TBB
#include 
#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 $)

安装库
为了将自己写好的库能够安装到系统中,给用户使用(通过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 
    $
    $
    )

# <<< 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() 

# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")

你可能感兴趣的:(C++,linux,c++,vscode)