https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/206000
>From 305cfad694a8fcf86692ec5170005286bcdbfa8f Mon Sep 17 00:00:00 2001 From: Jeff Bailey <[email protected]> Date: Thu, 25 Jun 2026 15:45:14 +0100 Subject: [PATCH 1/4] [clang-tidy] Fix llvm-header-guard for arbitrary worktree names The llvm-header-guard check failed to find the project root when working in git worktrees with names other than llvm-project or llvm. When `/llvm-project/` is not in the path, we now walk up the directory tree to find the project root by checking for a `.git` file/directory. The path is then canonicalized to start with `/llvm-project/` before calculating the header guard. Assisted-by: Automated tooling, human reviewed. --- .../clang-tidy/llvm/HeaderGuardCheck.cpp | 42 ++++++++- .../clang-tidy/llvm/HeaderGuardCheck.h | 3 + .../unittests/clang-tidy/LLVMModuleTest.cpp | 90 +++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp index ef8b6b1dfb8f7..f34ce4e90505f 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -8,6 +8,7 @@ #include "HeaderGuardCheck.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" namespace clang::tidy::llvm_check { @@ -16,9 +17,48 @@ LLVMHeaderGuardCheck::LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) : HeaderGuardCheck(Name, Context) {} +// Attempt to find the root of the LLVM project monorepo by walking up the +// directory tree from Filename and looking for a ".git" file/directory. +// This allows us to find the root even when working in git worktrees with +// arbitrary names. +std::string +LLVMHeaderGuardCheck::findLLVMProjectRoot(StringRef Filename) const { + SmallString<256> Path = Filename; + SmallString<256> Parent = llvm::sys::path::parent_path(Path); + while (!Parent.empty() && Parent != Path) { + Path = Parent; + + // Check for .git (file or directory) which indicates the root of the + // git repository or worktree. + SmallString<256> GitPath = Path; + llvm::sys::path::append(GitPath, ".git"); + if (llvm::sys::fs::exists(GitPath)) + return std::string(Path); + + Parent = llvm::sys::path::parent_path(Path); + } + return ""; +} + std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, StringRef OldGuard) { - std::string Guard = tooling::getAbsolutePath(Filename); + const std::string AbsolutePath = tooling::getAbsolutePath(Filename); + std::string Guard = AbsolutePath; + + // Check for "/llvm-project/" using a path with normalized slashes to ensure + // Windows paths (which use backslashes) are matched correctly. + const std::string CanonicalPath = + llvm::sys::path::convert_to_slash(AbsolutePath); + if (!StringRef(CanonicalPath).contains("/llvm-project/")) { + const std::string Root = findLLVMProjectRoot(AbsolutePath); + if (!Root.empty()) { + StringRef RelativePath = StringRef(AbsolutePath).substr(Root.size()); + if (!RelativePath.empty() && + llvm::sys::path::is_separator(RelativePath.front())) + RelativePath = RelativePath.drop_front(); + Guard = ("/llvm-project/" + RelativePath).str(); + } + } // When running under Windows, need to convert the path separators from // `\` to `/`. diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h index cfc920bb85e23..20d8014710531 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h @@ -22,6 +22,9 @@ class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { bool shouldSuggestEndifComment(StringRef Filename) override { return false; } std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; + +protected: + virtual std::string findLLVMProjectRoot(StringRef Filename) const; }; } // namespace clang::tidy::llvm_check diff --git a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp index e1338c6ef1d5f..9638a116ec904 100644 --- a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -60,6 +60,24 @@ runHeaderGuardCheckWithEndif(StringRef Code, const Twine &Filename, std::optional<StringRef> ExpectedWarning) { return runCheck<WithEndifComment>(Code, Filename, std::move(ExpectedWarning)); } +static std::string GlobalMockRoot; + +struct MockLLVMHeaderGuardCheck : public LLVMHeaderGuardCheck { + MockLLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : LLVMHeaderGuardCheck(Name, Context) {} + std::string findLLVMProjectRoot(StringRef Filename) const override { + return GlobalMockRoot; + } +}; + +static std::string +runMockHeaderGuardCheck(StringRef Code, const Twine &Filename, + std::optional<StringRef> ExpectedWarning, + std::string MockRoot) { + GlobalMockRoot = MockRoot; + return runCheck<MockLLVMHeaderGuardCheck>(Code, Filename, + std::move(ExpectedWarning)); +} } // namespace TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { @@ -297,6 +315,78 @@ TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { "", "\\\\?\\C:\\llvm-project\\clang-tools-extra\\clangd\\foo.h", StringRef("header is missing header guard"))); #endif + + // Test path outside any git repo (real root detection should fail and not + // hang). + EXPECT_EQ("#ifndef NON_EXISTENT_DIRECTORY_123_X_H\n" + "#define NON_EXISTENT_DIRECTORY_123_X_H\n" + "\n" + "\n" + "#endif\n", + runHeaderGuardCheck("", "/non_existent_directory_123/x.h", + StringRef("header is missing header guard"))); + + // Test single-component absolute path (loop boundary, doesn't hang). + EXPECT_EQ("#ifndef X_H\n" + "#define X_H\n" + "\n" + "\n" + "#endif\n", + runHeaderGuardCheck("", "/x.h", + StringRef("header is missing header guard"))); +} + +TEST(LLVMHeaderGuardCheckTest, MockedProjectRoot) { + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/my/mock/root/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "/my/mock/root")); + + EXPECT_EQ( + "#ifndef SOME_OTHER_PATH_LIBC_SRC_STRING_STRLEN_H\n" + "#define SOME_OTHER_PATH_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck("", "/some/other/path/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "")); + + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/path/to/llvm-project/libc/src/string/strlen.h", + StringRef("header is missing header guard"), + "/different/mock/root")); + + // Test when root has a trailing slash. + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/my/mock/root/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "/my/mock/root/")); + +#ifdef WIN32 + // Test Windows-style paths through the new code path (mocked root). + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "C:\\my\\mock\\root\\libc\\src\\string\\strlen.h", + StringRef("header is missing header guard"), + "C:\\my\\mock\\root")); +#endif } TEST(IncludeOrderCheck, GTestHeaders) { >From 0e8ec797f94ee934255a289e5b30f226589c70cc Mon Sep 17 00:00:00 2001 From: Jeff Bailey <[email protected]> Date: Fri, 26 Jun 2026 15:17:02 +0100 Subject: [PATCH 2/4] [clang-tidy] Guard non-Windows absolute path tests in LLVMModuleTest The tests for paths outside any git repo and single-component absolute paths use absolute paths without 'llvm-project' or 'llvm' in them. On Windows, getAbsolutePath prepends the drive letter to these paths (since they start with '/' but lack drive letter, making them root-relative). This drive letter is not truncated by the legacy logic (as they don't contain 'llvm-project' or 'llvm'), resulting in guards starting with the drive letter (e.g. 'C_'), which fails the assertions. Guarded these tests with #ifndef WIN32 to skip them on Windows, as we already have MockedProjectRoot tests to verify Windows-style path handling. --- clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp index 9638a116ec904..3614fdb37eef0 100644 --- a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -316,6 +316,7 @@ TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { StringRef("header is missing header guard"))); #endif +#ifndef WIN32 // Test path outside any git repo (real root detection should fail and not // hang). EXPECT_EQ("#ifndef NON_EXISTENT_DIRECTORY_123_X_H\n" @@ -334,6 +335,7 @@ TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { "#endif\n", runHeaderGuardCheck("", "/x.h", StringRef("header is missing header guard"))); +#endif } TEST(LLVMHeaderGuardCheckTest, MockedProjectRoot) { >From dc1aab6ccc7b67bbf8018c091f507759c1317baa Mon Sep 17 00:00:00 2001 From: Jeff Bailey <[email protected]> Date: Fri, 26 Jun 2026 15:55:13 +0100 Subject: [PATCH 3/4] [clang-tidy] Use _WIN32 for new Windows guards (#206000) Replaced the legacy WIN32 macro with the standard compiler-defined _WIN32 macro for the new Windows-specific guards in LLVMModuleTest.cpp. This ensures that the non-Windows-compatible absolute path tests are correctly skipped on Windows CI bots. Assisted-by: Automated tooling, human reviewed. --- clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp index 3614fdb37eef0..d2ec4bcb145e6 100644 --- a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -316,7 +316,7 @@ TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { StringRef("header is missing header guard"))); #endif -#ifndef WIN32 +#ifndef _WIN32 // Test path outside any git repo (real root detection should fail and not // hang). EXPECT_EQ("#ifndef NON_EXISTENT_DIRECTORY_123_X_H\n" @@ -377,7 +377,7 @@ TEST(LLVMHeaderGuardCheckTest, MockedProjectRoot) { "", "/my/mock/root/libc/src/string/strlen.h", StringRef("header is missing header guard"), "/my/mock/root/")); -#ifdef WIN32 +#ifdef _WIN32 // Test Windows-style paths through the new code path (mocked root). EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" >From bf0b723e64faa6466b20ea3bd059fa7498ae21d6 Mon Sep 17 00:00:00 2001 From: Jeff Bailey <[email protected]> Date: Fri, 26 Jun 2026 16:58:15 +0100 Subject: [PATCH 4/4] [clang-tidy] Resolve mock root to absolute path (#206000) Updated runMockHeaderGuardCheck to resolve the mock root to an absolute path using tooling::getAbsolutePath. This resolves the drive letter prefix mismatch (e.g. C:) on Windows CI, allowing the Unix-style mocked tests to pass on Windows without being skipped. Assisted-by: Automated tooling, human reviewed. --- clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp index d2ec4bcb145e6..df5d6744a9b37 100644 --- a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -74,7 +74,7 @@ static std::string runMockHeaderGuardCheck(StringRef Code, const Twine &Filename, std::optional<StringRef> ExpectedWarning, std::string MockRoot) { - GlobalMockRoot = MockRoot; + GlobalMockRoot = MockRoot.empty() ? "" : tooling::getAbsolutePath(MockRoot); return runCheck<MockLLVMHeaderGuardCheck>(Code, Filename, std::move(ExpectedWarning)); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
