clangllvmlibtoolingcompilation-database

How to obtain the compilation command in a tool based on libtooling


I have built a custom tool (based on libtooling) for transforming source code. I used clang's own tutorial and managed to run my own custom FrontendAction. I now need to parse the compile flags that were provided to the tool (on the command line) to customise the transformation. However no matter what I do the CompilationDatabase seems to always return an empty list of compile commands.

This is the sample code:

#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"

static cl::OptionCategory MyToolCategory("CustomTool");
static cl::extrahelp MoreHelp("\nMore help text...");

int main(int argc, const char **argv)
{
    CommonOptionsParser op(argc, argv, MyToolCategory);                     // Parse the command-line arguments
    CompilationDatabase &Compilations = op.getCompilations();

    for (const auto &cmd: Compilations.getAllCompileCommands()) { //<---- this list is always empty!!!
        std::cout << "filename: " << cmd.Filename;
        // Do stuff with compile flags
    }

    ClangTool Tool(Compilations, op.getSourcePathList());           // Create a new Clang Tool instance (a LibTooling environment)
    return Tool.run(newFrontendActionFactory<MyASTFrontendAction>().get()); // Run custom Frontendaction
}

This is how I invoke the tool:

./custom-tool sample.c -- -I/some/include/path -std=gnu11

I want to be able to get the command line flags -I/some/include/path and -std=gnu11.


Solution

  • Compilation database

    Your compilation database (compile_commands.json) is either empty or non-existent. All compile commands are technically all of the commands in the database and, thus, empty.

    How does clang then uses the options that I provide

    Clang creates an ad-hoc wrapper around the CompilationDatabase object that adds those options to whatever file this CompilationDatabase has been asked about, even when this file is not in the database itself, and even when it doesn't exist.

    How can I obtain that information then

    Instead of iterating compilation commands, you can iterate over the source list and ask (aka getCompileCommands) the database to give the commands for that particular file.

    Working demo

    #include "clang/Tooling/CommonOptionsParser.h"
    
    using namespace clang;
    using namespace tooling;
    using namespace llvm;
    
    static cl::OptionCategory MyToolCategory("CustomTool");
    static cl::extrahelp MoreHelp("\nMore help text...");
    
    void print(const std::vector<CompileCommand> &Commands) {
      if (Commands.empty()) {
        return;
      }
      for (auto opt : Commands[0].CommandLine) {
        llvm::errs() << "\t" << opt << "\n";
      }
    }
    
    int main(int argc, const char **argv) {
      CommonOptionsParser OP(argc, argv,
                             MyToolCategory);
      CompilationDatabase &Compilations = OP.getCompilations();
    
      for (const auto &File :
           OP.getSourcePathList()) {
        llvm::errs() << "filename: " << File << "\n";
        llvm::errs() << "opts:\n";
        auto Commands = Compilations.getCompileCommands(File);
        print(Commands);
      }
    
      auto CommandsForFakeFile = Compilations.getCompileCommands("baloney.c");
      llvm::errs() << "fake filename: baloney.c\n";
      print(CommandsForFakeFile);
    
      return 0;
    }
    

    The code above generates the following output:

    > ./myTool test.cpp -- -I/some/include/path -std=gnu11
    filename: test.cpp
    opts:
        $PATH_TO_MYTOOL_EXEC/clang-tool
        -I/some/include/path
        -std=gnu11
        test.cpp
    fake filename: baloney.c
        $PATH_TO_MYTOOL_EXEC/clang-tool
        -I/some/include/path
        -std=gnu11
        baloney.c
    

    I hope this information will be useful! Happy hacking with Clang!