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-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/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/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/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/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/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/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/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index a29732ed555a2..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); @@ -3654,6 +3655,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 +3688,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/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index 3d375836b9753..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); @@ -461,7 +463,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; @@ -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/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/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/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/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/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 9a0a6db82faa5..a58265f88bff6 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); } @@ -147,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(); @@ -157,8 +155,21 @@ 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 = { + "-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/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/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-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/ClangScanDeps/modules-inferred-explicit-build.m b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m new file mode 100644 index 0000000000000..18a0c3bd441c2 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-inferred-explicit-build.m @@ -0,0 +1,20 @@ +// 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: %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 + +inferred a = 0; diff --git a/clang/test/ClangScanDeps/modules-inferred.m b/clang/test/ClangScanDeps/modules-inferred.m new file mode 100644 index 0000000000000..cca2306b50aca --- /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/module.modulemap" +// 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: } diff --git a/clang/test/Index/Core/scan-deps.m b/clang/test/Index/Core/scan-deps.m new file mode 100644 index 0000000000000..6bb38db04b81b --- /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: -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: -fno-implicit-modules -fno-implicit-module-maps 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/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}} 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 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/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..86c1a5f7864de --- /dev/null +++ b/clang/tools/libclang/CDependencies.cpp @@ -0,0 +1,228 @@ +//===- 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}); + } + + FD.AdditionalNonPathCommandLine = { + "-fno-implicit-modules", + "-fno-implicit-module-maps", + }; + + 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(MD.NonPathCommandLine); + } + 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(FD.AdditionalNonPathCommandLine); + 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..bcb5d2fc3bb68 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 @@ -19,6 +20,7 @@ set(SOURCES CXStoredDiagnostic.cpp CXString.cpp CXType.cpp + Driver.cpp Indexing.cpp FatalErrorHandler.cpp @@ -40,6 +42,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/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 2bbc8dca6cbb8..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 @@ -157,6 +159,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/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); +} 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 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