vscode中配置基于cmake的clangd开发环境

7/8/2023

vscode自带的C/C++工具实在是有些不友好,经常开发的时候找不到头文件,导致代码跳转和补全功能都有问题,于是乎决定抛弃Microsoft C/C++转向Clangd,配合clang-format来格式化代码。两者都是基于LLVM开发的插件,接下来让我看如何配置吧。

至于如何安装这些插件这里就不多做赘述,相信使用过vscode的同学都已经熟悉了,接下来我们第一步首先来写cmake文件来开启clangd的功能

# Clangd-Cmake

使用cmake来生成clangd需要的文件compile-command.json是很方便的,在最新的cmake版本中,我们可以直接打开一个配置项就ok了

cmake_minimum_required(VERSION 3.16)

project(unix-learning)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
#配置导出生成clangd需要编译命令的文件
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1
2
3
4
5
6

这个文件是这样子的

[
  {
    "directory": "/opt/unix-learn/build",
    "command": "/usr/bin/gcc  -I/usr/src/linux-headers-5.4.0-144/include -I/opt/unix-learn -g -o CMakeFiles/container_of.dir/container_of.c.o -c /opt/unix-learn/container_of.c",
    "file": "/opt/unix-learn/container_of.c"
  },
  ......
 ]
1
2
3
4
5
6
7
8

其中cmke配置的头文件路径会在command属性中看到,也就是-I/usr/src/linux-headers-5.4.0-144/include -I/opt/unix-learn这样当我们打开container_of.c这个文件的时候,就能直接点击进所有的头文件啦!

# Clangd能解决所有场景吗?

**但是这样真的可以解决我们所有C语言开发的场景吗?**其实不然,比如作者想在这个项目模块底下来写一些内核驱动程序,我们知道cmake来编译内核程序其实是很不方便的,但是整个项目却又要使用cmake来管理。我们可以来看一下如果我们使用cmake来编译内核程序,一般会如何写。

set(MODULE_NAME kexamples)

add_definitions(-D__KERNEL__ -DMODULE)

set(KSOURCE_FILES
    kernels.c
    rtnl.c
    genl.c
)

find_path(
        KERNEL_HEADERS_DIR
        include/linux/user.h
        PATHS /usr/src/kernels/${KERNEL_RELEASE}
)

if(KERNEL_HEADERS_DIR)
    message(STATUS "kernel header dir ${KERNEL_HEADERS_DIR}/include")
    include_directories(${KERNEL_HEADERS_DIR}/include)
endif(KERNEL_HEADERS_DIR)


#生成Kbuild文件
get_filename_component(KO_FILENAME_WE ${MODULE_NAME} NAME_WE)
set(KBUILD_CONTEXT "obj-m := ${KO_FILENAME_WE}.o\n${KO_FILENAME_WE}-objs = ")

foreach(SOURCE_FILE ${KSOURCE_FILES})
    string(FIND "${FILE_CONTENT}" "module_init(" KERNEL_INIT_INDEX)
    get_filename_component(FILENAME_WE ${SOURCE_FILE} NAME_WE)
    set(KBUILD_CONTEXT "${KBUILD_CONTEXT}${FILENAME_WE}.o ")
endforeach()
FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/Kbuild ${KBUILD_CONTEXT})

#忽略内核签名校验
set(KNAME ${MODULE_NAME}.ko)
set(KBUILD_CMD make -C ${KERNEL_HEADERS_DIR} modules M=${CMAKE_CURRENT_SOURCE_DIR} CONFIG_MODULE_SIG=n)

message(${KBUILD_CMD})

add_custom_command(OUTPUT ${KNAME}
    COMMAND ${KBUILD_CMD}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    DEPENDS ${SOURCE_FILE}
    VERBATIM
)
add_custom_target(${MODULE_NAME} ALL DEPENDS ${KNAME})

