Index: include/clang/Tooling/CommonOptionsParser.h =================================================================== --- include/clang/Tooling/CommonOptionsParser.h +++ include/clang/Tooling/CommonOptionsParser.h @@ -86,11 +86,12 @@ /// /// I also allows calls to set the required number of positional parameters. /// - /// This constructor exits program in case of error. + /// If \p ExitOnError is set (default), This constructor exits program in case + /// of error; otherwise, this sets the error flag and stores error messages. CommonOptionsParser(int &argc, const char **argv, llvm::cl::OptionCategory &Category, llvm::cl::NumOccurrencesFlag OccurrencesFlag, - const char *Overview = nullptr); + const char *Overview = nullptr, bool ExitOnError = true); /// Returns a reference to the loaded compilations database. CompilationDatabase &getCompilations() { @@ -102,9 +103,15 @@ return SourcePathList; } + bool hasError() const { return HasError; } + + const std::string &getErrorMessage() const { return ErrorMessage; } + static const char *const HelpMessage; private: + bool HasError; + std::string ErrorMessage; std::unique_ptr Compilations; std::vector SourcePathList; std::vector ExtraArgsBefore; Index: include/clang/Tooling/ToolExecutorPluginRegistry.h =================================================================== --- /dev/null +++ include/clang/Tooling/ToolExecutorPluginRegistry.h @@ -0,0 +1,24 @@ +//===--- ToolExecutorPluginRegistry.h - -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H +#define LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H + +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace tooling { + +typedef llvm::Registry ToolExecutorPluginRegistry; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H Index: include/clang/Tooling/Tooling.h =================================================================== --- include/clang/Tooling/Tooling.h +++ include/clang/Tooling/Tooling.h @@ -31,18 +31,21 @@ #define LLVM_CLANG_TOOLING_TOOLING_H #include "clang/AST/ASTConsumer.h" -#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "clang/Driver/Util.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Lex/ModuleLoader.h" #include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/Twine.h" #include "llvm/Option/Option.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" #include #include #include @@ -334,7 +337,9 @@ /// The file manager is shared between all translation units. FileManager &getFiles() { return *Files; } - private: + llvm::ArrayRef getSourcePaths() const { return SourcePaths; } + +private: const CompilationDatabase &Compilations; std::vector SourcePaths; std::shared_ptr PCHContainerOps; @@ -456,6 +461,131 @@ clang::DiagnosticsEngine *Diagnostics, const llvm::opt::ArgStringList &CC1Args); +/// \brief Interface for tools that output (key, value) pairs as the result. +class ResultReporter { +public: + virtual ~ResultReporter() {} + virtual void ReportResult(llvm::StringRef Key, llvm::StringRef Value) = 0; +}; + +/// \brief Interface for classes that execute clang FrontendActions on code. +/// +/// This can be extended to support running FrontendActions in different +/// execution mode e.g. on a specific set of files or all files in the codebase. +class ToolExecutor { +public: + virtual ~ToolExecutor() {} + + /// \brief Returns the name of a specific executor. + virtual llvm::StringRef getExecutorName() const = 0; + + /// \brief Runs the each action with arguments adjusted by the corresponding + /// ArgumentAdjuster on code. + virtual llvm::Error Execute( + llvm::ArrayRef> Actions) = 0; + + /// \brief Similar to the `Execute` that multiple actions except that this + /// takes in a single action and uses the default ArgumentsAdjusters e.g. + /// `ClangStripOutputAdjuster` and `ClangSyntaxOnlyAdjuster`. + llvm::Error Execute(ToolAction *Action); + + /// \brief Returns an execution-mode-specific ResultReporter. + virtual ResultReporter *GetResultReporter() = 0; +}; + +/// \brief Interface for factories that create speicifc executors based on the +/// commandline arguments. This is also used as a plugin to be registered into +/// ToolExecutorPluginRegistry. +class ToolExecutorPlugin { +public: + virtual ~ToolExecutorPlugin() {} + + /// \brief Create a ToolExecutor based on the commandline arguments. If the + /// set of commandline arguments is not supported by the ToolExecutor, this + /// returns an error. + virtual llvm::Expected> + create(int &argc, const char **argv, llvm::cl::OptionCategory &Category) = 0; +}; + +/// \brief This creates a ToolExecutor that is in the global registry and +/// supports the given commandline arguments. +llvm::Expected> +createExecutor(int &argc, const char **argv, llvm::cl::OptionCategory &Category, + const char *Overview = nullptr); + +/// \brief A stand alone executor that runs FrontendActions on a given set of +/// files. +/// FIXME: This is currently just a wrapper of `ClangTool`. Merge `ClangTool` +/// implementation into the this when all tools are switched to use executors. +class StandaloneToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + /// \brief Init with \p CompilationDatabase and the paths of all files to be + /// proccessed. + StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + /// \brief Init with \p CommonOptionsParser. This is expected to be used by + /// `createExecutor` based on commandline options. The executor takes + /// ownership of \p Options. + StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + llvm::StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error Execute(llvm::ArrayRef> + Actions) override; + + /// \brief Set a \c DiagnosticConsumer to use during parsing. + void setDiagnosticConsumer(DiagnosticConsumer *DiagConsumer) { + Tool.setDiagnosticConsumer(DiagConsumer); + } + + /// \brief Map a virtual file to be used while running the tool. + /// + /// \param FilePath The path at which the content will be mapped. + /// \param Content A buffer of the file's content. + void mapVirtualFile(StringRef FilePath, StringRef Content) { + Tool.mapVirtualFile(FilePath, Content); + } + + ResultReporter *GetResultReporter() override { + return &Reporter; + }; + + llvm::ArrayRef getSourcePaths() const { + return Tool.getSourcePaths(); + } + + /// \brief Returns the file manager used in the tool. + /// + /// The file manager is shared between all translation units. + FileManager &getFiles() { return Tool.getFiles(); } + +private: + class SimpleResultReporter : public ResultReporter { + public: + SimpleResultReporter( + std::vector> *KVResults); + + void ReportResult(llvm::StringRef Key, llvm::StringRef Value); + + private: + std::vector> &KVResults; + }; + // Used to store the parser when the executor is initialized with parser. + std::unique_ptr OptionsParser; + ClangTool Tool; + std::vector> KVResults; + SimpleResultReporter Reporter; +}; + } // end namespace tooling } // end namespace clang Index: lib/Tooling/CommonOptionsParser.cpp =================================================================== --- lib/Tooling/CommonOptionsParser.cpp +++ lib/Tooling/CommonOptionsParser.cpp @@ -94,30 +94,43 @@ CommonOptionsParser::CommonOptionsParser( int &argc, const char **argv, cl::OptionCategory &Category, - llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { - static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview, + bool ExitOnError) { + static cl::OptionCategory CommonOptionCategory("Clang tools common options."); - static cl::opt BuildPath("p", cl::desc("Build path"), - cl::Optional, cl::cat(Category)); + static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden, + cl::cat(CommonOptionCategory)); + + static cl::opt BuildPath( + "p", cl::desc("Build path"), cl::Optional, cl::cat(CommonOptionCategory)); static cl::list SourcePaths( cl::Positional, cl::desc(" [... ]"), OccurrencesFlag, - cl::cat(Category)); + cl::cat(CommonOptionCategory)); static cl::list ArgsAfter( "extra-arg", cl::desc("Additional argument to append to the compiler command line"), - cl::cat(Category)); + cl::cat(CommonOptionCategory)); static cl::list ArgsBefore( "extra-arg-before", cl::desc("Additional argument to prepend to the compiler command line"), - cl::cat(Category)); + cl::cat(CommonOptionCategory)); + + llvm::cl::ResetAllOptionOccurrences(); - cl::HideUnrelatedOptions(Category); + cl::HideUnrelatedOptions({&Category, &CommonOptionCategory}); Compilations.reset(FixedCompilationDatabase::loadFromCommandLine(argc, argv)); - cl::ParseCommandLineOptions(argc, argv, Overview); + llvm::raw_string_ostream OS(ErrorMessage); + HasError = !cl::ParseCommandLineOptions(argc, argv, Overview, + ExitOnError ? nullptr : &OS); + assert(!(ExitOnError && HasError) && + "Command-line parsing must have succeeded with ExitOnError set."); + // Stop initializing if command-line option parsing failed. + if (HasError) + return; cl::PrintOptionValues(); SourcePathList = SourcePaths; Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -25,10 +25,12 @@ #include "clang/Lex/PreprocessorOptions.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Config/llvm-config.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" @@ -38,6 +40,8 @@ #define DEBUG_TYPE "clang-tooling" +LLVM_INSTANTIATE_REGISTRY(clang::tooling::ToolExecutorPluginRegistry) + namespace clang { namespace tooling { @@ -535,5 +539,110 @@ return std::move(ASTs[0]); } +llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +llvm::Error ToolExecutor::Execute(ToolAction *Action){ + return Execute({{combineAdjusters(getClangStripOutputAdjuster(), + getClangSyntaxOnlyAdjuster()), + Action}}); +} + +llvm::Expected> createExecutor( + int &argc, const char **argv, llvm::cl::OptionCategory &Category, + const char *Overview) { + std::string ErrorMessage; + llvm::raw_string_ostream OS(ErrorMessage); + for (auto I = ToolExecutorPluginRegistry::begin(), + E = ToolExecutorPluginRegistry::end(); + I != E; ++I) { + std::unique_ptr Plugin(I->instantiate()); + std::vector args(argv, argv+argc); + int arg_num = argc; + llvm::Expected> Executor = + Plugin->create(arg_num, argv, Category); + if (!Executor) { + OS << "Failed to create '" << I->getName() + << "': " << llvm::toString(Executor.takeError()) << "\n"; + llvm::cl::ResetAllOptionOccurrences(); + continue; + } + argc = arg_num; + return std::move(*Executor); + } + OS.flush(); + return make_string_error(ErrorMessage); +} + +const char *StandaloneToolExecutor::ExecutorName = "StandaloneToolExector"; + +StandaloneToolExecutor::StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps) + : Tool(Compilations, SourcePaths), Reporter(&KVResults) {} + +StandaloneToolExecutor::StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps) + : OptionsParser(std::move(Options)), + Tool(OptionsParser->getCompilations(), OptionsParser->getSourcePathList(), + PCHContainerOps), + Reporter(&KVResults) {} + +llvm::Error StandaloneToolExecutor::Execute( + llvm::ArrayRef> Actions) { + if (Actions.empty()) + return make_string_error("No action to execute."); + + if (Actions.size() != 1) + return make_string_error( + "Only support executing exactly 1 ToolAction at this point."); + + auto &Action = Actions.front(); + Tool.appendArgumentsAdjuster(Action.first); + if (int Ret = Tool.run(Action.second)) + return make_string_error("Failed to run ToolAction."); + + return llvm::Error::success(); +} + +StandaloneToolExecutor::SimpleResultReporter::SimpleResultReporter( + std::vector> *KVResults) + : KVResults(*KVResults) {} + +void StandaloneToolExecutor::SimpleResultReporter::ReportResult( + llvm::StringRef Key, llvm::StringRef Value) { + +} + +class StandaloneToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + auto OptionsParser = llvm::make_unique( + argc, argv, Category, llvm::cl::OneOrMore, /*Overview=*/nullptr, + /*ExitOnError=*/false); + if (OptionsParser->hasError()) + return make_string_error("[StandaloneToolExecutorPlugin] " + + OptionsParser->getErrorMessage()); + if (OptionsParser->getSourcePathList().empty()) + return make_string_error( + "[StandaloneToolExecutorPlugin] No positional argument found."); + return llvm::make_unique(std::move(OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +volatile int ToolExecutorPluginAnchorSource = 0; + +static ToolExecutorPluginRegistry::Add + X("standalone-executor", + "Runs ToolActions on a set of files provided via positional arguments."); + } // end namespace tooling } // end namespace clang Index: unittests/Tooling/ToolingTest.cpp =================================================================== --- unittests/Tooling/ToolingTest.cpp +++ unittests/Tooling/ToolingTest.cpp @@ -15,16 +15,20 @@ #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include #include +using testing::ElementsAre; + namespace clang { namespace tooling { @@ -533,5 +537,117 @@ } #endif +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +class TestToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + TestToolExecutor(std::unique_ptr Options) + : OptionsParser(std::move(Options)) {} + + llvm::StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error Execute(llvm::ArrayRef> + Actions) override { + return llvm::Error::success(); + } + + ResultReporter *GetResultReporter() override { + return nullptr; + }; + + llvm::ArrayRef getSourcePaths() const { + return OptionsParser->getSourcePathList(); + } + +private: + std::unique_ptr OptionsParser; + std::string SourcePaths; +}; + +const char *TestToolExecutor::ExecutorName = "TestToolExecutor"; + +class TestToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + static llvm::cl::opt TestExecutor("test_executor", + llvm::cl::desc("Use TestToolExecutor")); + auto OptionsParser = llvm::make_unique( + argc, argv, Category, llvm::cl::OneOrMore, /*Overview=*/nullptr, + /*ExitOnError=*/false); + if (OptionsParser->hasError()) + return make_string_error("[TestToolExecutorPlugin] " + + OptionsParser->getErrorMessage()); + if (!TestExecutor) + return make_string_error( + "[TestToolExecutorPlugin] --test_executor is not set."); + return llvm::make_unique(std::move(OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +extern volatile int ToolExecutorPluginAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED TestToolExecutorPluginAnchorDest = + ToolExecutorPluginAnchorSource; + +static ToolExecutorPluginRegistry::Add + X("test-executor", + "Plugin for TestToolExecutor."); + +TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "--fake_flag_no_no_no", "f"}; + int argc = argv.size(); + auto Executor = createExecutor(argc, &argv[0], TestCategory); + ASSERT_FALSE((bool)Executor); + llvm::consumeError(Executor.takeError()); +} + +TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) { + llvm::cl::ResetCommandLineParser(); + + llvm::cl::opt BeforeReset( + "before_reset", llvm::cl::desc("Defined before reset."), + llvm::cl::init("")); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + + llvm::cl::ResetAllOptionOccurrences(); + + std::vector argv = {"prog", "--before_reset=set", "f"}; + int argc = argv.size(); + auto Executor = createExecutor(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(BeforeReset, "set"); +} + +TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "standalone.cpp"}; + int argc = argv.size(); + auto Executor = createExecutor(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), + StandaloneToolExecutor::ExecutorName); +} + +TEST(CreateToolExecutorTest, CreateTestToolExecutor) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "test.cpp", "--test_executor"}; + int argc = argv.size(); + auto Executor = createExecutor(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName); +} + } // end namespace tooling } // end namespace clang