20 января 2016 г.

CLang API: минимальный проект (clang-3.7)

После долгого перерыва я решил вернуться к попыткам использования clang API по нескольким причинам. Во-первых, clang API за это время продвинулся довольно далеко и стал "мощнее", так что хочется попробовать новые версии. Во-вторых, в старых статьях используется более ранние версии clang, а мне регулярно задают вопросы типа "почему у меня не компилируется/линкуется", поэтому хочется и обновить свои знания и добавить статьям актуальности. В-третьих, за пять лет внятных примеров с использованием clang так и не появилось, надо исправить такую несправедливость. Ну и в конце концов меня не оставляет надежда реализовать одну идею со статическим анализом кода, о которой, может быть, напишу позже.

Итак, попробуем написать и собрать минимальный проект для clang API на платформе Linux, используя последнюю стабильную версию llvm и clang. На момент написания статьи это версия 3.7.0.

Предыдущая статья


Сборка и установка clang

Так уж повелось, что если вы хотите использовать clang API, то вам не удастся установить нужные пакеты из репозиториев дистрибутива Linux. Выход здесь только один - собрать clang вручную. Однако здесь тоже есть подводный камень - даже если у вас в системе уже есть llvm, и (о, чудо) он нужной вам версии, то собрать отдельно только clang у вас не получится, это просто не предусмотрено разработчиками. Поэтому нужно будет полностью собрать из исходников и llvm и clang.

На сайте clang есть инструкция по сборке. Но если вы будете копипастить и выполнять оттуда команды, то вы получите не стабильную сборку, а какую-то текущую версию продукта, еще находящуюся в разработке и толком не проверенную. Чтобы избежать дальнейших проблем с "сырым" продуктом, я советую брать именно стабильную версию. Для этого вместо скачивания исходников из папки trunk, нужно взять исходники с меткой конкретной версии (в моем случае это 3.7.0):

например, было
svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

стало
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_370/final llvm
svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_370/final clang

Кроме этого я советую устанавливать собранные llvm и clang в отдельную папку, а не в /usr. Это позволит избежать конфликтов с "родными" версиями, поставляемыми с дистрибутивом, или какими-то другими, если вы хотите, чтобы у вас было несколько разных версий clang в системе. У себя я установил всё это в папку $HOME/opt/llvm.

Итак, опуская проверку требований к установке, набор команд по скачиванию исходников, сборке и установке в сухом остатке будет такой:

> mkdir ~/clang-build
> cd ~/clang-build
> svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_370/final llvm
> cd ~/clang-build/llvm/tools
> svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_370/final clang
> cd ~/clang-build/llvm/tools/clang/tools
> svn co http://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_370/final extra
> cd ~/clang-build/llvm/projects
> svn co http://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_370/final compiler-rt
> cd ~/clang-build/
> mkdir build
> cd ~/clang-build/build
> cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH=$HOME/opt/llvm ../llvm
> make
> make install

После успешного выполнения всех этих команд в папке $HOME/opt/llvm будут находится папки bin, include, lib и share.

Теперь можно проверить всё ли правильно вы собрали и установили:

