はじめに
いろいろと調べて理解した(つもりの)ことをメモしておきます。
内容は、非効率だったり間違いだったりする可能性があります。
環境
- Ubuntu Server 20.04.2 LTS
- Clang 10.0.0
- CMake 3.16.3
- Ninja 1.10.0
参照情報
- https://github.com/llvm/llvm-project/tree/llvmorg-10.0.0
- https://clang.llvm.org/doxygen/ (※)
- https://llvm.org/doxygen/ (※)
- https://cmake.org/cmake/help/v3.16/
※2021/03/08 現在は "13.0.0git" の表記があり、実行環境とバージョンが異なる点に留意。
準備
以下のようにパッケージをインストールしました。
sudo apt-get install clang
sudo apt-get install libclang-dev
sudo apt-get install cmake
sudo apt-get install ninja-build
以下のようにディレクトリを作成しました。また AddClang.cmake をダウンロードしました。
mkdir -p ~/workspace/sample
cd ~/workspace/sample
mkdir build src
mkdir -p cmake/modules
cd cmake/modules
curl -L -O https://github.com/llvm/llvm-project/raw/llvmorg-10.0.0/clang/cmake/modules/AddClang.cmake
AddClang.cmake は後述の CMakeLists.txt の中で使用したいのですが、Clang のパッケージに含まれていないようだったのでダウンロードしました。
以下のリンク先のやりとりから、AddClang.cmake は今後の Clang のバージョンのパッケージには含まれるような気がします。
ソースファイルの作成
Sample.cpp というファイル名で作成しました。
cd ~/workspace/sample/src
code Sample.cpp
内容はチュートリアルにあるサンプルコード (以下のリンク先) をそのまま使用しました。
CMakeLists.txt の作成
Sample.cpp と同じディレクトリに作成しました。
cd ~/workspace/sample/src
code CMakeLists.txt
内容は以下のようにしました。
cmake_minimum_required(VERSION 3.16.3)
project(sample)
# LLVMConfig.cmake を読み込む。
find_package(LLVM REQUIRED CONFIG)
# ClangConfig.cmake を読み込む。
find_package(Clang REQUIRED CONFIG)
# LLVM_LINK_LLVM_DYLIB は LLVMConfig.cmake の中で ON に設定されている。
# CLANG_LINK_CLANG_DYLIB も ON に設定する。
set(CLANG_LINK_CLANG_DYLIB ${LLVM_LINK_LLVM_DYLIB})
# add_clang_executable の中で使用される add_llvm_executable のために AddLLVM.cmake を読み込む。
list(APPEND CMAKE_MODULE_PATH ${LLVM_DIR})
include(AddLLVM)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules")
include(AddClang)
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${CLANG_INCLUDE_DIRS})
# https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0/clang/cmake/modules/AddClang.cmake#L139
add_clang_executable(sample Sample.cpp)
# https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0/clang/cmake/modules/AddClang.cmake#L181
# CLANG_LINK_CLANG_DYLIB があると動的リンク (clang-cpp とリンク) になる。
clang_target_link_libraries(sample PRIVATE clangFrontend clangTooling)
ビルド
以下のようにビルドしました。compile_commands.json も生成するようにしました。
cd ~/workspace/sample/build
cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ../src
ninja
サンプルをそのまま実行
以下のように SyntaxOnlyAction は何も表示しない Action でした。
$ cd ~/workspace/sample/build
$ ./sample -p compile_commands.json ../src/Sample.cpp
(表示なし)
Action を書き換えて実行
以下のように SyntaxOnlyAction を DumpRawTokensAction に書き換えて実行してみました。
$ cd ~/workspace/sample/src
$ cp Sample.cpp Sample.cpp.old
$ code Sample.cpp
$ diff Sample.cpp.old Sample.cpp
27c27
< return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>().get());
---
> return Tool.run(newFrontendActionFactory<clang::DumpRawTokensAction>().get());
$ cd ~/workspace/sample/build
$ ninja
$ ./sample -p compile_commands.json ../src/Sample.cpp
comment '// Declares clang::SyntaxOnlyAction.' [StartOfLine] Loc=</home/ayweak/workspace/sample/src/Sample.cpp:1:1>
unknown '
' Loc=</home/ayweak/workspace/sample/src/Sample.cpp:1:37>
hash '#' [StartOfLine] Loc=</home/ayweak/workspace/sample/src/Sample.cpp:2:1>
...省略...
字句解析して得られたトークンの情報を表示してくれました。
その他にもいくつかの Action が用意されているようです。以下のリンク先で確認できました。
同じ表示は clang -cc1 -dump-raw-tokens で可能
今回の調査の後に気づいたのですが、clang の -dump-raw-tokens オプションで同じ表示が可能でした。
$ clang -cc1 --help
...省略...
-dump-raw-tokens Lex file in raw mode and dump raw tokens
...省略...
$ cd ~/workspace/sample
$ clang -cc1 -dump-raw-tokens src/Sample.cpp
comment '// Declares clang::SyntaxOnlyAction.' [StartOfLine] Loc=<src/Sample.cpp:1:1>
unknown '
' Loc=<src/Sample.cpp:1:37>
hash '#' [StartOfLine] Loc=<src/Sample.cpp:2:1>
...省略...
独自の Action を作成して実行
トークンの情報を JSON 形式で表示したいと考えて、以下の処理を参考に JSONDumpRawTokensAction を作成しました。
- https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0/clang/lib/Frontend/FrontendActions.cpp#L741
- https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0/clang/lib/Lex/Preprocessor.cpp#L233
- https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0/llvm/include/llvm/Support/JSON.h#L703
JSONDumpRawTokensAction.h は以下のように作成しました。src 内に配置しました。
#ifndef JSON_DUMP_RAW_TOKENS_ACTION_H
#define JSON_DUMP_RAW_TOKENS_ACTION_H
#include "clang/Frontend/FrontendAction.h"
class JSONDumpRawTokensAction : public clang::PreprocessorFrontendAction {
protected:
void ExecuteAction() override;
};
#endif
JSONDumpRawTokensAction.cpp は以下のように作成しました。src 内に配置しました。
#include "JSONDumpRawTokensAction.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
void JSONDumpRawTokensAction::ExecuteAction() {
Preprocessor &PP = getCompilerInstance().getPreprocessor();
SourceManager &SM = PP.getSourceManager();
llvm::json::OStream J(llvm::errs());
const llvm::MemoryBuffer *FromFile = SM.getBuffer(SM.getMainFileID());
Lexer RawLex(SM.getMainFileID(), FromFile, SM, PP.getLangOpts());
RawLex.SetKeepWhitespaceMode(true);
Token RawTok;
RawLex.LexFromRawLexer(RawTok);
J.object([&]{
if (RawTok.is(tok::eof)) {
return;
}
J.attribute("file", SM.getFilename(RawTok.getLocation()));
J.attributeArray("tokens", [&]{
while (RawTok.isNot(tok::eof)) {
J.object([&]{
J.attribute("kind", tok::getTokenName(RawTok.getKind()));
J.attribute("spelling", PP.getSpelling(RawTok));
if (RawTok.isAtStartOfLine()) {
J.attribute("start_of_line", true);
}
if (RawTok.hasLeadingSpace()) {
J.attribute("leading_space", true);
}
if (RawTok.isExpandDisabled()) {
J.attribute("expand_disabled", true);
}
if (RawTok.needsCleaning()) {
const char *Start = SM.getCharacterData(RawTok.getLocation());
J.attribute("unclean", StringRef(Start, RawTok.getLength()));
}
J.attribute("line", SM.getSpellingLineNumber(RawTok.getLocation()));
J.attribute("column", SM.getSpellingColumnNumber(RawTok.getLocation()));
});
RawLex.LexFromRawLexer(RawTok);
}
});
});
}
Sample.cpp は以下のように変更しました。
$ diff Sample.cpp.old Sample.cpp
0a1
> #include "JSONDumpRawTokensAction.h"
27c28
< return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>().get());
---
> return Tool.run(newFrontendActionFactory<JSONDumpRawTokensAction>().get());
CMakeLists.txt は以下のように変更しました。
$ diff CMakeLists.txt.old CMakeLists.txt
19c19
< add_clang_executable(sample Sample.cpp)
---
> add_clang_executable(sample Sample.cpp JSONDumpRawTokensAction.cpp)
ビルドして実行してみると JSON 形式で表示することを確認できました。
$ cd ~/workspace/sample/build
$ cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ../src
$ ninja
$ ./sample -p compile_commands.json ../src/Sample.cpp 2>&1 | python3 -mjson.tool
{
"file": "/home/ayweak/workspace/sample/src/Sample.cpp",
"tokens": [
{
"kind": "hash",
"spelling": "#",
"start_of_line": true,
"line": 1,
"column": 1
},
...省略...
まとめ
ある程度の方法は理解できたような気がします。