https://github.com/jansvoboda11 updated https://github.com/llvm/llvm-project/pull/217061
>From 125a5dc4cd50502b50dd0dd530f778eece5b98b2 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 | 111 ++++++++++ ...modules-ignore-search-path-optimize-args.c | 194 ++++++++++++++++++ .../test/Driver/modules-ignore-search-path.c | 7 + .../test/Modules/modules-ignore-search-path.c | 91 ++++++++ 11 files changed, 463 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..5055bacfcbf91 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-context-hash-ignore-search-path.c @@ -0,0 +1,111 @@ +// Ensure -fmodules-ignore-search-path drops the path from the module build and +// from its context hash, so a TU that passes such a path shares one module with +// a TU that never mentioned it, while the path itself is still available to the +// TU that did. + +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: sed "s|DIR|%/t|g" %t/cdb.json.in > %t/cdb.json + +// RUN: clang-scan-deps -compilation-database %t/cdb.json -j 1 \ +// RUN: -optimize-args=none -format experimental-full -o %t/deps.json + +// RUN: ls %t/cache/*/Common-*.pcm | count 1 + +// RUN: cat %t/deps.json | sed 's:\\\\\?:/:g' | FileCheck -DPREFIX=%/t %s + +// CHECK: { +// CHECK-NEXT: "modules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/common/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NOT: "-fmodules-ignore-search-path +// CHECK-NOT: "[[PREFIX]]/extra" +// CHECK: "-I" +// CHECK-NEXT: "[[PREFIX]]/common" +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH:.*]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/common/module.modulemap", +// CHECK-NEXT: "[[PREFIX]]/common/common.h" +// CHECK-NEXT: ], +// CHECK-NEXT: "link-libraries": [], +// CHECK-NEXT: "name": "Common" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "translation-units": [ +// CHECK-NEXT: { +// CHECK-NEXT: "commands": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-context-hash": "{{.*}}", +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH]]", +// CHECK-NEXT: "module-name": "Common" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "command-line": [ +// CHECK: "-I", +// CHECK-NEXT: "[[PREFIX]]/common", +// CHECK: ], +// CHECK-NEXT: "executable": "{{.*}}", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/tu.c" +// CHECK-NEXT: ], +// CHECK-NEXT: "input-file": "[[PREFIX]]/tu.c" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "commands": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-context-hash": "{{.*}}", +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH]]", +// CHECK-NEXT: "module-name": "Common" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "command-line": [ +// CHECK: "-fmodules-ignore-search-path=[[PREFIX]]/extra", +// CHECK-NEXT: "-I", +// CHECK-NEXT: "[[PREFIX]]/common", +// CHECK-NEXT: "-I", +// CHECK-NEXT: "[[PREFIX]]/extra", +// CHECK: ], +// CHECK-NEXT: "executable": "{{.*}}", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/tu.c" +// CHECK-NEXT: ], +// CHECK-NEXT: "input-file": "[[PREFIX]]/tu.c" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } + +//--- cdb.json.in +[ + { + "directory": "DIR", + "command": "clang -c DIR/tu.c -o DIR/tu1.o -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -I DIR/common", + "file": "DIR/tu.c" + }, + { + "directory": "DIR", + "command": "clang -c DIR/tu.c -o DIR/tu1.o -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -I DIR/common -I DIR/extra -fmodules-ignore-search-path=DIR/extra", + "file": "DIR/tu.c" + } +] + +//--- common/module.modulemap +module Common { header "common.h" } +//--- common/common.h + +//--- extra/module.modulemap +//--- extra/extra.h + +//--- tu.c +#include "common.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..586087a391f28 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-ignore-search-path-optimize-args.c @@ -0,0 +1,194 @@ +// Check that -fmodules-ignore-search-path composes with the scanner's own +// usage-based pruning of header search paths (-optimize-args=header-search). + +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: sed "s|DIR|%/t|g" %t/cdb.json.in > %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 -o %t/deps.json + +// RUN: cat %t/deps.json | sed 's:\\\\\?:/:g' | FileCheck -DPREFIX=%/t %s + +// CHECK: { +// CHECK-NEXT: "modules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/leaf/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NOT: "-fmodules-ignore-search-path +// CHECK-NOT: "-I" +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_LEAF:.*]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/leaf/module.modulemap", +// CHECK-NEXT: "[[PREFIX]]/leaf/leaf.h" +// CHECK-NEXT: ], +// CHECK-NEXT: "link-libraries": [], +// CHECK-NEXT: "name": "Leaf" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_LEAF]]", +// CHECK-NEXT: "module-name": "Leaf" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/middle/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NOT: "-fmodules-ignore-search-path +// CHECK: "-I" +// CHECK-NEXT: "[[PREFIX]]/leaf" +// CHECK-NOT: "-I" +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_MIDDLE:.*]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/middle/module.modulemap", +// CHECK-NEXT: "[[PREFIX]]/middle/middle.h", +// CHECK-NEXT: "[[PREFIX]]/leaf/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "link-libraries": [], +// CHECK-NEXT: "name": "Middle" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MIDDLE]]", +// CHECK-NEXT: "module-name": "Middle" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/top/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NOT: "-fmodules-ignore-search-path +// CHECK: "-I" +// CHECK-NEXT: "[[PREFIX]]/middle" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/leaf" +// CHECK-NOT: "-I" +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_TOP:.*]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/top/module.modulemap", +// CHECK-NEXT: "[[PREFIX]]/top/top.h", +// CHECK-NEXT: "[[PREFIX]]/middle/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "link-libraries": [], +// CHECK-NEXT: "name": "Top" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "translation-units": [ +// CHECK-NEXT: { +// CHECK-NEXT: "commands": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-context-hash": "[[HASH_TU:.*]]", +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_TOP]]", +// CHECK-NEXT: "module-name": "Top" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "command-line": [ +// CHECK: "-fmodules-ignore-search-path=[[PREFIX]]/ignored1" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/ignored1" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/top" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/middle" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/leaf" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/unused" +// CHECK: ], +// CHECK-NEXT: "executable": "{{.*}}", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/tu.c", +// CHECK-NEXT: "[[PREFIX]]/ignored1/ignored.h" +// CHECK-NEXT: ], +// CHECK-NEXT: "input-file": "[[PREFIX]]/tu.c" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "commands": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-context-hash": "[[HASH_TU]]", +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_TOP]]", +// CHECK-NEXT: "module-name": "Top" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "command-line": [ +// CHECK: "-fmodules-ignore-search-path=[[PREFIX]]/ignored2" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/ignored2" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/top" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/middle" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/leaf" +// CHECK-NEXT: "-I" +// CHECK-NEXT: "[[PREFIX]]/unused" +// CHECK: ], +// CHECK-NEXT: "executable": "{{.*}}", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/tu.c", +// CHECK-NEXT: "[[PREFIX]]/ignored2/ignored.h" +// CHECK-NEXT: ], +// CHECK-NEXT: "input-file": "[[PREFIX]]/tu.c" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } + +//--- cdb.json.in +[ + { + "directory": "DIR", + "command": "clang -c DIR/tu.c -o DIR/tu1.o -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -I DIR/ignored1 -I DIR/top -I DIR/middle -I DIR/leaf -I DIR/unused -fmodules-ignore-search-path=DIR/ignored1", + "file": "DIR/tu.c" + }, + { + "directory": "DIR", + "command": "clang -c DIR/tu.c -o DIR/tu2.o -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -I DIR/ignored2 -I DIR/top -I DIR/middle -I DIR/leaf -I DIR/unused -fmodules-ignore-search-path=DIR/ignored2", + "file": "DIR/tu.c" + } +] + +//--- top/module.modulemap +module Top { header "top.h" } + +//--- top/top.h +#include "middle.h" + +//--- middle/module.modulemap +module Middle { header "middle.h" } + +//--- middle/middle.h +#include "leaf.h" + +//--- leaf/module.modulemap +module Leaf { header "leaf.h" } + +//--- leaf/leaf.h +int leaf(void); + +//--- unused/unused.h +int unused(void); + +//--- ignored1/ignored.h +int ignored(void); + +//--- ignored2/ignored.h +int ignored(void); + +//--- tu.c +#include "top.h" +#include "ignored.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..e667698a204f4 --- /dev/null +++ b/clang/test/Modules/modules-ignore-search-path.c @@ -0,0 +1,91 @@ +// 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 + +// Three "projects" that see the same modules 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/common -I %t/ignored1 -fmodules-ignore-search-path=%t/ignored1 %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/common -I %t/ignored2 -fmodules-ignore-search-path=%t/ignored2 %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/common -I %t/ignored3 -fmodules-ignore-search-path=%t/ignored3 %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/*/Leaf-*.pcm | count 1 +// RUN: ls %t/cache/*/Top-*.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/common -I %t/ignored1 %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/common -I %t/ignored2 %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/common -I %t/ignored3 %t/tu.c +// RUN: ls %t/cache/*/Leaf-*.pcm | count 3 +// RUN: ls %t/cache/*/Top-*.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/common -I %t/ignored1 -fmodules-ignore-search-path=%t/ignored1 \ +// RUN: %t/tu-module-needs-ignored.c 2>&1 | FileCheck -DPREFIX=%/t -check-prefix=NEEDED %s +// +// NEEDED: While building module 'NeedsIgnored' imported from [[PREFIX]]/tu-module-needs-ignored.c:1: +// NEEDED-NEXT: In file included from <module-includes>:1: +// NEEDED-NEXT: [[PREFIX]]/common/needs-ignored.h:1:10: fatal error: 'ignored.h' file not found +// NEEDED: [[PREFIX]]/tu-module-needs-ignored.c:1:10: fatal error: could not build module 'NeedsIgnored' + +// 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/common -I %t/ignored1 %t/tu-module-needs-ignored.c + +//--- common/module.modulemap +module Leaf { header "leaf.h" } +module Top { header "top.h" } +module NeedsIgnored { header "needs-ignored.h" } + +//--- common/leaf.h +//--- common/top.h +#include "leaf.h" +//--- common/needs-ignored.h +#include "ignored.h" + +//--- ignored1/ignored.h +//--- ignored2/ignored.h +//--- ignored3/ignored.h + +//--- tu.c +// Only the TU consumes the ignored path. +#include "ignored.h" +#include "top.h" + +//--- tu-module-needs-ignored.c +#include "needs-ignored.h" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
