diff --git a/CMakeLists.txt b/CMakeLists.txt index bf50018a..82436a0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,10 +104,29 @@ endif() if(MP_ENABLE_CLANG_TIDY OR MP_ENABLE_IWYU) # Workaround for nix from https://gitlab.kitware.com/cmake/cmake/-/issues/20912#note_793338 - # Nix injects header paths via $NIX_CFLAGS_COMPILE; CMake tags these as - # CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES and omits them from the compile - # database, so clang-tidy, which ignores $NIX_CFLAGS_COMPILE, can't find capnp - # headers. Setting them as standard passes them to clang-tidy. + # + # On non-nix systems, compilers find C++ standard library headers in standard + # locations like /usr/include, and find dependency headers through -I flags on + # the compiler command line. On nix neither is true: compiler binaries are + # wrapped by scripts that inject the standard library header locations + # themselves, and dependency header locations are passed to the wrapper through + # $NIX_CFLAGS_COMPILE instead of on the command line. So CMake records all these + # paths as CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES and leaves them off the + # command lines it writes to the compile database. + # + # This normally does not matter, because analysis tools have their own wrapper + # scripts that re-add these paths the same way. But the nixpkgs + # include-what-you-use wrapper only re-adds -isystem flags and drops the + # -cxx-isystem flags used for the C++ standard library, so IWYU cannot find + # standard headers like . Setting the implicit include directories as + # standard directories puts them back on every command line, which is what lets + # the buggy IWYU wrapper find them. + # + # This workaround is temporary: + # - clang-tidy no longer needs it: the nixpkgs clang-tools wrapper was fixed in + # https://github.com/NixOS/nixpkgs/pull/462747. + # - IWYU will stop needing it once its wrapper is fixed the same way (see + # https://github.com/bitcoin-core/libmultiprocess/pull/238#discussion_r3342093865). set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) endif() diff --git a/shell.nix b/shell.nix index 2d115fea..1f61698c 100644 --- a/shell.nix +++ b/shell.nix @@ -53,6 +53,23 @@ let })).override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; }); clang = if enableLibcxx then llvm.libcxxClang else llvm.clang; clang-tools = llvm.clang-tools.override { inherit enableLibcxx; }; + # IWYU parses source files with its own embedded clang frontend, locating + # standard library headers through CPATH/CPLUS_INCLUDE_PATH variables set by + # its nixpkgs wrapper script: + # https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/analysis/include-what-you-use/wrapper + # The wrapper derives those variables from the clang recorded in the + # derivation's `clang` attribute, which defaults to the toolchain IWYU was + # built against (libstdc++-flavored on Linux), not the toolchain this shell + # uses. Rebind it to this shell's compiler so IWYU resolves the same + # standard library the build uses. + # + # Mixing pkgs and crossPkgs here is intentional: IWYU comes from pkgs + # because it runs on the build machine, while `clang` comes from crossPkgs + # (via llvm) so that in a cross shell IWYU is baked with the cross + # toolchain's target headers — what it needs to analyze a cross build. + include-what-you-use = pkgs.include-what-you-use.overrideAttrs (old: { + inherit clang; + }); cmakeHashes = { "3.12.4" = "sha256-UlVYS/0EPrcXViz/iULUcvHA5GecSUHYS6raqbKOMZQ="; }; @@ -71,16 +88,31 @@ in crossPkgs.mkShell { ]; nativeBuildInputs = with pkgs; [ cmakeBuild - include-what-you-use ninja ] ++ lib.optional (gcc != null) gcc ++ lib.optionals (!minimal) [ - clang + # List clang-tools before clang so its wrapped tools take PATH priority + # (the first package in the list wins). Both packages provide the same + # tools (clangd, clang-tidy, clang-check, ...), but the clang package's + # copies are unwrapped and cannot find standard library headers like + # , while the clang-tools copies are wrapper scripts that add + # the C and C++ standard library include paths. + # https://web.archive.org/web/20260311024938/https://blog.kotatsu.dev/posts/2024-04-10-nixpkgs-clangd-missing-headers/ + # https://github.com/NixOS/nixpkgs/issues/76486 clang-tools + clang + include-what-you-use ]; CC = if gcc == null then null else "${gcc}/bin/gcc"; CXX = if gcc == null then null else "${gcc}/bin/g++"; - # Tell IWYU where its libc++ mapping lives + # Tell IWYU where its libc++ mapping file lives. libcxx.imp maps + # libc++-internal detail headers (e.g. <__vector/vector.h>) to the public + # headers IWYU should suggest instead. IWYU only applies mapping tables + # compiled into its binary unless a mapping file is passed explicitly — it + # does not discover the libcxx.imp shipped alongside the headers it parses — + # so CMakeLists.txt forwards this variable via -Xiwyu --mapping_file. Taking + # the file from llvm.libcxx keeps it consistent with the headers IWYU parses + # via the include-what-you-use override above. IWYU_MAPPING_FILE = if enableLibcxx then "${llvm.libcxx.dev}/include/c++/v1/libcxx.imp" else null; }