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)
#配置导出生成clangd需要编译命令的文件
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
这个文件是这样子的
[
{
"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"
},
......
]
其中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
)
这是作者一个内核模块下的Cmake文件,在主cmake文件中配置包含子目录:
`add_subdirectory(kernels)`
当我们使用cmake来进行编译的时候,我们会发现,在kernels这个目录下的源文件,并没有在compile-command.json
这个文件中生成,这是为什么呢?其实是因为内核模块的编译,我们需要借助make module命令,cmake中也是对这些命令进行一些拼装而已,这些源文件并没有交给cmake来编译管理。当让clangd也就生成不了这些文件的配置项了。要解决这个问题其实也很简单,只不过不是很优雅,只需要将这些内核模块的源文件交给cmake来编译就好了
add_library(noting SHARED ${KSOURCE_FILES})
之所以这么说,是因为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
这里配置比较繁杂,这里不做过多阐述,直接贴上作者的配置文件
--- 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
另外还有一个小技巧也是比较常用的,比如我们有些地方的文字不想让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
ok,到这里基本的环境配置就结束了!愉快的开发吧!