Search code examples
c++clangcode-generationclang++libclang

Get relative include path for file with clang tooling


I'm using clang tooling to auto generate some code from an existing code base.

In some cases I have an enum type that I want to use, so I want to include it. I'm able to get the path to the file to include by doing:

const EnumType *enumType = ...;
auto defineFileLoc =
        enumType->getDecl()->getDefinition()->getLocation();
const auto defineFilePath =
        sourceManager.getFilename(sourceManager.getExpansionLoc(defineFileLoc))
            .str();

This produces the full path of the file. I can include the full path, but this is ugly and not portable, so I was wondering if there is a way to generate the relative include path of the file.

I was thinking one way would be to get a list of the directories in the translation unit include path and then see if I can find something relative to that, but I am not able to find a way to get this with clang tooling. I use marchers, so the entire context I have for the specific match is a clang::ast_matchers::MatchFinder::MatchResult object.

Is there a way to get the list of directories in the include path for a specific translation unit? Or, is there a better way to do what I want?


Solution

  • The approach suggested in the question is right:

    get a list of the directories in the translation unit include path and then see if I can find something relative to that

    The header search path is stored in the HeaderSearchOptions class, specifically the UserEntries field. That has a vector of HeaderSearchOptions::Entry, each of which has a std::string Path.

    The HeaderSearchOptions can be retrieved from the CompilerInvocation using getHeaderSearchOpts.

    Getting the CompilerInvocation

    The procedure for getting a CompilerInvocation varies depending on exactly how your tool starts up.

    In my code, I use createInvocation() to get a CompilerInvocation, then use ASTUnit::LoadFromCompilerInvocation to run the parser and get an AST.

    If you are using ClangTool::buildASTs, you'll have an ASTUnit and can call its getHeaderSearchOpts method.

    If you are using ClangTool::runInvocation then your callback is handed a CompilerInvocation (which has getHeaderSearchOpts, mentioned above).

    If however you are also using newFrontendActionFactory (which is what the tutorial does), then you need to modify the call site to also pass a SourceFileCallbacks whose handleBeginSource retrieves the CompilerInvocation from the CompilerInstance by calling getInvocation.

    For example, define a class like:

    // Intercept `handleBeginSource` so we can get the invocation.
    class MySourceFileCallbacks : public clang::tooling::SourceFileCallbacks {
    public:
      virtual bool handleBeginSource(clang::CompilerInstance &ci)
      {
        llvm::outs() << "in handleBeginSource, hasInvocation: "
                     << ci.hasInvocation() << "\n";
    
        clang::CompilerInvocation &invoc = ci.getInvocation();
        clang::HeaderSearchOptions &hso = invoc.getHeaderSearchOpts();
        for (clang::HeaderSearchOptions::Entry const &entry : hso.UserEntries) {
          llvm::outs() << "  " << entry.Path << "\n";
        }
    
        return true;
      }
    };
    

    then modify the call to newFrontendActionFactory to be:

    MySourceFileCallbacks sfCallbacks;  // <--- add
    return Tool.run(newFrontendActionFactory(&finder, &sfCallbacks).get());
                                                   // ^^^^^^^^^^^^ add
    

    Constructing a relative path

    Given a file name as a std::string and a HeaderSearchOptions, the procedure for creating a relative path is mostly straightforward. However, you may want to take account of which Group each Entry is in, reflecting how it arose as a search path.

    Below is the code I use to do this, producing as output a complete #include directive:

    string ClangUtil::getIncludeSyntax(
      clang::HeaderSearchOptions const &headerSearchOptions,
      string const &fname,
      int * NULLABLE userEntryIndex)
    {
      // Avoid having to awkwardly check the pointer below.
      int dummy = 0;
      if (!userEntryIndex) {
        userEntryIndex = &dummy;
      }
    
      // Use naive ordered search.  This isn't 100% correct in certain
      // scenarios (e.g., involving -iquote), but should suffice for my
      // purposes.
      *userEntryIndex = 0;
      for (auto const &e : headerSearchOptions.UserEntries) {
        if (beginsWith(fname, e.Path)) {
          // Get the name without the path prefix.  The path is assumed to
          // end with a directory separator, and that is stripped from
          // 'fname' too.
          string relative = fname.substr(e.Path.size() + 1);
    
          if (e.Group == clang::frontend::Angled) {
            // Perhaps ironically, paths specified with the -I option are
            // classified by clang as "angled", but conventionally the
            // names in such directories are referenced using quotes.
            return string("\"") + relative + "\"";
          }
          else {
            return string("<") + relative + ">";
          }
        }
    
        ++(*userEntryIndex);
      }
    
      if (beginsWith(fname, "./")) {
        *userEntryIndex = -1;
    
        // Drop the "." path component.
        string trimmed = fname.substr(2);
        return string("\"") + trimmed + "\"";
      }
    
      // Not found among the search paths, use the absolute name with quotes.
      *userEntryIndex = -2;
      return string("\"") + fname + "\"";
    }
    

    The above code is part of my print-clang-ast repository, whose original purpose was to print AST details, but also functions as a collection of clang utilities.