> $HOME/opt/llvm/bin/llvm-config --version
3.7.0
> $HOME/opt/llvm/bin/clang --version
clang version 3.7.0 (tags/RELEASE_370/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix

Пишем код testclang.cpp

Код минимального проекта не простой, а очень простой:

#include "clang/Tooling/Tooling.h"

int main ()
{
  auto astUnit = clang::tooling::buildASTFromCode("struct C {};");
  return 0;
}

Сохраним это в файле testclang.cpp.

Пишем Makefile

LLVMBIN=$(HOME)/opt/llvm/bin
LLVMCONF=$(LLVMBIN)/llvm-config

CXXFLAGS += -g3
CXXFLAGS += $(shell $(LLVMCONF) --cxxflags)

LIBS += $(shell $(LLVMCONF) --ldflags) \
    -Wl,--start-group \
    $(shell $(LLVMCONF) --libs) \
    -lclangAnalysis \
    -lclangAST \
    -lclangASTMatchers \
    -lclangBasic \
    -lclangCodeGen \
    -lclangDriver \
    -lclangFrontend \
    -lclangEdit \
    -lclangIndex \
    -lclangLex \
    -lclangParse \
    -lclangRewrite \
    -lclangSema \
    -lclangSerialization \
    -lclangTooling \
    -Wl,--end-group
LIBS += $(shell $(LLVMCONF) --system-libs)

all:
    $(CXX) $(CXXFLAGS) -o ./a.out testclang.cpp $(LIBS)

На что нужно обратить внимание:

  • нужно получить ключи компиляции и линковки запуском llvm-config с параметрами --cxxflags и --ldflags соответственно
  • ключи компиляции содержат "-std=c++11", так что код будет компилироваться по стандарту C++11
  • получить список линкуемых библиотек llvm запуском llvm-config с параметром --libs
  • вручную добавить список линкуемых библиотек clang (можно добавить все библиотеки clang, хуже не будет)
  • для разрешения циклических зависимостей llvm и clang нужно использовать ключи линковки start-group/end-group (см. статью об этом)
  • в КОНЦЕ списка линкуемых библиотек добавить системные библиотеки, возвращаемые командой 'llvm-config --system-libs' (список библиотек может различаться на разных платформах)
  • и последнее, но не менее важное - обратите внимание, что все ключи линковки собраны в переменную LIBS и добавлены ПОСЛЕ имени компилируемого файла.

Строим

> make
g++ -g3 -I/home/user/opt/llvm/include  -fPIC -fvisibility-inlines-hidden -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wno-maybe-uninitialized -Wno-comment -std=c++11 -ffunction-sections -fdata-sections  -fno-exceptions -fno-rtti  -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -o ./a.out testclang.cpp -L/home/user/opt/llvm/lib  -Wl,--start-group -lLLVMLTO -lLLVMObjCARCOpts -lLLVMLinker -lLLVMBitWriter -lLLVMIRReader -lLLVMXCoreDisassembler -lLLVMXCoreCodeGen -lLLVMXCoreDesc -lLLVMXCoreInfo -lLLVMXCoreAsmPrinter -lLLVMSystemZDisassembler -lLLVMSystemZCodeGen -lLLVMSystemZAsmParser -lLLVMSystemZDesc -lLLVMSystemZInfo -lLLVMSystemZAsmPrinter -lLLVMSparcDisassembler -lLLVMSparcCodeGen -lLLVMSparcAsmParser -lLLVMSparcDesc -lLLVMSparcInfo -lLLVMSparcAsmPrinter -lLLVMPowerPCDisassembler -lLLVMPowerPCCodeGen -lLLVMPowerPCAsmParser -lLLVMPowerPCDesc -lLLVMPowerPCInfo -lLLVMPowerPCAsmPrinter -lLLVMNVPTXCodeGen -lLLVMNVPTXDesc -lLLVMNVPTXInfo -lLLVMNVPTXAsmPrinter -lLLVMMSP430CodeGen -lLLVMMSP430Desc -lLLVMMSP430Info -lLLVMMSP430AsmPrinter -lLLVMMipsDisassembler -lLLVMMipsCodeGen -lLLVMMipsAsmParser -lLLVMMipsDesc -lLLVMMipsInfo -lLLVMMipsAsmPrinter -lLLVMHexagonDisassembler -lLLVMHexagonCodeGen -lLLVMHexagonDesc -lLLVMHexagonInfo -lLLVMCppBackendCodeGen -lLLVMCppBackendInfo -lLLVMBPFCodeGen -lLLVMBPFDesc -lLLVMBPFInfo -lLLVMBPFAsmPrinter -lLLVMARMDisassembler -lLLVMARMCodeGen -lLLVMARMAsmParser -lLLVMARMDesc -lLLVMARMInfo -lLLVMARMAsmPrinter -lLLVMAMDGPUCodeGen -lLLVMAMDGPUAsmParser -lLLVMAMDGPUUtils -lLLVMAMDGPUDesc -lLLVMAMDGPUInfo -lLLVMAMDGPUAsmPrinter -lLLVMAArch64Disassembler -lLLVMAArch64CodeGen -lLLVMAArch64AsmParser -lLLVMAArch64Desc -lLLVMAArch64Info -lLLVMAArch64AsmPrinter -lLLVMAArch64Utils -lLLVMMIRParser -lLLVMAsmParser -lLLVMLibDriver -lLLVMOption -lLLVMDebugInfoPDB -lLLVMTableGen -lLLVMOrcJIT -lLLVMLineEditor -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMX86Desc -lLLVMMCDisassembler -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMMCJIT -lLLVMDebugInfoDWARF -lLLVMPasses -lLLVMipo -lLLVMVectorize -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMProfileData -lLLVMObject -lLLVMMCParser -lLLVMBitReader -lLLVMInstCombine -lLLVMInstrumentation -lLLVMTransformUtils -lLLVMipa -lLLVMMC -lLLVMAnalysis -lLLVMCore -lLLVMSupport -lclangAnalysis -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangDriver -lclangFrontend -lclangEdit -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangSema -lclangSerialization -lclangTooling -Wl,--end-group -lpthread -ldl -lz

...фигасе, у него там библиотек O_o

У меня без проблем собралось на Ubuntu 15 с g++ версии 4.8.3.

Заключение

Скачали исходники llvm и clang. Собрали и инсталлировали. Написали минимальный под с использованием clang API. Собрали его.

Всё. Удачи.

Исходный код

Исходники можно взять на GitHub.

Следующая статья


Комментариев нет:

Отправить комментарий