2019-06-13 05:45:28 +08:00
|
|
|
//===- ClangScanDeps.cpp - Implementation of clang-scan-deps --------------===//
|
2019-06-13 05:32:49 +08:00
|
|
|
//
|
2019-06-13 05:45:28 +08:00
|
|
|
// 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
|
2019-06-13 05:32:49 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
|
|
#include "clang/Tooling/CommonOptionsParser.h"
|
2019-08-07 04:43:25 +08:00
|
|
|
#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
|
2019-06-27 05:11:51 +08:00
|
|
|
#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
|
2019-06-13 05:32:49 +08:00
|
|
|
#include "clang/Tooling/JSONCompilationDatabase.h"
|
|
|
|
#include "llvm/Support/InitLLVM.h"
|
|
|
|
#include "llvm/Support/Options.h"
|
|
|
|
#include "llvm/Support/Program.h"
|
|
|
|
#include "llvm/Support/Signals.h"
|
|
|
|
#include "llvm/Support/Threading.h"
|
2019-06-13 05:52:36 +08:00
|
|
|
#include <mutex>
|
2019-06-13 05:32:49 +08:00
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
using namespace clang;
|
2019-06-27 05:11:51 +08:00
|
|
|
using namespace tooling::dependencies;
|
2019-06-13 05:32:49 +08:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2019-06-22 02:24:55 +08:00
|
|
|
class SharedStream {
|
|
|
|
public:
|
|
|
|
SharedStream(raw_ostream &OS) : OS(OS) {}
|
|
|
|
void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) {
|
|
|
|
std::unique_lock<std::mutex> LockGuard(Lock);
|
|
|
|
Fn(OS);
|
|
|
|
OS.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::mutex Lock;
|
|
|
|
raw_ostream &OS;
|
|
|
|
};
|
|
|
|
|
2019-06-13 05:32:49 +08:00
|
|
|
/// The high-level implementation of the dependency discovery tool that runs on
|
|
|
|
/// an individual worker thread.
|
|
|
|
class DependencyScanningTool {
|
|
|
|
public:
|
|
|
|
/// Construct a dependency scanning tool.
|
|
|
|
///
|
|
|
|
/// \param Compilations The reference to the compilation database that's
|
|
|
|
/// used by the clang tool.
|
2019-08-07 04:43:25 +08:00
|
|
|
DependencyScanningTool(DependencyScanningService &Service,
|
|
|
|
const tooling::CompilationDatabase &Compilations,
|
2019-06-27 05:11:51 +08:00
|
|
|
SharedStream &OS, SharedStream &Errs)
|
2019-08-07 04:43:25 +08:00
|
|
|
: Worker(Service), Compilations(Compilations), OS(OS), Errs(Errs) {}
|
2019-06-13 05:32:49 +08:00
|
|
|
|
2019-08-30 09:25:57 +08:00
|
|
|
/// Print out the dependency information into a string using the dependency
|
|
|
|
/// file format that is specified in the options (-MD is the default) and
|
|
|
|
/// return it.
|
|
|
|
///
|
|
|
|
/// \returns A \c StringError with the diagnostic output if clang errors
|
|
|
|
/// occurred, dependency file contents otherwise.
|
|
|
|
llvm::Expected<std::string> getDependencyFile(const std::string &Input,
|
|
|
|
StringRef CWD) {
|
|
|
|
/// Prints out all of the gathered dependencies into a string.
|
|
|
|
class DependencyPrinterConsumer : public DependencyConsumer {
|
|
|
|
public:
|
|
|
|
void handleFileDependency(const DependencyOutputOptions &Opts,
|
|
|
|
StringRef File) override {
|
|
|
|
if (!this->Opts)
|
|
|
|
this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
|
|
|
|
Dependencies.push_back(File);
|
|
|
|
}
|
|
|
|
|
|
|
|
void printDependencies(std::string &S) {
|
|
|
|
if (!Opts)
|
|
|
|
return;
|
|
|
|
|
|
|
|
class DependencyPrinter : public DependencyFileGenerator {
|
|
|
|
public:
|
|
|
|
DependencyPrinter(DependencyOutputOptions &Opts,
|
|
|
|
ArrayRef<std::string> Dependencies)
|
|
|
|
: DependencyFileGenerator(Opts) {
|
|
|
|
for (const auto &Dep : Dependencies)
|
|
|
|
addDependency(Dep);
|
|
|
|
}
|
|
|
|
|
|
|
|
void printDependencies(std::string &S) {
|
|
|
|
llvm::raw_string_ostream OS(S);
|
|
|
|
outputDependencyFile(OS);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DependencyPrinter Generator(*Opts, Dependencies);
|
|
|
|
Generator.printDependencies(S);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::unique_ptr<DependencyOutputOptions> Opts;
|
|
|
|
std::vector<std::string> Dependencies;
|
|
|
|
};
|
|
|
|
|
|
|
|
DependencyPrinterConsumer Consumer;
|
|
|
|
auto Result =
|
|
|
|
Worker.computeDependencies(Input, CWD, Compilations, Consumer);
|
|
|
|
if (Result)
|
|
|
|
return std::move(Result);
|
|
|
|
std::string Output;
|
|
|
|
Consumer.printDependencies(Output);
|
|
|
|
return Output;
|
|
|
|
}
|
|
|
|
|
2019-06-27 05:11:51 +08:00
|
|
|
/// Computes the dependencies for the given file and prints them out.
|
2019-06-13 05:32:49 +08:00
|
|
|
///
|
|
|
|
/// \returns True on error.
|
|
|
|
bool runOnFile(const std::string &Input, StringRef CWD) {
|
2019-08-30 09:25:57 +08:00
|
|
|
auto MaybeFile = getDependencyFile(Input, CWD);
|
2019-06-27 05:11:51 +08:00
|
|
|
if (!MaybeFile) {
|
|
|
|
llvm::handleAllErrors(
|
|
|
|
MaybeFile.takeError(), [this, &Input](llvm::StringError &Err) {
|
|
|
|
Errs.applyLocked([&](raw_ostream &OS) {
|
|
|
|
OS << "Error while scanning dependencies for " << Input << ":\n";
|
|
|
|
OS << Err.getMessage();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; });
|
|
|
|
return false;
|
2019-06-13 05:32:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2019-06-27 05:11:51 +08:00
|
|
|
DependencyScanningWorker Worker;
|
2019-06-13 05:32:49 +08:00
|
|
|
const tooling::CompilationDatabase &Compilations;
|
2019-06-22 02:24:55 +08:00
|
|
|
SharedStream &OS;
|
2019-06-27 05:11:51 +08:00
|
|
|
SharedStream &Errs;
|
2019-06-13 05:32:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
llvm::cl::opt<bool> Help("h", llvm::cl::desc("Alias for -help"),
|
|
|
|
llvm::cl::Hidden);
|
|
|
|
|
|
|
|
llvm::cl::OptionCategory DependencyScannerCategory("Tool options");
|
|
|
|
|
2019-08-07 04:43:25 +08:00
|
|
|
static llvm::cl::opt<ScanningMode> ScanMode(
|
|
|
|
"mode",
|
|
|
|
llvm::cl::desc("The preprocessing mode used to compute the dependencies"),
|
|
|
|
llvm::cl::values(
|
|
|
|
clEnumValN(ScanningMode::MinimizedSourcePreprocessing,
|
|
|
|
"preprocess-minimized-sources",
|
|
|
|
"The set of dependencies is computed by preprocessing the "
|
|
|
|
"source files that were minimized to only include the "
|
|
|
|
"contents that might affect the dependencies"),
|
|
|
|
clEnumValN(ScanningMode::CanonicalPreprocessing, "preprocess",
|
|
|
|
"The set of dependencies is computed by preprocessing the "
|
|
|
|
"unmodified source files")),
|
2019-09-13 03:00:32 +08:00
|
|
|
llvm::cl::init(ScanningMode::MinimizedSourcePreprocessing),
|
|
|
|
llvm::cl::cat(DependencyScannerCategory));
|
2019-08-07 04:43:25 +08:00
|
|
|
|
2019-06-13 05:32:49 +08:00
|
|
|
llvm::cl::opt<unsigned>
|
|
|
|
NumThreads("j", llvm::cl::Optional,
|
|
|
|
llvm::cl::desc("Number of worker threads to use (default: use "
|
|
|
|
"all concurrent threads)"),
|
2019-09-13 03:00:32 +08:00
|
|
|
llvm::cl::init(0), llvm::cl::cat(DependencyScannerCategory));
|
2019-06-13 05:32:49 +08:00
|
|
|
|
|
|
|
llvm::cl::opt<std::string>
|
|
|
|
CompilationDB("compilation-database",
|
|
|
|
llvm::cl::desc("Compilation database"), llvm::cl::Required,
|
|
|
|
llvm::cl::cat(DependencyScannerCategory));
|
|
|
|
|
2019-08-30 06:56:38 +08:00
|
|
|
llvm::cl::opt<bool> ReuseFileManager(
|
|
|
|
"reuse-filemanager",
|
|
|
|
llvm::cl::desc("Reuse the file manager and its cache between invocations."),
|
|
|
|
llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory));
|
|
|
|
|
2019-09-12 04:40:31 +08:00
|
|
|
llvm::cl::opt<bool> SkipExcludedPPRanges(
|
|
|
|
"skip-excluded-pp-ranges",
|
|
|
|
llvm::cl::desc(
|
|
|
|
"Use the preprocessor optimization that skips excluded conditionals by "
|
|
|
|
"bumping the buffer pointer in the lexer instead of lexing the tokens "
|
|
|
|
"until reaching the end directive."),
|
|
|
|
llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory));
|
|
|
|
|
2019-06-13 05:32:49 +08:00
|
|
|
} // end anonymous namespace
|
|
|
|
|
2019-09-12 08:48:45 +08:00
|
|
|
/// \returns object-file path derived from source-file path.
|
|
|
|
static std::string getObjFilePath(StringRef SrcFile) {
|
|
|
|
SmallString<128> ObjFileName(SrcFile);
|
|
|
|
llvm::sys::path::replace_extension(ObjFileName, "o");
|
|
|
|
return ObjFileName.str();
|
|
|
|
}
|
|
|
|
|
2019-06-13 05:32:49 +08:00
|
|
|
int main(int argc, const char **argv) {
|
|
|
|
llvm::InitLLVM X(argc, argv);
|
|
|
|
llvm::cl::HideUnrelatedOptions(DependencyScannerCategory);
|
|
|
|
if (!llvm::cl::ParseCommandLineOptions(argc, argv))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
std::string ErrorMessage;
|
|
|
|
std::unique_ptr<tooling::JSONCompilationDatabase> 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<std::pair<std::string, std::string>> 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 =
|
2019-08-15 07:04:18 +08:00
|
|
|
std::make_unique<tooling::ArgumentsAdjustingCompilations>(
|
2019-06-13 05:32:49 +08:00
|
|
|
std::move(Compilations));
|
|
|
|
AdjustingCompilations->appendArgumentsAdjuster(
|
2019-09-12 08:48:45 +08:00
|
|
|
[](const tooling::CommandLineArguments &Args, StringRef FileName) {
|
|
|
|
std::string LastO = "";
|
|
|
|
bool HasMT = false;
|
|
|
|
bool HasMQ = false;
|
|
|
|
bool HasMD = false;
|
|
|
|
// We need to find the last -o value.
|
|
|
|
if (!Args.empty()) {
|
|
|
|
std::size_t Idx = Args.size() - 1;
|
|
|
|
for (auto It = Args.rbegin(); It != Args.rend(); ++It) {
|
|
|
|
if (It != Args.rbegin()) {
|
|
|
|
if (Args[Idx] == "-o")
|
|
|
|
LastO = Args[Idx + 1];
|
|
|
|
if (Args[Idx] == "-MT")
|
|
|
|
HasMT = true;
|
|
|
|
if (Args[Idx] == "-MQ")
|
|
|
|
HasMQ = true;
|
|
|
|
if (Args[Idx] == "-MD")
|
|
|
|
HasMD = true;
|
|
|
|
}
|
|
|
|
--Idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If there's no -MT/-MQ Driver would add -MT with the value of the last
|
|
|
|
// -o option.
|
2019-06-13 05:32:49 +08:00
|
|
|
tooling::CommandLineArguments AdjustedArgs = Args;
|
|
|
|
AdjustedArgs.push_back("-o");
|
|
|
|
AdjustedArgs.push_back("/dev/null");
|
2019-09-12 08:48:45 +08:00
|
|
|
if (!HasMT && !HasMQ) {
|
|
|
|
AdjustedArgs.push_back("-MT");
|
|
|
|
// We're interested in source dependencies of an object file.
|
|
|
|
if (!HasMD) {
|
|
|
|
// FIXME: We are missing the directory unless the -o value is an
|
|
|
|
// absolute path.
|
|
|
|
AdjustedArgs.push_back(!LastO.empty() ? LastO
|
|
|
|
: getObjFilePath(FileName));
|
|
|
|
} else {
|
|
|
|
AdjustedArgs.push_back(FileName);
|
|
|
|
}
|
|
|
|
}
|
2019-06-13 05:32:49 +08:00
|
|
|
AdjustedArgs.push_back("-Xclang");
|
|
|
|
AdjustedArgs.push_back("-Eonly");
|
|
|
|
AdjustedArgs.push_back("-Xclang");
|
|
|
|
AdjustedArgs.push_back("-sys-header-deps");
|
2019-07-04 02:01:32 +08:00
|
|
|
AdjustedArgs.push_back("-Wno-error");
|
2019-06-13 05:32:49 +08:00
|
|
|
return AdjustedArgs;
|
|
|
|
});
|
|
|
|
|
2019-06-27 05:11:51 +08:00
|
|
|
SharedStream Errs(llvm::errs());
|
2019-06-22 02:24:55 +08:00
|
|
|
// Print out the dependency results to STDOUT by default.
|
|
|
|
SharedStream DependencyOS(llvm::outs());
|
2019-08-07 04:43:25 +08:00
|
|
|
|
2019-09-12 04:40:31 +08:00
|
|
|
DependencyScanningService Service(ScanMode, ReuseFileManager,
|
|
|
|
SkipExcludedPPRanges);
|
2019-08-13 08:36:35 +08:00
|
|
|
#if LLVM_ENABLE_THREADS
|
2019-06-13 05:32:49 +08:00
|
|
|
unsigned NumWorkers =
|
|
|
|
NumThreads == 0 ? llvm::hardware_concurrency() : NumThreads;
|
2019-08-13 08:36:35 +08:00
|
|
|
#else
|
|
|
|
unsigned NumWorkers = 1;
|
|
|
|
#endif
|
2019-06-13 05:32:49 +08:00
|
|
|
std::vector<std::unique_ptr<DependencyScanningTool>> WorkerTools;
|
|
|
|
for (unsigned I = 0; I < NumWorkers; ++I)
|
2019-08-15 07:04:18 +08:00
|
|
|
WorkerTools.push_back(std::make_unique<DependencyScanningTool>(
|
2019-08-07 04:43:25 +08:00
|
|
|
Service, *AdjustingCompilations, DependencyOS, Errs));
|
2019-06-13 05:32:49 +08:00
|
|
|
|
|
|
|
std::vector<std::thread> WorkerThreads;
|
|
|
|
std::atomic<bool> 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) {
|
2019-08-13 08:36:35 +08:00
|
|
|
auto Worker = [I, &Lock, &Index, &Inputs, &HadErrors, &WorkerTools]() {
|
|
|
|
while (true) {
|
|
|
|
std::string Input;
|
|
|
|
StringRef CWD;
|
|
|
|
// Take the next input.
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> LockGuard(Lock);
|
|
|
|
if (Index >= Inputs.size())
|
|
|
|
return;
|
|
|
|
const auto &Compilation = Inputs[Index++];
|
|
|
|
Input = Compilation.first;
|
|
|
|
CWD = Compilation.second;
|
|
|
|
}
|
|
|
|
// Run the tool on it.
|
|
|
|
if (WorkerTools[I]->runOnFile(Input, CWD))
|
|
|
|
HadErrors = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#if LLVM_ENABLE_THREADS
|
|
|
|
WorkerThreads.emplace_back(std::move(Worker));
|
|
|
|
#else
|
|
|
|
// Run the worker without spawning a thread when threads are disabled.
|
|
|
|
Worker();
|
|
|
|
#endif
|
2019-06-13 05:32:49 +08:00
|
|
|
}
|
|
|
|
for (auto &W : WorkerThreads)
|
|
|
|
W.join();
|
|
|
|
|
|
|
|
return HadErrors;
|
|
|
|
}
|