Index: test/ClangScanDeps/Inputs/regular_cdb.json =================================================================== --- /dev/null +++ test/ClangScanDeps/Inputs/regular_cdb.json @@ -0,0 +1,7 @@ +[ +{ + "directory": "DIR", + "command": "clang -c DIR/regular_cdb.cpp -IInputs -MD -MF DIR/regular_cdb.d", + "file": "DIR/regular_cdb.cpp" +} +] Index: test/ClangScanDeps/regular_cdb.cpp =================================================================== --- /dev/null +++ test/ClangScanDeps/regular_cdb.cpp @@ -0,0 +1,14 @@ +// RUN: rm -rf %t.dir +// RUN: rm -rf %t.cdb +// RUN: mkdir -p %t.dir +// RUN: cp %s %t.dir/regular_cdb.cpp +// RUN: mkdir %t.dir/Inputs +// RUN: cp %S/Inputs/header.h %t.dir/Inputs/header.h +// RUN: sed -e "s|DIR|%/t.dir|g" %S/Inputs/regular_cdb.json > %t.cdb +// RUN: clang-scan-deps -compilation-database %t.cdb -j 1 +// RUN: cat %t.dir/regular_cdb.d | FileCheck %s + +#include "header.h" + +// CHECK: regular_cdb.cpp +// CHECK-NEXT: Inputs/header.h Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_subdirectory(clang-fuzzer) add_clang_subdirectory(clang-import-test) add_clang_subdirectory(clang-offload-bundler) +add_clang_subdirectory(clang-scan-deps) add_clang_subdirectory(c-index-test) Index: tools/clang-scan-deps/CMakeLists.txt =================================================================== --- /dev/null +++ tools/clang-scan-deps/CMakeLists.txt @@ -0,0 +1,26 @@ +set(LLVM_LINK_COMPONENTS + Core + Support +) + +add_clang_tool(clang-scan-deps + ClangScanDeps.cpp + ) + +set(CLANG_SCAN_DEPS_LIB_DEPS + clangAST + clangBasic + clangCodeGen + clangDriver + clangFrontend + clangFrontendTool + clangLex + clangParse + clangTooling + ) + +target_link_libraries(clang-scan-deps + PRIVATE + ${CLANG_SCAN_DEPS_LIB_DEPS} + ) + Index: tools/clang-scan-deps/ClangScanDeps.cpp =================================================================== --- /dev/null +++ tools/clang-scan-deps/ClangScanDeps.cpp @@ -0,0 +1,211 @@ +//===-- ClangScanDeps.cpp - Implementation of clang-scan-deps -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/PCHContainerOperations.h" +#include "clang/FrontendTool/Utils.h" +#include "clang/Tooling/JSONCompilationDatabase.h" +#include "clang/Tooling/Tooling.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Options.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/Threading.h" +#include + +using namespace clang; + +namespace { + +/// A clang tool that runs the preprocessor only for the given compiler +/// invocation. +class PreprocessorOnlyTool : public tooling::ToolAction { +public: + PreprocessorOnlyTool(StringRef WorkingDirectory) + : WorkingDirectory(WorkingDirectory) {} + + bool runInvocation(std::shared_ptr Invocation, + FileManager *Files, + std::shared_ptr PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override { + // Create a compiler instance to handle the actual work. + CompilerInstance Compiler(std::move(PCHContainerOps)); + Compiler.setInvocation(std::move(Invocation)); + Files->getFileSystemOpts().WorkingDir = WorkingDirectory; + Compiler.setFileManager(Files); + + // Create the compiler's actual diagnostics engine. + Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); + if (!Compiler.hasDiagnostics()) + return false; + + Compiler.createSourceManager(*Files); + + auto Action = llvm::make_unique(); + const bool Success = Compiler.ExecuteAction(*Action); + Files->clearStatCache(); + return Success; + } + +private: + StringRef WorkingDirectory; +}; + +/// A proxy file system that doesn't call `chdir` when changing the working +/// directory of a clang tool. +class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem { +public: + ProxyFileSystemWithoutChdir( + llvm::IntrusiveRefCntPtr FS) + : ProxyFileSystem(std::move(FS)) {} + + llvm::ErrorOr getCurrentWorkingDirectory() const override { + assert(!CWD.empty() && "empty CWD"); + return CWD; + } + + std::error_code setCurrentWorkingDirectory(const Twine &Path) override { + CWD = Path.str(); + return {}; + } + +private: + std::string CWD; +}; + +/// The high-level implementation of the dependency discovery tool that runs on +/// an individual worker thread. +class DependencyScanningTool { +public: + DependencyScanningTool(const tooling::CompilationDatabase &Compilations) + : Compilations(Compilations) { + PCHContainerOps = std::make_shared(); + BaseFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); + } + + /// Computes the dependencies for the given file. + /// + /// \returns True on error. + bool runOnFile(const std::string &Input, StringRef CWD) { + BaseFS->setCurrentWorkingDirectory(CWD); + tooling::ClangTool Tool(Compilations, Input, PCHContainerOps, BaseFS); + Tool.clearArgumentsAdjusters(); + Tool.setRestoreWorkingDir(false); + PreprocessorOnlyTool Action(CWD); + return Tool.run(&Action); + } + +private: + const tooling::CompilationDatabase &Compilations; + std::shared_ptr PCHContainerOps; + /// The real filesystem used a base for all the operations performed by the + /// tool. + llvm::IntrusiveRefCntPtr BaseFS; +}; + +llvm::cl::opt Help("h", llvm::cl::desc("Alias for -help"), + llvm::cl::Hidden); + +llvm::cl::OptionCategory DependencyScannerCategory("Tool options"); + +llvm::cl::opt + NumThreads("j", llvm::cl::Optional, + llvm::cl::desc("Number of worker threads to use"), + llvm::cl::init(0)); + +llvm::cl::opt + CompilationDB("compilation-database", + llvm::cl::desc("Compilation database"), llvm::cl::Required, + llvm::cl::cat(DependencyScannerCategory)); + +} // end anonymous namespace + +int main(int argc, const char **argv) { + llvm::cl::ResetAllOptionOccurrences(); + llvm::cl::HideUnrelatedOptions(DependencyScannerCategory); + if (!llvm::cl::ParseCommandLineOptions(argc, argv)) + return 1; + + std::string ErrorMessage; + std::unique_ptr Compilations = + tooling::JSONCompilationDatabase::loadFromFile( + CompilationDB, ErrorMessage, + tooling::JSONCommandLineSyntax::AutoDetect); + if (!Compilations) { + llvm::errs() << "error: " << ErrorMessage << "\n"; + return 1; + } + + llvm::cl::PrintOptionValues(); + + // By default the tool runs on all inputs in the CDB. + std::vector> Inputs; + for (const auto &Command : Compilations->getAllCompileCommands()) + Inputs.emplace_back(Command.Filename, Command.Directory); + + // The command options are rewritten to run Clang in preprocessor only mode. + auto AdjustingCompilations = + llvm::make_unique( + std::move(Compilations)); + AdjustingCompilations->appendArgumentsAdjuster( + [](const tooling::CommandLineArguments &Args, StringRef /*unused*/) { + tooling::CommandLineArguments AdjustedArgs = Args; + AdjustedArgs.push_back("-o"); + AdjustedArgs.push_back("/dev/null"); + AdjustedArgs.push_back("-Xclang"); + AdjustedArgs.push_back("-Eonly"); + AdjustedArgs.push_back("-Xclang"); + AdjustedArgs.push_back("-sys-header-deps"); + return AdjustedArgs; + }); + + unsigned NumWorkers = + NumThreads == 0 ? llvm::hardware_concurrency() : NumThreads; + std::vector> WorkerTools; + for (unsigned I = 0; I < NumWorkers; ++I) + WorkerTools.push_back( + llvm::make_unique(*AdjustingCompilations)); + + std::vector WorkerThreads; + std::atomic HadErrors(false); + std::mutex Lock; + size_t Index = 0; + + llvm::outs() << "Running clang-scan-deps on " << Inputs.size() + << " files using " << NumWorkers << " workers\n"; + for (unsigned I = 0; I < NumWorkers; ++I) { + WorkerThreads.emplace_back( + [I, &Lock, &Index, &Inputs, &HadErrors, &WorkerTools]() { + while (true) { + std::string Input; + StringRef CWD; + // Take the next input. + { + std::unique_lock LockGuard(Lock); + if (Index >= Inputs.size()) + return; + const auto &Compilation = Inputs[Index++]; + Input = Compilation.first; + CWD = Compilation.second; + } + // Run the tool on it. + HadErrors = WorkerTools[I]->runOnFile(Input, CWD); + } + }); + } + for (auto &W : WorkerThreads) + W.join(); + + return HadErrors; +}