From 3e60b065115d4a544b8872a3ea2782780796a824 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 15 Nov 2019 17:38:10 -0800 Subject: [PATCH 01/10] [clang][clang-scan-deps] Add an experimental C API. This adds an experimental C API for clang-scan-deps. It provides both the full module dependency graph along with a flattened list of dependencies. See clang/include/clang-c/Dependencies.h for the API and documentation. You can test it out using: c-index-test core --scan-deps -- clang --cc1 This will output a list of modules and then the direct dependencies of the main translation unit. --- clang/include/clang-c/Dependencies.h | 223 ++++++++++++++++++ .../DependencyScanningWorker.h | 9 + .../DependencyScanningWorker.cpp | 26 ++ clang/test/Index/Core/scan-deps.m | 29 +++ clang/tools/c-index-test/CMakeLists.txt | 1 + clang/tools/c-index-test/core_main.cpp | 86 ++++++- clang/tools/libclang/CDependencies.cpp | 222 +++++++++++++++++ clang/tools/libclang/CMakeLists.txt | 2 + clang/tools/libclang/CXString.cpp | 17 ++ clang/tools/libclang/CXString.h | 3 + clang/tools/libclang/libclang.exports | 7 + llvm/include/llvm/ADT/FunctionExtras.h | 31 +++ 12 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 clang/include/clang-c/Dependencies.h create mode 100644 clang/test/Index/Core/scan-deps.m create mode 100644 clang/tools/libclang/CDependencies.cpp diff --git a/clang/include/clang-c/Dependencies.h b/clang/include/clang-c/Dependencies.h new file mode 100644 index 0000000000000..5ea6e791aa1fd --- /dev/null +++ b/clang/include/clang-c/Dependencies.h @@ -0,0 +1,223 @@ +/*==-- clang-c/Dependencies.h - Dependency Discovery C Interface --*- C -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header provides a dependency discovery interface similar to *| +|* clang-scan-deps. *| +|* *| +|* An example of its usage is available in c-index-test/core_main.cpp. *| +|* *| +|* EXPERIMENTAL: These interfaces are experimental and will change. If you *| +|* use these be prepared for them to change without notice on any commit. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_CLANG_C_DEPENDENCIES_H +#define LLVM_CLANG_C_DEPENDENCIES_H + +#include "clang-c/BuildSystem.h" +#include "clang-c/CXErrorCode.h" +#include "clang-c/CXString.h" +#include "clang-c/Platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup SCAN_DEPS Dependency scanning service. + * @{ + */ + +typedef struct { + CXString Name; + /** + * The context hash of a module represents the set of compiler options that + * may make one version of a module incompatible from another. This includes + * things like language mode, predefined macros, header search paths, etc... + * + * Modules with the same name but a different \c ContextHash should be treated + * as separate modules for the purpose of a build. + */ + CXString ContextHash; + + /** + * The path to the modulemap file which defines this module. + * + * This can be used to explicitly build this module. This file will + * additionally appear in \c FileDeps as a dependency. + */ + CXString ModuleMapPath; + + /** + * The list of files which this module directly depends on. + * + * If any of these change then the module needs to be rebuilt. + */ + CXStringSet *FileDeps; + + /** + * The list of modules which this module direct depends on. + * + * This does include the context hash. The format is + * `:` + */ + CXStringSet *ModuleDeps; + + /** + * The full command line needed to build this module. + * + * Not including `-fmodule-file=` or `-o`. + */ + CXStringSet *BuildArguments; +} CXModuleDependency; + +typedef struct { + int Count; + CXModuleDependency *Modules; +} CXModuleDependencySet; + +/** + * See \c CXModuleDependency for the meaning of these fields, with the addition + * that they represent only the direct dependencies for \c CXDependencyMode_Full + * mode. + */ +typedef struct { + CXString ContextHash; + CXStringSet *FileDeps; + CXStringSet *ModuleDeps; + + /** + * Additional arguments to append to the build of this file. + * + * This contains things like disabling implicit modules. This does not include + * the `-fmodule-file=` arguments that are needed. + */ + CXStringSet *AdditionalArguments; +} CXFileDependencies; + +CINDEX_LINKAGE void +clang_experimental_ModuleDependencySet_dispose(CXModuleDependencySet *MD); + +CINDEX_LINKAGE void +clang_experimental_FileDependencies_dispose(CXFileDependencies *ID); + +/** + * Object encapsulating instance of a dependency scanner service. + * + * The dependency scanner service is a global instance that owns the + * global cache and other global state that's shared between the dependency + * scanner workers. The service APIs are thread safe. + */ +typedef struct CXOpaqueDependencyScannerService *CXDependencyScannerService; + +/** + * The mode to report module dependencies in. + */ +typedef enum { + /** + * Flatten all module dependencies. This reports the full transitive set of + * header and module map dependencies needed to do an implicit module build. + */ + CXDependencyMode_Flat, + + /** + * Report the full module graph. This reports only the direct dependencies of + * each file, and calls a callback for each module that is discovered. + */ + CXDependencyMode_Full, +} CXDependencyMode; + +/** + * Create a \c CXDependencyScannerService object. + * Must be disposed with \c clang_DependencyScannerService_dispose(). + */ +CINDEX_LINKAGE CXDependencyScannerService +clang_experimental_DependencyScannerService_create_v0(CXDependencyMode Format); + +/** + * Dispose of a \c CXDependencyScannerService object. + * + * The service object must be disposed of after the workers are disposed of. + */ +CINDEX_LINKAGE void clang_experimental_DependencyScannerService_dispose_v0( + CXDependencyScannerService); + +/** + * Object encapsulating instance of a dependency scanner worker. + * + * The dependency scanner workers are expected to be used in separate worker + * threads. An individual worker is not thread safe. + * + * Operations on a worker are not thread-safe and should only be used from a + * single thread at a time. They are intended to be used by a single dedicated + * thread in a thread pool, but they are not inherently pinned to a thread. + */ +typedef struct CXOpaqueDependencyScannerWorker *CXDependencyScannerWorker; + +/** + * Create a \c CXDependencyScannerWorker object. + * Must be disposed with + * \c clang_experimental_DependencyScannerWorker_dispose_v0(). + */ +CINDEX_LINKAGE CXDependencyScannerWorker + clang_experimental_DependencyScannerWorker_create_v0( + CXDependencyScannerService); + +CINDEX_LINKAGE void clang_experimental_DependencyScannerWorker_dispose_v0( + CXDependencyScannerWorker); + +/** + * A callback that is called whenever a module is discovered when in + * \c CXDependencyMode_Full mode. + * + * \param Context the context that was passed to + * \c clang_experimental_DependencyScannerWorker_getFileDependencies_v0. + * \param MDS the list of discovered modules. Must be freed by calling + * \c clang_experimental_ModuleDependencySet_dispose. + */ +typedef void CXModuleDiscoveredCallback(void *Context, + CXModuleDependencySet *MDS); + +/** + * Returns the list of file dependencies for a particular compiler invocation. + * + * \param argc the number of compiler invocation arguments (including argv[0]). + * \param argv the compiler invocation arguments (including argv[0]). + * the invocation may be a -cc1 clang invocation or a driver + * invocation. + * \param WorkingDirectory the directory in which the invocation runs. + * \param MDC a callback that is called whenever a new module is discovered. + * This may receive the same module on different workers. This should + * be NULL if + * \c clang_experimental_DependencyScannerService_create_v0 was + * called with \c CXDependencyMode_Flat. This callback will be called + * on the same thread that called this function. + * \param Context the context that will be passed to \c MDC each time it is + * called. + * \param [out] error the error string to pass back to client (if any). + * + * \returns A pointer to a CXFileDependencies on success, NULL otherwise. The + * CXFileDependencies must be freed by calling + * \c clang_experimental_FileDependencies_dispose. + */ +CINDEX_LINKAGE CXFileDependencies * +clang_experimental_DependencyScannerWorker_getFileDependencies_v0( + CXDependencyScannerWorker Worker, int argc, const char *const *argv, + const char *WorkingDirectory, CXModuleDiscoveredCallback *MDC, + void *Context, CXString *error); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif // LLVM_CLANG_C_DEPENDENCIES_H diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h index 689119330c412..22c84e032fd31 100644 --- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h +++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h @@ -63,6 +63,15 @@ class DependencyScanningWorker { const CompilationDatabase &CDB, DependencyConsumer &Consumer); + llvm::Error + computeDependenciesForClangInvocation(StringRef WorkingDirectory, + ArrayRef Arguments, + DependencyConsumer &Consumer); + + ScanningOutputFormat getFormat() const { return Format; } + + llvm::StringSet<> AlreadySeen; + private: IntrusiveRefCntPtr DiagOpts; std::shared_ptr PCHContainerOps; diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp index 32bbc578d2db5..dc85f2df9225f 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -220,3 +220,29 @@ llvm::Error DependencyScanningWorker::computeDependencies( return !Tool.run(&Action); }); } + +llvm::Error DependencyScanningWorker::computeDependenciesForClangInvocation( + StringRef WorkingDirectory, ArrayRef Arguments, + DependencyConsumer &Consumer) { + RealFS->setCurrentWorkingDirectory(WorkingDirectory); + return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) { + IntrusiveRefCntPtr DiagID = new DiagnosticIDs(); + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + DiagnosticsEngine Diags(DiagID, &*DiagOpts, &DC, /*ShouldOwnClient=*/false); + + llvm::opt::ArgStringList CC1Args; + for (const auto &Arg : Arguments) + CC1Args.push_back(Arg.c_str()); + std::unique_ptr Invocation( + newInvocation(&Diags, CC1Args)); + + DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS, + PPSkipMappings.get(), Format); + + llvm::IntrusiveRefCntPtr FM = Files; + if (!FM) + FM = new FileManager(FileSystemOptions(), RealFS); + return Action.runInvocation(std::move(Invocation), FM.get(), + PCHContainerOps, &DC); + }); +} diff --git a/clang/test/Index/Core/scan-deps.m b/clang/test/Index/Core/scan-deps.m new file mode 100644 index 0000000000000..2453ca5dfe1ef --- /dev/null +++ b/clang/test/Index/Core/scan-deps.m @@ -0,0 +1,29 @@ +// RUN: rm -rf %t.mcp +// RUN: echo %S > %t.result +// RUN: c-index-test core --scan-deps %S -- %clang -cc1 -I %S/Inputs/module \ +// RUN: -fmodules -fmodules-cache-path=%t.mcp -fimplicit-module-maps \ +// RUN: -o FoE.o -x objective-c %s >> %t.result +// RUN: cat %t.result | sed 's/\\/\//g' | FileCheck %s + +@import ModA; + +// CHECK: [[PREFIX:.*]] +// CHECK-NEXT: modules: +// CHECK-NEXT: module: +// CHECK-NEXT: name: ModA +// CHECK-NEXT: context-hash: [[CONTEXT_HASH:[A-Z0-9]+]] +// CHECK-NEXT: module-map-path: [[PREFIX]]/Inputs/module/module.modulemap +// CHECK-NEXT: module-deps: +// CHECK-NEXT: file-deps: +// CHECK-NEXT: [[PREFIX]]/Inputs/module/ModA.h +// CHECK-NEXT: [[PREFIX]]/Inputs/module/SubModA.h +// CHECK-NEXT: [[PREFIX]]/Inputs/module/SubSubModA.h +// CHECK-NEXT: [[PREFIX]]/Inputs/module/module.modulemap +// CHECK-NEXT: build-args: +// CHECK-NEXT: dependencies: +// CHECK-NEXT: context-hash: [[CONTEXT_HASH]] +// CHECK-NEXT: module-deps: +// CHECK-NEXT: ModA:[[CONTEXT_HASH]] +// CHECK-NEXT: file-deps: +// CHECK-NEXT: [[PREFIX]]/scan-deps.m +// CHECK-NEXT: additional-build-args: diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt index dd76de05826af..dbca85e20cffb 100644 --- a/clang/tools/c-index-test/CMakeLists.txt +++ b/clang/tools/c-index-test/CMakeLists.txt @@ -37,6 +37,7 @@ else() clangAST clangBasic clangCodeGen + clangDependencyScanning clangDirectoryWatcher clangFrontend clangIndex diff --git a/clang/tools/c-index-test/core_main.cpp b/clang/tools/c-index-test/core_main.cpp index 900aadb471427..8118a867cd547 100644 --- a/clang/tools/c-index-test/core_main.cpp +++ b/clang/tools/c-index-test/core_main.cpp @@ -8,6 +8,7 @@ #include "JSONAggregation.h" #include "indexstore/IndexStoreCXX.h" +#include "clang-c/Dependencies.h" #include "clang/DirectoryWatcher/DirectoryWatcher.h" #include "clang/AST/Mangle.h" #include "clang/Basic/LangOptions.h" @@ -24,12 +25,13 @@ #include "clang/Index/USRGeneration.h" #include "clang/Lex/Preprocessor.h" #include "clang/Serialization/ASTReader.h" +#include "llvm/ADT/FunctionExtras.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" -#include "llvm/Support/PrettyStackTrace.h" #include using namespace clang; @@ -47,6 +49,7 @@ enum class ActionType { PrintUnit, PrintStoreFormatVersion, AggregateAsJSON, + ScanDeps, WatchDir, }; @@ -67,6 +70,8 @@ Action(cl::desc("Action:"), cl::init(ActionType::None), "print-store-format-version", "Print store format version"), clEnumValN(ActionType::AggregateAsJSON, "aggregate-json", "Aggregate index data in JSON format"), + clEnumValN(ActionType::ScanDeps, "scan-deps", + "Get file dependencies"), clEnumValN(ActionType::WatchDir, "watch-dir", "Watch directory for file events")), cl::cat(IndexTestCoreCategory)); @@ -630,6 +635,77 @@ static void printSymbolNameAndUSR(const clang::Module *Mod, raw_ostream &OS) { generateFullUSRForModule(Mod, OS); } +static int scanDeps(ArrayRef Args, std::string WorkingDirectory) { + CXDependencyScannerService Service = + clang_experimental_DependencyScannerService_create_v0( + CXDependencyMode_Full); + CXDependencyScannerWorker Worker = + clang_experimental_DependencyScannerWorker_create_v0(Service); + CXString Error; + + auto Callback = [&](CXModuleDependencySet *MDS) { + llvm::outs() << "modules:\n"; + for (const auto &M : llvm::makeArrayRef(MDS->Modules, MDS->Count)) { + llvm::outs() << " module:\n" + << " name: " << clang_getCString(M.Name) << "\n" + << " context-hash: " << clang_getCString(M.ContextHash) + << "\n" + << " module-map-path: " + << clang_getCString(M.ModuleMapPath) << "\n" + << " module-deps:\n"; + for (const auto &ModuleName : + llvm::makeArrayRef(M.ModuleDeps->Strings, M.ModuleDeps->Count)) + llvm::outs() << " " << clang_getCString(ModuleName) << "\n"; + llvm::outs() << " file-deps:\n"; + for (const auto &FileName : + llvm::makeArrayRef(M.FileDeps->Strings, M.FileDeps->Count)) + llvm::outs() << " " << clang_getCString(FileName) << "\n"; + llvm::outs() << " build-args:"; + for (const auto &Arg : llvm::makeArrayRef(M.BuildArguments->Strings, + M.BuildArguments->Count)) + llvm::outs() << " " << clang_getCString(Arg); + llvm::outs() << "\n"; + } + clang_experimental_ModuleDependencySet_dispose(MDS); + }; + + auto CB = + functionObjectToCCallbackRef(Callback); + + CXFileDependencies *Result = + clang_experimental_DependencyScannerWorker_getFileDependencies_v0( + Worker, Args.size(), Args.data(), WorkingDirectory.c_str(), + CB.Callback, CB.Context, &Error); + if (!Result) { + llvm::errs() << "error: failed to get dependencies\n"; + llvm::errs() << clang_getCString(Error) << "\n"; + clang_disposeString(Error); + return 1; + } + llvm::outs() << "dependencies:\n"; + llvm::outs() << " context-hash: " << clang_getCString(Result->ContextHash) + << "\n" + << " module-deps:\n"; + for (const auto &ModuleName : llvm::makeArrayRef(Result->ModuleDeps->Strings, + Result->ModuleDeps->Count)) + llvm::outs() << " " << clang_getCString(ModuleName) << "\n"; + llvm::outs() << " file-deps:\n"; + for (const auto &FileName : + llvm::makeArrayRef(Result->FileDeps->Strings, Result->FileDeps->Count)) + llvm::outs() << " " << clang_getCString(FileName) << "\n"; + llvm::outs() << " additional-build-args:"; + for (const auto &Arg : + llvm::makeArrayRef(Result->AdditionalArguments->Strings, + Result->AdditionalArguments->Count)) + llvm::outs() << " " << clang_getCString(Arg); + llvm::outs() << "\n"; + + clang_experimental_FileDependencies_dispose(Result); + clang_experimental_DependencyScannerWorker_dispose_v0(Worker); + clang_experimental_DependencyScannerService_dispose_v0(Service); + return 0; +} + static void printSymbol(const IndexRecordDecl &Rec, raw_ostream &OS) { printSymbolInfo(Rec.SymInfo, OS); OS << " | "; @@ -918,6 +994,14 @@ int indextest_core_main(int argc, const char **argv) { } return aggregateDataAsJSON(storePath, OS); } + + if (options::Action == ActionType::ScanDeps) { + if (options::InputFiles.empty()) { + errs() << "error: missing working directory\n"; + return 1; + } + return scanDeps(CompArgs, options::InputFiles[0]); + } if (options::Action == ActionType::WatchDir) { if (options::InputFiles.empty()) { diff --git a/clang/tools/libclang/CDependencies.cpp b/clang/tools/libclang/CDependencies.cpp new file mode 100644 index 0000000000000..9366505cc8398 --- /dev/null +++ b/clang/tools/libclang/CDependencies.cpp @@ -0,0 +1,222 @@ +//===- CDependencies.cpp - Dependency Discovery C Interface ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implements the dependency discovery interface. It provides a C library for +// the functionality that clang-scan-deps provides. +// +//===----------------------------------------------------------------------===// + +#include "CXString.h" + +#include "clang-c/Dependencies.h" + +#include "clang/Tooling/DependencyScanning/DependencyScanningService.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" + +using namespace clang; +using namespace clang::tooling::dependencies; + +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(DependencyScanningService, + CXDependencyScannerService) +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(DependencyScanningWorker, + CXDependencyScannerWorker) + +inline ScanningOutputFormat unwrap(CXDependencyMode Format) { + switch (Format) { + case CXDependencyMode_Flat: + return ScanningOutputFormat::Make; + case CXDependencyMode_Full: + return ScanningOutputFormat::Full; + } +} + +void clang_experimental_ModuleDependencySet_dispose( + CXModuleDependencySet *MDS) { + for (int I = 0; I < MDS->Count; ++I) { + CXModuleDependency &MD = MDS->Modules[I]; + clang_disposeString(MD.Name); + clang_disposeString(MD.ContextHash); + clang_disposeString(MD.ModuleMapPath); + clang_disposeStringSet(MD.FileDeps); + clang_disposeStringSet(MD.ModuleDeps); + } + delete[] MDS->Modules; + delete MDS; +} + +CXDependencyScannerService +clang_experimental_DependencyScannerService_create_v0(CXDependencyMode Format) { + return wrap(new DependencyScanningService( + ScanningMode::MinimizedSourcePreprocessing, unwrap(Format), + /*ReuseFilemanager=*/false)); +} + +void clang_experimental_DependencyScannerService_dispose_v0( + CXDependencyScannerService Service) { + delete unwrap(Service); +} + +void clang_experimental_FileDependencies_dispose(CXFileDependencies *ID) { + clang_disposeString(ID->ContextHash); + clang_disposeStringSet(ID->FileDeps); + clang_disposeStringSet(ID->ModuleDeps); + delete ID; +} + +CXDependencyScannerWorker clang_experimental_DependencyScannerWorker_create_v0( + CXDependencyScannerService Service) { + return wrap(new DependencyScanningWorker(*unwrap(Service))); +} + +void clang_experimental_DependencyScannerWorker_dispose_v0( + CXDependencyScannerWorker Worker) { + delete unwrap(Worker); +} + +static CXFileDependencies * +getFlatDependencies(DependencyScanningWorker *Worker, + ArrayRef Compilation, + const char *WorkingDirectory, CXString *error) { + // TODO: Implement flat deps. + return nullptr; +} + +namespace { +class FullDependencyConsumer : public DependencyConsumer { +public: + FullDependencyConsumer(const llvm::StringSet<> &AlreadySeen) + : AlreadySeen(AlreadySeen) {} + + void handleFileDependency(const DependencyOutputOptions &Opts, + StringRef File) override { + if (OutputPaths.empty()) + OutputPaths = Opts.Targets; + Dependencies.push_back(std::string(File)); + } + + void handleModuleDependency(ModuleDeps MD) override { + ClangModuleDeps[MD.ContextHash + MD.ModuleName] = std::move(MD); + } + + void handleContextHash(std::string Hash) override { + ContextHash = std::move(Hash); + } + + FullDependenciesResult getFullDependencies() const { + FullDependencies FD; + + FD.ContextHash = std::move(ContextHash); + + FD.FileDeps.assign(Dependencies.begin(), Dependencies.end()); + + for (auto &&M : ClangModuleDeps) { + auto &MD = M.second; + if (MD.ImportedByMainFile) + FD.ClangModuleDeps.push_back({MD.ModuleName, ContextHash}); + } + + FullDependenciesResult FDR; + + for (auto &&M : ClangModuleDeps) { + // TODO: Avoid handleModuleDependency even being called for modules + // we've already seen. + if (AlreadySeen.count(M.first)) + continue; + FDR.DiscoveredModules.push_back(std::move(M.second)); + } + + FDR.FullDeps = std::move(FD); + return FDR; + } + +private: + std::vector Dependencies; + std::unordered_map ClangModuleDeps; + std::string ContextHash; + std::vector OutputPaths; + const llvm::StringSet<> &AlreadySeen; +}; +} // namespace + +static CXFileDependencies *getFullDependencies( + DependencyScanningWorker *Worker, ArrayRef Compilation, + const char *WorkingDirectory, CXModuleDiscoveredCallback *MDC, + void *Context, CXString *error) { + FullDependencyConsumer Consumer(Worker->AlreadySeen); + llvm::Error Result = Worker->computeDependenciesForClangInvocation( + WorkingDirectory, Compilation, Consumer); + + if (Result) { + std::string Str; + llvm::raw_string_ostream OS(Str); + llvm::handleAllErrors(std::move(Result), + [&](const llvm::ErrorInfoBase &EI) { EI.log(OS); }); + *error = cxstring::createDup(OS.str()); + return nullptr; + } + + FullDependenciesResult FDR = Consumer.getFullDependencies(); + + if (!FDR.DiscoveredModules.empty()) { + CXModuleDependencySet *MDS = new CXModuleDependencySet; + MDS->Count = FDR.DiscoveredModules.size(); + MDS->Modules = new CXModuleDependency[MDS->Count]; + for (int I = 0; I < MDS->Count; ++I) { + CXModuleDependency &M = MDS->Modules[I]; + const ModuleDeps &MD = FDR.DiscoveredModules[I]; + M.Name = cxstring::createDup(MD.ModuleName); + M.ContextHash = cxstring::createDup(MD.ContextHash); + M.ModuleMapPath = cxstring::createDup(MD.ClangModuleMapFile); + M.FileDeps = cxstring::createSet(MD.FileDeps); + std::vector Modules; + for (const ClangModuleDep &CMD : MD.ClangModuleDeps) + Modules.push_back(CMD.ModuleName + ":" + CMD.ContextHash); + M.ModuleDeps = cxstring::createSet(Modules); + M.BuildArguments = cxstring::createSet(std::vector{}); + } + MDC(Context, MDS); + } + + const FullDependencies &FD = FDR.FullDeps; + CXFileDependencies *FDeps = new CXFileDependencies; + FDeps->ContextHash = cxstring::createDup(FD.ContextHash); + FDeps->FileDeps = cxstring::createSet(FD.FileDeps); + std::vector Modules; + for (const ClangModuleDep &CMD : FD.ClangModuleDeps) + Modules.push_back(CMD.ModuleName + ":" + CMD.ContextHash); + FDeps->ModuleDeps = cxstring::createSet(Modules); + FDeps->AdditionalArguments = cxstring::createSet(std::vector{}); + return FDeps; +} + +CXFileDependencies * +clang_experimental_DependencyScannerWorker_getFileDependencies_v0( + CXDependencyScannerWorker W, int argc, const char *const *argv, + const char *WorkingDirectory, CXModuleDiscoveredCallback *MDC, + void *Context, CXString *error) { + if (!W || argc < 2) + return nullptr; + if (error) + *error = cxstring::createEmpty(); + + DependencyScanningWorker *Worker = unwrap(W); + + std::vector Compilation; + if (StringRef(argv[1]) == "-cc1") + for (int i = 2; i < argc; ++i) + Compilation.push_back(argv[i]); + else { + return nullptr; // TODO: Run the driver to get -cc1 args. + } + + if (Worker->getFormat() == ScanningOutputFormat::Full) + return getFullDependencies(Worker, Compilation, WorkingDirectory, MDC, + Context, error); + return getFlatDependencies(Worker, Compilation, WorkingDirectory, error); +} diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt index 192bad104d523..54b6607df7a7f 100644 --- a/clang/tools/libclang/CMakeLists.txt +++ b/clang/tools/libclang/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES ARCMigrate.cpp BuildSystem.cpp + CDependencies.cpp CIndex.cpp CIndexCXX.cpp CIndexCodeCompletion.cpp @@ -40,6 +41,7 @@ set(LIBS clangASTMatchers clangAPINotes clangBasic + clangDependencyScanning clangDriver clangFrontend clangIndex diff --git a/clang/tools/libclang/CXString.cpp b/clang/tools/libclang/CXString.cpp index 2754795f4a647..ce4964df9fca9 100644 --- a/clang/tools/libclang/CXString.cpp +++ b/clang/tools/libclang/CXString.cpp @@ -119,6 +119,23 @@ CXStringSet *createSet(const std::vector &Strings) { return Set; } +CXStringSet *createSet(const llvm::StringSet<> &StringsUnordered) { + std::vector Strings; + + for (auto SI = StringsUnordered.begin(), + SE = StringsUnordered.end(); SI != SE; ++SI) + Strings.push_back(SI->getKey()); + + llvm::sort(Strings); + + CXStringSet *Set = new CXStringSet; + Set->Count = Strings.size(); + Set->Strings = new CXString[Set->Count]; + int I = 0; + for (auto SI = Strings.begin(), SE = Strings.end(); SI != SE; ++SI) + Set->Strings[I++] = createDup(*SI); + return Set; +} //===----------------------------------------------------------------------===// // String pools. diff --git a/clang/tools/libclang/CXString.h b/clang/tools/libclang/CXString.h index 809bdec3d677f..5f9351c0a7c45 100644 --- a/clang/tools/libclang/CXString.h +++ b/clang/tools/libclang/CXString.h @@ -17,6 +17,7 @@ #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/Compiler.h" #include #include @@ -69,6 +70,8 @@ CXString createCXString(CXStringBuf *buf); CXStringSet *createSet(const std::vector &Strings); +CXStringSet *createSet(const llvm::StringSet<> &Strings); + /// A string pool used for fast allocation/deallocation of strings. class CXStringPool { public: diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports index 2bbc8dca6cbb8..035bfb91b8e1e 100644 --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -157,6 +157,13 @@ clang_equalLocations clang_equalRanges clang_equalTypes clang_executeOnThread +clang_experimental_DependencyScannerService_create_v0 +clang_experimental_DependencyScannerService_dispose_v0 +clang_experimental_DependencyScannerWorker_create_v0 +clang_experimental_DependencyScannerWorker_dispose_v0 +clang_experimental_DependencyScannerWorker_getFileDependencies_v0 +clang_experimental_FileDependencies_dispose +clang_experimental_ModuleDependencySet_dispose clang_findIncludesInFile clang_findIncludesInFileWithBlock clang_findReferencesInFile diff --git a/llvm/include/llvm/ADT/FunctionExtras.h b/llvm/include/llvm/ADT/FunctionExtras.h index 121aa527a5dac..69df9352bf5cd 100644 --- a/llvm/include/llvm/ADT/FunctionExtras.h +++ b/llvm/include/llvm/ADT/FunctionExtras.h @@ -287,6 +287,37 @@ class unique_function { } }; +template struct FunctionObjectCallback { + void *Context; + CallTy *Callback; +}; + +namespace detail { +template +struct functionObjectToCCallbackRefImpl; + +template +struct functionObjectToCCallbackRefImpl { + static FunctionObjectCallback impl(FuncTy &F) { + auto Func = +[](void *C, Args... V) -> Ret { + return (*reinterpret_cast *>(C))( + std::forward(V)...); + }; + + return {&F, Func}; + } +}; +} // namespace detail + +/// Returns a function pointer and context pair suitable for use as a C +/// callback. +/// +/// \param F the function object to turn into a C callback. The returned +/// callback has the same lifetime as F. +template +auto functionObjectToCCallbackRef(FuncTy &F) { + return detail::functionObjectToCCallbackRefImpl::impl(F); +} } // end namespace llvm #endif // LLVM_ADT_FUNCTION_H From 1fdae62265b1603bdc97491c5b653962be356d80 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Mon, 16 Dec 2019 18:26:33 -0800 Subject: [PATCH 02/10] [libclang] Add clang_Driver_getExternalActionsForCommand_v0 This function returns the equivalent of -### --- clang/include/clang-c/Driver.h | 78 +++++++++++++++ clang/tools/libclang/CMakeLists.txt | 1 + clang/tools/libclang/Driver.cpp | 125 ++++++++++++++++++++++++ clang/tools/libclang/libclang.exports | 2 + clang/unittests/libclang/CMakeLists.txt | 1 + clang/unittests/libclang/DriverTest.cpp | 75 ++++++++++++++ 6 files changed, 282 insertions(+) create mode 100644 clang/include/clang-c/Driver.h create mode 100644 clang/tools/libclang/Driver.cpp create mode 100644 clang/unittests/libclang/DriverTest.cpp diff --git a/clang/include/clang-c/Driver.h b/clang/include/clang-c/Driver.h new file mode 100644 index 0000000000000..54dd1b0b043a3 --- /dev/null +++ b/clang/include/clang-c/Driver.h @@ -0,0 +1,78 @@ +/*==-- clang-c/Driver.h - A C Interface for the Clang Driver ------*- C -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header provides a C API for extracting information from the clang *| +|* driver. *| +|* *| +\*===----------------------------------------------------------------------===*/ + + +#ifndef CLANG_CLANG_C_DRIVER +#define CLANG_CLANG_C_DRIVER + +#include "clang-c/Index.h" +#include "clang-c/Platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Contains the command line arguments for an external action. Same format as + * provided to main. + */ +typedef struct { + /* Number of arguments in ArgV */ + int ArgC; + /* Null terminated array of pointers to null terminated argument strings */ + const char **ArgV; +} CXExternalAction; + +/** + * Contains the list of external actions clang would invoke. + */ +typedef struct { + int Count; + CXExternalAction **Actions; +} CXExternalActionList; + +/** + * Get the external actions that the clang driver will invoke for the given + * command line. + * + * \param ArgC number of arguments in \p ArgV. + * \param ArgV array of null terminated arguments. Doesn't need to be null + * terminated. + * \param Environment must be null. + * \param WorkingDirectory a null terminated path to the working directory to + * use for this invocation. `nullptr` to use the current working directory of + * the process. + * \param OutDiags will be set to a \c CXDiagnosticSet if there's an error. + * Must be freed by calling \c clang_disposeDiagnosticSet . + * \returns A pointer to a \c CXExternalActionList on success, null on failure. + * The returned \c CXExternalActionList must be freed by calling + * \c clang_Driver_ExternalActionList_dispose . + */ +CINDEX_LINKAGE CXExternalActionList * +clang_Driver_getExternalActionsForCommand_v0(int ArgC, const char **ArgV, + const char **Environment, + const char *WorkingDirectory, + CXDiagnosticSet *OutDiags); + +/** + * Deallocate a \c CXExternalActionList + */ +CINDEX_LINKAGE void +clang_Driver_ExternalActionList_dispose(CXExternalActionList *EAL); + +#ifdef __cplusplus +} +#endif + +#endif // CLANG_CLANG_C_DRIVER diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt index 54b6607df7a7f..bcb5d2fc3bb68 100644 --- a/clang/tools/libclang/CMakeLists.txt +++ b/clang/tools/libclang/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES CXStoredDiagnostic.cpp CXString.cpp CXType.cpp + Driver.cpp Indexing.cpp FatalErrorHandler.cpp diff --git a/clang/tools/libclang/Driver.cpp b/clang/tools/libclang/Driver.cpp new file mode 100644 index 0000000000000..1a5294a54138c --- /dev/null +++ b/clang/tools/libclang/Driver.cpp @@ -0,0 +1,125 @@ +//===- Driver.cpp - A C Interface for the Clang Driver --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides a C API for extracting information from the clang driver. +// +//===----------------------------------------------------------------------===// + +#include "clang-c/Driver.h" + +#include "CIndexDiagnostic.h" + +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/ADT/ArrayRef.h" + +using namespace clang; + +class CXDiagnosticSetDiagnosticConsumer : public DiagnosticConsumer { + SmallVector Errors; +public: + + void HandleDiagnostic(DiagnosticsEngine::Level level, + const Diagnostic &Info) override { + if (level >= DiagnosticsEngine::Error) + Errors.push_back(StoredDiagnostic(level, Info)); + } + + CXDiagnosticSet getDiagnosticSet() { + return cxdiag::createStoredDiags(Errors, LangOptions()); + } +}; + +CXExternalActionList * +clang_Driver_getExternalActionsForCommand_v0(int ArgC, const char **ArgV, + const char **Environment, + const char *WorkingDirectory, + CXDiagnosticSet *OutDiags) { + if (OutDiags) + *OutDiags = nullptr; + + // Non empty environments are not currently supported. + if (Environment) + return nullptr; + + // ArgV must at least include the compiler executable name. + if (ArgC < 1) + return nullptr; + + CXDiagnosticSetDiagnosticConsumer DiagConsumer; + auto Diags = CompilerInstance::createDiagnostics(new DiagnosticOptions, + &DiagConsumer, false); + + // Use createPhysicalFileSystem instead of getRealFileSystem so that + // setCurrentWorkingDirectory doesn't change the working directory of the + // process. + std::unique_ptr VFS = + llvm::vfs::createPhysicalFileSystem(); + if (WorkingDirectory) + if (std::error_code EC = + VFS->setCurrentWorkingDirectory(WorkingDirectory)) { + Diags->Report(diag::err_drv_unable_to_set_working_directory) << + WorkingDirectory; + if (OutDiags) + *OutDiags = DiagConsumer.getDiagnosticSet(); + return nullptr; + } + + driver::Driver TheDriver(ArgV[0], llvm::sys::getDefaultTargetTriple(), *Diags, + VFS.release()); + TheDriver.setCheckInputsExist(false); + std::unique_ptr C( + TheDriver.BuildCompilation(llvm::makeArrayRef(ArgV, ArgC))); + if (!C) { + if (OutDiags) + *OutDiags = DiagConsumer.getDiagnosticSet(); + return nullptr; + } + + const driver::JobList &Jobs = C->getJobs(); + CXExternalAction **Actions = new CXExternalAction *[Jobs.size()]; + int AI = 0; + for (auto &&J : Jobs) { + // First calculate the total space we'll need for this action's arguments. + llvm::opt::ArgStringList Args = J.getArguments(); + Args.insert(Args.begin(), J.getExecutable()); + int ArgSpace = (Args.size() + 1) * sizeof(const char *); + for (auto &&Arg : Args) + ArgSpace += strlen(Arg) + 1; // Null terminator + + // Tail allocate the space for the strings. + auto Action = + new ((CXExternalAction *)malloc(sizeof(CXExternalAction) + ArgSpace)) + CXExternalAction; + Action->ArgC = Args.size(); + Action->ArgV = reinterpret_cast(Action + 1); + Action->ArgV[Args.size()] = nullptr; + char *StrTable = ((char *)Action) + sizeof(CXExternalAction) + + (Args.size() + 1) * sizeof(const char *); + int I = 0; + for (auto &&Arg : Args) { + Action->ArgV[I++] = strcpy(StrTable, Arg); + StrTable += strlen(Arg) + 1; + } + Actions[AI++] = Action; + } + + return new CXExternalActionList{(int)Jobs.size(), Actions}; +} + +void clang_Driver_ExternalActionList_dispose(CXExternalActionList *EAL) { + if (!EAL) + return; + + for (int I = 0; I < EAL->Count; ++I) + free(EAL->Actions[I]); + delete[] EAL->Actions; + delete EAL; +} diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports index 035bfb91b8e1e..b3f4c7b0f36f6 100644 --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -49,6 +49,8 @@ clang_Cursor_isObjCOptional clang_Cursor_isVariadic clang_Cursor_getModule clang_Cursor_getStorageClass +clang_Driver_ExternalActionList_dispose +clang_Driver_getExternalActionsForCommand_v0 clang_File_isEqual clang_File_tryGetRealPathName clang_Module_getASTFile diff --git a/clang/unittests/libclang/CMakeLists.txt b/clang/unittests/libclang/CMakeLists.txt index b3644a0e710e1..51288a8b91deb 100644 --- a/clang/unittests/libclang/CMakeLists.txt +++ b/clang/unittests/libclang/CMakeLists.txt @@ -1,5 +1,6 @@ add_clang_unittest(libclangTests LibclangTest.cpp + DriverTest.cpp ) target_link_libraries(libclangTests diff --git a/clang/unittests/libclang/DriverTest.cpp b/clang/unittests/libclang/DriverTest.cpp new file mode 100644 index 0000000000000..6a2713fb9a775 --- /dev/null +++ b/clang/unittests/libclang/DriverTest.cpp @@ -0,0 +1,75 @@ +//===---- DriverTest.cpp --------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang-c/Driver.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Debug.h" +#include "gtest/gtest.h" + +#define DEBUG_TYPE "driver-test" + +TEST(DriverTests, Basic) { + const char *ArgV[] = {"clang", "-w", "t.cpp", "-o", "t.ll"}; + + CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0( + GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, nullptr, nullptr); + ASSERT_NE(EAL, nullptr); + ASSERT_EQ(EAL->Count, 2); + auto *CompileAction = EAL->Actions[0]; + ASSERT_GE(CompileAction->ArgC, 2); + EXPECT_STREQ(CompileAction->ArgV[0], "clang"); + EXPECT_STREQ(CompileAction->ArgV[1], "-cc1"); + + clang_Driver_ExternalActionList_dispose(EAL); +} + +TEST(DriverTests, WorkingDirectory) { + const char *ArgV[] = {"clang", "-c", "t.cpp", "-o", "t.o"}; + + CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0( + GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/", nullptr); + ASSERT_NE(EAL, nullptr); + ASSERT_EQ(EAL->Count, 1); + auto *CompileAction = EAL->Actions[0]; + + const char **FDCD = std::find(CompileAction->ArgV, CompileAction->ArgV + + CompileAction->ArgC, + llvm::StringRef("-fdebug-compilation-dir")); + ASSERT_NE(FDCD, CompileAction->ArgV + CompileAction->ArgC); + ASSERT_NE(FDCD + 1, CompileAction->ArgV + CompileAction->ArgC); + EXPECT_STREQ(*(FDCD + 1), "/"); + + clang_Driver_ExternalActionList_dispose(EAL); +} + +TEST(DriverTests, Diagnostics) { + const char *ArgV[] = {"clang", "-c", "nosuchfile.cpp", "-o", "t.o"}; + + CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0( + GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/no/such/working/dir", nullptr); + EXPECT_EQ(nullptr, EAL); + clang_Driver_ExternalActionList_dispose(EAL); + + CXDiagnosticSet Diags; + EAL = clang_Driver_getExternalActionsForCommand_v0( + GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/no/such/working/dir", &Diags); + EXPECT_EQ(nullptr, EAL); + ASSERT_NE(nullptr, Diags); + + unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags); + ASSERT_EQ(1u, NumDiags); + CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, 0); + CXString Str = clang_formatDiagnostic(Diag, 0); + EXPECT_STREQ(clang_getCString(Str), + "error: unable to set working directory: /no/such/working/dir"); + clang_disposeString(Str); + + clang_disposeDiagnosticSet(Diags); + clang_Driver_ExternalActionList_dispose(EAL); +} From 5286f371be86e391cff0c6dfd2bf36dad1c4c6e0 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Wed, 8 Jan 2020 16:41:17 -0800 Subject: [PATCH 03/10] [Clang][cc1] Add -remove-preceeding-explicit-module-build-incompatible-options Removes any arguments before this one that would be incompatible with explicitly building a module. This includes things like -o and input files. This option can be used to append arguments to convert a build of a translation unit with implicit modules into an explicit build of a specific module. --- clang/include/clang/Driver/CC1Options.td | 12 +++++++++ clang/lib/Frontend/CompilerInvocation.cpp | 25 +++++++++++++++++++ ...plicit-module-build-incompatible-options.c | 6 +++++ llvm/include/llvm/Option/ArgList.h | 12 +++++++++ 4 files changed, 55 insertions(+) create mode 100644 clang/test/Modules/remove-preceeding-explicit-module-build-incompatible-options.c diff --git a/clang/include/clang/Driver/CC1Options.td b/clang/include/clang/Driver/CC1Options.td index bda0411459b69..4f79ca739da0d 100644 --- a/clang/include/clang/Driver/CC1Options.td +++ b/clang/include/clang/Driver/CC1Options.td @@ -12,6 +12,18 @@ let Flags = [CC1Option, NoDriverOption] in { +//===----------------------------------------------------------------------===// +// Option Options +//===----------------------------------------------------------------------===// + +def remove_preceeding_explicit_module_build_incompatible_options : + Flag<["-"], "remove-preceeding-explicit-module-build-incompatible-options">, + HelpText<"Removes any arguments before this one that would be incompatible " + "with explicitly building a module. This includes things like -o " + "and input files. This option can be used to append arguments to " + "convert a build of a translation unit with implicit modules " + "into an explicit build of a specific module.">; + //===----------------------------------------------------------------------===// // Target Options //===----------------------------------------------------------------------===// diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index a29732ed555a2..bda9ddc8e77a5 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3654,6 +3654,28 @@ static void ParseTargetArgs(TargetOptions &Opts, ArgList &Args, } } +static void removeExplicitModuleBuildIncompatibleOptions(InputArgList &Args) { + auto REMBIO = llvm::find_if(Args, [](const Arg *A){ + return A->getOption().getID() == + OPT_remove_preceeding_explicit_module_build_incompatible_options; + }); + if (REMBIO == Args.end()) + return; + + llvm::SmallPtrSet BeforeREMBIO; + for (auto I = Args.begin(); I != REMBIO; ++I) + BeforeREMBIO.insert(*I); + + Args.eraseArgIf([&](const Arg *A) { + if (!BeforeREMBIO.count(A)) + return false; + const Option &O = A->getOption(); + return O.matches(OPT_INPUT) || + O.matches(OPT_Action_Group) || + O.matches(OPT__output); + }); +} + bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, ArrayRef CommandLineArgs, DiagnosticsEngine &Diags) { @@ -3665,6 +3687,9 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, unsigned MissingArgIndex, MissingArgCount; InputArgList Args = Opts.ParseArgs(CommandLineArgs, MissingArgIndex, MissingArgCount, IncludedFlagsBitmask); + + removeExplicitModuleBuildIncompatibleOptions(Args); + LangOptions &LangOpts = *Res.getLangOpts(); // Check for missing argument error. diff --git a/clang/test/Modules/remove-preceeding-explicit-module-build-incompatible-options.c b/clang/test/Modules/remove-preceeding-explicit-module-build-incompatible-options.c new file mode 100644 index 0000000000000..8754dd2ee78e6 --- /dev/null +++ b/clang/test/Modules/remove-preceeding-explicit-module-build-incompatible-options.c @@ -0,0 +1,6 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -emit-obj -o %t.o input-that-doesnt-exist.c \ +// RUN: -remove-preceeding-explicit-module-build-incompatible-options \ +// RUN: -emit-llvm-bc -o %t.bc %s +// RUN: not ls %t.o +// RUN: llvm-dis %t.bc diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h index 74bfadcba7267..af83a9024a67d 100644 --- a/llvm/include/llvm/Option/ArgList.h +++ b/llvm/include/llvm/Option/ArgList.h @@ -228,6 +228,18 @@ class ArgList { /// eraseArg - Remove any option matching \p Id. void eraseArg(OptSpecifier Id); + + /// eraseArgIf - Remove every `const Arg *A` for which P(A) is true. + template + void eraseArgIf(Pred P) { + for (Arg *const &A : Args) { + if (P(A)) { + Arg **ArgsBegin = Args.data(); + ArgsBegin[&A - ArgsBegin] = nullptr; + // Don't update OptRanges as it's not required and would be slow to do. + } + } + } /// @} /// @name Arg Access From b05c4d38f0bf9bd1458a170b7e32275ea7f1d0ed Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Wed, 8 Jan 2020 16:43:14 -0800 Subject: [PATCH 04/10] [Clang][ScanDeps] Actually pass the non path command line modifications. --- .../DependencyScanning/DependencyScanningTool.cpp | 5 +++++ .../DependencyScanning/ModuleDepCollector.cpp | 6 ++++-- clang/test/ClangScanDeps/modules-full.cpp | 12 +++++++++--- clang/test/Index/Core/scan-deps.m | 4 ++-- clang/tools/libclang/CDependencies.cpp | 10 ++++++++-- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp index 16040c2f46260..5a070983721fa 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -128,6 +128,11 @@ DependencyScanningTool::getFullDependencies( if (MD.ImportedByMainFile) FD.ClangModuleDeps.push_back({MD.ModuleName, ContextHash}); } + + FD.AdditionalNonPathCommandLine = { + "-fno-implicit-modules", + "-fno-implicit-module-maps", + }; FullDependenciesResult FDR; diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 9a0a6db82faa5..39a71d86486bc 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -53,8 +53,6 @@ void dependencies::detail::appendCommonModuleArguments( } }; - Result.push_back("-fno-implicit-modules"); - Result.push_back("-fno-implicit-module-maps"); AddArgs(Modules); } @@ -159,6 +157,10 @@ void ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { *MF, true, true, [&](const serialization::InputFile &IF, bool isSystem) { MD.FileDeps.insert(IF.getFile()->getName()); }); + MD.NonPathCommandLine = { + "-remove-preceeding-explicit-module-build-incompatible-options", + "-fno-implicit-modules", "-emit-module", "-fmodule-name=" + MD.ModuleName, + }; llvm::DenseSet AddedModules; addAllSubmoduleDeps(M, MD, AddedModules); diff --git a/clang/test/ClangScanDeps/modules-full.cpp b/clang/test/ClangScanDeps/modules-full.cpp index 1e6a740c27392..599001743dcdc 100644 --- a/clang/test/ClangScanDeps/modules-full.cpp +++ b/clang/test/ClangScanDeps/modules-full.cpp @@ -31,8 +31,10 @@ // CHECK-NEXT: ], // CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", // CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-remove-preceeding-explicit-module-build-incompatible-options", // CHECK-NEXT: "-fno-implicit-modules", -// CHECK-NEXT: "-fno-implicit-module-maps", +// CHECK-NEXT: "-emit-module", +// CHECK-NEXT: "-fmodule-name=header1", // CHECK-NEXT: "-fmodule-file=[[PREFIX]]/module-cache/[[CONTEXT_HASH_H1]]/header2-{{[A-Z0-9]+}}.pcm", // CHECK-NEXT: "-fmodule-map-file=[[PREFIX]]/Inputs/module.modulemap" // CHECK-NEXT: ], @@ -47,8 +49,10 @@ // CHECK-NEXT: "clang-module-deps": [], // CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", // CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-remove-preceeding-explicit-module-build-incompatible-options", // CHECK-NEXT: "-fno-implicit-modules", -// CHECK-NEXT: "-fno-implicit-module-maps" +// CHECK-NEXT: "-emit-module", +// CHECK-NEXT: "-fmodule-name=header1" // CHECK-NEXT: ], // CHECK-NEXT: "context-hash": "[[CONTEXT_HASH_H2:[A-Z0-9]+]]", // CHECK-NEXT: "file-deps": [ @@ -61,8 +65,10 @@ // CHECK-NEXT: "clang-module-deps": [], // CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", // CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-remove-preceeding-explicit-module-build-incompatible-options", // CHECK-NEXT: "-fno-implicit-modules", -// CHECK-NEXT: "-fno-implicit-module-maps" +// CHECK-NEXT: "-emit-module", +// CHECK-NEXT: "-fmodule-name=header2" // CHECK-NEXT: ], // CHECK-NEXT: "context-hash": "[[CONTEXT_HASH_H1]]", // CHECK-NEXT: "file-deps": [ diff --git a/clang/test/Index/Core/scan-deps.m b/clang/test/Index/Core/scan-deps.m index 2453ca5dfe1ef..6bb38db04b81b 100644 --- a/clang/test/Index/Core/scan-deps.m +++ b/clang/test/Index/Core/scan-deps.m @@ -19,11 +19,11 @@ // CHECK-NEXT: [[PREFIX]]/Inputs/module/SubModA.h // CHECK-NEXT: [[PREFIX]]/Inputs/module/SubSubModA.h // CHECK-NEXT: [[PREFIX]]/Inputs/module/module.modulemap -// CHECK-NEXT: build-args: +// CHECK-NEXT: build-args: -remove-preceeding-explicit-module-build-incompatible-options -fno-implicit-modules -emit-module -fmodule-name=ModA // CHECK-NEXT: dependencies: // CHECK-NEXT: context-hash: [[CONTEXT_HASH]] // CHECK-NEXT: module-deps: // CHECK-NEXT: ModA:[[CONTEXT_HASH]] // CHECK-NEXT: file-deps: // CHECK-NEXT: [[PREFIX]]/scan-deps.m -// CHECK-NEXT: additional-build-args: +// CHECK-NEXT: additional-build-args: -fno-implicit-modules -fno-implicit-module-maps diff --git a/clang/tools/libclang/CDependencies.cpp b/clang/tools/libclang/CDependencies.cpp index 9366505cc8398..86c1a5f7864de 100644 --- a/clang/tools/libclang/CDependencies.cpp +++ b/clang/tools/libclang/CDependencies.cpp @@ -121,6 +121,11 @@ class FullDependencyConsumer : public DependencyConsumer { FD.ClangModuleDeps.push_back({MD.ModuleName, ContextHash}); } + FD.AdditionalNonPathCommandLine = { + "-fno-implicit-modules", + "-fno-implicit-module-maps", + }; + FullDependenciesResult FDR; for (auto &&M : ClangModuleDeps) { @@ -178,7 +183,7 @@ static CXFileDependencies *getFullDependencies( for (const ClangModuleDep &CMD : MD.ClangModuleDeps) Modules.push_back(CMD.ModuleName + ":" + CMD.ContextHash); M.ModuleDeps = cxstring::createSet(Modules); - M.BuildArguments = cxstring::createSet(std::vector{}); + M.BuildArguments = cxstring::createSet(MD.NonPathCommandLine); } MDC(Context, MDS); } @@ -191,7 +196,8 @@ static CXFileDependencies *getFullDependencies( for (const ClangModuleDep &CMD : FD.ClangModuleDeps) Modules.push_back(CMD.ModuleName + ":" + CMD.ContextHash); FDeps->ModuleDeps = cxstring::createSet(Modules); - FDeps->AdditionalArguments = cxstring::createSet(std::vector{}); + FDeps->AdditionalArguments = + cxstring::createSet(FD.AdditionalNonPathCommandLine); return FDeps; } From 3eae6a83be0c351a6fe19f665ca6cd3690775288 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Thu, 9 Jan 2020 15:31:36 -0800 Subject: [PATCH 05/10] [Clang][ScanDeps] Use the module map a module was inferred from for inferred modules. --- .../DependencyScanning/ModuleDepCollector.cpp | 2 +- .../Frameworks/Sub.framework/Headers/Sub.h | 0 .../Inferred.framework/Headers/Inferred.h | 1 + .../Inputs/frameworks/module.modulemap | 1 + .../Inputs/modules_inferred_cdb.json | 7 +++ clang/test/ClangScanDeps/modules-inferred.m | 61 +++++++++++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Frameworks/Sub.framework/Headers/Sub.h create mode 100644 clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Headers/Inferred.h create mode 100644 clang/test/ClangScanDeps/Inputs/frameworks/module.modulemap create mode 100644 clang/test/ClangScanDeps/Inputs/modules_inferred_cdb.json create mode 100644 clang/test/ClangScanDeps/modules-inferred.m diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 39a71d86486bc..a253e20a8527f 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -145,7 +145,7 @@ void ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { const FileEntry *ModuleMap = Instance.getPreprocessor() .getHeaderSearchInfo() .getModuleMap() - .getContainingModuleMapFile(M); + .getModuleMapFileForUniquing(M); MD.ClangModuleMapFile = std::string(ModuleMap ? ModuleMap->getName() : ""); MD.ModuleName = M->getFullModuleName(); diff --git a/clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Frameworks/Sub.framework/Headers/Sub.h b/clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Frameworks/Sub.framework/Headers/Sub.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Headers/Inferred.h b/clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Headers/Inferred.h new file mode 100644 index 0000000000000..1855e4fad5f8d --- /dev/null +++ b/clang/test/ClangScanDeps/Inputs/frameworks/Inferred.framework/Headers/Inferred.h @@ -0,0 +1 @@ +typedef int inferred; diff --git a/clang/test/ClangScanDeps/Inputs/frameworks/module.modulemap b/clang/test/ClangScanDeps/Inputs/frameworks/module.modulemap new file mode 100644 index 0000000000000..e3bad873c7e7b --- /dev/null +++ b/clang/test/ClangScanDeps/Inputs/frameworks/module.modulemap @@ -0,0 +1 @@ +framework module * {} diff --git a/clang/test/ClangScanDeps/Inputs/modules_inferred_cdb.json b/clang/test/ClangScanDeps/Inputs/modules_inferred_cdb.json new file mode 100644 index 0000000000000..c6123e30994ee --- /dev/null +++ b/clang/test/ClangScanDeps/Inputs/modules_inferred_cdb.json @@ -0,0 +1,7 @@ +[ +{ + "directory": "DIR", + "command": "clang -E DIR/modules_cdb_input.cpp -FFRAMEWORKS -fmodules -fmodules-cache-path=DIR/module-cache -fimplicit-modules -fimplicit-module-maps", + "file": "DIR/modules_cdb_input.cpp" +} +] diff --git a/clang/test/ClangScanDeps/modules-inferred.m b/clang/test/ClangScanDeps/modules-inferred.m new file mode 100644 index 0000000000000..3e41e7c01ac14 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-inferred.m @@ -0,0 +1,61 @@ +// RUN: rm -rf %t.dir +// RUN: rm -rf %t.cdb +// RUN: mkdir -p %t.dir +// RUN: cp %s %t.dir/modules_cdb_input.cpp +// RUN: sed -e "s|DIR|%/t.dir|g" -e "s|FRAMEWORKS|%S/Inputs/frameworks|g" \ +// RUN: %S/Inputs/modules_inferred_cdb.json > %t.cdb +// +// RUN: echo -%t.dir > %t.result +// RUN: echo -%S >> %t.result +// RUN: clang-scan-deps -compilation-database %t.cdb -j 1 -full-command-line \ +// RUN: -mode preprocess-minimized-sources -format experimental-full >> %t.result +// RUN: cat %t.result | sed 's/\\/\//g' | FileCheck --check-prefixes=CHECK %s + +#include + +inferred a = 0; + +// CHECK: -[[PREFIX:.*]] +// CHECK-NEXT: -[[SOURCEDIR:.*]] +// CHECK-NEXT: { +// CHECK-NEXT: "modules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [], +// CHECK-NEXT: "clang-modulemap-file": "[[SOURCEDIR]]/Inputs/frameworks/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-remove-preceeding-explicit-module-build-incompatible-options", +// CHECK-NEXT: "-fno-implicit-modules", +// CHECK-NEXT: "-emit-module", +// CHECK-NEXT: "-fmodule-name=Inferred" +// CHECK-NEXT: ], +// CHECK-NEXT: "context-hash": "[[CONTEXT_HASH_H1:[A-Z0-9]+]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/Frameworks/Sub.framework/Headers/Sub.h", +// CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/Headers/Inferred.h", +// CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/__inferred_module.map" +// CHECK-NEXT: ], +// CHECK-NEXT: "name": "Inferred" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "translation-units": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-context-hash": "[[CONTEXT_HASH_H1]]", +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[CONTEXT_HASH_H1]]", +// CHECK-NEXT: "module-name": "Inferred" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-fno-implicit-modules", +// CHECK-NEXT: "-fno-implicit-module-maps", +// CHECK-NEXT: "-fmodule-file=[[PREFIX]]/module-cache/[[CONTEXT_HASH_H1]]/Inferred-{{[A-Z0-9]+}}.pcm", +// CHECK-NEXT: "-fmodule-map-file=[[SOURCEDIR]]/Inputs/frameworks/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/modules_cdb_input.cpp" +// CHECK-NEXT: ], +// CHECK-NEXT: "input-file": "[[PREFIX]]/modules_cdb_input.cpp" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } From 9327c60da885b5c1295d4237375f40e033912ac7 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 17 Jan 2020 17:45:53 -0800 Subject: [PATCH 06/10] [Clang] Temporarily comment out assert. This assert fires for ObjectiveC id, SEL, and Class declarations when the module is explicitly built. For implicit builds it works fine. This reproduces with: typedef struct objc_class Class; struct objc_class; As a module imported into another module, and both the above and that module imported into a third TU. Changing the name (`Class`) or reordering the declarations does not assert. rdar://58552906 --- clang/lib/Sema/IdentifierResolver.cpp | 6 +++++- clang/test/Modules/Inputs/module.map | 5 +++++ clang/test/Modules/Inputs/objc_redef_indirect.h | 1 + clang/test/Modules/Inputs/weird_objc.h | 2 ++ clang/test/Modules/objc_redef.m | 8 ++++++++ 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 clang/test/Modules/Inputs/objc_redef_indirect.h diff --git a/clang/lib/Sema/IdentifierResolver.cpp b/clang/lib/Sema/IdentifierResolver.cpp index c1bc8f79a309e..a6555ffa88f6c 100644 --- a/clang/lib/Sema/IdentifierResolver.cpp +++ b/clang/lib/Sema/IdentifierResolver.cpp @@ -217,7 +217,11 @@ void IdentifierResolver::RemoveDecl(NamedDecl *D) { assert(Ptr && "Didn't find this decl on its identifier's chain!"); if (isDeclPtr(Ptr)) { - assert(D == Ptr && "Didn't find this decl on its identifier's chain!"); + // FIXME: The following assert fires for ObjectiveC id, SEL, and Class + // declarations when the module is explicitly built. For implicit builds + // it works fine. rdar://58552906 + + // assert(D == Ptr && "Didn't find this decl on its identifier's chain!"); Name.setFETokenInfo(nullptr); return; } diff --git a/clang/test/Modules/Inputs/module.map b/clang/test/Modules/Inputs/module.map index 3f128c0bb0e0f..d78e42cc99672 100644 --- a/clang/test/Modules/Inputs/module.map +++ b/clang/test/Modules/Inputs/module.map @@ -475,3 +475,8 @@ module template_nontrivial1 { header "template-nontrivial1.h" export * } + +module objc_redef_indirect { + header "objc_redef_indirect.h" + export * +} diff --git a/clang/test/Modules/Inputs/objc_redef_indirect.h b/clang/test/Modules/Inputs/objc_redef_indirect.h new file mode 100644 index 0000000000000..2bdd1898ffbf8 --- /dev/null +++ b/clang/test/Modules/Inputs/objc_redef_indirect.h @@ -0,0 +1 @@ +@import weird_objc; diff --git a/clang/test/Modules/Inputs/weird_objc.h b/clang/test/Modules/Inputs/weird_objc.h index 8acaf746e855c..e2ca9a5103120 100644 --- a/clang/test/Modules/Inputs/weird_objc.h +++ b/clang/test/Modules/Inputs/weird_objc.h @@ -1 +1,3 @@ typedef struct objc_object { void *super; int wibble; } *id; +typedef struct A *Class; +struct A; diff --git a/clang/test/Modules/objc_redef.m b/clang/test/Modules/objc_redef.m index 2e57f41bc6624..ab3a77ae68a97 100644 --- a/clang/test/Modules/objc_redef.m +++ b/clang/test/Modules/objc_redef.m @@ -1,5 +1,6 @@ @import redeclarations_left; @import weird_objc; +@import objc_redef_indirect; int test(id x) { return x->wibble; @@ -9,5 +10,12 @@ int test(id x) { // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x objective-c -fmodules-cache-path=%t -emit-module -fmodule-name=redeclarations_left %S/Inputs/module.map // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x objective-c -fmodules-cache-path=%t -emit-module -fmodule-name=weird_objc %S/Inputs/module.map // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -I %S/Inputs %s -verify + +// Try explicit too. + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x objective-c -emit-module -fmodule-name=redeclarations_left %S/Inputs/module.map -o %t/redeclarations_left.pcm +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x objective-c -emit-module -fmodule-name=weird_objc %S/Inputs/module.map -o %t/weird_objc.pcm +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x objective-c -emit-module -fmodule-file=%t/weird_objc.pcm -fmodule-name=objc_redef_indirect %S/Inputs/module.map -o %t/objc_redef_indirect.pcm -I %S/Inputs +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodule-file=%t/redeclarations_left.pcm -fmodule-file=%t/weird_objc.pcm -fmodule-file=%t/objc_redef_indirect.pcm -I %S/Inputs %s -verify // expected-no-diagnostics From 2d28507121d64dbe9971eb859fd37cc0022f9fd0 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Wed, 8 Jan 2020 16:39:04 -0800 Subject: [PATCH 07/10] [Clang][cc1] Support -fno-implicit-module-maps in -cc1. Gives the last of -f{no-}implicit-module-maps precedence. rdar://58883354 --- clang/include/clang/Driver/Options.td | 2 +- clang/lib/Frontend/CompilerInvocation.cpp | 3 ++- clang/test/Modules/no-implicit-maps.cpp | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index ea3c65733c6fe..07f3837cdb182 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1548,7 +1548,7 @@ def fno_merge_all_constants : Flag<["-"], "fno-merge-all-constants">, Group, Group, Flags<[DriverOption]>; def fno_implicit_module_maps : Flag <["-"], "fno-implicit-module-maps">, Group, - Flags<[DriverOption]>; + Flags<[DriverOption, CC1Option]>; def fno_module_maps : Flag <["-"], "fno-module-maps">, Alias; def fno_modules_decluse : Flag <["-"], "fno-modules-decluse">, Group, Flags<[DriverOption]>; diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index bda9ddc8e77a5..8a2f77fb8f7eb 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2226,7 +2226,8 @@ static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args, Opts.ModulesStrictContextHash = Args.hasArg(OPT_fmodules_strict_context_hash); Opts.ModulesValidateDiagnosticOptions = !Args.hasArg(OPT_fmodules_disable_diagnostic_validation); - Opts.ImplicitModuleMaps = Args.hasArg(OPT_fimplicit_module_maps); + Opts.ImplicitModuleMaps = Args.hasFlag(OPT_fimplicit_module_maps, + OPT_fno_implicit_module_maps, false); Opts.ModuleMapFileHomeIsCwd = Args.hasArg(OPT_fmodule_map_file_home_is_cwd); Opts.ModuleCachePruneInterval = getLastArgIntValue(Args, OPT_fmodules_prune_interval, 7 * 24 * 60 * 60); diff --git a/clang/test/Modules/no-implicit-maps.cpp b/clang/test/Modules/no-implicit-maps.cpp index ddcad5df1e6f1..3ee7728048367 100644 --- a/clang/test/Modules/no-implicit-maps.cpp +++ b/clang/test/Modules/no-implicit-maps.cpp @@ -1,3 +1,6 @@ // RUN: rm -rf %t // RUN: %clang_cc1 -x objective-c -fmodules-cache-path=%t -fmodules -I %S/Inputs/private %s -verify +// RUN: %clang_cc1 -x objective-c -fmodules-cache-path=%t -fmodules \ +// RUN: -I %S/Inputs/private %s -verify -fimplicit-module-maps \ +// RUN: -fno-implicit-module-maps @import libPrivate1; // expected-error {{not found}} From 1815e2418e91d0c394e951b054ecbca68de1dc90 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 17 Jan 2020 17:47:28 -0800 Subject: [PATCH 08/10] [Clang] Allow explicitly building an inferred module. Building the actual module still fails, but make sure it fails for the right reason. --- clang/lib/Frontend/FrontendAction.cpp | 2 +- .../test/ClangScanDeps/module-deps-to-rsp.py | 76 +++++++++++++++++++ .../modules-inferred-explicit-build.m | 17 +++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 clang/test/ClangScanDeps/module-deps-to-rsp.py create mode 100644 clang/test/ClangScanDeps/modules-inferred-explicit-build.m diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index 3d375836b9753..a57dd796658b5 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -461,7 +461,7 @@ static Module *prepareToBuildModule(CompilerInstance &CI, // Dig out the module definition. HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); Module *M = HS.lookupModule(CI.getLangOpts().CurrentModule, - /*AllowSearch=*/false); + /*AllowSearch=*/true); if (!M) { CI.getDiagnostics().Report(diag::err_missing_module) << CI.getLangOpts().CurrentModule << ModuleMapFilename; diff --git a/clang/test/ClangScanDeps/module-deps-to-rsp.py b/clang/test/ClangScanDeps/module-deps-to-rsp.py new file mode 100755 index 0000000000000..45a6bcc90af1f --- /dev/null +++ b/clang/test/ClangScanDeps/module-deps-to-rsp.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import argparse +import json +import sys + +class ModuleNotFoundError(Exception): + def __init__(self, module_name): + self.module_name = module_name + +class FullDeps: + def __init__(self): + self.modules = dict() + self.translation_units = str() + +def getModulePathArgs(modules, full_deps): + cmd = [] + for md in modules: + m = full_deps.modules[md['module-name'] + '-' + md['context-hash']] + cmd += [u'-fmodule-map-file=' + m['clang-modulemap-file']] + cmd += [u'-fmodule-file=' + md['module-name'] + '-' + md['context-hash'] + '.pcm'] + return cmd + +def getCommandLineForModule(module_name, full_deps): + for m in full_deps.modules.values(): + if m['name'] == module_name: + module = m + break + else: + raise ModuleNotFoundError(module_name) + + cmd = m['command-line'] + cmd += getModulePathArgs(m['clang-module-deps'], full_deps) + cmd += [u'-o', m['name'] + '-' + m['context-hash'] + '.pcm'] + cmd += [m['clang-modulemap-file']] + + return cmd + +def getCommandLineForTU(tu, full_deps): + cmd = tu['command-line'] + cmd = [a for a in cmd if not (a.startswith('-fmodule-map-file=') or a.startswith('-fmodule-file='))] + cmd += getModulePathArgs(tu['clang-module-deps'], full_deps) + return cmd + +def parseFullDeps(json): + ret = FullDeps() + for m in json['modules']: + ret.modules[m['name'] + '-' + m['context-hash']] = m + ret.translation_units = json['translation-units'] + return ret + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("full_deps_file", help="Path to the full dependencies json file", + type=str) + action = parser.add_mutually_exclusive_group(required=True) + action.add_argument("--module-name", help="The name of the module to get arguments for", + type=str) + action.add_argument("--tu-index", help="The index of the translation unit to get arguments for", + type=int) + args = parser.parse_args() + + full_deps = parseFullDeps(json.load(open(args.full_deps_file, 'r'))) + + try: + if args.module_name: + print(" ".join(getCommandLineForModule(args.module_name, full_deps))) + + elif args.tu_index != None: + print(" ".join(getCommandLineForTU(full_deps.translation_units[args.tu_index], full_deps))) + except: + print("Unexpected error:", sys.exc_info()[0]) + raise + +if __name__ == '__main__': + main() diff --git a/clang/test/ClangScanDeps/modules-inferred-explicit-build.m b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m new file mode 100644 index 0000000000000..8915a77cc1abf --- /dev/null +++ b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m @@ -0,0 +1,17 @@ +// RUN: rm -rf %t.dir +// RUN: rm -rf %t.cdb +// RUN: mkdir -p %t.dir +// RUN: cp %s %t.dir/modules_cdb_input.cpp +// RUN: sed -e "s|DIR|%/t.dir|g" -e "s|FRAMEWORKS|%/S/Inputs/frameworks|g" \ +// RUN: %S/Inputs/modules_inferred_cdb.json > %t.cdb +// +// RUN: clang-scan-deps -compilation-database %t.cdb -j 1 -full-command-line \ +// RUN: -mode preprocess-minimized-sources -format experimental-full > %t.db +// RUN: %S/module-deps-to-rsp.py %t.db --module-name=Inferred > %t.inferred.rsp +// RUN: %S/module-deps-to-rsp.py %t.db --tu-index=0 > %t.tu.rsp +// RUN: not %clang_cc1 -E %t.dir/modules_cdb_input.cpp -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps @%t.inferred.rsp 2>&1 | grep "'Inferred.h' file not found" +// RUN: not %clang_cc1 -E %t.dir/modules_cdb_input.cpp -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps @%t.tu.rsp + +#include + +inferred a = 0; From 0672a6a4937e4aad9a5f56eed5279a5740c32c88 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 17 Jan 2020 17:58:13 -0800 Subject: [PATCH 09/10] [Clang] Fix the header paths in `clang::Module` for inferred modules. The `UmbrellaAsWritten` and `NameAsWritten` fields in `clang::Module` are a lie for framework modules. For those they actually are the path to the header or umbrella relative to the `clang::Module::Directory`. The exception to this case is for inferred modules. Here it actually is the name as written, because we print out the module and read it back in when implicitly building modules. This causes a problem when explicitly building an inferred module, as we skip the printing out step. In order to fix this issue this patch adds a new field for the path we want to use in `getInputBufferForModule`. It also makes `NameAsWritten` actually be the name written in the module map file (or that would be, in the case of an inferred module). rdar://58619519 --- clang/include/clang/Basic/Module.h | 8 ++++- clang/include/clang/Lex/ModuleMap.h | 6 ++-- clang/lib/Basic/Module.cpp | 5 +-- clang/lib/Frontend/FrontendAction.cpp | 14 ++++---- clang/lib/Frontend/FrontendActions.cpp | 2 +- clang/lib/Lex/ModuleMap.cpp | 35 +++++++++++++------ clang/lib/Serialization/ASTReader.cpp | 8 +++-- .../modules-inferred-explicit-build.m | 7 ++-- 8 files changed, 57 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index b6a171a002cb8..26e04a3c2dea7 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -109,6 +109,9 @@ class Module { /// The name of the umbrella entry, as written in the module map. std::string UmbrellaAsWritten; + // The path to the umbrella entry relative to the root module's \c Directory. + std::string UmbrellaRelativeToRootModuleDirectory; + /// The module through which entities defined in this module will /// eventually be exposed, for use in "private" modules. std::string ExportAsModule; @@ -156,6 +159,7 @@ class Module { /// file. struct Header { std::string NameAsWritten; + std::string PathRelativeToRootModuleDirectory; const FileEntry *Entry; explicit operator bool() { return Entry; } @@ -165,6 +169,7 @@ class Module { /// file. struct DirectoryName { std::string NameAsWritten; + std::string PathRelativeToRootModuleDirectory; const DirectoryEntry *Entry; explicit operator bool() { return Entry; } @@ -491,7 +496,8 @@ class Module { /// module. Header getUmbrellaHeader() const { if (auto *E = Umbrella.dyn_cast()) - return Header{UmbrellaAsWritten, E}; + return Header{UmbrellaAsWritten, UmbrellaRelativeToRootModuleDirectory, + E}; return Header{}; } diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index 6bd36cd83d979..380f44d6ab991 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -653,12 +653,14 @@ class ModuleMap { /// Sets the umbrella header of the given module to the given /// header. void setUmbrellaHeader(Module *Mod, const FileEntry *UmbrellaHeader, - Twine NameAsWritten); + Twine NameAsWritten, + Twine PathRelativeToRootModuleDirectory); /// Sets the umbrella directory of the given module to the given /// directory. void setUmbrellaDir(Module *Mod, const DirectoryEntry *UmbrellaDir, - Twine NameAsWritten); + Twine NameAsWritten, + Twine PathRelativeToRootModuleDirectory); /// Adds this header to the given module. /// \param Role The role of the header wrt the module. diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index 8c15127c9f1d6..2edbbea5769a2 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -243,9 +243,10 @@ bool Module::fullModuleNameIs(ArrayRef nameParts) const { Module::DirectoryName Module::getUmbrellaDir() const { if (Header U = getUmbrellaHeader()) - return {"", U.Entry->getDir()}; + return {"", "", U.Entry->getDir()}; - return {UmbrellaAsWritten, Umbrella.dyn_cast()}; + return {UmbrellaAsWritten, UmbrellaRelativeToRootModuleDirectory, + Umbrella.dyn_cast()}; } ArrayRef Module::getTopHeaders(FileManager &FileMgr) { diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index a57dd796658b5..fcdbeaa7c0690 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -346,7 +346,8 @@ static std::error_code collectModuleHeaderIncludes( // file relative to the module build directory (the directory containing // the module map file) so this will find the same file that we found // while parsing the module map. - addHeaderInclude(H.NameAsWritten, Includes, LangOpts, Module->IsExternC); + addHeaderInclude(H.PathRelativeToRootModuleDirectory, Includes, LangOpts, + Module->IsExternC); } } // Note that Module->PrivateHeaders will not be a TopHeader. @@ -355,8 +356,8 @@ static std::error_code collectModuleHeaderIncludes( Module->addTopHeader(UmbrellaHeader.Entry); if (Module->Parent) // Include the umbrella header for submodules. - addHeaderInclude(UmbrellaHeader.NameAsWritten, Includes, LangOpts, - Module->IsExternC); + addHeaderInclude(UmbrellaHeader.PathRelativeToRootModuleDirectory, + Includes, LangOpts, Module->IsExternC); } else if (Module::DirectoryName UmbrellaDir = Module->getUmbrellaDir()) { // Add all of the headers we find in this subdirectory. std::error_code EC; @@ -389,7 +390,8 @@ static std::error_code collectModuleHeaderIncludes( auto PathIt = llvm::sys::path::rbegin(Dir->path()); for (int I = 0; I != Dir.level() + 1; ++I, ++PathIt) Components.push_back(*PathIt); - SmallString<128> RelativeHeader(UmbrellaDir.NameAsWritten); + SmallString<128> RelativeHeader( + UmbrellaDir.PathRelativeToRootModuleDirectory); for (auto It = Components.rbegin(), End = Components.rend(); It != End; ++It) llvm::sys::path::append(RelativeHeader, *It); @@ -519,8 +521,8 @@ getInputBufferForModule(CompilerInstance &CI, Module *M) { SmallString<256> HeaderContents; std::error_code Err = std::error_code(); if (Module::Header UmbrellaHeader = M->getUmbrellaHeader()) - addHeaderInclude(UmbrellaHeader.NameAsWritten, HeaderContents, - CI.getLangOpts(), M->IsExternC); + addHeaderInclude(UmbrellaHeader.PathRelativeToRootModuleDirectory, + HeaderContents, CI.getLangOpts(), M->IsExternC); Err = collectModuleHeaderIncludes( CI.getLangOpts(), FileMgr, CI.getDiagnostics(), CI.getPreprocessor().getHeaderSearchInfo().getModuleMap(), M, diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index 1331a41b93a57..fe6d5cf1a0781 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -295,7 +295,7 @@ bool GenerateHeaderModuleAction::BeginSourceFileAction( << Name; continue; } - Headers.push_back({std::string(Name), &FE->getFileEntry()}); + Headers.push_back({std::string(Name), std::string(Name), &FE->getFileEntry()}); } HS.getModuleMap().createHeaderModule(CI.getLangOpts().CurrentModule, Headers); diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index 7f940fbfb2666..c84192862111e 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -260,9 +260,9 @@ void ModuleMap::resolveHeader(Module *Mod, << UmbrellaMod->getFullModuleName(); else // Record this umbrella header. - setUmbrellaHeader(Mod, File, RelativePathName.str()); + setUmbrellaHeader(Mod, File, Header.FileName, RelativePathName.str()); } else { - Module::Header H = {std::string(RelativePathName.str()), File}; + Module::Header H = {Header.FileName, std::string(RelativePathName.str()), File}; if (Header.Kind == Module::HK_Excluded) excludeHeader(Mod, H); else @@ -305,7 +305,7 @@ bool ModuleMap::resolveAsBuiltinHeader( return false; auto Role = headerKindToRole(Header.Kind); - Module::Header H = {std::string(Path.str()), *File}; + Module::Header H = {Header.FileName, std::string(Path.str()), *File}; addHeader(Mod, H, Role); return true; } @@ -1027,11 +1027,14 @@ Module *ModuleMap::inferFrameworkModule(const DirectoryEntry *FrameworkDir, Result->NoUndeclaredIncludes |= Attrs.NoUndeclaredIncludes; Result->Directory = FrameworkDir; + // Chop off the first framework bit, as that is implied. + StringRef RelativePath = + UmbrellaName.str().substr( + Result->getTopLevelModule()->Directory->getName().size()); + RelativePath = llvm::sys::path::relative_path(RelativePath); + // umbrella header "umbrella-header-name" - // - // The "Headers/" component of the name is implied because this is - // a framework module. - setUmbrellaHeader(Result, *UmbrellaHeader, ModuleName + ".h"); + setUmbrellaHeader(Result, *UmbrellaHeader, ModuleName + ".h", RelativePath); // export * Result->Exports.push_back(Module::ExportDecl(nullptr, true)); @@ -1091,6 +1094,9 @@ Module *ModuleMap::inferFrameworkModule(const DirectoryEntry *FrameworkDir, if (!Result->isSubFramework()) { inferFrameworkLink(Result, FrameworkDir, FileMgr); } + + Result->dump(); + llvm::errs() << Result->Directory->getName() << "\n"; return Result; } @@ -1111,10 +1117,13 @@ Module *ModuleMap::createShadowedModule(StringRef Name, bool IsFramework, } void ModuleMap::setUmbrellaHeader(Module *Mod, const FileEntry *UmbrellaHeader, - Twine NameAsWritten) { + Twine NameAsWritten, + Twine PathRelativeToRootModuleDirectory) { Headers[UmbrellaHeader].push_back(KnownHeader(Mod, NormalHeader)); Mod->Umbrella = UmbrellaHeader; Mod->UmbrellaAsWritten = NameAsWritten.str(); + Mod->UmbrellaRelativeToRootModuleDirectory = + PathRelativeToRootModuleDirectory.str(); UmbrellaDirs[UmbrellaHeader->getDir()] = Mod; // Notify callbacks that we just added a new header. @@ -1123,9 +1132,12 @@ void ModuleMap::setUmbrellaHeader(Module *Mod, const FileEntry *UmbrellaHeader, } void ModuleMap::setUmbrellaDir(Module *Mod, const DirectoryEntry *UmbrellaDir, - Twine NameAsWritten) { + Twine NameAsWritten, + Twine PathRelativeToRootModuleDirectory) { Mod->Umbrella = UmbrellaDir; Mod->UmbrellaAsWritten = NameAsWritten.str(); + Mod->UmbrellaRelativeToRootModuleDirectory = + PathRelativeToRootModuleDirectory.str(); UmbrellaDirs[UmbrellaDir] = Mod; } @@ -2394,6 +2406,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { } std::string DirName = std::string(Tok.getString()); + std::string DirNameAsWritten = DirName; SourceLocation DirNameLoc = consumeToken(); // Check whether we already have an umbrella. @@ -2435,7 +2448,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { for (llvm::vfs::recursive_directory_iterator I(FS, Dir->getName(), EC), E; I != E && !EC; I.increment(EC)) { if (auto FE = SourceMgr.getFileManager().getFile(I->path())) { - Module::Header Header = {std::string(I->path()), *FE}; + Module::Header Header = {"", std::string(I->path()), *FE}; Headers.push_back(std::move(Header)); } } @@ -2456,7 +2469,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { } // Record this umbrella directory. - Map.setUmbrellaDir(ActiveModule, Dir, DirName); + Map.setUmbrellaDir(ActiveModule, Dir, DirNameAsWritten, DirName); } /// Parse a module export declaration. diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 5ae656e3d340a..c5a2283c2fd28 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -1919,7 +1919,7 @@ HeaderFileInfoTrait::ReadData(internal_key_ref key, const unsigned char *d, // FIXME: This is not always the right filename-as-written, but we're not // going to use this information to rebuild the module, so it doesn't make // a lot of difference. - Module::Header H = {std::string(key.Filename), *FileMgr.getFile(Filename)}; + Module::Header H = {std::string(key.Filename), "", *FileMgr.getFile(Filename)}; ModMap.addHeader(Mod, H, HeaderRole, /*Imported*/true); HFI.isModuleHeader |= !(HeaderRole & ModuleMap::TextualHeader); } @@ -5548,7 +5548,8 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { ResolveImportedPath(F, Filename); if (auto Umbrella = PP.getFileManager().getFile(Filename)) { if (!CurrentModule->getUmbrellaHeader()) - ModMap.setUmbrellaHeader(CurrentModule, *Umbrella, Blob); + // FIXME: NameAsWritten + ModMap.setUmbrellaHeader(CurrentModule, *Umbrella, Blob, ""); else if (CurrentModule->getUmbrellaHeader().Entry != *Umbrella) { if ((ClientLoadCapabilities & ARR_OutOfDate) == 0) Error("mismatched umbrella headers in submodule"); @@ -5581,7 +5582,8 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { ResolveImportedPath(F, Dirname); if (auto Umbrella = PP.getFileManager().getDirectory(Dirname)) { if (!CurrentModule->getUmbrellaDir()) - ModMap.setUmbrellaDir(CurrentModule, *Umbrella, Blob); + // FIXME: NameAsWritten + ModMap.setUmbrellaDir(CurrentModule, *Umbrella, Blob, ""); else if (CurrentModule->getUmbrellaDir().Entry != *Umbrella) { if ((ClientLoadCapabilities & ARR_OutOfDate) == 0) Error("mismatched umbrella directories in submodule"); diff --git a/clang/test/ClangScanDeps/modules-inferred-explicit-build.m b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m index 8915a77cc1abf..18a0c3bd441c2 100644 --- a/clang/test/ClangScanDeps/modules-inferred-explicit-build.m +++ b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m @@ -9,8 +9,11 @@ // RUN: -mode preprocess-minimized-sources -format experimental-full > %t.db // RUN: %S/module-deps-to-rsp.py %t.db --module-name=Inferred > %t.inferred.rsp // RUN: %S/module-deps-to-rsp.py %t.db --tu-index=0 > %t.tu.rsp -// RUN: not %clang_cc1 -E %t.dir/modules_cdb_input.cpp -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps @%t.inferred.rsp 2>&1 | grep "'Inferred.h' file not found" -// RUN: not %clang_cc1 -E %t.dir/modules_cdb_input.cpp -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps @%t.tu.rsp +// RUN: %clang_cc1 -x objective-c -E %t.dir/modules_cdb_input.cpp \ +// RUN: -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps \ +// RUN: @%t.inferred.rsp +// RUN: %clang_cc1 -x objective-c -E %t.dir/modules_cdb_input.cpp \ +// RUN: -F%S/Inputs/frameworks -fmodules -fimplicit-module-maps @%t.tu.rsp #include From 21c4c103bf65398150532ae1ec2ca79ef6bcace9 Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 24 Jan 2020 12:52:25 -0800 Subject: [PATCH 10/10] [Clang][ScanDeps] Ignore __inferred_module.map dependency. This shows up with inferred modules, but it doesn't exist on disk, so don't report it as a dependency. --- .../Tooling/DependencyScanning/ModuleDepCollector.cpp | 9 +++++++++ clang/test/ClangScanDeps/modules-inferred.m | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index a253e20a8527f..a58265f88bff6 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -155,6 +155,15 @@ void ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { MDC.Instance.getASTReader()->getModuleManager().lookup(M->getASTFile()); MDC.Instance.getASTReader()->visitInputFiles( *MF, true, true, [&](const serialization::InputFile &IF, bool isSystem) { + // __inferred_module.map is the result of the way in which an implicit + // module build handles inferred modules. It adds an overlay VFS with + // this file in the proper directory and relies on the rest of Clang to + // handle it like normal. With explicitly built modules we don't need + // to play VFS tricks, so replace it with the correct module map. + if (IF.getFile()->getName().endswith("__inferred_module.map")) { + MD.FileDeps.insert(ModuleMap->getName()); + return; + } MD.FileDeps.insert(IF.getFile()->getName()); }); MD.NonPathCommandLine = { diff --git a/clang/test/ClangScanDeps/modules-inferred.m b/clang/test/ClangScanDeps/modules-inferred.m index 3e41e7c01ac14..cca2306b50aca 100644 --- a/clang/test/ClangScanDeps/modules-inferred.m +++ b/clang/test/ClangScanDeps/modules-inferred.m @@ -32,7 +32,7 @@ // CHECK-NEXT: "file-deps": [ // CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/Frameworks/Sub.framework/Headers/Sub.h", // CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/Headers/Inferred.h", -// CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/Inferred.framework/__inferred_module.map" +// CHECK-NEXT: "[[SOURCEDIR]]/Inputs/frameworks/module.modulemap" // CHECK-NEXT: ], // CHECK-NEXT: "name": "Inferred" // CHECK-NEXT: }