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?
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
.
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
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.