尝试把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.ClassCase
    value:           lower_case
  - key:             readability-identifier-naming.VariableCase
    value:           lower_case
  - key:             readability-identifier-naming.FunctionCase
    value:           lower_case
  - key:             readability-identifier-naming.MacroDefinitionCase
    value:           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 0

print(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 buildcmake --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 0

print(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 subprocess

def 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 changes
  LibFrameBufferInit();
  ^
/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 tidy

CUSTOM_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 subprocess

def 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.ClassCase
    value:           lower_case
  - key:             readability-identifier-naming.VariableCase
    value:           lower_case
  - key:             readability-identifier-naming.FunctionIgnoredRegexp
    value:           '__.*'
  - key:             readability-identifier-naming.ConstexprVariableCase
    value:           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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker技术相关学习三

一、Docker镜像仓库管理 1.docker仓库&#xff1a;用于存储和分发docker镜像的集中式存储库&#xff0c;开发者可以将自己创建的镜像推送到仓库中也可以从仓库中拉取所需要的镜像。 2.docker仓库&#xff1a; 公有仓库&#xff08;docker hub&#xff09;&#xff1a;任何人都可…

挑战项目 --- 微服务编程测评系统(在线OJ系统)

一、前言 1.为什么要做项目 面试官要问项目&#xff0c;考察你到底是理论派还是实战派&#xff1f; 1.希望从你的项目中看到你的真实能力和对知识的灵活运用。 2.展示你在面对问题和需求时的思考方式及解决问题的能力。 3.面试官会就你项目提出一些问题&#xff0c;或扩展需求…

Python 与 PostgreSQL 集成:深入 psycopg2 的应用与实践

title: Python 与 PostgreSQL 集成:深入 psycopg2 的应用与实践 date: 2025/2/4 updated: 2025/2/4 author: cmdragon excerpt: PostgreSQL 作为开源关系型数据库的佼佼者,因其强大的功能与性能被广泛应用于各种项目中。而 Python 则因其简洁易用的语法、丰富的库和强大的…

计算机从何而来?计算技术将向何处发展?

计算机的前生&#xff1a;机械计算工具的演进 算盘是计算机的起点&#xff0c;它其实是一台“机械式半自动化运算器”。打算盘的“口诀”其实就是它的编程语言&#xff0c;算盘珠就是它的存储器。 第二阶段是可以做四则运算的加法器、乘法器。1642年&#xff0c;法国数学家帕斯…

【Blazor学习笔记】.NET Blazor学习笔记

我是大标题 我学习Blazor的顺序是基于Blazor University&#xff0c;然后实际内容不完全基于它&#xff0c;因为它的例子还是基于.NET Core 3.1做的&#xff0c;距离现在很遥远了。 截至本文撰写的时间&#xff0c;2025年&#xff0c;最新的.NET是.NET9了都&#xff0c;可能1…

MapReduce分区

目录 1. MapReduce分区1.1 哈希分区1.2 自定义分区 2. 成绩分组2.1 Map2.2 Partition2.3 Reduce 3. 代码和结果3.1 pom.xml中依赖配置3.2 工具类util3.3 GroupScores3.4 结果 参考 本文引用的Apache Hadoop源代码基于Apache许可证 2.0&#xff0c;详情请参阅 Apache许可证2.0。…

重生之我在异世界学编程之C语言:深入指针篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文&#xff08;1&#xff09;内置数…

deep generative model stanford lecture note3 --- latent variable

1 Introduction 自回归模型随着gpt的出现取得很大的成功&#xff0c;还是有很多工程上的问题并不是很适合使用自回归模型&#xff1a; 1&#xff09;自回归需要的算力太大&#xff0c;满足不了实时性要求&#xff1a;例如在自动驾驶的轨迹预测任务中&#xff0c;如果要用纯自回…

STM32_SD卡的SDIO通信_DMA读写

本篇&#xff0c;将使用CubeMXKeil&#xff0c;创建一个SD卡的DMA读写工程。 目录 一、简述 二、CubeMX 配置 SDIO DMA 三、Keil 编辑代码 四、实验效果 实现效果&#xff0c;如下图&#xff1a; 一、简述 上篇已简单介绍了SD、SDIO&#xff0c;本篇不再啰嗦&#xff0c;…

互联网行业常用12个数据分析指标和八大模型

本文目录 前言 一、互联网线上业务数据分析的12个指标 1. 用户数据&#xff08;4个&#xff09; (1) 存量&#xff08;DAU/MAU&#xff09; (2) 新增用户 (3) 健康程度&#xff08;留存率&#xff09; (4) 渠道来源 2. 用户行为数据&#xff08;4个&#xff09; (1) 次数/频率…

【学术投稿-2025年计算机视觉研究进展与应用国际学术会议 (ACVRA 2025)】从计算机基础到HTML开发:Web开发的第一步

会议官网&#xff1a;www.acvra.org 简介 2025年计算机视觉研究进展与应用&#xff08;ACVRA 2025&#xff09;将于2025年2月28-3月2日在中国广州召开&#xff0c;将汇聚世界各地的顶尖学者、研究人员和行业专家&#xff0c;聚焦计算机视觉领域的最新研究动态与应用成就。本次…

【Unity踩坑】Unity项目管理员权限问题(Unity is running as administrator )

问题描述&#xff1a; 使用Unity Hub打开或新建项目时会有下面的提示。 解决方法&#xff1a; 打开“本地安全策略”&#xff1a; 在Windows搜索栏中输入secpol.msc并回车&#xff0c;或者从“运行”对话框&#xff08;Win R&#xff0c;然后输入secpol.msc&#xff09;启…

开发板上Qt运行的环境变量的三条设置语句的详解

在终端中运行下面三句命令用于配置开发板上Qt运行的环境变量&#xff1a; export QT_QPA_GENERIC_PLUGINStslib:/dev/input/event1 export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0 export QT_QPA_FONTDIR/usr/lib/fonts/设置成功后可以用下面的语句检查设置成功没有 echo $QT_QPA…

【PyQt】pyqt小案例实现简易文本编辑器

pyqt小案例实现简易文本编辑器 分析 实现了一个简单的文本编辑器&#xff0c;使用PyQt5框架构建。以下是代码的主要功能和特点&#xff1a; 主窗口类 (MyWindow): 继承自 QWidget 类。使用 .ui 文件加载用户界面布局。设置窗口标题、状态栏消息等。创建菜单栏及其子菜单项&…

Java 数据库连接池:HikariCP 与 Druid 的对比

Java 数据库连接池&#xff1a;HikariCP 与 Druid 的对比 数据库连接池&#xff1a;HikariCP 1. 卓越的性能表现 HikariCP 在数据库连接池领域以其卓越的性能脱颖而出。 其字节码经过精心优化&#xff0c;减少了不必要的开销&#xff0c;使得连接获取和释放的速度极快。 在…

PHP实现混合加密方式,提高加密的安全性(代码解密)

代码1&#xff1a; <?php // 需要加密的内容 $plaintext 授权服务器拒绝连接;// 1. AES加密部分 $aesKey openssl_random_pseudo_bytes(32); // 生成256位AES密钥 $iv openssl_random_pseudo_bytes(16); // 生成128位IV// AES加密&#xff08;CBC模式&#xff09…

Turing Complete-3位解码器

要求如下&#xff1a; 就是搭建一个3-8译码器 思路一&#xff1a; 使用四种判断来解决问题。 判断一&#xff1a;3个输入中是否有0个绿色。 解决办法&#xff1a;三个输入通过三输入或门再取反。 判断二&#xff1a;3个输入中是否有1个绿色&#xff0c;并确定是输入1、输入…

我主编的电子技术实验手册(24)——RL并联电路

本专栏是笔者主编教材&#xff08;图0所示&#xff09;的电子版&#xff0c;依托简易的元器件和仪表安排了30多个实验&#xff0c;主要面向经费不太充足的中高职院校。每个实验都安排了必不可少的【预习知识】&#xff0c;精心设计的【实验步骤】&#xff0c;全面丰富的【思考习…

手写MVVM框架-模板渲染1

虚拟dom创建好了&#xff0c;依赖也收集好了&#xff0c;这个时候就该去渲染dom了&#xff0c;把页面上的 { {name}} 渲染成具体的值。 渲染之前我们给原型上添加一个render方法 //代码在src/core/render.jsexport function renderMixin(MiniVue) {MiniVue.prototype.$render …

人类心智逆向工程:AGI的认知科学基础

文章目录 引言:为何需要逆向工程人类心智?一、逆向工程的定义与目标1.1 什么是逆向工程?1.2 AGI逆向工程的核心目标二、认知科学的四大支柱与AGI2.1 神经科学:大脑的硬件解剖2.2 心理学:心智的行为建模2.3 语言学:符号与意义的桥梁2.4 哲学:意识与自我模型的争议三、逆向…