Search code examples
cmakeclangllvmninjaclang-tidy

Clang Tidy fails with error: no such file or directory: '/ARG'; did you mean '/ARG'?


I am currently in the process of changing my project from just msvc to clang-cl. The source code is almost exclusively c++17, apart from some thirdparty libs i am using.

The app complies and runs fine with clang-cl, but i am having problems with getting clang-tidy to work. One of the later goals will be to use in clang-tidy in a gitlab workflow, but currently i am trying to get it to work locally, so i am running commands from console.

I have read the and followed the instructions in the LLVM documentation, and i also checked that yes, the compile command are indeed exported into compile_commands.json. I also checked the CMakeCache and it also seems to be in order.

Tools used:

  • Visual Studio 2019 16.11.35
  • CMake version 3.28.3
  • Ninja version 1.9.0
  • Clang-cl (Clang bundled with VS 2019, clang version 12.0.0, Target: i686-pc-windows-msvc)
  • Clang-tidy (LLVM bundled with VS 2019, LLVM version 12.0.0, Optimized build, Default target: i686-pc-windows-msvc)

Target platform is Windows 11. Here is a Minimal Reproducible Example:

C:/Projects/MRE/CMakePresets.json

{
  "version": 8,
  "configurePresets": [
    {
      "name": "debug",
      "displayName": "Debug",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "architecture": {
        "value": "x86",
        "strategy": "external"
      },
      "cmakeExecutable": "C:\\CMake\\bin\\cmake.exe",
      "cacheVariables": {
        "CMAKE_MAKE_PROGRAM": "C:\\ninja\\ninja.exe",
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_C_COMPILER": "clang-cl",
        "CMAKE_C_FLAGS": "-m32",
        "CMAKE_CXX_COMPILER": "clang-cl",
        "CMAKE_CXX_FLAGS": "-m32",
        "CMAKE_EXE_LINKER_FLAGS": "/errorlimit:0 /debug:full",
        "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
      }
    },
    {
      "name": "release",
      "displayName": "Release",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "architecture": {
        "value": "x86",
        "strategy": "external"
      },
      "cmakeExecutable": "C:\\CMake\\bin\\cmake.exe",
      "cacheVariables": {
        "CMAKE_MAKE_PROGRAM": "C:\\ninja\\ninja.exe",
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_C_COMPILER": "clang-cl",
        "CMAKE_C_FLAGS": "-m32",
        "CMAKE_CXX_COMPILER": "clang-cl",
        "CMAKE_CXX_FLAGS": "-m32",
        "CMAKE_EXE_LINKER_FLAGS": "/errorlimit:0 /debug:full",
        "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "debug",
      "configurePreset": "debug"
    },
    {
      "name": "release",
      "configurePreset": "release"
    }
  ]
}

C:/Projects/MRE/CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
set(TARGET_NAME MRE_Main)

project(MRE VERSION 0.0.1 LANGUAGES C CXX)
set(SOURCES
    "Source/main.cpp"
)

add_executable(${TARGET_NAME} ${SOURCES})

C:/Projects/MRE/Source/main.cpp

#include <iostream>

int main() {
    std::cout << "Hello World!";
    return 0;
}

Building the project:

  1. Open Visual Studio 2019, and select 'Open a local folder'. Select C:/Projects/MRE folder, or wherever your copy is.
  2. Rewrite the path to CMake and Ninja in CMakePresets.json, to point to your installations.
  3. In VS, up at the toolbar press Project -> Configure MRE
  4. Build debug version of MRE_Main.exe

Alternatively you can build from console:

  1. Rewrite the path to CMake and Ninja in CMakePresets.json, to point to your installations.
  2. Open 'Developer Command Prompt for VS2019', and enter the following commands
  3. cd C:\Projects\MRE
  4. set PATH=C:\CMake\bin\;C:\ninja\;%PATH% (Rewrite paths to point to your installations)
  5. cmake --preset debug
  6. cmake --preset --build debug

Steps to reproduce:

  1. Open 'Developer Command Prompt for VS2019', and enter the following commands! If you built from console, and have the same console still open, then you can skip to step 4.
  2. cd C:\Projects\MRE
  3. set PATH=C:\CMake\bin\;C:\ninja\;%PATH% (Again, rewrite paths to point to your installations)
  4. clang-tidy -p=./build/debug/ Source/main.cpp

For me output is:

C:\Projects\MRE>clang-tidy -p=./build/debug/ Source/main.cpp
2 errors generated.
Error while processing C:\Projects\MRE\Source\main.cpp.
error: no such file or directory: '-fsyntax-only'; did you mean '-fsyntax-only'? [clang-diagnostic-error]
error: no such file or directory: '-resource-dir=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\lib\clang\12.0.0'; did you mean '-resource-dir=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\lib\clang\12.0.0'? [clang-diagnostic-error]
Found compiler error(s).

I checked on 2 different computers with the exact same tools installed, and i get the same results.

To me it seems the problem is an extra ';' at the end of complier arguments, but i can not find anyone with the same problem, and have no clue how to fix it.

I also checked for .clang-tidy files, and found none on the path. As a drastic measure, I deleted every single .clang-tidy file, no matter where it was. Still the same result. Not sure where the -fsyntax-only flag comes from.

Edit: Here is the requested clang-tidy --dump-config.

---
Checks:          'clang-diagnostic-*,clang-analyzer-*'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle:     none
User:            user
CheckOptions:
  - key:             llvm-else-after-return.WarnOnConditionVariables
    value:           '0'
  - key:             modernize-loop-convert.MinConfidence
    value:           reasonable
  - key:             modernize-replace-auto-ptr.IncludeStyle
    value:           llvm
  - key:             cert-str34-c.DiagnoseSignedUnsignedCharComparisons
    value:           '0'
  - key:             google-readability-namespace-comments.ShortNamespaceLines
    value:           '10'
  - key:             cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
    value:           '0'
  - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
    value:           '1'
  - key:             cert-dcl16-c.NewSuffixes
    value:           'L;LL;LU;LLU'
  - key:             google-readability-braces-around-statements.ShortStatementLines
    value:           '1'
  - key:             modernize-pass-by-value.IncludeStyle
    value:           llvm
  - key:             google-readability-namespace-comments.SpacesBeforeComments
    value:           '2'
  - key:             modernize-loop-convert.MaxCopySize
    value:           '16'
  - key:             cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
    value:           '1'
  - key:             modernize-use-nullptr.NullMacros
    value:           'NULL'
  - key:             llvm-qualified-auto.AddConstToQualified
    value:           '0'
  - key:             modernize-loop-convert.NamingStyle
    value:           CamelCase
  - key:             llvm-else-after-return.WarnOnUnfixable
    value:           '0'
  - key:             google-readability-function-size.StatementThreshold
    value:           '800'
...


Solution

  • The error is due to a bug in LLVM-12 and earlier relating to how it handles -- in the compile_commands.json command line. It was fixed in LLVM-13 or LLVM-14. It does not involve Visual Studio. It involves cmake only because cmake creates compile_commands.json with the -- switch that provokes the bug. The bug manifests on both Windows and Linux.

    Reproducer

    Create an empty source file, and a compile_commands.json that has a command line with -- before the file name. Then run clang-tidy on that file.

    Example showing the bug when using LLVM-11:

    $ cat test.cpp
    
    $ cat compile_commands.json
    [
    {
      "directory": "/home/scott/wrk/learn/clang/clang-tidy-syntax-only-bug",
      "command": "clang.exe -c -- test.cpp",
      "file": "/home/scott/wrk/learn/clang/clang-tidy-syntax-only-bug/test.cpp"
    }
    ]
    
    $ ~/opt/clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-16.04/bin/clang-tidy test.cpp
    2 errors generated.
    Error while processing /home/scott/wrk/learn/clang/clang-tidy-syntax-only-bug/test.cpp.
    error: no such file or directory: '-fsyntax-only'; did you mean '-fsyntax-only'? [clang-diagnostic-error]
    error: no such file or directory: '-resource-dir=/home/scott/opt/clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-16.04/lib/clang/11.0.1'; did you mean '-resource-dir=/home/scott/opt/clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-16.04/lib/clang/11.0.1'? [clang-diagnostic-error]
    Found compiler error(s).
    Exit 1
    

    Example showing that it works with LLVM-14, executed right after the previous command:

    $ ~/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang-tidy test.cpp
    (No output, exit code 0.)
    

    Solution

    Either upgrade to LLVM-14 or later, or remove the -- in the command line in compile_commands.json (and arrange to keep removing it despite cmake putting it there).

    As it happens, VS 2019 ships with LLVM-12, while VS 2022 ships with LLVM-17, so upgrading to VS 2022 also solves the problem when using its clang-tidy.

    Existing bug report

    This bug was filed against LLVM github as Issue 49639: Clang-Tidy reports wrong command line argument even though it is not specified at all back in May 2021. However, the cause couldn't be determined and it was eventually closed as invalid.

    Where does -fsyntax-only come from?

    It comes from clang-tidy itself, in Tooling.cpp:

    static std::vector<std::string>
    getSyntaxOnlyToolArgs(const Twine &ToolName,
                          const std::vector<std::string> &ExtraArgs,
                          StringRef FileName) {
      std::vector<std::string> Args;
      Args.push_back(ToolName.str());
      Args.push_back("-fsyntax-only");
      Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
      Args.push_back(FileName.str());
      return Args;
    }
    

    The -resource-dir argument also comes from the same file:

    static void injectResourceDir(CommandLineArguments &Args, const char *Argv0,
                                  void *MainAddr) {
      // Allow users to override the resource dir.
      for (StringRef Arg : Args)
        if (Arg.startswith("-resource-dir"))
          return;
    
      // If there's no override in place add our resource dir.
      Args.push_back("-resource-dir=" +
                     CompilerInvocation::GetResourcesPath(Argv0, MainAddr));
    }
    

    These options were evidently being placed after the -- that was found in compile_commands.json, and therefore interpreted as input file names by the rest of the driver infrastructure. That causes the err_drv_no_such_file_with_suggestion error message to be printed:

    def err_drv_no_such_file_with_suggestion : Error<
      "no such file or directory: '%0'; did you mean '%1'?">;
    

    Which change fixed it?

    I don't know. I couldn't find any other issues that seemed related (PR 66553 is in the vicinity but is too recent), and didn't dig deeper than what's reported above to find the bug itself.