| 1 | //===- lib/Tooling/AllTUsExecution.cpp - Execute actions on all TUs. ------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "clang/Tooling/AllTUsExecution.h" |
| 10 | #include "clang/Tooling/ToolExecutorPluginRegistry.h" |
| 11 | #include "llvm/Support/Regex.h" |
| 12 | #include "llvm/Support/ThreadPool.h" |
| 13 | #include "llvm/Support/Threading.h" |
| 14 | #include "llvm/Support/VirtualFileSystem.h" |
| 15 | |
| 16 | namespace clang { |
| 17 | namespace tooling { |
| 18 | |
| 19 | const char *AllTUsToolExecutor::ExecutorName = "AllTUsToolExecutor" ; |
| 20 | |
| 21 | namespace { |
| 22 | llvm::Error make_string_error(const llvm::Twine &Message) { |
| 23 | return llvm::make_error<llvm::StringError>(Args: Message, |
| 24 | Args: llvm::inconvertibleErrorCode()); |
| 25 | } |
| 26 | |
| 27 | ArgumentsAdjuster getDefaultArgumentsAdjusters() { |
| 28 | return combineAdjusters( |
| 29 | First: getClangStripOutputAdjuster(), |
| 30 | Second: combineAdjusters(First: getClangSyntaxOnlyAdjuster(), |
| 31 | Second: getClangStripDependencyFileAdjuster())); |
| 32 | } |
| 33 | |
| 34 | class ThreadSafeToolResults : public ToolResults { |
| 35 | public: |
| 36 | void addResult(StringRef Key, StringRef Value) override { |
| 37 | std::unique_lock<std::mutex> LockGuard(Mutex); |
| 38 | Results.addResult(Key, Value); |
| 39 | } |
| 40 | |
| 41 | std::vector<std::pair<llvm::StringRef, llvm::StringRef>> |
| 42 | AllKVResults() override { |
| 43 | return Results.AllKVResults(); |
| 44 | } |
| 45 | |
| 46 | void forEachResult(llvm::function_ref<void(StringRef Key, StringRef Value)> |
| 47 | Callback) override { |
| 48 | Results.forEachResult(Callback); |
| 49 | } |
| 50 | |
| 51 | private: |
| 52 | InMemoryToolResults Results; |
| 53 | std::mutex Mutex; |
| 54 | }; |
| 55 | |
| 56 | } // namespace |
| 57 | |
| 58 | llvm::cl::opt<std::string> |
| 59 | Filter("filter" , |
| 60 | llvm::cl::desc("Only process files that match this filter. " |
| 61 | "This flag only applies to all-TUs." ), |
| 62 | llvm::cl::init(Val: ".*" )); |
| 63 | |
| 64 | AllTUsToolExecutor::AllTUsToolExecutor( |
| 65 | const CompilationDatabase &Compilations, unsigned ThreadCount, |
| 66 | std::shared_ptr<PCHContainerOperations> PCHContainerOps) |
| 67 | : Compilations(Compilations), Results(new ThreadSafeToolResults), |
| 68 | Context(Results.get()), ThreadCount(ThreadCount) {} |
| 69 | |
| 70 | AllTUsToolExecutor::AllTUsToolExecutor( |
| 71 | CommonOptionsParser Options, unsigned ThreadCount, |
| 72 | std::shared_ptr<PCHContainerOperations> PCHContainerOps) |
| 73 | : OptionsParser(std::move(Options)), |
| 74 | Compilations(OptionsParser->getCompilations()), |
| 75 | Results(new ThreadSafeToolResults), Context(Results.get()), |
| 76 | ThreadCount(ThreadCount) {} |
| 77 | |
| 78 | llvm::Error AllTUsToolExecutor::execute( |
| 79 | llvm::ArrayRef< |
| 80 | std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>> |
| 81 | Actions) { |
| 82 | if (Actions.empty()) |
| 83 | return make_string_error(Message: "No action to execute." ); |
| 84 | |
| 85 | if (Actions.size() != 1) |
| 86 | return make_string_error( |
| 87 | Message: "Only support executing exactly 1 action at this point." ); |
| 88 | |
| 89 | std::string ErrorMsg; |
| 90 | std::mutex TUMutex; |
| 91 | auto AppendError = [&](llvm::Twine Err) { |
| 92 | std::unique_lock<std::mutex> LockGuard(TUMutex); |
| 93 | ErrorMsg += Err.str(); |
| 94 | }; |
| 95 | |
| 96 | auto Log = [&](llvm::Twine Msg) { |
| 97 | std::unique_lock<std::mutex> LockGuard(TUMutex); |
| 98 | llvm::errs() << Msg.str() << "\n" ; |
| 99 | }; |
| 100 | |
| 101 | std::vector<std::string> Files; |
| 102 | llvm::Regex RegexFilter(Filter); |
| 103 | for (const auto& File : Compilations.getAllFiles()) { |
| 104 | if (RegexFilter.match(String: File)) |
| 105 | Files.push_back(x: File); |
| 106 | } |
| 107 | // Add a counter to track the progress. |
| 108 | const std::string TotalNumStr = std::to_string(val: Files.size()); |
| 109 | unsigned Counter = 0; |
| 110 | auto Count = [&]() { |
| 111 | std::unique_lock<std::mutex> LockGuard(TUMutex); |
| 112 | return ++Counter; |
| 113 | }; |
| 114 | |
| 115 | auto &Action = Actions.front(); |
| 116 | |
| 117 | { |
| 118 | llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(ThreadCount)); |
| 119 | for (std::string File : Files) { |
| 120 | Pool.async( |
| 121 | F: [&](std::string Path) { |
| 122 | Log("[" + std::to_string(val: Count()) + "/" + TotalNumStr + |
| 123 | "] Processing file " + Path); |
| 124 | // Each thread gets an independent copy of a VFS to allow different |
| 125 | // concurrent working directories. |
| 126 | IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = |
| 127 | llvm::vfs::createPhysicalFileSystem(); |
| 128 | ClangTool Tool(Compilations, {Path}, |
| 129 | std::make_shared<PCHContainerOperations>(), FS); |
| 130 | Tool.appendArgumentsAdjuster(Adjuster: Action.second); |
| 131 | Tool.appendArgumentsAdjuster(Adjuster: getDefaultArgumentsAdjusters()); |
| 132 | for (const auto &FileAndContent : OverlayFiles) |
| 133 | Tool.mapVirtualFile(FilePath: FileAndContent.first(), |
| 134 | Content: FileAndContent.second); |
| 135 | if (Tool.run(Action: Action.first.get())) |
| 136 | AppendError(llvm::Twine("Failed to run action on " ) + Path + |
| 137 | "\n" ); |
| 138 | }, |
| 139 | ArgList&: File); |
| 140 | } |
| 141 | // Make sure all tasks have finished before resetting the working directory. |
| 142 | Pool.wait(); |
| 143 | } |
| 144 | |
| 145 | if (!ErrorMsg.empty()) |
| 146 | return make_string_error(Message: ErrorMsg); |
| 147 | |
| 148 | return llvm::Error::success(); |
| 149 | } |
| 150 | |
| 151 | llvm::cl::opt<unsigned> ExecutorConcurrency( |
| 152 | "execute-concurrency" , |
| 153 | llvm::cl::desc("The number of threads used to process all files in " |
| 154 | "parallel. Set to 0 for hardware concurrency. " |
| 155 | "This flag only applies to all-TUs." ), |
| 156 | llvm::cl::init(Val: 0)); |
| 157 | |
| 158 | class AllTUsToolExecutorPlugin : public ToolExecutorPlugin { |
| 159 | public: |
| 160 | llvm::Expected<std::unique_ptr<ToolExecutor>> |
| 161 | create(CommonOptionsParser &OptionsParser) override { |
| 162 | if (OptionsParser.getSourcePathList().empty()) |
| 163 | return make_string_error( |
| 164 | Message: "[AllTUsToolExecutorPlugin] Please provide a directory/file path in " |
| 165 | "the compilation database." ); |
| 166 | return std::make_unique<AllTUsToolExecutor>(args: std::move(OptionsParser), |
| 167 | args&: ExecutorConcurrency); |
| 168 | } |
| 169 | }; |
| 170 | |
| 171 | static ToolExecutorPluginRegistry::Add<AllTUsToolExecutorPlugin> |
| 172 | X("all-TUs" , "Runs FrontendActions on all TUs in the compilation database. " |
| 173 | "Tool results are stored in memory." ); |
| 174 | |
| 175 | // This anchor is used to force the linker to link in the generated object file |
| 176 | // and thus register the plugin. |
| 177 | volatile int AllTUsToolExecutorAnchorSource = 0; |
| 178 | |
| 179 | } // end namespace tooling |
| 180 | } // end namespace clang |
| 181 | |