Search code examples
c++clang-tidy

clang-tidy-10 and compile_commands.json does not support relative paths


There are various posts about this - but I can't seem to find a definitive answer. Some people are saying it works and others not, and yet others saying its tools / IDEs that are the issue.

So I made as small an example as I could. Here are the two files I have:

compile_commands.json

[
    {
        "arguments": [ "gcc", "test.cpp" ],
        "directory": "/home/user/development/sandbox/comp_commands",
        "file": "test.cpp"
    }
]

test.cpp

bool is_it()
{
    // no return value - give clang warning
}

Now if I run the command clang-tidy test.cpp in the same directory as the files I get:

[user] comp_commands$ clang-tidy test.cpp 
1 warning generated.
test.cpp:4:1: warning: non-void function does not return a value [clang-diagnostic-return-type]
}

... as expected. However if I change the line in the compile_commands.json directory to:

        "directory": ".",

Then I get the output:

[user] comp_commands$ clang-tidy test.cpp 
Skipping /home/user/development/sandbox/comp_commands/test.cpp. Compile command not found.

here "MaskRay"s answer suggests this is fine/works. Since I am using no IDE, just clang-tidy directly- it can't be the IDE...

Is it possible to use relative paths in the compile_commands.json file? - I am generating this file via a script and can use either, but I specifically want it to be relative paths.


Solution

  • Short answer

    No, the directory in compile_commands.json cannot be relative. It must be absolute to work with clang-tidy.

    What the spec says

    The specification for compile_commands.json says:

    • directory: The working directory of the compilation. All paths specified in the command or file fields must be either absolute or relative to this directory.

    That is not very explicit, but as it acknowledges the possibility of relative paths in other attributes, while saying nothing about a relative directory, this is weak evidence that the path must be absolute.

    What the source says

    The code that prints Compile command not found. is in ClangTool::run:

    std::vector<CompileCommand> CompileCommandsForFile =
        Compilations.getCompileCommands(File);
    if (CompileCommandsForFile.empty()) {
      llvm::errs() << "Skipping " << File << ". Compile command not found.\n";
      FileSkipped = true;
      continue;
    }
    

    This is calling JSONCompilationDatabase::getCompileCommands, which consults the MatchTrie member, which is built in JSONCompilationDatabase::parse, and includes these key lines:

    if (!Directory) {
      ErrorMessage = "Missing key: \"directory\".";
      return false;
    }
    SmallString<8> FileStorage;
    StringRef FileName = File->getValue(FileStorage);
    SmallString<128> NativeFilePath;
    if (llvm::sys::path::is_relative(FileName)) {
      SmallString<8> DirectoryStorage;
      SmallString<128> AbsolutePath(                       // (1)
          Directory->getValue(DirectoryStorage));
      llvm::sys::path::append(AbsolutePath, FileName);     // (2)
      llvm::sys::path::native(AbsolutePath, NativeFilePath);
    } else {
      llvm::sys::path::native(FileName, NativeFilePath);
    }
    

    Although this code is willing to handle a relative FileName, the Directory value is simply assumed to be absolute at (1), as its value (as a YAML string) is concatenated with FileName at (2) and the result treated as absolute from then on.

    When the input file name test.cpp is passed on the command line, the tooling infrastructure turns that into the absolute path /home/user/development/sandbox/comp_commands/test.cpp, then compares it as a string (via MatchTrie) to the path built by the above code, which will be ./test.cpp and hence not match.

    What about ccls?

    ccls is a separate project that happens to also consume the compile_commands.json format, but with an extension to allow relative directory attributes.

    That extension is not present in clang-tidy.