C 语言环境:VSCode+CMake
高效稳定的编译环境是构建好项目的基础。对于构建 C 语言项目,本文介绍了 CMake 、构建工具、编译工具的关系,并分析了 VSCode+CMake 在跨平台的多个使用场景。本文还介绍了搭建 C 语言环境的示例: ① Windows 本地编译(VSCode+CMake+MSYS2);② Linux 远程编译(VSCode:remote+CMake+Make+GCC);③ Windows 交叉编译(VSCode+CMake+交叉编译链)。
一、原理

从右向左,编译出二进制文件通过以下几个模块:
① 编译工具:常用编译链有 gcc、clang-llvm、mingw-w64 等,通常由由汇编工具、核心编译器和相关目标库(如 Glibc、musl 等)。编译工具链与体系结构、操作系统密切相关,成为搭建构建环境的关键问题。
② 构建工具:如 Ninja、Make 等,它们可以基于脚本(如 .ninja、makefile)指定编译规则,然后调用编译器实现自动化编译,而不需要使用命令行直接调用和给与参数。然而,在不同平台上使用构建工具、维护构建脚本依然具有不少工作量。
③ Cmake 工具:Cmake 体现了一种目标导向的设计,通过指定编译所依赖的元素,Cmake 可以自动生成构建项目所需的脚本(如 Makefile),然后调用相应的构建工具完成构建;另一方面,Cmake 具有更好的跨平台性,Cmake 可以集成下游一系列工具(“再套一层” 思想),完成各类平台工具链的整合。
二、VSCode + CMake 跨平台使用场景

