内容简介:这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。代开终端下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的
这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。
下载源码
代开终端
sudo mkdir llvm sudo chown `whoami` llvm cd llvm export LLVM_HOME=`pwd` git clone -b release_60 https://github.com/llvm-mirror/llvm.git llvm git clone -b release_60 https://github.com/llvm-mirror/clang.git llvm/tools/clang git clone -b release_60 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra git clone -b release_60 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt 复制代码
下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的 release_60
。
安装cmake
如果没有安装 cmake
需要安装一下,后面需要用。
brew update brew install cmake 复制代码
开始编写插件
cd到 llvm/llvm/tools/clang/examples
1.打开这个目录下的 CMakeLists.txt
文件,然后添加 add_subdirectory(CodeChecker)
2.在当前目录创建新的文件夹 CodeChecker
,并cd到CodeChecker
mkdir CodeChecker cd CodeChecker 复制代码
3.在新建的 CodeChecker
目录下创建三个文件
touch CMakeLists.txt touch CodeChecker.cpp touch CodeChecker.exports 复制代码
在新创建的 CMakeLists.txt
中添加
if( NOT MSVC ) # MSVC mangles symbols differently if( NOT LLVM_REQUIRES_RTTI ) if( NOT LLVM_REQUIRES_EH ) set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports) endif() endif() endif() add_llvm_loadable_module(CodeChecker CodeChecker.cpp) if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN)) target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE} clangAST clangBasic clangFrontend LLVMSupport ) endif() 复制代码
在 CodeChecker.cpp
文件中加入
#include <iostream> #include <stdio.h> #include <string> #include <fstream> #include <sstream> #include <algorithm> #include <functional> #include <vector> #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" using namespace clang; using namespace std; namespace { static vector<string> split(const string &s, char delim) { vector<string> elems; stringstream ss; ss.str(s); string item; while (getline(ss, item, delim)) { elems.push_back(item); } return elems; } class CodeVisitor : public RecursiveASTVisitor<CodeVisitor> { private: CompilerInstance &Instance; ASTContext *Context; public: void setASTContext (ASTContext &context) { this -> Context = &context; } private: /** 判断是否为用户源码 @param decl 声明 @return true 为用户源码,false 非用户源码 */ bool isUserSourceCode (Decl *decl) { string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str(); if (filename.empty()) return false; //非XCode中的源码都认为是用户源码 if(filename.find("/Applications/Xcode.app/") == 0) return false; return true; } /** 检测类名是否存在小写开头 @param decl 类声明 */ void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isLowercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toUppercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测类名是否包含下划线 @param decl 类声明 */ void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl) { StringRef className = decl -> getName(); //类名不能包含下划线 size_t underscorePos = className.find('_'); if (underscorePos != StringRef::npos) { //修正提示 std::string tempName = className; std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_'); tempName.erase(end_pos, tempName.end()); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden"); SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos); diagEngine.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测方法名是否存在大写开头 @param decl 方法声明 */ void checkMethodNameForUppercaseName(ObjCMethodDecl *decl) { //检查名称的每部分,都不允许以大写字母开头 Selector sel = decl -> getSelector(); int selectorPartCount = decl -> getNumSelectorLocs(); for (int i = 0; i < selectorPartCount; i++) { StringRef selName = sel.getNameForSlot(i); char c = selName[0]; if (isUppercase(c)) { //修正提示 std::string tempName = selName; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl -> getSelectorLoc(i); SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } } /** 检测方法中定义的参数名称是否存在大写开头 @param decl 方法声明 */ void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl) { for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++) { ParmVarDecl *parmVarDecl = *it; StringRef name = parmVarDecl -> getName(); char c = name[0]; if (isUppercase(c)) { //修正提示 std::string tempName = name; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = parmVarDecl -> getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } } /** 检测方法实现是否超过500行代码 @param decl 方法声明 */ void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl) { if (decl -> hasBody()) { //存在方法体 Stmt *methodBody = decl -> getBody(); string srcCode; srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()), methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1); vector<string> lines = split(srcCode, '\n'); if(lines.size() > 500) { DiagnosticsEngine &D = Instance.getDiagnostics(); unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines"); D.Report(decl -> getSourceRange().getBegin(), diagID); } } } /** 检测属性名是否存在大写开头 @param decl 属性声明 */ void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl) { bool checkUppercaseNameIndex = 0; StringRef name = decl -> getName(); if (name.find('_') == 0) { //表示以下划线开头 checkUppercaseNameIndex = 1; } //名称必须以小写字母开头 char c = name[checkUppercaseNameIndex]; if (isUppercase(c)) { //修正提示 std::string tempName = name; tempName[checkUppercaseNameIndex] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测属性名是否包含下划线 @param decl 属性声明 */ void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl) { StringRef name = decl -> getName(); if (name.size() == 1) { //不需要检测 return; } //类名不能包含下划线 size_t underscorePos = name.find('_', 1); if (underscorePos != StringRef::npos) { //修正提示 std::string tempName = name; std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_'); tempName.erase(end_pos, tempName.end()); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden"); SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos); diagEngine.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测委托属性是否有使用weak修饰 @param decl 属性声明 */ void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl) { QualType type = decl -> getType(); StringRef typeStr = type.getAsString(); //Delegate if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos) { ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes(); string typeSrcCode; typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()), decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding()); if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak)) { DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak."); diagEngine.Report(decl -> getLocation(), diagID); } } } /** 检测常量名称是否存在小写开头 @param decl 常量声明 */ void checkConstantNameForLowercaseName (VarDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isLowercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toUppercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测变量名称是否存在大写开头 @param decl 变量声明 */ void checkVarNameForUppercaseName (VarDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isUppercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测变量名称 @param decl 变量声明 */ void checkVarName(VarDecl *decl) { if (decl -> isStaticLocal()) { //静态变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } else if (decl -> isLocalVarDecl()) { //本地变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } else if (decl -> isFileVarDecl()) { //文件定义变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } } public: CodeVisitor (CompilerInstance &Instance) :Instance(Instance) { } /** 观察ObjC的类声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration) { if (isUserSourceCode(declaration)) { checkClassNameForLowercaseName(declaration); checkClassNameForUnderscoreInName(declaration); } return true; } /** 观察类方法声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCMethodDecl(ObjCMethodDecl *declaration) { if (isUserSourceCode(declaration)) { checkMethodNameForUppercaseName(declaration); checkMethodParamsNameForUppercaseName(declaration); checkMethodBodyForOver500Lines(declaration); } return true; } /** 观察类属性声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration) { if (isUserSourceCode(declaration)) { checkPropertyNameForUppercaseName(declaration); checkPropertyNameForUnderscoreInName(declaration); checkDelegatePropertyForUsageWeak(declaration); } return true; } /** 观察变量声明 @param declaration 声明对象 @return 返回 */ bool VisitVarDecl(VarDecl *declaration) { if (isUserSourceCode(declaration)) { checkVarName(declaration); } return true; } /** 观察枚举常量声明 @param declaration 声明对象 @return 返回 */ // bool VisitEnumConstantDecl (EnumConstantDecl *declaration) // { // return true; // } }; class CodeConsumer : public ASTConsumer { CompilerInstance &Instance; std::set<std::string> ParsedTemplates; public: CodeConsumer(CompilerInstance &Instance, std::set<std::string> ParsedTemplates) : Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance) { } bool HandleTopLevelDecl(DeclGroupRef DG) override { return true; } void HandleTranslationUnit(ASTContext& context) override { visitor.setASTContext(context); visitor.TraverseDecl(context.getTranslationUnitDecl()); } private: CodeVisitor visitor; }; class CodeASTAction : public PluginASTAction { std::set<std::string> ParsedTemplates; protected: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) override { return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) override { // DiagnosticsEngine &D = CI.getDiagnostics(); // D.Report(D.getCustomDiagID(DiagnosticsEngine::Error, // "My plugin Started...")); return true; } }; } static clang::FrontendPluginRegistry::Add<CodeASTAction> X("CodeChecker", "Code Checker"); 复制代码
使用cmake编译源代码
1.cd到 llvm
,注意不是 llvm/llvm
,执行
mkdir llvm_build && cd llvm_build cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel 复制代码
2.然后会在llvm_build文件目录中看到LLVM.xcodeproj,用xcode打开,选择 Automatically Create Schemes
3.编译 clang,CodeChecker,libclang
4.在 llvm_build/Debug/lib
目录下可以找到我们的插件,如下图:
Xcode集成Plugin
创建需要加载插件的项目,在Build Settings栏目中的OTHER_CFLAGS添加上如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字 复制代码
我把插件拷贝的桌面了,所以我的是:
-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker 复制代码
然后你build项目,可能有 unable to load plugin '/Users/roy.cao/Desktop/CodeChecker.dylib'
的error,这是由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误。
在Build Settings栏目中新增两项用户定义的设置,分别为CC和CXX CC对应的是自己编译的clang的绝对路径,CXX对应的是自己编译的clang++的绝对路径
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang /Users/roy.cao/llvm/llvm_build/Debug/bin/clang++ 复制代码
再build,会有以下错误:
在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality的Default改为NO.
再build可能会出现一大堆系统库的 symbol not found
错误
这个时候需要在刚刚OTHER_CFLAGS的
-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker 复制代码
后面再加上 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.0.sdk
注意 iPhoneSimulator12.0.sdk
,每个人的可能不同,需要到目录下看看自己的版本。 那么完整的就是下图这样:
再build就能做代码风格检查啦,庆祝一下吧~~~~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 在 Visual Studio 中使用 EditorConfig 统一代码风格(含原生与插件)
- 谷歌开源项目风格指南之 Python 风格指南
- JavaScript代码风格要素
- Flutter主题风格
- Go 测试风格指南
- 软件架构和架构风格
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
视觉SLAM十四讲
高翔、张涛、等 / 电子工业出版社 / 2017-3 / 75
《视觉SLAM十四讲:从理论到实践》系统介绍了视觉SLAM(同时定位与地图构建)所需的基本知识与核心算法,既包括数学理论基础,如三维空间的刚体运动、非线性优化,又包括计算机视觉的算法实现,例如多视图几何、回环检测等。此外,还提供了大量的实例代码供读者学习研究,从而更深入地掌握这些内容。 《视觉SLAM十四讲:从理论到实践》可以作为对SLAM 感兴趣的研究人员的入门自学材料,也可以作为SLAM......一起来看看 《视觉SLAM十四讲》 这本书的介绍吧!