diff --git a/source/tool/GenerateAction.cpp b/source/tool/GenerateAction.cpp new file mode 100644 index 000000000..a4928c415 --- /dev/null +++ b/source/tool/GenerateAction.cpp @@ -0,0 +1,106 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// Licensed 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 +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdox +// + +#include "Options.hpp" +#include "api/ConfigImpl.hpp" +#include +#include +#include +#include +#include + +namespace clang { +namespace mrdox { + +int +DoGenerateAction(Reporter& R) +{ + auto& generators = getGenerators(); + + // Calculate additional YAML settings from command line options. + std::string extraYaml; + { + llvm::raw_string_ostream os(extraYaml); + if(IgnoreMappingFailures.getValue()) + os << "ignore-failures: true\n"; + } + + // Load configuration file + if(! ConfigPath.hasArgStr()) + { + llvm::errs() << + "Missing configuration file path argument.\n"; + return EXIT_FAILURE; + } + std::error_code ec; + auto config = loadConfigFile(ConfigPath, extraYaml, ec); + if(ec) + { + (void)R.error(ec, "load config file '", ConfigPath, "'"); + return EXIT_FAILURE; + } + + // Load the compilation database + if(InputPaths.empty()) + { + llvm::errs() << + "Missing path to compilation database argument.\n"; + return EXIT_FAILURE; + } + if(InputPaths.size() > 1) + { + llvm::errs() << + "Expected one input path argument, got more than one.\n"; + return EXIT_FAILURE; + } + std::string errorMessage; + auto compilations = + tooling::JSONCompilationDatabase::loadFromFile( + InputPaths.front(), errorMessage, + tooling::JSONCommandLineSyntax::AutoDetect); + if(! compilations) + { + llvm::errs() << errorMessage << '\n'; + return EXIT_FAILURE; + } + + // Create the ToolExecutor from the compilation database + int ThreadCount = 0; + auto ex = std::make_unique( + *compilations, ThreadCount); + + // Create the generator + auto generator = generators.find(FormatType.getValue()); + if(! generator) + { + R.print("Generator '", FormatType.getValue(), "' not found."); + return EXIT_FAILURE; + } + + // Run the tool, this can take a while + auto corpus = Corpus::build(*ex, config, R); + if(R.error(corpus, "build the documentation corpus")) + return EXIT_FAILURE; + + // Run the generator. + if(config->verboseOutput) + llvm::outs() << "Generating docs...\n"; + auto err = generator->build(OutputPath.getValue(), **corpus, R); + if(err) + { + R.print(err.message(), "generate '", OutputPath, "'"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +} // mrdox +} // clang diff --git a/source/tool/Options.cpp b/source/tool/Options.cpp new file mode 100644 index 000000000..10e999744 --- /dev/null +++ b/source/tool/Options.cpp @@ -0,0 +1,95 @@ +// +// Licensed 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 +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdox +// + +#include "Options.hpp" + +namespace clang { +namespace mrdox { + +char const* Overview = +R"(Generate reference documentation, run tests against +a set of input vectors, or update a set of reference tests.)"; + +llvm::cl::OptionCategory Category("mrdox options"); + +llvm::cl::extrahelp ExtraHelp( +R"(Usage: + + mrdox .. ( compile-commands ) + + mrdox .. --action ( "test" | "update" ) ( dir | file )... + +Examples + + mrdox --action test friend.cpp + + mrdox --format adoc compile_commands.json +)"); + +llvm::cl::opt ToolAction( + "action", + llvm::cl::desc(R"(Which action should be performed)"), + llvm::cl::init(Action::generate), + llvm::cl::values( + clEnumVal(test, "Compare output against expected"), + clEnumVal(update, "Update all expected xml files"), + clEnumVal(generate, "Generate reference documentation")), + llvm::cl::cat(Category)); + +// Test options + +llvm::cl::opt badOption( + "bad", + llvm::cl::desc("Write a .bad.xml file for each test failure"), + llvm::cl::init(true), + llvm::cl::cat(Category)); + +llvm::cl::opt adocOption( + "adoc", + llvm::cl::desc("Write the corresponding Asciidoc (adoc) file for each input test file"), + llvm::cl::init(false), + llvm::cl::cat(Category)); + +// Generate options + +llvm::cl::opt FormatType( + "format", + llvm::cl::desc("Format for outputted docs (\"adoc\" or \"xml\")."), + llvm::cl::init("adoc"), + llvm::cl::cat(Category)); + +// Common options + +llvm::cl::opt IgnoreMappingFailures( + "ignore-map-errors", + llvm::cl::desc("Continue if files are not mapped correctly."), + llvm::cl::init(true), + llvm::cl::cat(Category)); + +llvm::cl::opt ConfigPath( + "config", + llvm::cl::desc(R"(The config filename relative to the repository root)"), + llvm::cl::init("mrdox.yaml"), + llvm::cl::cat(Category)); + +llvm::cl::opt OutputPath( + "output", + llvm::cl::desc("Directory or file for generating output."), + llvm::cl::init("."), + llvm::cl::cat(Category)); + +llvm::cl::list InputPaths( + "inputs", + llvm::cl::Sink, + llvm::cl::desc("The path to the compilation database, or one or more .cpp files to test."), + llvm::cl::cat(Category)); + +} // mrdox +} // clang diff --git a/source/tool/Options.hpp b/source/tool/Options.hpp new file mode 100644 index 000000000..2740e478d --- /dev/null +++ b/source/tool/Options.hpp @@ -0,0 +1,48 @@ +// +// Licensed 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 +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdox +// + +#ifndef MRDOX_TOOL_OPTIONS_HPP +#define MRDOX_TOOL_OPTIONS_HPP + +#include +#include + +namespace clang { +namespace mrdox { + +enum Action : int +{ + test, + update, + generate +}; + +extern char const* Overview; +extern llvm::cl::OptionCategory Category; +extern llvm::cl::extrahelp ExtraHelp; +extern llvm::cl::opt ToolAction; + +// Test options +extern llvm::cl::opt badOption; +extern llvm::cl::opt adocOption; + +// Generate options +extern llvm::cl::opt FormatType; + +// Common options +extern llvm::cl::opt IgnoreMappingFailures; +extern llvm::cl::opt ConfigPath; +extern llvm::cl::opt OutputPath; +extern llvm::cl::list InputPaths; + +} // mrdox +} // clang + +#endif diff --git a/source/tool/SingleFileDB.hpp b/source/tool/SingleFileDB.hpp new file mode 100644 index 000000000..89d5edfe6 --- /dev/null +++ b/source/tool/SingleFileDB.hpp @@ -0,0 +1,73 @@ +// +// Licensed 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 +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdox +// + +#ifndef MRDOX_TOOL_SINGLEFILEDB_HPP +#define MRDOX_TOOL_SINGLEFILEDB_HPP + +#include +#include +#include +#include + +namespace clang { +namespace mrdox { + +/** Compilation database for a single .cpp file. +*/ +class SingleFileDB + : public tooling::CompilationDatabase +{ + std::vector cc_; + +public: + SingleFileDB( + llvm::StringRef dir, + llvm::StringRef file) + { + std::vector cmds; + cmds.emplace_back("clang"); + cmds.emplace_back("-std=c++20"); + cmds.emplace_back("-pedantic-errors"); + cmds.emplace_back("-Werror"); + cmds.emplace_back(file); + cc_.emplace_back( + dir, + file, + std::move(cmds), + dir); + cc_.back().Heuristic = "unit test"; + } + + std::vector + getCompileCommands( + llvm::StringRef FilePath) const override + { + if(! FilePath.equals(cc_.front().Filename)) + return {}; + return { cc_.front() }; + } + + std::vector + getAllFiles() const override + { + return { cc_.front().Filename }; + } + + std::vector + getAllCompileCommands() const override + { + return { cc_.front() }; + } +}; + +} // mrdox +} // clang + +#endif diff --git a/source/tool/TestAction.cpp b/source/tool/TestAction.cpp new file mode 100644 index 000000000..5fd599b6b --- /dev/null +++ b/source/tool/TestAction.cpp @@ -0,0 +1,487 @@ +// +// Licensed 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 +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdox +// + +#include "Options.hpp" +#include "SingleFileDB.hpp" +#include "api/ConfigImpl.hpp" +#include "api/Support/Debug.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace clang { +namespace mrdox { + +extern void dumpCommentTypes(); +extern void dumpCommentCommands(); + +using SmallString = llvm::SmallString<0>; + +//------------------------------------------------ + +struct Results +{ + using clock_type = std::chrono::system_clock; + + clock_type::time_point startTime; + std::atomic numberOfDirs = 0; + std::atomic numberOfFiles = 0; + std::atomic numberOfErrors = 0; + std::atomic numberOfFailures = 0; + std::atomic numberofFilesWritten = 0; + + Results() + : startTime(clock_type::now()) + { + } + + // Return the number of milliseconds of elapsed time + auto + elapsedMilliseconds() const noexcept + { + return std::chrono::duration_cast< + std::chrono::milliseconds>( + clock_type::now() - startTime).count(); + } +}; + +//------------------------------------------------ + +// We need a different config for each directory +// or file passed on the command line, and thus +// each input path must have a separate TestRunner. + +class TestRunner +{ + Results& results_; + std::string extraYaml_; + std::shared_ptr config_; + Config::WorkGroup wg_; + Reporter& R_; + Generator const* xmlGen_; + Generator const* adocGen_; + + std::shared_ptr + makeConfig( + llvm::StringRef workingDir); + + llvm::Error + writeFile( + llvm::StringRef filePath, + llvm::StringRef contents); + + llvm::Error + handleFile( + llvm::StringRef filePath, + std::shared_ptr const& config); + + llvm::Error + handleDir( + llvm::StringRef dirPath); + +public: + TestRunner( + Results& results, + llvm::StringRef extraYaml, + Reporter& R); + + /** Check a single file, or a directory recursively. + + This function checks the specified path + and blocks until completed. + */ + llvm::Error + checkPath( + llvm::StringRef inputPath); +}; + +//------------------------------------------------ + +TestRunner:: +TestRunner( + Results& results, + llvm::StringRef extraYaml, + Reporter& R) + : results_(results) + , extraYaml_(extraYaml) + , config_([&extraYaml] + { + std::error_code ec; + auto config = loadConfigString( + "", extraYaml.str(), ec); + Assert(! ec); + return config; + }()) + , wg_(config_.get()) + , R_(R) + , xmlGen_(getGenerators().find("xml")) + , adocGen_(getGenerators().find("adoc")) +{ + Assert(xmlGen_ != nullptr); + Assert(adocGen_ != nullptr); +} + +std::shared_ptr +TestRunner:: +makeConfig( + llvm::StringRef workingDir) +{ + std::string configYaml; + llvm::raw_string_ostream(configYaml) << + "verbose: false\n" + "source-root: " << workingDir << "\n" + "with-private: true\n" + "generator:\n" + " xml:\n" + " index: false\n" + " prolog: true\n"; + + std::error_code ec; + auto config = loadConfigString( + workingDir, configYaml, + ec); + Assert(! ec); + return config; +} + +llvm::Error +TestRunner:: +writeFile( + llvm::StringRef filePath, + llvm::StringRef contents) +{ + std::error_code ec; + llvm::raw_fd_ostream os(filePath, ec, llvm::sys::fs::OF_None); + if(ec) + { + results_.numberOfErrors++; + return makeError("raw_fd_ostream returned ", ec); + } + os << contents; + if(os.error()) + { + results_.numberOfErrors++; + return makeError("raw_fd_ostream::write returned ", os.error()); + } + //R_.print("File '", outputPath, "' written."); + results_.numberofFilesWritten++; + return llvm::Error::success(); +} + +llvm::Error +TestRunner:: +handleFile( + llvm::StringRef filePath, + std::shared_ptr const& config) +{ + namespace fs = llvm::sys::fs; + namespace path = llvm::sys::path; + + Assert(path::extension(filePath).compare_insensitive(".cpp") == 0); + + results_.numberOfFiles++; + + SmallString dirPath = filePath; + path::remove_filename(dirPath); + + SmallString outputPath = filePath; + path::replace_extension(outputPath, xmlGen_->fileExtension()); + + // Build Corpus + std::unique_ptr corpus; + { + SingleFileDB db(dirPath, filePath); + tooling::StandaloneToolExecutor ex(db, { std::string(filePath) }); + auto result = Corpus::build(ex, config, R_); + if(R_.error(result, "build Corpus for '", filePath, "'")) + { + results_.numberOfErrors++; + return llvm::Error::success(); // keep going + } + corpus = std::move(result.get()); + } + + // Generate XML + std::string generatedXml; + if(R_.error( + xmlGen_->buildOneString(generatedXml, *corpus, R_), + "build XML string for '", filePath, "'")) + { + results_.numberOfErrors++; + return llvm::Error::success(); // keep going + } + + if(ToolAction == Action::test) + { + // Open and load XML comparison file + std::unique_ptr expectedXml; + { + auto result = llvm::MemoryBuffer::getFile(outputPath, false); + if(! result) + { + // File could not be loaded + results_.numberOfErrors++; + + if( result.getError() != std::errc::no_such_file_or_directory) + { + // Some kind of system problem + (void)R_.error(result.getError(), "load '", outputPath, "'"); + return llvm::Error::success(); // keep going + } + + // File does not exist, so write it + if(auto err = writeFile(outputPath, generatedXml)) + return llvm::Error::success(); + } + else + { + expectedXml = std::move(result.get()); + } + } + + // Compare the generated output with the expected output + if( expectedXml && + generatedXml != expectedXml->getBuffer()) + { + // The output did not match + results_.numberOfFailures++; + R_.print("Failed: '", filePath, "'\n"); + + if(badOption.getValue()) + { + // Write the .bad.xml file + auto bad = outputPath; + path::replace_extension(bad, "bad.xml"); + { + std::error_code ec; + llvm::raw_fd_ostream os(bad, ec, llvm::sys::fs::OF_None); + if (ec) { + results_.numberOfErrors++; + return makeError("raw_fd_ostream returned ", ec); + } + os << generatedXml; + } + + auto diff = llvm::sys::findProgramByName("diff"); + + if (!diff.getError()) + { + path::replace_extension(bad, "xml"); + std::array args { + diff.get(), "-u", "--color", bad, outputPath }; + llvm::sys::ExecuteAndWait(diff.get(), args); + } + + // Fix the path for the code that follows + outputPath.pop_back_n(8); + path::replace_extension(outputPath, "xml"); + } + } + else + { + // success + } + } + else if(ToolAction == Action::update) + { + // Refresh the expected output file + if(auto err = writeFile(outputPath, generatedXml)) + { + results_.numberOfErrors++; + return llvm::Error::success(); + } + } + + // Write Asciidoc if requested + if(adocOption.getValue()) + { + path::replace_extension(outputPath, adocGen_->fileExtension()); + if(R_.error( + adocGen_->buildOne(outputPath.str(), *corpus, R_), + "write '", outputPath, "'")) + return llvm::Error::success(); // keep going + } + + return llvm::Error::success(); +} + +llvm::Error +TestRunner:: +handleDir( + llvm::StringRef dirPath) +{ + namespace fs = llvm::sys::fs; + namespace path = llvm::sys::path; + + results_.numberOfDirs++; + + // Set up directory iterator + std::error_code ec; + fs::directory_iterator iter(dirPath, ec, false); + if(ec) + return makeError("directory_iterator returned ", ec); + fs::directory_iterator const end{}; + + auto const config = makeConfig(dirPath); + + while(iter != end) + { + if(iter->type() == fs::file_type::directory_file) + { + if(auto err = handleDir(iter->path())) + return err; + } + else if( + iter->type() == fs::file_type::regular_file && + path::extension(iter->path()).equals_insensitive(".cpp")) + { + wg_.post( + [this, config, filePath = SmallString(iter->path())] + { + handleFile(filePath, config).operator bool(); + }); + } + else + { + // we don't handle this type + } + iter.increment(ec); + if(ec) + return makeError("directory_iterator returned ", ec); + } + return llvm::Error::success(); +} + +llvm::Error +TestRunner:: +checkPath( + llvm::StringRef inputPath) +{ + namespace fs = llvm::sys::fs; + namespace path = llvm::sys::path; + + // See if inputPath references a file or directory + fs::file_status fileStatus; + if(auto ec = fs::status(inputPath, fileStatus)) + { + results_.numberOfErrors++; + return makeError("fs::status returned '", ec, "'"); + } + + if(fileStatus.type() == fs::file_type::regular_file) + { + auto const ext = path::extension(inputPath); + if(! ext.equals_insensitive(".cpp")) + return makeError("expected a .cpp file"); + + // Calculate the workingDir + SmallString workingDir(inputPath); + path::remove_filename(workingDir); + path::remove_dots(workingDir, true); + + auto config = makeConfig(workingDir); + auto err = handleFile(inputPath, config); + wg_.wait(); + return err; + } + + if(fileStatus.type() == fs::file_type::directory_file) + { + // Iterate this directory and all its children + SmallString dirPath(inputPath); + path::remove_dots(dirPath, true); + auto err = handleDir(dirPath); + wg_.wait(); + return err; + } + + return makeError("wrong fs::file_type=", static_cast(fileStatus.type())); +} + +int +DoTestAction(Reporter& R) +{ + using namespace clang::mrdox; + + std::string extraYaml; + llvm::raw_string_ostream(extraYaml) << + "concurrency: 1\n"; + Results results; + for(auto const& inputPath : InputPaths) + { + TestRunner instance(results, extraYaml, R); + if(auto err = instance.checkPath(inputPath)) + if(R.error(err, "check path '", inputPath, "'")) + break; + } + + auto& os = debug_outs(); + if(results.numberofFilesWritten > 0) + os << + results.numberofFilesWritten << " files written\n"; + os << + "Checked " << + results.numberOfFiles << " files (" << + results.numberOfDirs << " dirs)"; + if( results.numberOfErrors > 0 || + results.numberOfFailures > 0) + { + if( results.numberOfErrors > 0) + { + os << + ", with " << + results.numberOfErrors << " errors"; + if(results.numberOfFailures > 0) + os << + " and " << results.numberOfFailures << + " failures"; + } + else + { + os << + ", with " << + results.numberOfFailures << + " failures"; + } + } + auto milli = results.elapsedMilliseconds(); + if(milli < 10000) + os << + " in " << milli << " milliseconds\n"; +#if 0 + else if(milli < 10000) + os << + " in " << std::setprecision(1) << + double(milli) / 1000 << " seconds\n"; +#endif + else + os << + " in " << ((milli + 500) / 1000) << + " seconds\n"; + + if( results.numberOfFailures > 0 || + results.numberOfErrors > 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +} // mrdox +} // clang diff --git a/source/tool/ToolMain.cpp b/source/tool/ToolMain.cpp index 19d7cbe7a..941fedfb5 100644 --- a/source/tool/ToolMain.cpp +++ b/source/tool/ToolMain.cpp @@ -9,162 +9,74 @@ // Official repository: https://github.com/cppalliance/mrdox // -// This tool for generating C and C++ documentation from source code -// and comments. Generally, it runs a LibTooling FrontendAction on source files, -// mapping each declaration in those files to its USR and serializing relevant -// information into LLVM bitcode. It then runs a pass over the collected -// declaration information, reducing by USR. There is an option to dump this -// intermediate result to bitcode. Finally, it hands the reduced information -// off to a generator, which does the final parsing from the intermediate -// representation to the desired output format. +//------------------------------------------------ + +// This is a tool for generating reference documentation from C++ source code. +// It runs a LibTooling FrontendAction on source files, mapping each declaration +// in those files to its USR and serializing relevant information into LLVM +// bitcode. It then runs a pass over the collected declaration information, +// reducing by USR. Finally, it hands the reduced information off to a generator, +// which does the final parsing from the intermediate representation to the +// desired output format. +// +// The tool comes with these builtin generators: // +// XML +// Asciidoc +// Bitstream +// +// Furthermore, additional generators can be implemented as dynamically +// loaded library "plugins" discovered at runtime. These generators can +// be implemented without including LLVM headers or linking to LLVM +// libraries. + +//------------------------------------------------ -#include "api/ConfigImpl.hpp" +#include "Options.hpp" #include "api/Support/Debug.hpp" -#include -#include #include #include -#include -#include -#include #include +#include +#include +#include namespace clang { namespace mrdox { -//------------------------------------------------ - -namespace { - -const char* Overview = -R"(Generates documentation from source code and comments. - -Examples - - $ mrdox mrdox.yml - $ mrdox --config=mrdox.yml --output ./docs -)"; - -static -llvm::cl::extrahelp -CommonHelp( - tooling::CommonOptionsParser::HelpMessage); - -static -llvm::cl::OptionCategory -ToolCategory("mrdox options"); - -static -llvm::cl::opt -ConfigPath( - "config", - llvm::cl::desc(R"(The config filename relative to the repository root)"), - llvm::cl::init("mrdox.yaml"), - llvm::cl::cat(ToolCategory)); - -static -llvm::cl::opt -FormatType( - "format", - llvm::cl::desc("Format for outputted docs (\"adoc\" or \"xml\")."), - llvm::cl::init("adoc"), - llvm::cl::cat(ToolCategory)); - -static -llvm::cl::opt -IgnoreMappingFailures( - "ignore-map-errors", - llvm::cl::desc("Continue if files are not mapped correctly."), - llvm::cl::init(true), - llvm::cl::cat(ToolCategory)); - -static -llvm::cl::opt -OutputPath( - "output", - llvm::cl::desc("Directory or file for generating output."), - llvm::cl::init("."), - llvm::cl::cat(ToolCategory)); - -} // (anon) - -//------------------------------------------------ - -void -toolMain( - int argc, const char** argv, - Reporter& R) -{ - auto& generators = getGenerators(); - - llvm::cl::SetVersionPrinter( - &clang::mrdox::print_version); - - // parse command line options - auto optionsResult = tooling::CommonOptionsParser::create( - argc, argv, ToolCategory, llvm::cl::OneOrMore, Overview); - if(R.error(optionsResult, "calculate command line options")) - return; - - // Convert command line args to YAML - std::string extraYaml; - { - llvm::raw_string_ostream os(extraYaml); - if(IgnoreMappingFailures.getValue()) - os << "ignore-failures: true\n"; - } - std::error_code ec; - auto config = loadConfigFile(ConfigPath, - "", - ec); - if(ec) - return (void)R.error(ec, "load config file '", ConfigPath, "'"); - -// config->IgnoreMappingFailures = IgnoreMappingFailures; - - // create the executor - // VFALCO the 2nd parameter is ThreadCount and we should - // get this from the configuration. Or better yet why - // do we require the Executor in the Corpus API in the - // first place? - auto ex = std::make_unique( - optionsResult->getCompilations(), 0); - - // create the generator - auto generator = generators.find(FormatType.getValue()); - if(! generator) - { - R.print("Generator '", FormatType.getValue(), "' not found."); - return; - } - - // Run the tool, this can take a while - auto corpus = Corpus::build(*ex, config, R); - if(R.error(corpus, "build the documentation corpus")) - return; - - // Run the generator. - if(config->verboseOutput) - llvm::outs() << "Generating docs...\n"; - auto err = generator->build(OutputPath.getValue(), **corpus, R); - if(err) - R.print(err.message(), "generate '", OutputPath, "'"); -} +extern int DoGenerateAction(Reporter&); +extern int DoTestAction(Reporter&); } // mrdox } // clang -//------------------------------------------------ - int main(int argc, char const** argv) { using namespace clang::mrdox; - debugEnableHeapChecking(); + // VFALCO this heap checking is too strong for + // a clang tool's model of what is actually a leak. + // clang::mrdox::debugEnableHeapChecking(); + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + llvm::cl::SetVersionPrinter(&print_version); + + { + std::string errorString; + llvm::raw_string_ostream os(errorString); + if(! llvm::cl::ParseCommandLineOptions( + argc, argv, Overview, &os, nullptr)) + { + llvm::errs() << errorString; + return EXIT_FAILURE; + } + } Reporter R; - toolMain(argc, argv, R); - return R.getExitCode(); + int toolResult; + if(clang::mrdox::ToolAction == Action::generate) + toolResult = DoGenerateAction(R); + else + toolResult = DoTestAction(R); + return toolResult; }