add_custom_command(
    TARGET ${MODULE_NAME}
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${KNAME} ${EXECUTABLE_OUTPUT_PATH}
    COMMAND make -C ${KERNEL_HEADERS_DIR} M=${CMAKE_CURRENT_SOURCE_DIR} clean
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

这是作者一个内核模块下的Cmake文件,在主cmake文件中配置包含子目录:

add_subdirectory(kernels)
1

当我们使用cmake来进行编译的时候,我们会发现,在kernels这个目录下的源文件,并没有在compile-command.json这个文件中生成,这是为什么呢?其实是因为内核模块的编译,我们需要借助make module命令,cmake中也是对这些命令进行一些拼装而已,这些源文件并没有交给cmake来编译管理。当让clangd也就生成不了这些文件的配置项了。要解决这个问题其实也很简单,只不过不是很优雅,只需要将这些内核模块的源文件交给cmake来编译就好了

add_library(noting SHARED ${KSOURCE_FILES})
1

之所以这么说,是因为cmake中的指令add_library并不可以将这些内核模块的源文件链接到一个动态库中,所有的内核模块源文件必须使用make来编译,所以这里会有一些错误,为了防止这个指令不影响上面的cmake指令,我们直接给他放到最后,这样就可以生成对应内核模块源文件的compile-command.json配置项了!

# Clang-format

我们禁用了Microsoft C/C++相关的插件后,代码格式化就会成为默认的了,我们再使用这个插件来配置一下代码格式化。

首先在项目根目录执行命令生成google style的配置文件并且重定向到.clang-format:

clang-format -style=google -dump-config > .clang-format
1

这里配置比较繁杂,这里不做过多阐述,直接贴上作者的配置文件

---
BasedOnStyle: Google
---
Language: Cpp
AccessModifierOffset: -4
AllowAllConstructorInitializersOnNextLine: false
AllowShortCaseLabelsOnASingleLine: true
AlwaysBreakBeforeMultilineStrings: false
BraceWrapping:
  AfterClass: true
  AfterControlStatement: Always
  AfterEnum: true
  AfterFunction: true
  AfterStruct: true
  AfterUnion: true
  AfterExternBlock: true
  BeforeCatch: true
  BeforeElse: true
BreakBeforeBraces: Custom
CommentPragmas: "^ NOLINT:"
IndentWidth: 4
PointerAlignment: Right
SortIncludes: false
SortUsingDeclarations: false
SpacesBeforeTrailingComments: 1
SpacesInContainerLiterals: false
Standard: Cpp11
TabWidth: 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

另外还有一个小技巧也是比较常用的,比如我们有些地方的文字不想让clang-format来格式化,比如一些注释啊什么的,如果我们对对齐的注释被格式化了就会很难看,这个时候我们可以用// clang-format off/on来禁用/开启 clang-format格式化功能,例如:

// clang-format off
/*
 * Generic Netlink uses the standard Netlink subsystem as a transport layer
 * which means that the foundation of the Generic Netlink message is the
 * standard Netlink message format - the only difference is the inclusion of a
 * Generic Netlink message header. The format of the message is defined as shown
 * below:
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                Netlink message header (nlmsghdr)              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Generic Netlink message header (genlmsghdr)         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------
 |             Optional user specific message header             |      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+      |->nlattrs
 |           Optional Generic Netlink message payload            |      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------
*/
// clang-format on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 指定C/C++编译器版本

众所周知vscode知识一个编辑器、他并不内置编译器,我们使用clangd插件,其实他内置了clang这个编译器,如果我们想设置vscode的风格为C语言或者C++语言,应该怎么配置?博主这里被卡了好久,查找了许多资料。

首先在macos上,默认使用的C++的 -std=c++03选项,C语言是C99,我们目前开发C或者C++程序,都是使用很高的版本,比如我平时开发使用C11C++也是11,另外C++的版本有时甚至已经用到了C++20。所以这里的默认配置显然不符合了。这里直接贴出来配置的位置:

image-20231227164354300

这里的配置可以参考https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-x.

另外如果仅仅配置了这里,很多人都没有什么问题了,但是博主的环境很会给我找事干,我这里配置好之后,仍然不能使用C++11的语法,我也不卖关子了,我这里是因为还需要指定clang的cxx选项。

  1. shit+command+p打开User Setting
  2. 增加配置"clang.cxxflags": ["-std=c++11"]
  3. shit+command+p restart language server
  4. 可以愉快的编码了
嘉宾
路文飞