Clang tooling preprocess file

I’m using clang tooling library and I want to do the simplest thing possible - take a source code and preprocess it.


std::string preprocess(std::string code);

After several hours of different attempts and searching the Internet, my code looks like this:

std::string preprocess(std::string code) {
  namespace ct = clang::tooling;
  llvm::vfs::InMemoryFileSystem fs;
  fs.addFile("A.cpp", time(0), llvm::MemoryBuffer::getMemBuffer(code));
  fs.addFile("B.cpp", time(0), llvm::MemoryBuffer::getMemBuffer(""));
  ct::FixedCompilationDatabase db("", {});
  ct::ClangTool tool(db
                     , {"A.cpp"}
                     , std::make_shared<clang::PCHContainerOperations>()
                     , {&fs});
  auto add_args = [](const ct::CommandLineArguments& _args,
                     llvm::StringRef Filename) -> ct::CommandLineArguments {
    auto args = _args;
    args.push_back("-std=c++20");
    args.push_back("-P");
    args.push_back("-E");
    args.push_back("-o");
    args.push_back("B.cpp");
    return args;
  };
  tool.appendArgumentsAdjuster(add_args);
  auto factory = ct::newFrontendActionFactory<clang::PreprocessOnlyAction>();
  int result = tool.run(&*factory);
  if (result != 0)
    return {};
  llvm::ErrorOr buf = fs.getBufferForFile("B.cpp");
  if (!buf)
    return {};
  return std::string(buf->get()->getBuffer());
}

And its not working. No errors, but no output too. How even to check what compiler invocation sees in A.cpp?

I had to study the source code inside the clang, in the end the implementation looks like this:

#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/CompilerInstance.h>

// its a copy from clang/Frontend/FrontendActions.h/cpp, but:
// * with output stream customization
// * without modules support (i dont need it)
// * without binary mode, i dont care about \r\n and \n on different systems
struct print_preprocessed_action : clang::PreprocessorFrontendAction {
  llvm::raw_ostream& out;

  explicit print_preprocessed_action(llvm::raw_ostream& out [[clang::lifetimebound]]) : out(out) {}
  bool hasPCHSupport() const override { return false; }
  void ExecuteAction() override {
    auto& CI = getCompilerInstance();
    clang::DoPrintPreprocessedInput(CI.getPreprocessor(), &out, CI.getPreprocessorOutputOpts());
  }
};

struct printer_factory : clang::tooling::FrontendActionFactory {
  llvm::raw_ostream& out;

  explicit printer_factory(llvm::raw_ostream& out [[clang::lifetimebound]]) : out(out) {}
  std::unique_ptr<clang::FrontendAction> create() override {
    return std::unique_ptr<clang::FrontendAction>(new print_preprocessed_action(out));
  }
};

bool preprocess(std::string_view code, llvm::raw_ostream& out) {
  // annoying asserts prevent stack creation in any case, even valid cases
  llvm::IntrusiveRefCntPtr mfs(new llvm::vfs::InMemoryFileSystem);
  mfs->addFile("A.cpp", 0, llvm::MemoryBuffer::getMemBuffer(code));
  printer_factory factory(out);
  clang::tooling::ToolInvocation invocation(
      {"PP-tool", "-std=c++20", "-E", "-P", "A.cpp"}, &factory,
      new clang::FileManager(clang::FileSystemOptions(), std::move(mfs)),
      std::make_shared<clang::PCHContainerOperations>());
  return invocation.run();
}

And usage:


  std::string result;
  llvm::raw_string_ostream out(result);
  preprocess(R"(
    #define merge_all_expand2(a, b) a ## b
    #define merge_all_expand(a, b) merge_all_expand2(a, b)
    #define concat_all(head, ...) merge_all_expand(head, __VA_OPT__(__THIS_MACRO__(__VA_ARGS__)))
    0: concat_all(aa, bb, cc)
    1: [concat_all()])",
             out);
  std::cout << result;