clang 开发应用xcode 编译检查的插件 一:构建篇

版权声明:未经博主同意不得转载 https://blog.csdn.net/bluefish89/article/details/77994054

Clang是llvm的编译器前端,非常适合进行源码分析.目前开源的oclint就是基于clang进行的代码静态检查.工作中遇到了一些问题需要进行代码分析,所以学习了插件的开发流程.既然开发插件就要有合适的IDE,Mac上最合适的无疑是xcode了.本文将讲述如何使用xcode开发clang插件,在此之前请先了解clang的相关知识.

一、搭建环境

1.获取Clang源码

由于是要使用到Xcode中,因此最好还是从苹果官网中获取LLVM的源码,目前版本是Xcode8.1下的LLVM的源码
https://opensource.apple.com/tarballs/clang/clang-800.0.42.1.tar.gz
PS:
a.查看xcode对应的clang版本 https://trac.macports.org/wiki/XcodeVersionInfo
b.官网查询对应发布版本 https://opensource.apple.com/source/clang/

这不只是Clang部分的源码,其中LLVM主要的子项目包括:

  • LLVM Core: 包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。

  • Clang: 一个C/C++/Objective-C编译器,致力于提供令人惊讶的快速编译,极其有用的错误和警告信息,提供一个可用于构建很棒的源代码级别的工具.

  • dragonegg gcc插件,可将GCC的优化和代码生成器替换为LLVM的相应工具。

  • LLDB: 基于LLVM提供的库和Clang构建的优秀的本地调试器。 libc++、libc++
    ABI 符合标准的,高性能的C++标准库实现,以及对C++11的完整支持。

  • compiler-rt: 针对”__fixunsdfdi”和其他目标机器上没有一个核心IR(intermediate
    representation)对应的短原生指令序列时,提供高度调优过的底层代码生成支持。

  • OpenMP: Clang中对多平台并行编程的runtime支持。 vmkit: 基于LLVM的Java和.NET虚拟机实现

  • polly :支持高级别的循环和数据本地化优化支持的LLVM框架。 libclc: OpenCL(开放运算语言)标准库的实现

  • klee: 基于LLVM编译基础设施的符号化虚拟机 SAFECode: 内存安全的C/C++编译器

  • lld :clang/llvm内置的链接器

2.编译插件

下载源码完成后解压目录,接下来就是要做编译LLVM的工作了。

找到 [解压llvm目录]/src/tools/clang/examples 目录,在里面新建一个目录如MyPlugin。然后修改example目录的CMakeLists.txt文件,添加一项:

add_subdirectory(MyPlugin)

然后进入创建的MyPlugin目录,创建三个文件,分别是:

CMakeList.txt
MyPlugin.cpp
MyPlugin.exports

然后在新建的CMakeList.txt中加入下面内容:

# If we don't need RTTI or EH, there's no reason to export anything
# from the plugin.
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}/MyPlugins.exports)
    endif()
  endif()
endif()

add_llvm_loadable_module(MyPlugin MyPlugin.cpp)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  target_link_libraries(MyPlugin ${cmake_2_8_12_PRIVATE}
    clangAST
    clangBasic
    clangFrontend
    LLVMSupport
    )
endif()

然后在插件文件MyPlugin.cpp中,添加下面的内容:

#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"

using namespace clang;

namespace
{
    class MyPluginConsumer : public ASTConsumer
    {
    CompilerInstance &Instance;
    std::set<std::string> ParsedTemplates;
    public:
        MyPluginConsumer(CompilerInstance &Instance,
                               std::set<std::string> ParsedTemplates)
        : Instance(Instance), ParsedTemplates(ParsedTemplates) {}
    };

    class MyPluginASTAction : public PluginASTAction
    {
    std::set<std::string> ParsedTemplates;
    protected:
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                       llvm::StringRef) override
        {
            return llvm::make_unique<MobCodeConsumer>(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<MyPluginASTAction>
X("MyPlugin", “My plugin");

上面的代码中主要先看看MyPluginASTAction的ParseArgs方法,这是一个插件的入口函数,在这个方法里面调用了一个叫DiagnosticsEngine对象的Report方法,这段代码的主要功能是向编译器报告一个错误,而错误的描述就是“My plugin Started…”,下面会有具体的演示效果。关于其它部分的代码现在可以暂时不用理会,后续的章节会进行详细的说明。

现在先回到解压llvm目录的根目录,首先来对这些源码生成一个Xcode工程,源码项目的编译是由cmake管理(关于cmake详细资料请参考:cmake官方教程),因此生成Xcode工程非常方便。执行下面的shell命令:

cd 解压llvm目录
mkdir build && cd build
cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64 -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON -DLIBCXX_INCLUDE_TESTS=OFF -DCOMPILER_RT_INCLUDE_TESTS=OFF -DCOMPILER_RT_ENABLE_IOS=OFF ../src

等待执行,提示成功后即可看到目录下多了一个build目录,点进去就可以看到一个Xcode的工程文件。双击打开项目,然后执行All_BUILD的scheme等待完成即可(这里面会有一个compiler_rt的编译报错,表示无法编译compiler_rt,由于这块不涉及插件编写所以可以暂时忽略)。

接着选择MyPlugin的scheme继续buid,编译成功后会在Debug/lib目录中多出一个叫MyPlugin.dylib文件,该文件便是我们要在xcode中用的插件。

PS:项目中可以看到刚才我们创建的cpp文件
这里写图片描述

3. 添加一个简单的插件项目到自己的项目

创建一个自己的工程项目,这里我用的是testPlugin
打开要使用插件的Xcode项目,在build settings一栏中对Other C Flags一项进行编辑,调整为:

-Xclang -load -Xclang /llvm/build/Debug/lib/MyPlugin.dylib -Xclang -add-plugin -Xclang MyPlugin

注:最后一项-Xclang MyPlugin中的MyPlugin为插件名字,一定要是自己设置的插件名称,否则无法调用插件。
由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误,如下图所示:
这里写图片描述

不一致的Clang版本错误

为了解决这个问题需要调整Xcode中使用的Clang编译器,将默认的编译器改为我们自己编译出来的编译器。具体的方法是在build settings中再添加两项自定义项(Editor-> Add build settings -> Add User-Defined Setting):

CC = / [解压llvm目录]/build/Debug/bin/clang
CXX =/ [解压llvm目录]/build/Debug/bin/clang++

目的用于指定Xcode的编译器从之前默认的,改为自定义的Clang编译器(注:CC和CXX中需要指定为你编译出来的Clang所在的绝对路径)。
Common + B 编译则可以看到一个插件输出的错误提示。
这里写图片描述

Good job!!
该错误正是我们D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
“My plugin Started..."));
输出的错误。
至此我们成功的在自己的项目中使用了自己编译的clang插件。

摘录:
https://my.oschina.net/vimfung/blog/866109
https://github.com/LiuShulong/SLClangTutorial/blob/master/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E7%BC%96%E5%86%99clang%E6%8F%92%E4%BB%B6%E5%92%8Clibtool.md
http://kangwang1988.github.io/tech/2016/10/31/check-code-style-using-clang-plugin.html

猜你喜欢

转载自blog.csdn.net/bluefish89/article/details/77994054