① 号链中,将 MSYS2 环境集成到 VSCode 的 CMake 配置里,在 Windows 系统本机自己编译 EXE 程序。
② 号链中,对于具备编译环境的 Linux 系统,例如虚拟机或者服务器,在 Windows 端通过 VSCode ssh 远程连接,并调用其上的环境完成项目构建和编译。
③ 号链展现了交叉编译的情况,嵌入式开发板(例如 Rk3588)提供有 Windows 下可使用的交叉编译链(Ninja+Clang-LLVM),则可在 Windows 系统编译后烧录到开发板运行程序。
④ 号链展现了这一情况:由于 Windows 端交叉编译链不如 Linux 上容易获取,在 Linux 虚拟机中部署嵌入式设备的交叉编译链(如 arm-linux-gnueabihf-gcc),再将编译出的二进制程序烧录到嵌入式开发板上。
三、示例:VSCode+CMake+MSYS2(Windows 本地编译)
MSYS2 是 Windows上的 C 编译工具,相比 visual studio 更轻量,并且拥有类似 Linux 的工具和库。
首先,在 C 盘创建 C:\C_ENV,下载 MSYS2 安装到目录中,下载地址:
https://mirror.tuna.tsinghua.edu.cn/help/msys2/
使用 ucrt 环境(运行 msys64/ucrt64.exe 终端)安装编译工具链:
pacman -S mingw-w64-ucrt-x86_64-toolchain
此时,可以直接使用 msys64\ucrt64\bin 中的 gcc.exe 了。下面将相关工具集成到 VSCode+CMake,我们采取手动配置 CMake 工具链的方式,主要需要设置:① Cmake 路径;② 编译工具路径; ③ 构建脚本生成器类型。
① 安装 VSCode 及其 Cmake Tools 插件;安装 Windows Cmake 程序,如果未添加环境变量,则需要在 VSCode 设置中指定 Cmake Path,Cmake 程序下载地址:https://cmake.org/download/
② 在 C:\C_ENV 下新建 Toolchains 文件夹,创建文件 toolchain_mingw-w64-ucrt-x86_64.cmake:
# toolchain_mingw-w64-ucrt-x86_64.cmake
# Specify the Comiler Path
file(TO_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/../msys64/ucrt64" TOOLCHAIN_DIR)
message(STATUS "Toolchain_Dir:${TOOLCHAIN_DIR}")
# Specify the Cross Tools Path
file(TO_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/../msys64/ucrt64" CROSS_TOOLS_DIR)
message(STATUS "Cross_Tools_Dir:${CROSS_TOOLS_DIR}")
# Clean and Configure Path Variable
unset(ENV{PATH})
set(ENV{PATH} "${CROSS_TOOLS_DIR}/bin")
message(PATH: "$ENV{PATH}")
# Generator
set(CMAKE_GENERATOR "MinGW Makefiles" CACHE INTERNAL "" FORCE)
# Specify Compiler
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/gcc.exe)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/g++.exe)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_DIR}/bin/as.exe)
set(CMAKE_RC_COMPILER ${TOOLCHAIN_DIR}/bin/windres.exe)
set(CMAKE_ADDR2LINE ${TOOLCHAIN_DIR}/bin/addr2line.exe)
set(CMAKE_AR ${TOOLCHAIN_DIR}/bin/gcc-ar.exe)
set(CMAKE_LINKER ${TOOLCHAIN_DIR}/bin/ld.bfd.exe)
set(CMAKE_NM ${TOOLCHAIN_DIR}/bin/gcc-nm.exe)
set(CMAKE_OBJCOBY ${TOOLCHAIN_DIR}/bin/objcopy.exe)
set(CMAKE_OBJDUMP ${TOOLCHAIN_DIR}/bin/objdump.exe)
set(CMAKE_RANLIB ${TOOLCHAIN_DIR}/bin/gcc-ranlib.exe)
set(CMAKE_READELF ${TOOLCHAIN_DIR}/bin/readelf.exe)
set(CMAKE_STRIP ${TOOLCHAIN_DIR}/bin/strip.exe)
set(CMAKE_SIZE ${TOOLCHAIN_DIR}/bin/size.exe)
# Configure Find Path
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_DIR})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
③ 创建 C:\Users{User_Name}\AppData\Local\CMakeTools\cmake-tools-kits.json 作为 Cmake 的全局配置,并将上一步配置的 toolchain_mingw-w64-ucrt-x86_64 写入进去:
[
{
"name": "mingw-w64-x86_64(ucrt)",
"toolchainFile": "C:/C_ENV/Toolchains/toolchain_mingw-w64-ucrt-x86_64.cmake",
"isTrusted": true,
"environmentVariables": {
"CMT_MINGW_PATH": "C:/C_ENV/msys64/ucrt64/bin"
},
"preferredGenerator": {
"name": "MinGW Makefiles"
}
}
]
至此,所有环境配置完毕。下面创建一个简单的 Hello 项目,在目录下分别创建 main.c 和 CMakeLists.txt:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
getchar();
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(CmakeHello)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
# 指定源文件
add_executable(${PROJECT_NAME} main.c)
使用 VSCode 打开项目,选择我们手动配置的工具链 mingw-w64-x86_64(ucrt),CMake 配置正常,按 F7 构建和编译项目,成功运行。
注①:可能需要将 msys64\ucrt64\bin 添加到环境变量,编译的程序才能正常运行。
注②:由 MSYS2 编译的程序是不能链接到 visual studio 编译出的动态库上的;此外,如果想要编译可移植的程序,项目的 CMakeList.txt 可能要针对目标环境进行调整,例如 Windows 系统和 Linux 系统中程序需要链接的动态库可能不同。
四、示例:VSCode+CMake 连接虚拟机(Linux 远程编译)
对于具有编译环境,并且编译后直接运行程序的 Linux 虚拟机/服务器,可以采用 VSCode ssh 方式远程连接项目,并且调用虚拟机/服务器上面的编译工具完成项目的构建。
首先,需要将 VSCode 连接到 Linux 虚拟机,我用的是 Virtualbox-ubuntu 20.04
① VSCode 安装插件:Remote Development;并且开启 Linux ssh 服务:
sudo apt-get install openssh-server
sudo ps -e | grep ssh # 检查 sshd 是否启动
② 使用 ifconfig 查看虚拟机的网络地址,保证主机能够 ping 虚拟机。注:Virtualbox 虚拟机的网卡模式默认为NAT 模式可以让虚机上网,但是主机无法 ping,需要将网卡设置为桥接模式(虚机也可以上网),相关说明:https://blog.csdn.net/y1534414425/article/details/122354784
③ Ctrl+shift+p,输入 connect current window to host,通过 ssh 连接到虚拟机的 Linux 系统。
连接到虚拟机后,下面配置 CMake C 语言编译环境。① 与 Windows 上类似,首先需要保证虚拟机已经安装相关编译和构建工具(cmake、make、gcc);② 在远程的 VSCode 中安装 CMake Tools 插件; ③ 配置 CMake 所需的工具链,即 cmake-tools-kits.json 和 toolchain.cmake,注意配置文件应放在虚拟机里。
打开 /home/{user-name}/.local/share/CMakeTools/cmake-tools-kits.json,发现已自动添加本地编译环境:
[
{
"name": "GCC 9.4.0 x86_64-linux-gnu",
"compilers": {
"C": "/usr/bin/gcc",
"CXX": "/usr/bin/g++"
},
"isTrusted": true
}
]
由于本地环境相对简单,无需再配置 toolchain.cmake;下面可以使用 VSCode 远程打开 Hello 项目,选择虚拟机上配置好的 GCC 9.4.0 x86_64-linux-gnu 编译工具链,按 F7 构建和编译项目,并成功运行。
注:CMake Path 要设置正确,可以直接设置为 cmake,否则无法正确调用远程 Linux 主机安装的 cmake 工具。
五、示例:VSCode+CMake 交叉编译
以虚拟机 ubuntu20.04 为例,将其看作一块 x86_64 架构,linux 操作系统的开发板,在 Windows 端直接交叉编译,并传输到目标系统中运行。对于交叉编译,最关键的是找到匹配 host & target 的交叉编译链,这里使用 x86_64-unknown-linux-gnu-7.2.0,来自:https://gitee.com/logbug/win2linux
运行该 gcc 程序,发现它依赖于 cygwin 环境(需要 cygwin1.dll);安装 cygwin 后,编译器启动正常。
下面在 Toolchains 文件夹下新建 toolchain_gcc-7.2.0-linux-x86_64:注意:① 交叉编译链中相关工具的路径正确;② 要引入 cygwin 环境的路径;③ 构建脚本生成器使用 Unix Makefiles;④ 指定交叉编译目标机属性;⑤ 配置交叉编译链 Sysroot Path 正确。
# toolchain_gcc-7.2.0-linux-x86_64.cmake
# Specify the Comiler Path
file(TO_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/../x86_64-unknown-linux-gnu-7.2.0" TOOLCHAIN_DIR)
message(STATUS "Toolchain_Dir:${TOOLCHAIN_DIR}")
# Specify the Cross Tools Path
file(TO_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/../Cygwin64" CROSS_TOOLS_DIR)
message(STATUS "Cross_Tools_Dir:${CROSS_TOOLS_DIR}")
# Clean and Configure Path Variable
unset(ENV{PATH})
set(ENV{PATH} "${CROSS_TOOLS_DIR}/bin;${TOOLCHAIN_DIR}/bin")
message(PATH: "$ENV{PATH}")
# Generator
set(CMAKE_GENERATOR "Unix Makefiles" CACHE INTERNAL "" FORCE)
# Configure Cross Compiler Target Machine
set(CMAKE_SYSTEM_NAME "Linux")
set(CMAKE_SYSTEM_PROCESSOR "x64")
set(CMAKE_CROSSCOMPILING "TRUE")
# Configure Sysroot Path
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/x86_64-unknown-linux-gnu/sysroot)
# Specify Compiler
set(TOOLCHAIN_PREFIX "x86_64-unknown-linux-gnu-")
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}gcc.exe)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}g++.exe)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}as.exe)
set(CMAKE_ADDR2LINE ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}addr2line.exe)
set(CMAKE_AR ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}gcc-ar.exe)
set(CMAKE_LINKER ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}ld.exe)
set(CMAKE_NM ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}gcc-nm.exe)
set(CMAKE_OBJCOBY ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}objcopy.exe)
set(CMAKE_OBJDUMP ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}objdump.exe)
set(CMAKE_RANLIB ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}gcc-ranlib.exe)
set(CMAKE_READELF ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}readelf.exe)
set(CMAKE_STRIP ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}strip.exe)
set(CMAKE_SIZE ${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_PREFIX}size.exe)
# Configure Find Path
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_DIR})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
在 cmake-tools-kits.json 添加 toolchain_gcc-7.2.0-linux-x86_64:
{
"name": "gcc-linux-x86_64.cmake(7.2.0)",
"toolchainFile": "C:/C_ENV/Toolchains/toolchain_gcc-7.2.0-linux-x86_64.cmake",
"isTrusted": true,
"environmentVariables": {
"CMT_MINGW_PATH": "C:/C_ENV/Cygwin64/bin"
},
"preferredGenerator": {
"name": "Unix Makefiles"
}
}
在 Windows 端打开 Hello 项目,选择 toolchain_gcc-7.2.0-linux-x86_64 工具链,编译出 linux-x86 上运行的二进制程序,传输至虚拟机 ubuntu20.04 执行成功。
注①:对于交叉编译而言,编译器既需要在宿主机运行,也与目标机的操作系统、体系结构紧密关联。所以,要找到匹配 host & target 的编译工具。例如:gcc 版本与 ubuntu 版本相关,过高版本会导致编译的程序无法执行;面向嵌入式开发板编译时,要注意开发板体系结构,使用大/小端,是否有 hard float 运算等。
注②:可以使用 SCP 在多个设备之间传输文件;可以使用 SecureCRT 整合多个设备的终端和记录日志。