Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using clang as a library in C++ project

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.

like image 730
gPats Avatar asked Oct 16 '25 04:10

gPats


1 Answers

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.

like image 188
Scott McPeak Avatar answered Oct 18 '25 19:10

Scott McPeak