vscode 使用 CMake

安装插件

CMake和CMake Tools

c++等等

  • CMake插件主要功能是CMake语法高亮、自动补全
  • CMake Tools的功能主要是结合VSCode IDE使用CMake这个工具,比如生成CMake项目、构建CMake项目等

新建CMake项目

使用步骤

  1. 创建目录结构:在一个空目录下,分别创建 main.cpp 和 CMakeLists.txt 文件,将上述代码分别复制进去
  2. 配置和构建:
  • 打开终端,进入该目录所在路径。
  1. 按下 Ctrl + Shift + P(在 VS Code 中),选择 CMake: Configure 配置项目,CMake 会根据 CMakeLists.txt 生成构建文件。
  2. 然后选择 CMake: Build 来构建项目,构建成功后,在 bin 目录下会生成名为 test 的可执行文件(在不同系统下可执行文件格式不同,如 Windows 下是 .exe 后缀 )。
  • 运行可执行文件,即可看到输出结果。
    image.png

使用cmake来配合程序的编译

使用单个CMakeLists.txt

结构如下:
image.png

cmake_minimum_required(VERSION 3.16.0)
project(MyProject)
set(CMAKE_CXX_STANDARD 11)

#设置可执行文件的输出路径为项目源文件目录下的bin目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#将项目源文件目录添加到头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/MyNet)

#查找项目源文件目录下的所有源文件,并将其列表存储在SRC LIST变量中
aux_source_directory(${PROJECT_SOURCE_DIR}/MyNet SRC_LIST)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")

#创建名为MyNet的可执行文件,源文件来自SRC LIST变量
add_executable(
    MyNet
    ${SRC_LIST}
)

单个CMakeLists.txt的优势

  • 项目结构简单:当项目规模较小、目录结构不复杂时,使用单个CMakeLists.txt,可以将项目的源文件查找、头文件路径设置、编译选项配置以及可执行文件或库的生成等操作集中在一个文件里管理。像图中项目,通过在一个CMakeLists.txt中设置,就能实现把MyNet目录下源文件添加到编译体系,以及指定头文件搜索路径等操作。
  • 维护方便:开发者只需关注一个配置文件,修改编译相关设置(如添加源文件目录、调整编译选项等)时,直接在这一个文件中操作即可,减少在多个配置文件间切换的成本,降低出错概率。

用一个CMakeLists.txt编译所有源文件的实现方式

  • 查找源文件:利用aux_source_directory指令,如图中aux_source_directory(${PROJECT_SOURCE_DIR}/MyNet SRC_LIST) ,能把指定目录(这里是MyNet )下所有源文件收集起来,存储到变量(SRC_LIST )中,后续可用于创建可执行文件或库。
  • 指定头文件路径:通过include_directories指令设置头文件搜索路径,让编译器能找到源文件中#include引用的头文件。如include_directories(${PROJECT_SOURCE_DIR}/MyNet) ,保证编译过程顺利进行。
  • 创建目标:使用add_executable(创建可执行文件 )或add_library(创建库文件 )等指令,结合前面收集的源文件变量,生成最终的可执行程序或库。图中add_executable(MyNet ${SRC_LIST})就是利用SRC_LIST里的源文件来创建名为MyNet的可执行文件。

构建项目流程:
3. 打开终端,进入该目录所在路径。
4. 按下 Ctrl + Shift + P(在 VS Code 中),选择 CMake: Configure 配置项目,CMake 会根据 CMakeLists.txt 生成构建文件。
5. 然后选择 CMake: Build 来构建项目,构建成功后,在 bin 目录下会生成名为 MyProject 的可执行文件(在不同系统下可执行文件格式不同,如 Windows 下是 .exe 后缀 )。

多目录、多 CMakeLists.txt分层级管理(1个可执行文件)

目的:创建 RTMP 服务器的可执行程序,依赖于MyNet库(模块作为库,主程序链接库)

  • 适用场景:模块间存在依赖关系,需要复用代码。
  • 优势:避免代码重复,结构更清晰。
MyProject/
├── CMakeLists.txt          # 根目录CMake配置(总控)
├── MyNet/                  # 网络模块(库)
│   └── CMakeLists.txt
└── RtmpServer/             # RTMP服务器模块(可执行程序)
    └── CMakeLists.txt
  1. 整体结构:主 CMakeLists.txt 位于项目根目录,负责定义全局设置并引入子模块
cmake_minimum_required(VERSION 3.16.0)
project(MyProject)
set(CMAKE_CXX_STANDARD 11)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#将MyNet目录作为子项目处理,该目录下必须存在独立的CMakeLists.txt

add_subdirectory(MyNet)
#同理,处理RtmpServer子项目,该目录也需包含CMakeLists.txt。
add_subdirectory(RtmpServer)
  1. 子目录的 CMakeLists.txt 示例
    MyNet/CMakeLists.txt
 # 收集网络模块的源文件
