Clang-plugin. How to compile/link out of tree ? Linking error on some machines

Hello,

  • I am developping a clang plugin out-of-tree.

  • My plugin generates some extra code, and
    use the ClangFormat lib to format it.

  • I have a strange linking error with a clangFormat.a symbol
    but only on some llvm installations.
    I compile as shown below, i.e. not linking with any ClangASTxxx.a, Clangxxx.a lib.
    It works, but on one of our machines, a symbol in clangFormat.a is not found.

  • Tiny example of a plugin which reproduces the issue
    (the PrintFunctionNames of the llvm sources, where I removed almost everything but a call to clang::format::getLLVMStyle).

#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Format/Format.h"
using namespace clang;

namespace {
class PrintFunctionsConsumer : public ASTConsumer {
public:
  PrintFunctionsConsumer() = default;
  bool HandleTopLevelDecl(DeclGroupRef DG) override { return true; }
  void HandleTranslationUnit(ASTContext& context) override {
    auto  style = clang::format::getLLVMStyle(); // <<-------- PROBLEM ...
  }
};

class PrintFunctionNamesAction : public PluginASTAction {
protected:
  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) override {
    return std::make_unique<PrintFunctionsConsumer>();
  }
 PluginASTAction::ActionType getActionType() override { return ReplaceAction; }
 bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) override { return true; }
 void PrintHelp(llvm::raw_ostream& ros) { }
};
}

static FrontendPluginRegistry::Add<PrintFunctionNamesAction>
X("print-fns", "print function names");

Which I compile using a simple cmake

cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(pfn VERSION 0.1.0 LANGUAGES C CXX)

# Load LLVM/Clang cmake
set(LLVM_CONFIG llvm-config CACHE STRING "llvm-config")
execute_process(COMMAND ${LLVM_CONFIG} --prefix OUTPUT_VARIABLE LLVM_ROOT_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
list(APPEND CMAKE_PREFIX_PATH "${LLVM_ROOT_DIR}/lib/cmake/clang/")
find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(AddLLVM)

# Plugin
add_llvm_library(pfn MODULE PrintFunctionNames.cpp PLUGIN_TOOL clang)
target_link_libraries(pfn PRIVATE "$<$<PLATFORM_ID:Darwin>:-undefined dynamic_lookup>")
target_include_directories(pfn PRIVATE ${LLVM_INCLUDE_DIR})

# Necessary on some machines ?
#target_link_libraries(pfn PRIVATE ${LLVM_LIBRARY_DIR}/libclangFormat.a)

When I use it

clang++ -fplugin=./pfn.so -c a_small_piece_of_code.cpp

it works well on OS X (clang 16, brew installed), Ubuntu (22.10, clang-15),
but on one of our computing center machine, CentOS, llvm-15 installed by Spack,
I get the error

.../clang-15: symbol lookup error: ./pfn.so: undefined symbol: _ZN5clang6format12getLLVMStyleENS0_11FormatStyle12LanguageKindE

I could solve there it by explicitly linking to clangFormat.a (Cmakefile last line).
But then, it works ok on OS X, but on Ubuntu it fails as

pure virtual method called
terminate called without an active exception

Questions :

  • Can someone tell me how exactly an out-of-tree plugin should be compiled ?
    The documentation does not cover this.

  • Is add_llvm_library correct here ?

  • Why is the behaviour of clang different on some installation ???

There is a guideline for using LLVM as a dependency in out-of-tree projects:
https://llvm.org/docs/CMake.html#embedding-llvm-in-your-project

It does not use add_llvm_library! It uses the standard CMake facilities for declaring dependencies.

Thank you for the reference, but it does not solve my issue.

I can change the CMakeLists back to use the standard add_library (Cf below).
But the problem stays the same.

On at least one machine, I need to link to clangFormat,
not with other llvm installations.

I guess my main question now is:
to link a clang plugin, do I need to explicitly link to the clangXXX libraries
(clangAST, clangBasic, clangFormat, and so on), or not ?
To link a clang tool, I would, but for a (out of tree) clang plugin ?

# .... First part is the same as above

find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)

include_directories(${LLVM_INCLUDE_DIRS})
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
add_definitions(${LLVM_DEFINITIONS_LIST})

# Add library
add_library(pfn SHARED PrintFunctionNames.cpp )
set_target_properties(pfn PROPERTIES PREFIX "") # change the name to pfn.so, instead of libpfn.so
target_link_libraries(pfn PRIVATE "$<$<PLATFORM_ID:Darwin>:-undefined dynamic_lookup>")

To be on the safe side, I would and I do list the dependencies explicitly.

Maybe you have machines with LLVM as shared libraries and machines with LLVM as static archives?!?

  • If I try to systematically link to libclangFormat,
    it then fails on e.g.Ubuntu (cf quotation below).
    That is the issue: I do not know a priori if I need/can link explicitly or not,
    (which makes portability very hard …).

  • All machines have libclangXXX.a static libraries.