尝试把clang-tidy集成到AWTK项目
前言
项目经过一段时间的耕耘终于进入了团队开发阶段,期间出现了很多问题,其中一个就是开会讨论团队的代码风格规范,目前项目代码风格比较混乱,有的模块是驼峰,有的模块是匈牙利,后面经过讨论,决定采用匈牙利,和awtk库的api风格一致。
讨论完之后就是改代码了,有十几个模块几百个函数要改,一个个人工去改显然费时费力,改的时候就在想这种东西有没有自动化的做法了?
于是下班开始探索一番,首先是尝试用AI写一个批量扫描文件,用正则匹配不符合规则的python脚本,结果费时费力,效果明显不好。
不对,c/c++发展了十几年,这类问题难道没有现成的方案?
后面搜了点原理,正则的思路对于这类问题显然不对,没法识别函数和变量,那只能用抽象语法树了。对象是c/c++,应该是一个类似c/c++编译器的静态分析工具。
最后找到了clang-tidy。
尝试集成1.0
首先下载clang-tidy:
pip install clang-tidy
假设示例项目src结构如下:
src
├── application.c
├── common
│ ├── navigator.c
│ ├── navigator.h
│ └── temperature.h
├── main.c
├── modules
│ ├── libframebuffer.c
│ └── libframebuffer.h
├── pages
│ └── home_page.c
└── SConscript
要修改的文件例:libframebuffer,里面api是驼峰格式,需要自动改成匈牙利格式,home_page有引用。
libframebuffer.h
#ifndef LIBFRAMEBUFFER_H
#define LIBFRAMEBUFFER_H
typedef struct {int width;int height;int bpp;void* buffer;
} LibFrameBuffer;LibFrameBuffer* LibFrameBufferInit();void LibFrameBufferFree(LibFrameBuffer* fb);#endif
libframebuffer.c
#include "libframebuffer.h"
#include <stdlib.h>LibFrameBuffer* LibFrameBufferInit()
{return (LibFrameBuffer*)malloc(sizeof(LibFrameBuffer));
}void LibFrameBufferFree(LibFrameBuffer* fb)
{free(fb);
}
home_page.c
#include "awtk.h"
#include "libframebuffer.h"/*** 初始化窗口的子控件*/
static ret_t visit_init_child(void* ctx, const void* iter) {widget_t* win = WIDGET(ctx);widget_t* widget = WIDGET(iter);const char* name = widget->name;// 初始化指定名称的控件(设置属性或注册事件),请保证控件名称在窗口上唯一if (name != NULL && *name != '\0') {}return RET_OK;
}/*** 初始化窗口*/
ret_t home_page_init(widget_t* win, void* ctx) {(void)ctx;return_value_if_fail(win != NULL, RET_BAD_PARAMS);widget_foreach(win, visit_init_child, win);LibFrameBufferInit();return RET_OK;
}
在项目根目录下设置好.clang-tidy,这个是clang-tidy的配置文件, clang-tidy有很多的check项,和代码命名风格相关的是readability-identifier-naming,这个checker下面有非常多类型的拼写设置,我这里设置了类, 变量,函数,宏四个类型的拼写项,其中前三个的lower_case对应的就是匈牙利小写写法,最后一个UPPER_CASE是全大写写法。
readability-identifier-naming的具体设置可见:https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html
Checks: >readability-identifier-naming
CheckOptions:- key: readability-identifier-naming.ClassCasevalue: lower_case- key: readability-identifier-naming.VariableCasevalue: lower_case- key: readability-identifier-naming.FunctionCasevalue: lower_case- key: readability-identifier-naming.MacroDefinitionCasevalue: UPPER_CASE
clang-tidy本身相当于半个编译器,会对翻译单元的头文件进行处理,而不是像脚本那样单纯进行文本解析,所以在给clang-tidy一个找不到头文件的源文件时会导致报错:
zhangdalin@huwyi-ubuntu:~/AWStudioProjects/awtk_clang_tidy_test$ clang-tidy src/pages/home_page.
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "src/pages/home_page."
No compilation database found in /home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages or any parent directory
fixed-compilation-database: Error while opening fixed database: No such file or directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
Error while processing /home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page..
error: no input files [clang-diagnostic-error]
error: no such file or directory: '/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.' [clang-diagnostic-error]
error: unable to handle compilation, expected exactly one compiler job in '' [clang-diagnostic-error]
Found compiler error(s).
因此必须找到一个方法让clang-tidy能够读取到项目所有的源文件和头文件。
使用compiler_command.json
compiler_command.json上面记录了项目每个编译的编译命令,输入源文件和输出obj,类似于给工具一个符号表,clang-tidy通过这张符号表进行解析。
在CMake可以设置set(CMAKE_EXPORT_COMPILE_COMMANDS ON)生成compiler_command.json给clang-tidy,我于是去看scons有没有对应设置,还好有,见:https://scons.org/doc/latest/HTML/scons-user/ch27.html
在SConstruct和SConscript分别添加env.Tool('compilation_db')和env.CompilationDatabase()[0], scons就能在BIN_DIR得到compiler_command.json,我试过在SConscript把两个指令一起加进去,不知道为什么在windows会报找不到AttributeError: 'SConsEnvironment' object has no attribute '__COMPILATIONDB_Entry':的错误,Linux上就正常。我这里的情形是跨平台,windows和linux都需要考虑使用。
SConstruct
+env = helper.call(DefaultEnvironment)
+env.Tool('compilation_db')
SConscript
sources = Glob('**/*.c') + Glob('*.c')
+compile_database = env.CompilationDatabase()[0]
scons编译,在BIN_DIR得到compiler_command.json,验证通过。
接下来写clang-tidy调用逻辑,scons本身是python脚本,直接封装成函数就好了,难点在于如何让scons执行时,如果需要将封装函数也一并调用,对此,scons提供了Options选项, 可以自定义选项来控制调用逻辑,见:https://scons.org/doc/latest/HTML/scons-user/ch10.html
这里就提供一个--clang-tidy选项给scons,修改后的SConscript:
import os
import subprocess
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")
compile_database = env.CompilationDatabase()[0]
program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db} -header-filter=.* {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0print(f"clang_tidy option = {GetOption('clang_tidy')}")if GetOption('clang_tidy') == True:for source in sources:run_clang_tidy('',[source, compile_database], env)
执行,会看到clang-tidy把三方库的头文件也加进了检测,原因是设置用了-header-filter=.*来将项目头文件加入检测,不加这个选项是只能检测c文件的,而且函数只能检测到没有加入头文件声明的函数。
/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:569:9: warning: invalid case style for macro definition 'SDL_malloc' [readability-identifier-naming]
#define SDL_malloc malloc^~~~~~~~~~SDL_MALLOC
/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:570:9: warning: invalid case style for macro definition 'SDL_calloc' [readability-identifier-naming]
#define SDL_calloc calloc^~~~~~~~~~SDL_CALLOC
/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:571:9: warning: invalid case style for macro definition 'SDL_realloc' [readability-identifier-naming]
#define SDL_realloc realloc^~~~~~~~~~~SDL_REALLOC
解决方法是在.clang-tidy下设置HeaderFilterRegex, 原理是clang-tidy搜索的文件是绝对路径的列表,设置HeaderFilterRegex将只检测和设置的正则匹配的文件路径。
注意原来脚本的-header-filter=.*要去掉,否则会覆盖.clang-tidy上的设置,那就失效了。
.clang-tidy
HeaderFilterRegex: 'awtk_clang_tidy_test\\src\\.*|awtk_clang_tidy_test/src/.*' #左边匹配windows下项目路径,右边匹配linux下项目路径
SConscript
def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db} {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0
自此一个基础的集成就完成了。
Running: clang-tidy -p compile_commands.json common/navigator.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running: clang-tidy -p compile_commands.json modules/libframebuffer.c
679 warnings generated.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
尝试集成2.0
看似简单,实际引用到项目还是有问题,scons不像CMake, 编译compiler_command.json的过程和编译项目的过程是绑死的,一定要编译完项目才会生成compiler_command.json,我曾经试过把env.Program只生成json,发现json能够出来,但是里面不带任何项目相关的文件编译命令,只有编译整个项目才能更新compiler_command.json。
这就是scons的不便之处了,项目是没法先静态检测再编译的,只能放到编译后再检测了。如果是CMake,大可以通过cmake -S. -B build和cmake --build build把构建compiler_command.json和编译项目的过程按先后分开。
怎么让scons在env.Program后执行自定义命令?可以用env.Command, scons编译先后顺序涉及到builder的概念,这里就不深入了,参考:
https://stackoverflow.com/questions/36273482/scons-strange-execution-order
https://scons.org/doc/1.3.0/HTML/scons-user/c3721.html
修改后的SConscript如下:
import os
import subprocess
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")
compile_database = env.CompilationDatabase()[0]
program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db} {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0print(f"clang_tidy option = {GetOption('clang_tidy')}")if GetOption('clang_tidy') == True:for source in sources:# 会在env.Program之后执行env.Command(target=f"{os.path.basename(str(source))}.clang-tidy.log",source=[source, compile_database],action=Action(run_clang_tidy, cmdstr="Running clang-tidy on ${SOURCE}"))
把编译出的compile_commands.json删了,然后scons --clang-tidy测试,正常运行:
zhangdalin@huwyi-ubuntu:~/AWStudioProjects/awtk_clang_tidy_test$ scons --clang-tidy
scons: Reading SConscript files ...
APP_SCRIPTS_ROOT:/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/scripts
AWTK_ROOT: /home/zhangdalin/AWStudioProjects/awtk
AWTK_SCRIPTS_ROOT: /home/zhangdalin/AWStudioProjects/awtk/scripts
...
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/bin exist.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/lib exist.
False
AWTK_ROOT: /home/zhangdalin/AWStudioProjects/awtk
TKC_ONLY: False
{}
/home/zhangdalin/AWStudioProjects/awtk/bin/libawtk.so==>/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/bin
clang_tidy option = True
scons: done reading SConscript files.
scons: Building targets ...
Building compilation database src/compile_commands.json
Running clang-tidy on src/application.c
Running: clang-tidy -p src/compile_commands.json src/application.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/pages/home_page.c
Running: clang-tidy -p src/compile_commands.json src/pages/home_page.c
2027 warnings generated.
src/pages/home_page.c:8:13: warning: unused variable 'win' [clang-diagnostic-unused-variable]widget_t* win = WIDGET(ctx);^
Suppressed 2026 warnings (2026 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/modules/libframebuffer.c
Running: clang-tidy -p src/compile_commands.json src/modules/libframebuffer.c
679 warnings generated.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
Suppressed 677 warnings (677 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/main.c
Running: clang-tidy -p src/compile_commands.json src/main.c
2028 warnings generated.
Suppressed 2028 warnings (2028 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/common/navigator.c
Running: clang-tidy -p src/compile_commands.json src/common/navigator.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
scons: done building targets.
能正常运行了,这时候就可以加--fix选项去自动改naming错误了,我具体测试了下,还是没法全部一次性修改的,有些引用函数的文件就没有改到,在这个例子是只改了libframebuffer.c,libframebuffer.h没有修改。好在数量少,自动化工具能解决大部分。
然而还是有问题,这个方案只能在linux下使用,在windows上会报unable to handle compilation错误:
Running clang-tidy on src\modules\libframebuffer.c
Running: clang-tidy --fix -p src\compile_commands.json src\modules\libframebuffer.c
Error while processing D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\libframebuffer.c.
error: unable to handle compilation, expected exactly one compiler job in '' [clang-diagnostic-error]
warning: @C:\Users\z5843\AppData\Local\Temp\tmp_8x5etvp.lnk: 'linker' input unused [clang-diagnostic-unused-command-line-argument]
Found compiler errors, but -fix-errors was not specified.
Fixes have NOT been applied.
原因是我awtk在windows上设置的用msvc编译,scons对于msvc生成的compiler_command.json里面调用指令实际上要通过一层快捷方式文件去中转,称之为响应文件(response files), 因为windows的命令行有长度限制,没法一次调用太长的命令。
[{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpwbrtpkke.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\common\\navigator.c","output": "src\\common\\navigator.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpmf4y8gp4.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\modules\\libframebuffer.c","output": "src\\modules\\libframebuffer.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpjye3ldpl.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\pages\\home_page.c","output": "src\\pages\\home_page.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpjtyr5i36.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\application.c","output": "src\\application.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpn639b_bf.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\main.c","output": "src\\main.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpghyoi9f_.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "D:\\AWStudioProjects\\awtk\\3rd\\gtest\\googletest\\src\\gtest-all.cc","output": "D:\\AWStudioProjects\\awtk\\3rd\\gtest\\googletest\\src\\gtest-all.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpubp7ar8o.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "tests\\main.cc","output": "tests\\main.obj"}
]
这些快捷方式第一次编译才会创建,在第二次编译时就能复用,而且如果修改了源文件(比如修改代码或者开关宏),这些快捷方式就失效了,这就是为什么我改源文件或者删除compiler_command.json都会导致报unable to handle compilation错误。我在网上找了一圈都不知道怎么关掉这个响应文件。
其实也可以设置awtk编译方式为MINGW, 这样就没有问题了,可惜我实际项目一些三方库是msvc编译的,和mingw不兼容,这个选择就废了。
没法泛化到windows,方案还要继续改进。
BTW: CMake+MSVC在windows上是不会生成compiler_command.json的,不知道scons怎么做到的生成。

尝试集成3.0
其实不使用compiler_command.json, 也可以通过给clang-tidy直接指明源文件和头文件路径的方式来绕过,这样检测就不依赖于编译了。
可以通过直接用scons的配置文件,比较方便,也可以独立脚本,不过路径需要脚本去暴力匹配,如果项目庞大,写起匹配逻辑会比较麻烦,这里选用前者。
之前的脚本其实还有个问题,run_clang_tidy执行一次只检测一个文件,一个文件就执行一次clang-tidy命令,这样使用是低效的,实际上clang-tidy可以批量集成多个文件一起去分析。
run_clang_tidy修改,拆除来放到clang_tidy_helper.py,置于scripts文件夹:
import subprocessdef run_clang_tidy(flags, source_file_str, cxxflags, include_file_str):cmd = f"clang-tidy {flags} {source_file_str} -- {cxxflags} {include_file_str}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0
修改后的SConscript如下:
import os
import subprocess
from scripts.clang_tidy_helper import run_clang_tidy
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])if GetOption('clang_tidy') == True:sources_file_str = ' '.join(os.path.normpath(str(s)) for s in sources) flags = env['CCFLAGS'] include_file_str = ' '.join([f'-I{os.path.normpath(str(i))}' for i in env['CPPPATH']])run_clang_tidy('', sources_file_str, flags, include_file_str)
运行,可以看到输出不一样了,会统计文件数,累计warning和error数,能检测出libframebuffer的naming错误,不过这次头文件和源文件的error都可以显示了,加入–fix选项运行,发现头文件,源文件,引用的文件都被修正了。
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:10:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit();^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:10:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:12:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb);^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:12:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:10:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit();^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:10:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.c:28:3: note: FIX-IT applied suggested code changesLibFrameBufferInit();^
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:12:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb);^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:12:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.c:8:13: warning: unused variable 'win' [clang-diagnostic-unused-variable]widget_t* win = WIDGET(ctx);^
clang-tidy applied 7 of 7 suggested fixes.
Suppressed 8802 warnings (8802 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
scons: done building targets.
如果不想编译项目,单独做静态检测,也比较简单:
AddOption('--no-compile',dest='no_compile',metavar='BOOL',action='store_true',default=False,help="Don't compile the code")if GetOption('no_compile') == False:program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])
从这边开始,总算就没有什么比较难受的使用问题了,而且可以集成检测工具一起编译,也可以单独检测。
还有很多细节,比如改用run-clang-tidy.py脚本提速,ci/cd集成,时间原因先写到这里,后面有时间看看如何实现。
最终的脚本:
SConscript
import os
import subprocess
from scripts.clang_tidy_helper import run_clang_tidy
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")AddOption('--no-compile',dest='no_compile',metavar='BOOL',action='store_true',default=False,help="Don't compile the code")if GetOption('no_compile') == False:program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])if GetOption('clang_tidy') == True:sources_file_str = ' '.join(os.path.normpath(str(s.get_abspath())) for s in sources) flags = env['CCFLAGS'] include_file_str = ' '.join([f'-I{os.path.normpath(str(i))}' for i in env['CPPPATH']])run_clang_tidy('--fix ', sources_file_str, flags, include_file_str)
SConstruct
import os
import scripts.app_helper as app
import clang_tidy as tidyCUSTOM_WIDGET_LIBS = [{"root" : '3rd/awtk-widget-label-rotate','shared_libs': ['label_rotate'],'static_libs': []
}]DEPENDS_LIBS = CUSTOM_WIDGET_LIBS + []helper = app.Helper(ARGUMENTS)
helper.set_deps(DEPENDS_LIBS)# app.prepare_depends_libs(ARGUMENTS, helper, DEPENDS_LIBS)
env = helper.call(DefaultEnvironment)SConscriptFiles = ['src/SConscript', 'tests/SConscript']
helper.SConscript(SConscriptFiles)
scripts/clang_tidy_helper.py
import os
import sys
import platform
import re
import subprocessdef run_clang_tidy(flags, source_file_str, cxxflags, include_file_str):cmd = f"clang-tidy {flags} {source_file_str} -- {cxxflags} {include_file_str}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0
补充
clang-tidy不一定能一次解决所有的代码规范问题,比如私有函数习惯写__前缀,即使是用匈牙利写法也会被误报:
D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:11:6: warning: invalid case style for function '__lib_framebuffer_load_format' [readability-identifier-naming] 11 | void __lib_framebuffer_load_format(lib_framebuffer *fb){| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~| lib_framebuffer_load_format
D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:31:6: warning: invalid case style for function '__lib_framebuffer_init' [readability-identifier-naming]31 | void __lib_framebuffer_init(lib_framebuffer *fb, const char *path) {| ^~~~~~~~~~~~~~~~~~~~~~| lib_framebuffer_init
D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:73:6: warning: invalid case style for function '__lib_framebuffer_destroy' [readability-identifier-naming]73 | void __lib_framebuffer_destroy(lib_framebuffer *fb) {| ^~~~~~~~~~~~~~~~~~~~~~~~~| lib_framebuffer_destroy
clang-tidy做了一个workaround, 允许用正则的方式去忽略匹配的命名样式。
注意必须是全字匹配,部分匹配是不起效的。
Checks: >readability-identifier-naming
CheckOptions:- key: readability-identifier-naming.ClassCasevalue: lower_case- key: readability-identifier-naming.VariableCasevalue: lower_case- key: readability-identifier-naming.FunctionIgnoredRegexpvalue: '__.*'- key: readability-identifier-naming.ConstexprVariableCasevalue: UPPER_CASE
参考
https://github.com/SCons/scons/issues/4637
https://scons.org/doc/latest/HTML/scons-user/ch27.html
https://lrita.github.io/2023/03/21/auto-clang-tidy-cpp-code/
相关文章:
尝试把clang-tidy集成到AWTK项目
前言 项目经过一段时间的耕耘终于进入了团队开发阶段,期间出现了很多问题,其中一个就是开会讨论团队的代码风格规范,目前项目代码风格比较混乱,有的模块是驼峰,有的模块是匈牙利,后面经过讨论,…...
【学习笔记】深度学习网络-正则化方法
作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程,深度学习领域研究生必读教材),开始深度学习领域学习,深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…...
介绍一下Mybatis的底层原理(包括一二级缓存)
表面上我们的就是Sql语句和我们的java对象进行映射,然后Mapper代理然后调用方法来操作数据库 底层的话我们就涉及到Sqlsession和Configuration 首先说一下SqlSession, 它可以被视为与数据库交互的一个会话,用于执行 SQL 语句(Ex…...
WordPress使用(1)
1. 概述 WordPress是一个开源博客框架,配合不同主题,可以有多种展现方式,博客、企业官网、CMS系统等,都可以很好的实现。 官网:博客工具、发布平台和内容管理系统 – WordPress.org China 简体中文,这里可…...
BUUCTF_[安洵杯 2019]easy_web(preg_match绕过/MD5强碰撞绕过/代码审计)
打开靶场,出现下面的静态html页面,也没有找到什么有价值的信息。 查看页面源代码 在url里发现了img传参还有cmd 求img参数 这里先从img传参入手,这里我发现img传参好像是base64的样子 进行解码,解码之后还像是base64的样子再次进…...
C基础寒假练习(4)
输入带空格的字符串,求单词个数、 #include <stdio.h> // 计算字符串长度的函数 size_t my_strlen(const char *str) {size_t len 0;while (str[len] ! \0) {len;}return len; }int main() {char str[100];printf("请输入一个字符串: ");fgets(…...
git error: invalid path
git clone GitHub - guanpengchn/awesome-books: :books: 开发者推荐阅读的书籍 在windows上想把这个仓库拉取下来,发现本地git仓库创建 但只有一个.git隐藏文件夹,其他文件都处于删除状态。 问题: Cloning into awesome-books... remote:…...
MySQL 事务实现原理( 详解 )
MySQL 主要是通过: 锁、Redo Log、Undo Log、MVCC来实现事务 事务的隔离性利用锁机制实现 原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。 Redo Log(重做日志):记录事务对数据库的所有修改,在崩溃时恢复未提交的更改,保证事务…...
git基础使用--1--版本控制的基本概念
文章目录 git基础使用--1--版本控制的基本概念1.版本控制的需求背景,即为啥需要版本控制2. 集中式版本控制SVN3. 分布式版本控制 Git4. SVN和Git的比较 git基础使用–1–版本控制的基本概念 1.版本控制的需求背景,即为啥需要版本控制 先说啥叫版本&…...
Spring RESTful API 设计与实现
Spring RESTful API的设计与实现极大地提升了开发效率和系统可维护性,通过遵循RESTful设计原则,使得API结构清晰、行为一致,便于扩展和维护。它在构建微服务架构中扮演着核心角色,支持松耦合的通信,同时通过标准的HTTP协议和数据格式增强了系统的互操作性。结合Spring Sec…...
Unity飞行代码 超仿真 保姆级教程
本文使用Rigidbody控制飞机,基本不会穿模。 效果 飞行效果 这是一条优雅的广告 如果你也在开发飞机大战等类型的飞行游戏,欢迎在主页搜索博文并参考。 搜索词:Unity游戏(Assault空对地打击)开发。 脚本编写 首先是完整代码。 using System.Co…...
【自学笔记】Git的重点知识点-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 Git基础知识Git高级操作与概念Git常用命令 总结 Git基础知识 Git简介 Git是一种分布式版本控制系统,用于记录文件内容的改动,便于开发者追踪…...
力扣73矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 输入:matrix [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0,1],[0,0,0],[1,0,1]] 输入:matrix [[0,1,2,0],[3,4,5,2],[…...
登录认证(5):过滤器:Filter
统一拦截 上文我们提到(登录认证(4):令牌技术),现在大部分项目都使用JWT令牌来进行会话跟踪,来完成登录功能。有了JWT令牌可以标识用户的登录状态,但是完整的登录逻辑如图所示&…...
python算法和数据结构刷题[1]:数组、矩阵、字符串
一画图二伪代码三写代码 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)-CSDN博客 算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn) 面试经典 150 题 - 学习计…...
详解u3d之AssetBundle
一.AssetBundle的概念 “AssetBundle”可以指两种不同但相关的东西。 1.1 AssetBundle指的是u3d在磁盘上生成的存放资源的目录 目录包含两种类型文件(下文简称AB包): 一个序列化文件,其中包含分解为各个对象并写入此单个文件的资源。资源文件&#x…...
接口测试通用测试用例
接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。 测试的重点是检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系等。 现在很多系统前后端架构是分离的,从安全层面来说,只依赖前段进行限…...
深入理解 C# 与.NET 框架
.NET学习资料 .NET学习资料 .NET学习资料 一、引言 在现代软件开发领域,C# 与.NET 框架是构建 Windows、Web、移动及云应用的强大工具。C# 作为一种面向对象的编程语言,而.NET 框架则是一个综合性的开发平台,它们紧密结合,为开…...
CSS 图像、媒体和表单元素的样式化指南
CSS 图像、媒体和表单元素的样式化指南 1. 替换元素:图像和视频1.1 调整图像大小示例代码:调整图像大小 1.2 使用 object-fit 控制图像显示示例代码:使用 object-fit 2. 布局中的替换元素示例代码:Grid 布局中的图像 3. 表单元素的…...
【BUUCTF杂项题】荷兰宽带数据泄露、九连环
一.荷兰宽带数据泄露 打开发现是一个.bin为后缀的二进制文件,因为提示宽带数据泄露,考虑是宽带路由器方向的隐写 补充:大多数现代路由器都可以让您备份一个文件路由器的配置文件,软件RouterPassView可以读取这个路由配置文件。 用…...
Shell特殊状态变量以及常用内置变量总结
目录 1. 特殊的状态变量 1.1 $?(上一个命令的退出状态) 1.2 $$(当前进程的 PID) 1.3 $!(后台进程的 PID) 1.4 $_(上一条命令的最后一个参数) 2.常用shell内置变量 2.1 echo&…...
蓝桥杯思维训练营(三)
文章目录 题目详解680.验证回文串 II30.魔塔游戏徒步旅行中的补给问题观光景点组合得分问题 题目详解 680.验证回文串 II 680.验证回文串 II 思路分析:这个题目的关键就是,按照正常来判断对应位置是否相等,如果不相等,那么就判…...
基于RTOS的STM32游戏机
1.游戏机的主要功能 所有游戏都来着B站JL单片机博主开源 这款游戏机具备存档与继续游戏功能,允许玩家在任何时候退出当前游戏并保存进度,以便日后随时并继续之前的冒险。不仅如此,游戏机还支持多任务处理,玩家可以在退出当前游戏…...
Leetcode 3440. Reschedule Meetings for Maximum Free Time II
Leetcode 3440. Reschedule Meetings for Maximum Free Time II 1. 解题思路2. 代码实现 题目链接:3440. Reschedule Meetings for Maximum Free Time II 1. 解题思路 这一题某种意义上来说甚至是上一题Leetcode 3439的简化版本(关于这一题的解答可以…...
计算机网络——三种交换技术
目录 电路交换——用于电话网络 电路交换的优点: 电路交换的缺点: 报文交换——用于电报网络 报文交换的优点: 报文交换的缺点: 分组交换——用于现代计算机网络 分组交换的优点: 分组交换的缺点 电路交换——…...
[ Spring ] Spring Boot Mybatis++ 2025
文章目录 StructureMyBatis Controller AbilitiesConfigure Plugins and RepositoriesApply Plugins and Add DependenciesMyBatis Spring PropertiesMyBatis ApplicationMyBatis BeansMyBatis MapperMyBatis Query Builder Structure this blog introduce 3 ways using mybat…...
【力扣题解】922. 按奇偶排序数组 II
😊博主目前也在学习,有错误欢迎指正😊 🌈保持热爱 奔赴星海🌈 文章目录 一、题目1、题目描述2、基础框架3、原题链接 二、解题报告1、思路分析2、代码详解 三、本题知识 一、题目 1、题目描述 给定一个非负整数数组 n…...
HTML5教程之标签(2)
HTML5 <b> 标签 实例 在HTML5中,你可以使用<b>标签来对某些文本实现加粗的效果,请参考下述的示例: <p>这是一个普通的文本- <b>这是一个加粗文本</b>。</p> 尝试一下 浏览器支持 所有主流浏览器都支…...
Verilog基础(一):基础元素
verilog基础 我先说,看了肯定会忘,但是重要的是这个过程,我们知道了概念,知道了以后在哪里查询。语法都是术,通用的概念是术。所以如果你有相关的软件编程经验,那么其实开启这个学习之旅,你会感受到熟悉,也会感受到别致。 入门 - 如何开始 欢迎来到二进制的世界,数字…...
Vue 图片引用方式详解:静态资源与动态路径访问
目录 前言1. 引用 public/ 目录2. assets/ 目录3. 远程服务器4. Vue Router 动态访问5. 总结6. 扩展(图片不显示) 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 在 Vue 开发中&#x…...