aux_source_directory(. NET_SRC)

# 创建静态库
add_library(MyNet STATIC ${NET_SRC})

# 设置头文件目录,供其他模块引用
target_include_directories(MyNet PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

RtmpServer/CMakeLists.txt

# 收集服务器模块的源文件
aux_source_directory(. SERVER_SRC)

# 添加可执行文件,链接MyNet库
add_executable(RtmpServer ${SERVER_SRC})
target_link_libraries(RtmpServer PRIVATE MyNet)

项目优势

  1. 模块化管理:
    • 每个模块(如网络、服务器)有独立的 CMakeLists.txt,便于团队分工。
    • 修改某个模块的编译规则不影响其他模块。
  2. 依赖清晰:
    • 通过target_link_libraries明确模块间的依赖关系(如 RtmpServer 依赖 MyNet)。
    • 避免全局变量污染,提高可维护性。
  3. 输出统一:
    • 所有可执行文件和库都输出到bin目录,便于部署和测试。

执行流程

  1. 在项目根目录创建build目录并进入:

    mkdir build && cd build
    
  2. 运行 CMake 生成构建文件:

    cmake ..
    
  3. 编译项目

    cmake --build .
    
  4. 最终输出结构:

    MyProject/
    ├── build/              # 构建目录
    ├── bin/                # 输出目录
    │   ├── RtmpServer      # 可执行文件
    │   └── libMyNet.a      # 静态库
    ├── CMakeLists.txt      # 主CMake文件
    ├── MyNet/              # 网络模块
    │   ├── CMakeLists.txt
    │   └── *.cpp/*.h
    └── RtmpServer/         # 服务器模块
        ├── CMakeLists.txt
        └── *.cpp/*.h
    

多目录、多 CMakeLists.txt分层级管理(每个模块都生成独立的可执行文件)

每个模块都生成独立的可执行文件;

  • 每个模块的 CMakeLists.txt 中继续使用add_executable,因为要每个模块都生成独立的可执行文件
  • 确保每个模块有main函数:
      1. 每个模块的源文件中必须包含main()函数作为入口点(例如MyNet/main.cpp和RtmpServer/main.cpp)。
  • 适用场景:每个模块是独立程序,不需要相互依赖。

项目结构

MyProject/
├── CMakeLists.txt          # 根目录配置
├── MyNet/                  # 网络模块(生成可执行文件)
│   ├── CMakeLists.txt
│   ├── Socket.cpp
│   ├── Socket.h
│   └── main.cpp            # MyNet模块的入口文件
└── RtmpServer/             # RTMP服务器模块(生成可执行文件)
    ├── CMakeLists.txt
    ├── RtmpServer.cpp
    ├── RtmpServer.h
    └── main.cpp            # RtmpServer模块的入口文件

根目录 CMakeLists.txt

cmake_minimum_required(VERSION 3.16.0)
project(MyProject)
set(CMAKE_CXX_STANDARD 11)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(MyNet)
add_subdirectory(RtmpServer)

MyNet/CMakeLists.txt

project(MyNet)

# 添加头文件搜索路径
include_directories(${CMAKE_SOURCE_DIR}/MyNet)

# 收集源文件(不包含main.cpp会导致链接错误,需包含所有必要文件)
aux_source_directory(. NET_SRC)

# 创建可执行文件
add_executable(MyNet ${NET_SRC})

RtmpServer/CMakeLists.txt

project(RtmpServer)

# 添加头文件搜索路径
include_directories(${CMAKE_SOURCE_DIR}/RtmpServer)

# 收集源文件
aux_source_directory(. RTMP_SRC)

# 创建可执行文件
add_executable(RtmpServer ${RTMP_SRC})

ns3中 CMake结构

ns使用CMake的目的

  1. 模块化管理
    将核心功能拆分为独立模块(如 network、wifi、internet),每个模块有独立的 CMakeLists.txt,便于维护和选择性编译。

  2. 灵活配置编译选项
    通过 option() 定义大量编译开关(如是否启用日志、示例、测试等),用户可根据需求裁剪编译内容,提升构建效率。

  3. 依赖自动检测
    内置对第三方库(如 GSL、GTK3、Eigen)的检测逻辑,自动启用或禁用相关功能,减少手动配置成本。

ns-3.36/
├── CMakeLists.txt           # 根配置
├── src/
│   ├── CMakeLists.txt       # 包含所有模块
│   ├── core/
│   │   ├── CMakeLists.txt   # 编译core模块
│   │   ├── model/           # 源代码
│   │   └── test/            # 测试代码
│   └── network/             # 依赖core的模块
│       └── CMakeLists.txt

根目录下CMakeList.txt

  1. 项目根目录的主配置文件CMakeList.txt,定义全局设置和子目录。
#-----------------------基础配置----------------

#最低 CMake 版本:
cmake_minimum_required(VERSION 3.12)
# 项目信息与编译器检查
project(NS3 CXX C)
set(GNU_MinVersion 10.1.0)

#-----------------------编译器版本限制----------------
set(AppleClang_MinVersion 13.1.6)
set(Clang_MinVersion 11.0.0)
set(GNU_MinVersion 10.1.0)

#-----------------------编译选项控制----------------
# 定义了众多通用的编译选项,如 NS3_ASSERT、NS3_DES_METRICS 等,每个选项都通过 option 命令来声明,用户可以在构建时根据需求开启或关闭这些功能,例如:

# 语法:option(<选项的名称> "<对选项的描述>" [选项的默认值])
# 在 CMake 代码中使用选项:
	# if(NS3_ASSERT)
	  add_definitions(-DNS3_ASSERT_ENABLED)  # 定义预处理宏
	endif()
option(NS3_ASSERT "Enable assert on failure" OFF)   # 启用断言
option(NS3_LOG "Enable logging to be built" OFF)      # 启用日志
option(NS3_GSL "Build with GSL support" ON)           # 启用 GSL 库支持
option(NS3_PYTHON_BINDINGS "Build ns-3 python bindings" OFF)  # 禁用 Python 绑定
option(NS3_CCACHE "Use Ccache to speed up recompilation" ON)  # 启用缓存加速编译
option(NS3_FAST_LINKERS "Use Mold or LLD to speed up linking" ON)  # 启用快速链接器

#-----------------------模块管理----------------
# 扫描项目源目录下 src 和 contrib 子目录中的模块,将找到的模块名称存储到对应的变量libs_to_build中

# 语法:libs_to_build:变量 ${CMAKE_CURRENT_SOURCE_DIR}/src:指定了要扫描的目录路径
# ${CMAKE_CURRENT_SOURCE_DIR}:CMake 内置变量,表示当前处理的 CMakeLists.txt 文件所在的目录

subdirlist(libs_to_build ${CMAKE_CURRENT_SOURCE_DIR}/src)
subdirlist(contrib_libs_to_build ${CMAKE_CURRENT_SOURCE_DIR}/contrib)

filter_enabled_and_disabled_modules(...)#根据用户输入(如 NS3_ENABLED_MODULES)或配置文件(.ns3rc)过滤要编译的模块,避免编译冗余代码。

#-----------------------构建流程控制----------------

# 添加子目录,用于将指定目录中的 CMake 项目(通常是子项目)纳入当前构建过程。这个命令允许你将一个大型项目分解为多个独立的模块或子目录,每个子目录有自己的 CMakeLists.txt 文件,从而实现模块化的构建管理

add_subdirectory(src)       # 编译核心模块
add_subdirectory(examples)  # 编译示例代码
add_subdirectory(scratch)   # 编译用户自定义脚本

src目录下CMakeList.txt

主要负责处理 src 目录下的各个模块相关构建事宜,它既要遵循根目录 CMakeLists.txt 中设定的全局配置,又要为每个具体模块创建合适的构建目标、处理模块间依赖关系以及根据不同的编译选项(如静态库、单库构建等)进行相应的处理,以此来构建出完整的 src 目录下的项目内容,并与其他部分(如外部库、测试等)协同完成整个项目的构建工作

# 将 libs_to_build 变量的值赋给 libs 变量,根目录定义的libs_to_build
set(libs "${libs_to_build}")

#-----------------------添加具体子模块目录----------------
# Process subdirectories
# 遍历 libs 列表中的每个模块名称(libname),对于每个模块,检查其在 `src` 目录下对应的子目录中是否存在 CMakeLists.txt 文件。如果存在就add_subdirectory
foreach(libname ${libs})
  if(EXISTS ${PROJECT_SOURCE_DIR}/src/${libname}/CMakeLists.txt)
    message(STATUS "Processing src/${libname}")
    add_subdirectory(${libname})
  else()
    message(${HIGHLIGHTED_STATUS}
            "Skipping src/${libname}: it does not contain a CMakeLists.txt file"
    )
  endif()
endforeach()
#-----------------------禁用不在libs列表里的模块----------------
# Scan for disabled modules that define settings prefixed with NS3_ or ENABLE_
# so that we can initialize them as turned off (setting as temporary variables
# not to affect the cached variables that are set when normally processed)
subdirlist(modules ${CMAKE_CURRENT_SOURCE_DIR})# src 下所有子目录存储到 modules 
foreach(libname ${modules})
  if(NOT (${libname} IN_LIST libs))
    # Skip module directories without CMakeLists.txt files
    if(NOT (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${libname}/CMakeLists.txt))
      continue()
    endif()

    file(READ ${CMAKE_CURRENT_SOURCE_DIR}/${libname}/CMakeLists.txt
         lib_cmakelists_contents
    )
    string(REGEX MATCHALL "(NS3_[a-zA-Z0-9_]*)|(ENABLE_[a-zA-Z0-9_]*)" flags
                 "${lib_cmakelists_contents}"
    )
    foreach(flag ${flags})
      # Skip reason flags
      if(${flag} MATCHES "REASON")
        continue()
      endif()
      # Skip flags already defined
      if(DEFINED ${flag})
        continue()
      endif()
      # Set flag to off
      set(${flag} OFF PARENT_SCOPE)
    endforeach()
  endif()
endforeach()

#-----------------------依据全局配置中关于是否构建静态库、单库以及预编译头文件等相关选项,结合项目中定义的模块列表(ns3-libs 和 ns3-contrib-libs)进行操作--------------

#-----------------------静态库构建----------------
if(${NS3_STATIC})#  NS3_STATIC 选项被启用时
  add_library(# 创建一个名为 ${lib-ns3-static} 的静态库目标
    ${lib-ns3-static} STATIC ${PROJECT_SOURCE_DIR}/build-support/empty.cc
                             "${lib-ns3-static-objs}"
  )
  # 此处省略中间部分代码展示
  target_link_libraries( # 将静态库与相关的静态链接标志、外部静态库等进行链接
    ${lib-ns3-static} ${STATIC_LINK_FLAGS} ${LIB_AS_NEEDED_PRE_STATIC}
    ${ns3-external-static-libs} ${LIB_AS_NEEDED_POST_STATIC}
  )
  if(${NS3_CLANG_TIMETRACE})
    add_dependencies(timeTraceReport ${lib-ns3-static})
  endif()
endif()

模块目录(如src/internet/)的CMakeLists.txt

这个 src/internet 目录下的 CMakeLists.txt 文件主要负责构建 ns3 项目中 internet 模块相关的库、管理其头文件、组织测试源文件,并通过 build_lib 函数(自定义函数,功能基于项目设定)来整合这些元素进行模块构建,同时与上层 CMakeLists.txt 文件配合,依据全局配置和 src 目录的构建管理逻辑来完成自身模块的构建任务,融入整个项目构建体系。

#-----------------源文件、头文件和私有头文件定义---------------
# 通过 set 命令分别定义了三个变量:source_files、header_files 和 private_header_files,用于收集 internet 模块相关的源文件、对外公开的头文件以及特定平台(WIN32)下的私有头文件路径列表。这些文件列表明确了构建 internet 模块库时具体需要哪些代码文件
# 文件列表变量会作为关键参数传递给后续的 build_lib 函数,用于创建 internet 模块库以及相关构建操作
set(source_files
	helper/internet-stack-helper.cc
    helper/internet-trace-helper.cc
    helper/ipv4-address-helper.cc
    # 此处省略大量源文件路径列举
)

set(header_files
	helper/internet-stack-helper.h
    helper/internet-trace-helper.h
    helper/ipv4-address-helper.h
    # 此处省略大量头文件路径列举
)

set(private_header_files)
if(WIN32)
  set(private_header_files
      model/win32-internet.h
  )
endif()

#-----------------测试源文件定义---------------
set(test_sources
	test/global-route-manager-impl-test-suite.cc
    test/icmp-test.cc
    # 此处省略大量测试源文件路径列举
)

#-----------------build_lib 函数调用构建 `internet` 模块---------------
# build_lib函数会在内部执行创建库目标(如静态库或共享库)、设置头文件包含路径、处理依赖库链接以及组织测试构建等一系列操作,以完成 internet 模块的完整构建流程。
build_lib(
  LIBNAME internet # 模块名称(internet)
  # 前面定义好的源文件、头文件、私有头文件列表
  SOURCE_FILES ${source_files}
  HEADER_FILES ${header_files}
  PRIVATE_HEADER_FILES ${private_header_files}
  # 需要链接的其他库
  LIBRARIES_TO_LINK ${libbridge}
                    ${libtraffic-control}
  TEST_SOURCES ${test_sources}
)


NS-3 CMake 使用方法

1. 配置阶段(./ns3 configure)

通过命令行参数或编辑 ns3 脚本设置编译选项,例如:

# 启用示例和测试,禁用 Python 绑定
./ns3 configure --enable-examples --enable-tests --disable-python-bindings

# 手动指定编译器(解决版本不足问题)
CC=gcc-10 CXX=g++-10 ./ns3 configure

2. 编译阶段(./ns3 build)

执行编译命令,生成库文件和可执行文件:

./ns3 build  # 等价于在 build/ 目录执行 make -j$(nproc)

更多推荐