https://github.com/jansvoboda11 updated 
https://github.com/llvm/llvm-project/pull/217061

>From b275c877b6b79e5c42423e20f3c582a2026b4172 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <[email protected]>
Date: Tue, 18 Aug 2026 18:16:55 +0200
Subject: [PATCH] The search-path analogue of -fmodules-ignore-macro=: the
 named header search path is dropped from the context hash of every module and
 physically removed from every module-build invocation, and kept only for the
 translation unit itself.

The motivating case is a build system that hands every compile a per-target, 
per-architecture generated-headers directory (Xcode's DerivedSources, say) even 
though only a handful of TUs include anything from it. Under 
-fmodules-strict-context-hash those directories feed every module's context 
hash, so an SDK module like Foundation is rebuilt once per target rather than 
once per architecture. Ignoring the directories for modules lets those builds 
share a single module cache while the TUs that really do need them keep working.

This is only sound when no module needs the path: a lookup that would have 
resolved through an ignored path simply fails, exactly as ignoring a macro a 
module needs silently changes what that module sees. An empty path is rejected.

The context hash filters UserEntries through make_filter_range and still hands 
the result to HashBuilder::addRange, which hashes the element count first; 
hashing the survivors individually instead would change every strict context 
hash even when nothing is ignored, invalidating existing module caches. 
Verified that a TU's module cache directory is unchanged from before this patch 
when the option is not used. Keeping the entries out of the hash is also what 
keeps the "Module hash mismatch!" assertion in cloneForModuleCompileImpl() 
honest, since the clone physically drops them.

The dependency scanner prunes in makeCommonInvocationForModuleBuild, on the 
common invocation every module descends from, so the pruning is uniform and the 
search-path bit indices optimizeHeaderSearchOpts() works with still line up 
with what the real implicit builds used.

That alignment is what modules-ignore-search-path-optimize-args.c pins down: it
scans with -optimize-args=header-search, puts the ignored path first so dropping
it shifts every later index, and builds each resulting command line. Pruning 
only
the module clone and not the common invocation makes a module keep the wrong -I
entry rather than fail loudly -- the size guard in optimizeHeaderSearchOpts()
cannot catch it, since its BitVector is constructed from the entry count it is
compared against.
---
 clang/docs/ReleaseNotes.md                    |   8 ++
 clang/include/clang/Lex/HeaderSearchOptions.h |   7 +-
 clang/include/clang/Options/Options.td        |   3 +
 .../DependencyScanning/ModuleDepCollector.cpp |  12 ++
 clang/lib/Driver/ToolChains/Clang.cpp         |   2 +-
 clang/lib/Frontend/CompilerInstance.cpp       |  11 ++
 clang/lib/Frontend/CompilerInvocation.cpp     |  21 ++-
 .../modules-context-hash-ignore-search-path.c | 130 +++++++++++++++++
 ...modules-ignore-search-path-optimize-args.c | 132 ++++++++++++++++++
 .../test/Driver/modules-ignore-search-path.c  |   7 +
 .../test/Modules/modules-ignore-search-path.c | 103 ++++++++++++++
 11 files changed, 432 insertions(+), 4 deletions(-)
 create mode 100644 
clang/test/ClangScanDeps/modules-context-hash-ignore-search-path.c
 create mode 100644 
clang/test/ClangScanDeps/modules-ignore-search-path-optimize-args.c
 create mode 100644 clang/test/Driver/modules-ignore-search-path.c
 create mode 100644 clang/test/Modules/modules-ignore-search-path.c

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f5f9958543e34..3baedba21ed95 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -209,6 +209,14 @@ features cannot lower the translation-unit ABI level;
   `-fsanitize=shadow-call-stack`. The selected register must also be reserved
   with the matching `-ffixed-<reg>`.
 
+- Added `-fmodules-ignore-search-path=<path>`, the search-path counterpart to
+  `-fmodules-ignore-macro=<macro>`: the path is dropped from the context hash 
of
+  every module and physically removed from every module build, and kept only 
for
+  the translation unit itself. This lets builds that differ only in a 
per-target
+  generated-headers directory share one module cache, and is only sound when no
+  module needs the path -- a lookup that would have resolved through an ignored
+  path simply fails.
+
 ### Deprecated Compiler Flags
 
 ### Modified Compiler Flags
diff --git a/clang/include/clang/Lex/HeaderSearchOptions.h 
b/clang/include/clang/Lex/HeaderSearchOptions.h
index fd46ea223bae1..a521f38e28233 100644
--- a/clang/include/clang/Lex/HeaderSearchOptions.h
+++ b/clang/include/clang/Lex/HeaderSearchOptions.h
@@ -180,10 +180,13 @@ class HeaderSearchOptions {
   /// loading.
   uint64_t BuildSessionTimestamp = 0;
 
-  /// The set of macro names that should be ignored for the purposes
-  /// of computing the module hash.
+  /// The set of macro names that should be ignored by implicitly-built 
modules.
   llvm::SmallSetVector<llvm::CachedHashString, 16> ModulesIgnoreMacros;
 
+  /// The set of header search paths that should be ignored by implicitly-built
+  /// modules.
+  llvm::SmallSetVector<llvm::CachedHashString, 16> ModulesIgnoreSearchPaths;
+
   /// The set of user-provided virtual filesystem overlay files.
   std::vector<std::string> VFSOverlayFiles;
 
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index adc4224dd561c..ff085e374e3f9 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3883,6 +3883,9 @@ def fmodule_file : Joined<["-"], "fmodule-file=">,
 def fmodules_ignore_macro : Joined<["-"], "fmodules-ignore-macro=">, 
Group<f_Group>,
   Visibility<[ClangOption, CC1Option, CLOption]>,
   HelpText<"Ignore the definition of the given macro when building and loading 
modules">;
+def fmodules_ignore_search_path : Joined<["-"], 
"fmodules-ignore-search-path=">, Group<f_Group>,
+  Visibility<[ClangOption, CC1Option, CLOption]>,
+  HelpText<"Ignore the given header search path when building and loading 
modules">;
 def fmodules_strict_decluse : Flag <["-"], "fmodules-strict-decluse">, 
Group<f_Group>,
   Visibility<[ClangOption, CC1Option, CLOption]>,
   HelpText<"Like -fmodules-decluse but requires all headers to be in modules">,
diff --git a/clang/lib/DependencyScanning/ModuleDepCollector.cpp 
b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
index c7b7fcc8750c3..d48ac6b45cf46 100644
--- a/clang/lib/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
@@ -294,6 +294,18 @@ makeCommonInvocationForModuleBuild(CompilerInvocation CI) {
     CI.getHeaderSearchOpts().ModulesIgnoreMacros.clear();
   }
 
+  // Remove any header search paths that are explicitly ignored.
+  if (!CI.getHeaderSearchOpts().ModulesIgnoreSearchPaths.empty()) {
+    llvm::erase_if(
+        CI.getHeaderSearchOpts().UserEntries,
+        [&CI](const HeaderSearchOptions::Entry &E) {
+          return CI.getHeaderSearchOpts().ModulesIgnoreSearchPaths.contains(
+              llvm::CachedHashString(E.Path));
+        });
+    // Remove the now unused option.
+    CI.getHeaderSearchOpts().ModulesIgnoreSearchPaths.clear();
+  }
+
   return CI;
 }
 
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 54583fe3abbd8..8731188dab706 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4214,8 +4214,8 @@ static bool RenderModulesOptions(Compilation &C, const 
Driver &D,
   if (HaveClangModules)
     Args.AddLastArg(CmdArgs, options::OPT_fmodules_user_build_path);
 
-  // Pass through all -fmodules-ignore-macro arguments.
   Args.AddAllArgs(CmdArgs, options::OPT_fmodules_ignore_macro);
+  Args.AddAllArgs(CmdArgs, options::OPT_fmodules_ignore_search_path);
   Args.AddLastArg(CmdArgs, options::OPT_fmodules_prune_interval);
   Args.AddLastArg(CmdArgs, options::OPT_fmodules_prune_after);
 
diff --git a/clang/lib/Frontend/CompilerInstance.cpp 
b/clang/lib/Frontend/CompilerInstance.cpp
index 66662a786e6fc..83bbdd96d89eb 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -1164,6 +1164,17 @@ std::unique_ptr<CompilerInstance> 
CompilerInstance::cloneForModuleCompileImpl(
                    return HSOpts.ModulesIgnoreMacros.contains(
                        llvm::CachedHashString(MacroDef.split('=').first));
                  });
+  HSOpts.ModulesIgnoreMacros.clear();
+
+  // Remove any search paths that are explicitly ignored by the module.
+  // They aren't supposed to affect how the module is built anyway.
+  if (!HSOpts.ModulesIgnoreSearchPaths.empty())
+    llvm::erase_if(HSOpts.UserEntries,
+                   [&HSOpts](const HeaderSearchOptions::Entry &E) {
+                     return HSOpts.ModulesIgnoreSearchPaths.contains(
+                         llvm::CachedHashString(E.Path));
+                   });
+  HSOpts.ModulesIgnoreSearchPaths.clear();
 
   // If the original compiler invocation had -fmodule-name, pass it through.
   Invocation->getLangOpts().ModuleName =
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp 
b/clang/lib/Frontend/CompilerInvocation.cpp
index 9359848d3a29b..2f5e882d59084 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -3352,6 +3352,9 @@ static void GenerateHeaderSearchArgs(const 
HeaderSearchOptions &Opts,
   for (const auto &Macro : Opts.ModulesIgnoreMacros)
     GenerateArg(Consumer, OPT_fmodules_ignore_macro, Macro.val());
 
+  for (const auto &Path : Opts.ModulesIgnoreSearchPaths)
+    GenerateArg(Consumer, OPT_fmodules_ignore_search_path, Path.val());
+
   auto Matches = [](const HeaderSearchOptions::Entry &Entry,
                     llvm::ArrayRef<frontend::IncludeDirGroup> Groups,
                     std::optional<bool> IsFramework,
@@ -3476,6 +3479,9 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions 
&Opts, ArgList &Args,
         llvm::CachedHashString(MacroDef.split('=').first));
   }
 
+  for (const auto *A : Args.filtered(OPT_fmodules_ignore_search_path))
+    
Opts.ModulesIgnoreSearchPaths.insert(llvm::CachedHashString(A->getValue()));
+
   // Add -I... and -F... options in order.
   bool IsSysrootSpecified =
       Args.hasArg(OPT__sysroot_EQ) || Args.hasArg(OPT_isysroot);
@@ -5324,7 +5330,20 @@ std::string CompilerInvocation::computeContextHash() 
const {
 
   if (hsOpts.ModulesStrictContextHash) {
     HBuilder.addRange(hsOpts.SystemHeaderPrefixes);
-    HBuilder.addRange(hsOpts.UserEntries);
+
+    for (const auto &UserEntry : hsOpts.UserEntries) {
+      // If we're supposed to ignore this search path for the purposes of
+      // modules, don't put it into the hash.
+      if (!hsOpts.ModulesIgnoreSearchPaths.empty()) {
+        // Check whether we're ignoring this search path.
+        StringRef Path = UserEntry.Path;
+        if 
(hsOpts.ModulesIgnoreSearchPaths.count(llvm::CachedHashString(Path)))
+          continue;
+      }
+
+      HBuilder.add(UserEntry);
+    }
+
     HBuilder.addRange(hsOpts.VFSOverlayFiles);
 
     const DiagnosticOptions &diagOpts = getDiagnosticOpts();
diff --git a/clang/test/ClangScanDeps/modules-context-hash-ignore-search-path.c 
b/clang/test/ClangScanDeps/modules-context-hash-ignore-search-path.c
new file mode 100644
index 0000000000000..7cec524d2dc74
--- /dev/null
+++ b/clang/test/ClangScanDeps/modules-context-hash-ignore-search-path.c
@@ -0,0 +1,130 @@
+// Ensure -fmodules-ignore-search-path drops the path from the module build and
+// from its context hash, so two TUs that differ only in such a path share one
+// module, while the path itself is still available to the TUs themselves.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
+
+// -optimize-args=none so that the search-path pruning under test is the flag's
+// own, not -optimize-args=header-search's usage-based pruning (which would 
drop
+// an unused path from every module's command line regardless). The interaction
+// of the two is covered by modules-ignore-search-path-optimize-args.c.
+//
+// RUN: clang-scan-deps -compilation-database %t/cdb.json -j 1 \
+// RUN:   -optimize-args=none -format experimental-full > %t/deps.json
+// RUN: cat %t/deps.json | sed 's:\\\\\?:/:g' \
+// RUN:   | %scan-deps-filter 
--fields=command-line,context-hash,file-deps,input-file,name \
+// RUN:   | FileCheck -DPREFIX=%/t %s
+
+// tu1 and tu2 differ only in the generated-headers path and get two distinct
+// 'Mod' variants; tu3 and tu4 ignore theirs for modules and collapse onto a
+// third, shared one, whose command line mentions neither the path nor the 
flag.
+
+// CHECK:      {
+// CHECK-NEXT:   "modules": [
+// CHECK:          {
+// CHECK:            "command-line": [
+// CHECK:              "-I"
+// CHECK-NEXT:         "[[PREFIX]]/sdk"
+// CHECK-NEXT:         "-I"
+// CHECK-NEXT:         "[[PREFIX]]/gen1"
+// CHECK:            ]
+// CHECK:            "context-hash": "[[HASH_GEN1:.*]]"
+// CHECK:            "name": "Mod"
+// CHECK-NEXT:     }
+// CHECK-NEXT:     {
+// CHECK:            "command-line": [
+// CHECK:              "-I"
+// CHECK-NEXT:         "[[PREFIX]]/sdk"
+// CHECK-NEXT:         "-I"
+// CHECK-NEXT:         "[[PREFIX]]/gen2"
+// CHECK:            ]
+// CHECK:            "context-hash": "[[HASH_GEN2:.*]]"
+// CHECK:            "name": "Mod"
+// CHECK-NEXT:     }
+// CHECK-NEXT:     {
+// CHECK:            "command-line": [
+// CHECK-NOT:          "-fmodules-ignore-search-path
+// CHECK:              "-I"
+// CHECK-NEXT:         "[[PREFIX]]/sdk"
+// CHECK-NOT:          "[[PREFIX]]/gen
+// CHECK:            ]
+// CHECK:            "context-hash": "[[HASH_SHARED:.*]]"
+// CHECK:            "name": "Mod"
+// CHECK-NEXT:     }
+// CHECK-NEXT:   ]
+
+// Each TU keeps its own -I path -- the flag only ever prunes module builds, so
+// the TU still reads the generated header through it -- and points at the
+// variant computed above.
+
+// CHECK:            "context-hash": "[[HASH_GEN1]]"
+// CHECK:              "[[PREFIX]]/gen1"
+// CHECK:            "[[PREFIX]]/gen1/gen.h"
+// CHECK:          "input-file": "[[PREFIX]]/tu1.c"
+// CHECK:            "context-hash": "[[HASH_GEN2]]"
+// CHECK:              "[[PREFIX]]/gen2"
+// CHECK:            "[[PREFIX]]/gen2/gen.h"
+// CHECK:          "input-file": "[[PREFIX]]/tu2.c"
+// CHECK:            "context-hash": "[[HASH_SHARED]]"
+// CHECK:            "-fmodules-ignore-search-path=[[PREFIX]]/gen1"
+// CHECK:              "[[PREFIX]]/gen1"
+// CHECK:            "[[PREFIX]]/gen1/gen.h"
+// CHECK:          "input-file": "[[PREFIX]]/tu3.c"
+// CHECK:            "context-hash": "[[HASH_SHARED]]"
+// CHECK:            "-fmodules-ignore-search-path=[[PREFIX]]/gen2"
+// CHECK:              "[[PREFIX]]/gen2"
+// CHECK:            "[[PREFIX]]/gen2/gen.h"
+// CHECK:          "input-file": "[[PREFIX]]/tu4.c"
+
+//--- cdb.json.template
+[
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu1.c -I DIR/sdk -I DIR/gen1 -fmodules 
-fimplicit-module-maps -fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu1.c"
+  },
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu2.c -I DIR/sdk -I DIR/gen2 -fmodules 
-fimplicit-module-maps -fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu2.c"
+  },
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu3.c -I DIR/sdk -I DIR/gen1 
-fmodules-ignore-search-path=DIR/gen1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu3.c"
+  },
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu4.c -I DIR/sdk -I DIR/gen2 
-fmodules-ignore-search-path=DIR/gen2 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu4.c"
+  },
+]
+
+//--- sdk/module.modulemap
+module Mod { header "Mod.h" }
+
+//--- sdk/Mod.h
+
+//--- gen1/gen.h
+int gen(void);
+
+//--- gen2/gen.h
+int gen(void);
+
+//--- tu1.c
+#include "Mod.h"
+#include "gen.h"
+
+//--- tu2.c
+#include "Mod.h"
+#include "gen.h"
+
+//--- tu3.c
+#include "Mod.h"
+#include "gen.h"
+
+//--- tu4.c
+#include "Mod.h"
+#include "gen.h"
diff --git 
a/clang/test/ClangScanDeps/modules-ignore-search-path-optimize-args.c 
b/clang/test/ClangScanDeps/modules-ignore-search-path-optimize-args.c
new file mode 100644
index 0000000000000..a7063f08c9b55
--- /dev/null
+++ b/clang/test/ClangScanDeps/modules-ignore-search-path-optimize-args.c
@@ -0,0 +1,132 @@
+// Check that -fmodules-ignore-search-path composes with the scanner's own
+// usage-based pruning of header search paths (-optimize-args=header-search).
+//
+// Both prune HeaderSearchOptions::UserEntries, and the search path usage bit
+// vector serialized into each pcm indexes into the entry list that the 
implicit
+// module build inside the scanner actually saw. If the ignored paths were not
+// dropped from the common invocation that every module build descends from, 
the
+// two lists would disagree and the usage bits would select the wrong entries.
+// This goes unnoticed with -optimize-args=none, see the sibling test
+// modules-context-hash-ignore-search-path.c.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
+
+// The ignored path is the *first* -I, so dropping it shifts the index of every
+// path the modules do use.
+//
+// RUN: clang-scan-deps -compilation-database %t/cdb.json -j 1 \
+// RUN:   -optimize-args=header-search -format experimental-full > %t/deps.json
+// RUN: cat %t/deps.json | sed 's:\\\\\?:/:g' \
+// RUN:   | %scan-deps-filter 
--fields=command-line,context-hash,input-file,name \
+// RUN:   | FileCheck -DPREFIX=%/t %s
+
+// Each module keeps exactly the paths it resolved a header through: Mod finds
+// sub.h in sdkB, Top finds Mod.h in sdkA and sub.h in sdkB (through Mod), and
+// Sub needs no search path at all. The ignored gen paths, the never-used
+// 'unused' path and the flag itself are gone from all three.
+
+// CHECK:      "modules": [
+// CHECK:          "command-line": [
+// CHECK-NOT:        "-fmodules-ignore-search-path
+// CHECK-NOT:        "[[PREFIX]]/gen
+// CHECK-NOT:        "[[PREFIX]]/sdkA"
+// CHECK:            "-I"
+// CHECK-NEXT:       "[[PREFIX]]/sdkB"
+// CHECK-NOT:        "[[PREFIX]]/unused"
+// CHECK:          "context-hash": "[[HASH_MOD:.*]]"
+// CHECK-NEXT:     "name": "Mod"
+
+// CHECK:          "command-line": [
+// CHECK-NOT:        "-fmodules-ignore-search-path
+// CHECK-NOT:        "[[PREFIX]]/gen
+// CHECK-NOT:        "-I"
+// CHECK:          "context-hash": "[[HASH_SUB:.*]]"
+// CHECK-NEXT:     "name": "Sub"
+
+// CHECK:          "command-line": [
+// CHECK-NOT:        "-fmodules-ignore-search-path
+// CHECK-NOT:        "[[PREFIX]]/gen
+// CHECK:            "-I"
+// CHECK-NEXT:       "[[PREFIX]]/sdkA"
+// CHECK-NEXT:       "-I"
+// CHECK-NEXT:       "[[PREFIX]]/sdkB"
+// CHECK-NOT:        "[[PREFIX]]/unused"
+// CHECK:          "context-hash": "[[HASH_TOP:.*]]"
+// CHECK-NEXT:     "name": "Top"
+
+// Both TUs keep their own generated-headers path and still share all modules,
+// even though those paths differ.
+
+// CHECK:      "translation-units": [
+// CHECK:            "context-hash": "[[HASH_TOP]]"
+// CHECK:            "-fmodules-ignore-search-path=[[PREFIX]]/gen1"
+// CHECK:            "[[PREFIX]]/gen1"
+// CHECK:          "input-file": "[[PREFIX]]/tu1.c"
+// CHECK:            "context-hash": "[[HASH_TOP]]"
+// CHECK:            "-fmodules-ignore-search-path=[[PREFIX]]/gen2"
+// CHECK:            "[[PREFIX]]/gen2"
+// CHECK:          "input-file": "[[PREFIX]]/tu2.c"
+
+// The pruned command lines are not merely plausible, they still build.
+//
+// RUN: %deps-to-rsp %t/deps.json --module-name=Sub > %t/Sub.cc1.rsp
+// RUN: %deps-to-rsp %t/deps.json --module-name=Mod > %t/Mod.cc1.rsp
+// RUN: %deps-to-rsp %t/deps.json --module-name=Top > %t/Top.cc1.rsp
+// RUN: %deps-to-rsp %t/deps.json --tu-index=0 > %t/tu1.rsp
+// RUN: %deps-to-rsp %t/deps.json --tu-index=1 > %t/tu2.rsp
+// RUN: %clang @%t/Sub.cc1.rsp
+// RUN: %clang @%t/Mod.cc1.rsp
+// RUN: %clang @%t/Top.cc1.rsp
+// RUN: %clang @%t/tu1.rsp
+// RUN: %clang @%t/tu2.rsp
+
+//--- cdb.json.template
+[
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu1.c -I DIR/gen1 -I DIR/sdkA -I 
DIR/sdkB -I DIR/sdkC -I DIR/unused -fmodules-ignore-search-path=DIR/gen1 
-fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu1.c"
+  },
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu2.c -I DIR/gen2 -I DIR/sdkA -I 
DIR/sdkB -I DIR/sdkC -I DIR/unused -fmodules-ignore-search-path=DIR/gen2 
-fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache",
+    "file": "DIR/tu2.c"
+  },
+]
+
+//--- sdkC/module.modulemap
+module Top { header "top.h" }
+
+//--- sdkC/top.h
+#include "Mod.h"
+
+//--- sdkA/module.modulemap
+module Mod { header "Mod.h" }
+
+//--- sdkA/Mod.h
+#include "sub.h"
+
+//--- sdkB/module.modulemap
+module Sub { header "sub.h" }
+
+//--- sdkB/sub.h
+int sub(void);
+
+//--- unused/unused.h
+int unused(void);
+
+//--- gen1/gen.h
+int gen(void);
+
+//--- gen2/gen.h
+int gen(void);
+
+//--- tu1.c
+#include "top.h"
+#include "gen.h"
+
+//--- tu2.c
+#include "top.h"
+#include "gen.h"
diff --git a/clang/test/Driver/modules-ignore-search-path.c 
b/clang/test/Driver/modules-ignore-search-path.c
new file mode 100644
index 0000000000000..c032af9ab32cf
--- /dev/null
+++ b/clang/test/Driver/modules-ignore-search-path.c
@@ -0,0 +1,7 @@
+// Check that -fmodules-ignore-search-path reaches cc1 through the driver.
+
+// RUN: %clang -### -c -fmodules -fmodules-ignore-search-path=/tmp/gen1 \
+// RUN:   -fmodules-ignore-search-path=/tmp/gen2 %s 2>&1 | FileCheck %s
+//
+// CHECK: "-fmodules-ignore-search-path=/tmp/gen1"
+// CHECK-SAME: "-fmodules-ignore-search-path=/tmp/gen2"
diff --git a/clang/test/Modules/modules-ignore-search-path.c 
b/clang/test/Modules/modules-ignore-search-path.c
new file mode 100644
index 0000000000000..fb7e13f0d052c
--- /dev/null
+++ b/clang/test/Modules/modules-ignore-search-path.c
@@ -0,0 +1,103 @@
+// Tests -fmodules-ignore-search-path=P for implicitly-built modules: the path 
is
+// dropped from the context hash of every module and physically removed from
+// every module build, and kept only for the translation unit itself. It is the
+// -fmodules-ignore-macro treatment applied to a search path rather than a 
macro,
+// and like it, it applies to the regular context hash, so builds differing 
only
+// in that path share modules.
+
+// RUN: rm -rf %t && split-file %s %t
+
+// The option round-trips through cc1 argument generation.
+//
+// RUN: %clang_cc1 -round-trip-args -fsyntax-only -fmodules \
+// RUN:   -fimplicit-module-maps -fmodules-cache-path=%t/rtcache -I %t/sdk \
+// RUN:   -I %t/gen1 -fmodules-ignore-search-path=%t/gen1 %t/tu.c
+
+// Three "projects" that see the same SDK but stage per-build generated headers
+// at a different path each. Only the TU includes them; no module does. Under
+// strict context hash those -I paths alone would fragment every module's hash.
+//
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen1 -fmodules-ignore-search-path=%t/gen1 %t/tu.c
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen2 -fmodules-ignore-search-path=%t/gen2 %t/tu.c
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen3 -fmodules-ignore-search-path=%t/gen3 %t/tu.c
+
+// All three projects share a single context hash for every module: exactly one
+// pcm of each exists in the whole cache.
+//
+// RUN: ls %t/cache/*/Ins-*.pcm | count 1
+// RUN: ls %t/cache/*/Dep-*.pcm | count 1
+
+// Without the annotation the -I path feeds every module's context hash, so 
both
+// modules fragment: one per project.
+//
+// RUN: rm -rf %t/cache
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen1 %t/tu.c
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen2 %t/tu.c
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen3 %t/tu.c
+// RUN: ls %t/cache/*/Ins-*.pcm | count 3
+// RUN: ls %t/cache/*/Dep-*.pcm | count 3
+
+// The path really is gone from the module builds, not merely unhashed: a 
module
+// that does reach for a header only that path provides fails. That failure is 
the
+// documented cost of asserting a path no module needs.
+//
+// RUN: rm -rf %t/cache
+// RUN: not %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen1 -fmodules-ignore-search-path=%t/gen1 \
+// RUN:   %t/tu-module-needs-it.c 2>&1 | FileCheck -check-prefix=NEEDED %s
+//
+// NEEDED: 'gen.h' file not found
+
+// And it is only the *ignored* path that goes: an unannotated -I path still
+// works from inside a module, and still keys it.
+//
+// RUN: rm -rf %t/cache
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-strict-context-hash -fmodules-cache-path=%t/cache \
+// RUN:   -I %t/sdk -I %t/gen1 %t/tu-module-needs-it.c
+
+//--- sdk/module.modulemap
+module Ins { header "ins.h" }
+module Dep { header "dep.h" }
+module NeedsGen { header "needsgen.h" }
+
+//--- sdk/ins.h
+int ins(void);
+
+//--- sdk/dep.h
+#include "ins.h"
+int dep(void);
+
+//--- sdk/needsgen.h
+#include "gen.h"
+int needsgen(void);
+
+//--- gen1/gen.h
+int gen(void);
+
+//--- gen2/gen.h
+int gen(void);
+
+//--- gen3/gen.h
+int gen(void);
+
+//--- tu.c
+// Only the TU consumes the ignored path.
+#include "gen.h"
+#include "dep.h"
+
+//--- tu-module-needs-it.c
+#include "needsgen.h"

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to