diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -345,6 +345,10 @@ /// module depends. llvm::SmallSetVector Imports; + /// The set of top-level modules that affected the compilation of this module, + /// but were not imported. + llvm::SmallSetVector AffectingModules; + /// Describes an exported module. /// /// The pointer is the module being re-exported, while the bit will be true diff --git a/clang/include/clang/Lex/ModuleLoader.h b/clang/include/clang/Lex/ModuleLoader.h --- a/clang/include/clang/Lex/ModuleLoader.h +++ b/clang/include/clang/Lex/ModuleLoader.h @@ -51,6 +51,11 @@ ModuleLoadResult() = default; ModuleLoadResult(Module *M) : Storage(M, Normal) {} ModuleLoadResult(LoadResultKind Kind) : Storage(nullptr, Kind) {} + ModuleLoadResult(Module *M, LoadResultKind Kind) : Storage(M, Kind) {} + + operator bool() const { + return Storage.getInt() == Normal && Storage.getPointer(); + } operator Module *() const { return Storage.getPointer(); } diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -860,6 +860,10 @@ /// The files that have been included. IncludedFilesSet IncludedFiles; + /// The set of top-level modules that affected preprocessing, but were not + /// imported. + llvm::SmallSetVector AffectingModules; + /// The set of known macros exported from modules. llvm::FoldingSet ModuleMacros; @@ -1331,6 +1335,12 @@ /// \} + /// Get the set of top-level modules that affected preprocessing, but were not + /// imported. + const llvm::SmallSetVector &getAffectingModules() const { + return AffectingModules; + } + /// Mark the file as included. /// Returns true if this is the first time the file was included. bool markIncluded(const FileEntry *File) { diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -829,6 +829,9 @@ /// Specifies the name of the module that will eventually /// re-export the entities in this module. SUBMODULE_EXPORT_AS = 17, + + /// Specifies affecting modules that were not imported. + SUBMODULE_AFFECTING_MODULES = 18, }; /// Record types used within a comments block. diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -689,7 +689,7 @@ Module *Mod; /// The kind of module reference. - enum { Import, Export, Conflict } Kind; + enum { Import, Export, Conflict, Affecting } Kind; /// The local ID of the module that is being exported. unsigned ID; diff --git a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h --- a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h +++ b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h @@ -176,6 +176,13 @@ llvm::DenseSet &AddedModules); void addModuleDep(const Module *M, ModuleDeps &MD, llvm::DenseSet &AddedModules); + + /// Traverses the affecting modules and updates \c MD with references to the + /// parent \c ModuleDepCollector info. + void addAllAffectingModules(const Module *M, ModuleDeps &MD, + llvm::DenseSet &AddedModules); + void addAffectingModule(const Module *M, ModuleDeps &MD, + llvm::DenseSet &AddedModules); }; /// Collects modular and non-modular dependencies of the main file by attaching diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -2099,7 +2099,7 @@ << Module->getFullModuleName() << SourceRange(Path.front().second, Path.back().second); - return ModuleLoadResult::MissingExpected; + return ModuleLoadResult(Module, ModuleLoadResult::MissingExpected); } // Check whether this module is available. diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -1563,6 +1563,8 @@ *SuggestedModule = ModuleMap::KnownHeader(); return true; } + // TODO: Add this module (or just its module map file) into something like + // `RequestingModule->AffectingModules`. return false; } } diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -2281,6 +2281,13 @@ if (Imported) { Action = Import; } else if (Imported.isMissingExpected()) { + Module *M = static_cast(Imported)->getTopLevelModule(); + if (!BuildingSubmoduleStack.empty()) { + if (Imported != BuildingSubmoduleStack.back().M) + BuildingSubmoduleStack.back().M->AffectingModules.insert(M); + } else { + AffectingModules.insert(M); + } // We failed to find a submodule that we assumed would exist (because it // was in the directory of an umbrella header, for instance), but no // actual module containing it exists (because the umbrella header is diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -4376,6 +4376,11 @@ Unresolved.Mod->Imports.insert(ResolvedMod); continue; + case UnresolvedModuleRef::Affecting: + if (ResolvedMod) + Unresolved.Mod->AffectingModules.insert(ResolvedMod); + continue; + case UnresolvedModuleRef::Export: if (ResolvedMod || Unresolved.IsWildcard) Unresolved.Mod->Exports.push_back( @@ -5674,6 +5679,18 @@ } break; + case SUBMODULE_AFFECTING_MODULES: + for (unsigned Idx = 0; Idx != Record.size(); ++Idx) { + UnresolvedModuleRef Unresolved; + Unresolved.File = &F; + Unresolved.Mod = CurrentModule; + Unresolved.ID = Record[Idx]; + Unresolved.Kind = UnresolvedModuleRef::Affecting; + Unresolved.IsWildcard = false; + UnresolvedModuleRefs.push_back(Unresolved); + } + break; + case SUBMODULE_EXPORTS: for (unsigned Idx = 0; Idx + 1 < Record.size(); Idx += 2) { UnresolvedModuleRef Unresolved; diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -883,6 +883,7 @@ RECORD(SUBMODULE_TOPHEADER); RECORD(SUBMODULE_UMBRELLA_DIR); RECORD(SUBMODULE_IMPORTS); + RECORD(SUBMODULE_AFFECTING_MODULES); RECORD(SUBMODULE_EXPORTS); RECORD(SUBMODULE_REQUIRES); RECORD(SUBMODULE_EXCLUDED_HEADER); @@ -2865,6 +2866,14 @@ Stream.EmitRecord(SUBMODULE_IMPORTS, Record); } + // Emit the modules affecting compilation that were not imported. + if (!Mod->AffectingModules.empty()) { + RecordData Record; + for (auto *I : Mod->AffectingModules) + Record.push_back(getSubmoduleID(I)); + Stream.EmitRecord(SUBMODULE_AFFECTING_MODULES, Record); + } + // Emit the exports. if (!Mod->Exports.empty()) { RecordData Record; diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -278,6 +278,11 @@ if (!MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) MDC.addFileDep(MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude); + for (const Module *M : + MDC.ScanInstance.getPreprocessor().getAffectingModules()) + if (!MDC.isPrebuiltModule(M)) + DirectModularDeps.insert(M); + for (const Module *M : DirectModularDeps) { // A top-level module might not be actually imported as a module when // -fmodule-name is used to compile a translation unit that imports this @@ -389,6 +394,8 @@ addAllSubmodulePrebuiltDeps(M, MD, SeenModules); llvm::DenseSet AddedModules; addAllSubmoduleDeps(M, MD, AddedModules); + llvm::DenseSet ProcessedModules; + addAllAffectingModules(M, MD, ProcessedModules); MD.BuildInvocation = MDC.makeInvocationForModuleBuildWithoutOutputs( MD, [&](CompilerInvocation &BuildInvocation) { @@ -461,6 +468,30 @@ } } +void ModuleDepCollectorPP::addAllAffectingModules( + const Module *M, ModuleDeps &MD, + llvm::DenseSet &AddedModules) { + addAffectingModule(M, MD, AddedModules); + + for (const Module *SubM : M->submodules()) + addAllAffectingModules(SubM, MD, AddedModules); +} + +void ModuleDepCollectorPP::addAffectingModule( + const Module *M, ModuleDeps &MD, + llvm::DenseSet &AddedModules) { + for (const Module *Affecting : M->AffectingModules) { + assert(Affecting == Affecting->getTopLevelModule() && + "Not quite import not top-level module"); + if (Affecting != M->getTopLevelModule() && + !MDC.isPrebuiltModule(Affecting)) { + ModuleID ImportID = handleTopLevelModule(Affecting); + if (AddedModules.insert(Affecting).second) + MD.ClangModuleDeps.push_back(ImportID); + } + } +} + ModuleDepCollector::ModuleDepCollector( std::unique_ptr Opts, CompilerInstance &ScanInstance, DependencyConsumer &C, diff --git a/clang/test/ClangScanDeps/modules-incomplete-umbrella.c b/clang/test/ClangScanDeps/modules-incomplete-umbrella.c new file mode 100644 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-incomplete-umbrella.c @@ -0,0 +1,202 @@ +// This test checks that modules loaded during compilation (but not imported) +// are still reported as dependencies. + +// RUN: rm -rf %t && mkdir %t +// RUN: split-file %s %t + +//--- frameworks/FW.framework/Modules/module.modulemap +framework module FW { + umbrella header "FW.h" + module * { export * } +} +//--- frameworks/FW.framework/Headers/FW.h +//--- frameworks/FW.framework/Modules/module.private.modulemap +framework module FW_Private { + umbrella header "FW_Private.h" + module * { export * } +} +//--- frameworks/FW.framework/PrivateHeaders/FW_Private.h +#include "One.h" +//--- frameworks/FW.framework/PrivateHeaders/One.h +//--- frameworks/FW.framework/PrivateHeaders/Two.h + +// Let's check we report the non-imported modular dependencies for a translation unit. + +//--- from_tu.cdb.json.template +[{ + "file": "DIR/from_tu.m", + "directory": "DIR", + "command": "clang -fmodules -fmodules-cache-path=DIR/cache -iframework DIR/frameworks -c DIR/from_tu.m -o DIR/from_tu.o" +}] +//--- from_tu.m +#include "FW/FW.h" +#include "FW/Two.h" + +// RUN: sed -e "s|DIR|%/t|g" %t/from_tu.cdb.json.template > %t/from_tu.cdb.json +// RUN: clang-scan-deps -compilation-database %t/from_tu.cdb.json -format experimental-full > %t/from_tu_result.json +// RUN: cat %t/from_tu_result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t --check-prefixes=CHECK_TU +// CHECK_TU: { +// CHECK_TU-NEXT: "modules": [ +// CHECK_TU-NEXT: { +// CHECK_TU-NEXT: "clang-module-deps": [], +// CHECK_TU-NEXT: "clang-modulemap-file": "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_TU-NEXT: "command-line": [ +// CHECK_TU: ], +// CHECK_TU-NEXT: "context-hash": "{{.*}}", +// CHECK_TU-NEXT: "file-deps": [ +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/Headers/FW.h", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap" +// CHECK_TU-NEXT: ], +// CHECK_TU-NEXT: "name": "FW" +// CHECK_TU-NEXT: }, +// CHECK_TU-NEXT: { +// CHECK_TU-NEXT: "clang-module-deps": [], +// CHECK_TU-NEXT: "clang-modulemap-file": "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap", +// CHECK_TU-NEXT: "command-line": [ +// CHECK_TU: ], +// CHECK_TU-NEXT: "context-hash": "{{.*}}", +// CHECK_TU-NEXT: "file-deps": [ +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/FW_Private.h", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/One.h" +// CHECK_TU-NEXT: ], +// CHECK_TU-NEXT: "name": "FW_Private" +// CHECK_TU-NEXT: } +// CHECK_TU-NEXT: ], +// CHECK_TU-NEXT: "translation-units": [ +// CHECK_TU-NEXT: { +// CHECK_TU-NEXT: "clang-context-hash": "{{.*}}", +// CHECK_TU-NEXT: "clang-module-deps": [ +// CHECK_TU-NEXT: { +// CHECK_TU-NEXT: "context-hash": "{{.*}}", +// CHECK_TU-NEXT: "module-name": "FW" +// CHECK_TU-NEXT: }, +// CHECK_TU-NEXT: { +// CHECK_TU-NEXT: "context-hash": "{{.*}}", +// CHECK_TU-NEXT: "module-name": "FW_Private" +// CHECK_TU-NEXT: } +// CHECK_TU-NEXT: ], +// CHECK_TU-NEXT: "command-line": [ +// CHECK_TU: "-fmodule-file={{.*}}/FW-{{.*}}.pcm" +// CHECK_TU: "-fmodule-file={{.*}}/FW_Private-{{.*}}.pcm" +// CHECK_TU: ], +// CHECK_TU-NEXT: "file-deps": [ +// CHECK_TU-NEXT: "[[PREFIX]]/from_tu.m", +// CHECK_TU-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/Two.h" +// CHECK_TU-NEXT: ], +// CHECK_TU-NEXT: "input-file": "[[PREFIX]]/from_tu.m" +// CHECK_TU-NEXT: } +// CHECK_TU-NEXT: ] +// CHECK_TU-NEXT: } + +// RUN: %deps-to-rsp %t/from_tu_result.json --module-name=FW > %t/FW.cc1.rsp +// RUN: %deps-to-rsp %t/from_tu_result.json --module-name=FW_Private > %t/FW_Private.cc1.rsp +// RUN: %deps-to-rsp %t/from_tu_result.json --tu-index=0 > %t/tu.rsp +// RUN: %clang @%t/FW.cc1.rsp +// RUN: %clang @%t/FW_Private.cc1.rsp +// RUN: %clang @%t/tu.rsp + +// Now let's check we report the dependencies for modules as well. + +//--- from_module.cdb.json.template +[{ + "file": "DIR/from_module.m", + "directory": "DIR", + "command": "clang -fmodules -fmodules-cache-path=DIR/cache -iframework DIR/frameworks -c DIR/from_module.m -o DIR/from_module.o" +}] +//--- module.modulemap +module Mod { header "Mod.h" } +//--- Mod.h +#include "FW/FW.h" +#include "FW/Two.h" +//--- from_module.m +#include "Mod.h" + +// RUN: sed -e "s|DIR|%/t|g" %t/from_module.cdb.json.template > %t/from_module.cdb.json +// RUN: clang-scan-deps -compilation-database %t/from_module.cdb.json -format experimental-full > %t/from_module_result.json +// RUN: cat %t/from_module_result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t --check-prefixes=CHECK_MODULE +// CHECK_MODULE: { +// CHECK_MODULE-NEXT: "modules": [ +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "clang-module-deps": [], +// CHECK_MODULE-NEXT: "clang-modulemap-file": "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_MODULE-NEXT: "command-line": [ +// CHECK_MODULE: ], +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "file-deps": [ +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Headers/FW.h", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap" +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "name": "FW" +// CHECK_MODULE-NEXT: }, +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "clang-module-deps": [], +// CHECK_MODULE-NEXT: "clang-modulemap-file": "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap", +// CHECK_MODULE-NEXT: "command-line": [ +// CHECK_MODULE: ], +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "file-deps": [ +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/FW_Private.h", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/One.h" +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "name": "FW_Private" +// CHECK_MODULE-NEXT: }, +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "clang-module-deps": [ +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "module-name": "FW" +// CHECK_MODULE-NEXT: }, +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "module-name": "FW_Private" +// CHECK_MODULE-NEXT: } +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "clang-modulemap-file": "[[PREFIX]]/module.modulemap", +// CHECK_MODULE-NEXT: "command-line": [ +// CHECK_MODULE: "-fmodule-file={{.*}}/FW-{{.*}}.pcm" +// CHECK_MODULE: "-fmodule-file={{.*}}/FW_Private-{{.*}}.pcm" +// CHECK_MODULE: ], +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "file-deps": [ +// CHECK_MODULE-NEXT: "[[PREFIX]]/Mod.h" +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.modulemap", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/Modules/module.private.modulemap", +// CHECK_MODULE-NEXT: "[[PREFIX]]/frameworks/FW.framework/PrivateHeaders/Two.h", +// CHECK_MODULE-NEXT: "[[PREFIX]]/module.modulemap" +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "name": "Mod" +// CHECK_MODULE-NEXT: } +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "translation-units": [ +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "clang-context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "clang-module-deps": [ +// CHECK_MODULE-NEXT: { +// CHECK_MODULE-NEXT: "context-hash": "{{.*}}", +// CHECK_MODULE-NEXT: "module-name": "Mod" +// CHECK_MODULE-NEXT: } +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "command-line": [ +// CHECK_MODULE: ], +// CHECK_MODULE-NEXT: "file-deps": [ +// CHECK_MODULE-NEXT: "[[PREFIX]]/from_module.m" +// CHECK_MODULE-NEXT: ], +// CHECK_MODULE-NEXT: "input-file": "[[PREFIX]]/from_module.m" +// CHECK_MODULE-NEXT: } +// CHECK_MODULE-NEXT: ] +// CHECK_MODULE-NEXT: } + +// RUN: %deps-to-rsp %t/from_module_result.json --module-name=FW > %t/FW.cc1.rsp +// RUN: %deps-to-rsp %t/from_module_result.json --module-name=FW_Private > %t/FW_Private.cc1.rsp +// RUN: %deps-to-rsp %t/from_module_result.json --module-name=Mod > %t/Mod.cc1.rsp +// RUN: %deps-to-rsp %t/from_module_result.json --tu-index=0 > %t/tu.rsp +// RUN: %clang @%t/FW.cc1.rsp +// RUN: %clang @%t/FW_Private.cc1.rsp +// RUN: %clang @%t/Mod.cc1.rsp +// RUN: %clang @%t/tu.rsp