I am trying to use clang as a library, but I am not sure how to link the files in the Makefile.
Trying out the ASTVisitor code from: https://clang.llvm.org/docs/RAVFrontendAction.html
Here is my Makefile for reference:
CC=g++
Includes= /usr/lib/llvm-6.0/include/
Libs= /usr/lib/llvm-6.0/lib/
CLANGLIBS=-lclangTooling -lclangFrontendTool -lclangFrontend -lclangDriver -lclangSerialization -lclangCodeGen -lclangParse -lclangSema -lclangStaticAnalyzerFrontend -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangAnalysis -lclangARCMigrate -lclangRewrite -lclangRewriteFrontend -lclangEdit -lclangAST -lclangLex -lclangBasic -lclang
run:
LD_PRELOAD=../../llvm-project/build/lib/libclang.so ./clang_parser.out
all: clang_parser.cpp
$(CC) -I$(Includes) -L$(Libs) clang_parser.cpp -o a.out $(CLANGLIBS)
clean:
rm clang_parser.out
I have installed clang as a library i.e. done sudo apt-get install libclang-dev
I get the following error:
clang_parser.cpp:13:10: fatal error: clang/Frontend/FrontendActions.h: No such file or directory
#include <clang/Frontend/FrontendActions.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Makefile:10: recipe for target 'all' failed
make: *** [all] Error 1
Any best practices on using apt installed packages in C/C++ projects are appreciated too.
Having gone through the process myself of creating a program that uses Clang as a library, I thought I'd post my own working Makefile based in part on the commands in the answer by gPats (more below):
Key elements:
It uses a binary distribution downloaded from
https://releases.llvm.org/download.html rather than
installing through apt-get
so I can control exactly
what version is in use.
Like the OP, I'm using (GNU) make
rather than cmake
to minimize the mystery of what is happening.
It uses the llvm-config
program to get most of the required
compile and link options. Specifically, llvm-config --cxxflags
gets the preprocessor and compilation options, and
llvm-config --ldflags --system-libs
gets the linker options.
Clang+LLVM can be linked either statically or dynamically, independent of how other libraries are linked.
For static linking:
I'm using the same hardcoded list of clang
libraries as the OP did.
I do not know a way to obtain this list other than by studying what
cmake
does in an llvm
source tree build. There is not anything
like clang-config
to help.
The llvm
libraries, obtained via llvm-config --libs
, must come after the clang
libraries.
(This is obvious in retrospect, but I wasted a fair bit of time since
the linker error messages are not easy to diagnose when dealing with
literally hundreds of unfamiliar libraries.)
For dynamic linking:
One only need link with libclang-cpp.so
to get all of
Clang and LLVM.
Linking is about four times faster, and the resulting binary is much smaller.
The downside is libclang-cpp.so
has to be available at
run time (naturally).
For completeness, I'm using GCC-9.3.0 on Linux Mint 20.1, x86_64.
My Makefile:
# clang-as-lib/Makefile
# Attempt to link with clang as a library.
# Originally based on:
# https://stackoverflow.com/questions/59888374/using-clang-as-a-library-in-c-project
# Default target.
all:
.PHONY: all
# ---- Configuration ----
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
# Downloaded from: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04
# Link with clang statically?
#
# If 1, then both clang and llvm are linked statically. Linking takes
# about 3 seconds, and the resulting binary is 48 MB.
#
# If 0, then both are obtained dynamically from libclang-cpp.so.
# Linking takes about 0.7s, and the binary is about 2.7 MB. But the .so
# file (which is about 176 MB) must be available at run time.
LINK_CLANG_STATICALLY = 1
# ---- llvm-config query results ----
# Due to using the ':=' operator (rather than '='), these queries are
# done exactly once for each 'make' invocation.
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config
# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)
# Set of LLVM libraries to link with, as -l flags, when linking
# statically. There are 163 of them in clang+llvm-14.0.0.
LLVM_LIBS := $(shell $(LLVM_CONFIG) --libs)
# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)
# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)
# ---- Compiler options ----
# C++ compiler.
CXX = g++
# Compiler options, including preprocessor options.
CXXFLAGS =
# Without optimization, adding -g increases compile time by ~20%.
#CXXFLAGS += -g
# Without -g, this increases compile time by ~10%. With -g -O2, the
# increase is ~50% over not having either.
#CXXFLAGS += -O2
CXXFLAGS += -Wall
# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment
# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)
# Linker options.
LDFLAGS =
ifeq ($(LINK_CLANG_STATICALLY),1)
# Set of clang libraries to link with. This list was obtained through
# trial and error.
LDFLAGS += -lclangTooling
LDFLAGS += -lclangFrontendTool
LDFLAGS += -lclangFrontend
LDFLAGS += -lclangDriver
LDFLAGS += -lclangSerialization
LDFLAGS += -lclangCodeGen
LDFLAGS += -lclangParse
LDFLAGS += -lclangSema
LDFLAGS += -lclangStaticAnalyzerFrontend
LDFLAGS += -lclangStaticAnalyzerCheckers
LDFLAGS += -lclangStaticAnalyzerCore
LDFLAGS += -lclangAnalysis
LDFLAGS += -lclangARCMigrate
LDFLAGS += -lclangRewrite
LDFLAGS += -lclangRewriteFrontend
LDFLAGS += -lclangEdit
LDFLAGS += -lclangAST
LDFLAGS += -lclangLex
LDFLAGS += -lclangBasic
LDFLAGS += -lclang
# *After* clang libs, the llvm libs.
LDFLAGS += $(LLVM_LIBS)
else # LINK_CLANG_STATICALLY==0
# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp
# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)
endif
# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)
# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cpp
$(CXX) -c -o $@ $(CXXFLAGS) $<
# Executable.
all: FindClassDecls.exe
FindClassDecls.exe: FindClassDecls.o
$(CXX) -g -Wall -o $@ $^ $(LDFLAGS)
# Run it.
.PHONY: run
run: FindClassDecls.exe
./FindClassDecls.exe "namespace n { namespace m { class C {}; } }"
.PHONY: clean
clean:
$(RM) *.o *.exe
# EOF
FindClassDecls.cpp (from RAVFrontendAction.html):
// FindClassDecls.cpp
// https://clang.llvm.org/docs/RAVFrontendAction.html
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
using namespace clang;
class FindNamedClassVisitor
: public RecursiveASTVisitor<FindNamedClassVisitor> {
public:
explicit FindNamedClassVisitor(ASTContext *Context)
: Context(Context) {}
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C") {
FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc());
if (FullLocation.isValid())
llvm::outs() << "Found declaration at "
<< FullLocation.getSpellingLineNumber() << ":"
<< FullLocation.getSpellingColumnNumber() << "\n";
}
return true;
}
private:
ASTContext *Context;
};
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
explicit FindNamedClassConsumer(ASTContext *Context)
: Visitor(Context) {}
virtual void HandleTranslationUnit(clang::ASTContext &Context) {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
FindNamedClassVisitor Visitor;
};
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile)
{
return std::make_unique<FindNamedClassConsumer>(&Compiler.getASTContext());
}
};
int main(int argc, char **argv) {
if (argc > 1) {
clang::tooling::runToolOnCode(std::make_unique<FindNamedClassAction>(), argv[1]);
}
}
// EOF
For additional clarity, the way the above differs from the answer by gPats is:
It uses a Clang installation directory that comes from unpacking a
binary distribution, rather than using what apt-get
installs.
It can do either static or dynamic linking (by changing the
LINK_CLANG_STATICALLY
variable), whereas that answer does
only static linking.
Most importantly, it is organized as a Makefile
that can be readily
adapted for use in a program with multiple source files, rather than
doing all compilation and linking in one command.
There's nothing wrong with gPats' answer; I used it as my starting
point. But I ran into some problems (partly due to my own mistakes)
while adapting it into a full-fledged Makefile
, hence I posted my own
answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With