diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95d4e4057..a144ce58c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: use-libcxx: {{#if (and (ieq compiler 'clang') (ge major 19)) }}true{{else}}false{{/if}} libcxx-runtimes: libcxx{{#if (ne compiler 'msvc')}};libcxxabi{{/if}} llvm-runtimes: {{#if (ine use-libcxx 'true') }}{{{ libcxx-runtimes }}}{{/if}} - llvm-hash: 3f797a8342c3dbe4a260b26f948d8776ff490431 + llvm-hash: dc4cef81d47c7bc4a3c4d58fbacf8a6359683fae llvm-build-preset-prefix: {{#if optimized-debug}}optimizeddebug{{else}}{{{lowercase build-type}}}{{/if}} llvm-build-preset-os: {{#if (ieq os 'windows') }}win{{else}}unix{{/if}} llvm-sanitizer: {{#if (eq compiler 'gcc')}}{{else if ubsan}}-UBSan{{else if asan}}-ASan{{else if msan}}-MSan{{/if}} diff --git a/bootstrap.py b/bootstrap.py index caaab3605..6f85633af 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -104,7 +104,7 @@ class InstallOptions: llvm_build_dir: str = "/build/<\"-\":if(cc)><\"-\":if(sanitizer)>" llvm_install_dir: str = "/install/<\"-\":if(cc)><\"-\":if(sanitizer)>" llvm_repo: str = "https://github.com/llvm/llvm-project.git" - llvm_commit: str = "a1b6e7ff393533a5c4f3bdfd4efe5da106e2de2b" + llvm_commit: str = "dc4cef81d47c7bc4a3c4d58fbacf8a6359683fae" # Libxml2 libxml2_src_dir: str = "/libxml2" diff --git a/docs/modules/ROOT/pages/install.adoc b/docs/modules/ROOT/pages/install.adoc index 563aeea65..8db83d0d8 100644 --- a/docs/modules/ROOT/pages/install.adoc +++ b/docs/modules/ROOT/pages/install.adoc @@ -259,7 +259,7 @@ cd .. === LLVM MrDocs uses LLVM to parse C++ code and extract documentation from it. -It depends on a recent version of LLVM: https://github.com/llvm/llvm-project/tree/a1b6e7ff393533a5c4f3bdfd4efe5da106e2de2b[a1b6e7f] +It depends on a recent version of LLVM: https://github.com/llvm/llvm-project/tree/dc4cef81d47c7bc4a3c4d58fbacf8a6359683fae[dc4cef8] **Download**: @@ -272,7 +272,7 @@ mkdir -p llvm-project <.> cd llvm-project git init <.> git remote add origin https://github.com/llvm/llvm-project.git <.> -git fetch --depth 1 origin a1b6e7ff393533a5c4f3bdfd4efe5da106e2de2b <.> +git fetch --depth 1 origin dc4cef81d47c7bc4a3c4d58fbacf8a6359683fae <.> git checkout FETCH_HEAD <.> ---- diff --git a/src/lib/AST/ClangHelpers.cpp b/src/lib/AST/ClangHelpers.cpp index 430d1c1f8..f973ed25b 100644 --- a/src/lib/AST/ClangHelpers.cpp +++ b/src/lib/AST/ClangHelpers.cpp @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include namespace mrdocs { @@ -453,4 +455,33 @@ isDocumented(clang::Decl const* D) return getDocumentation(D) != nullptr; } +bool +isClangCL(clang::tooling::CompileCommand const& cc) +{ + auto const& cmdline = cc.CommandLine; + + // ------------------------------------------------------ + // Convert to InputArgList + // ------------------------------------------------------ + // InputArgList is the input format for llvm functions + auto cmdLineCStrsView = std::views::transform(cmdline, &std::string::c_str); + std::vector const + cmdLineCStrs(cmdLineCStrsView.begin(), cmdLineCStrsView.end()); + llvm::opt::InputArgList const + args(cmdLineCStrs.data(), cmdLineCStrs.data() + cmdLineCStrs.size()); + + // ------------------------------------------------------ + // Get driver mode + // ------------------------------------------------------ + // The driver mode distinguishes between clang/gcc and msvc + // command line option formats. The value is deduced from + // the `-drive-mode` option or from `progName`. + // Common values are "gcc", "g++", "cpp", "cl" and "flang". + std::string const& progName = cmdline.front(); + clang::StringRef const driver_mode = clang::driver:: + getDriverMode(progName, cmdLineCStrs); + + return clang::driver::IsClangCL(driver_mode); +} + } // mrdocs diff --git a/src/lib/AST/ClangHelpers.hpp b/src/lib/AST/ClangHelpers.hpp index f2305f6fa..1b8554ac7 100644 --- a/src/lib/AST/ClangHelpers.hpp +++ b/src/lib/AST/ClangHelpers.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace mrdocs { @@ -1082,6 +1083,13 @@ namespace detail { report::trace("{}", MRDOCS_SYMBOL_TRACE_UNIQUE_NAME) #endif +/** Determine whether the driver mode is ClangCL. + + @param cc The compilation command to insepct. + */ +bool +isClangCL(clang::tooling::CompileCommand const& cc); + } // mrdocs #endif diff --git a/src/lib/AST/TerminalTypeVisitor.hpp b/src/lib/AST/TerminalTypeVisitor.hpp index 88bfd9947..aad5faad7 100644 --- a/src/lib/AST/TerminalTypeVisitor.hpp +++ b/src/lib/AST/TerminalTypeVisitor.hpp @@ -555,17 +555,6 @@ class TerminalTypeVisitor return true; } - bool - VisitDependentTemplateSpecializationType( - clang::DependentTemplateSpecializationType const* T) - { - MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); - const clang::DependentTemplateStorage &S = T->getDependentTemplateName(); - getDerived().buildTerminal(S.getQualifier(), S.getName().getIdentifier(), - T->template_arguments(), Quals_, IsPack_); - return true; - } - // Visit a template specialization such as `A` bool VisitTemplateSpecializationType( @@ -589,23 +578,36 @@ class TerminalTypeVisitor // The list of template parameters and a reference to // the templated scoped declaration - clang::NamedDecl* D = TN.getAsTemplateDecl(); - MRDOCS_SYMBOL_TRACE(TN, Visitor_.context_); - - if (!T->isTypeAlias()) + clang::NamedDecl* D = nullptr; + if (auto* CT = dyn_cast(T->getCanonicalTypeInternal()); + !T->isTypeAlias() && CT) { - if (auto* CT = dyn_cast(T->getCanonicalTypeInternal())) - { - MRDOCS_SYMBOL_TRACE(CT, Visitor_.context_); - D = CT->getOriginalDecl()->getDefinitionOrSelf(); - MRDOCS_SYMBOL_TRACE(D, Visitor_.context_); - } + MRDOCS_SYMBOL_TRACE(CT, Visitor_.context_); + D = CT->getDecl()->getDefinitionOrSelf(); + MRDOCS_SYMBOL_TRACE(D, Visitor_.context_); + } + else + { + D = TN.getAsTemplateDecl(/*IgnoreDeduced=*/true); + } + if (D) + { + getDerived().buildTerminal( + TN.getQualifier(), + D, + T->template_arguments(), + Quals_, + IsPack_); + } + else if (auto const* S = TN.getAsDependentTemplateName()) + { + getDerived().buildTerminal( + S->getQualifier(), + S->getName().getIdentifier(), + T->template_arguments(), + Quals_, + IsPack_); } - - getDerived().buildTerminal( - TN.getQualifier(), D, - T->template_arguments(), - Quals_, IsPack_); return true; } @@ -613,7 +615,7 @@ class TerminalTypeVisitor VisitRecordType( clang::RecordType const* T) { - clang::RecordDecl* RD = T->getOriginalDecl()->getDefinitionOrSelf(); + clang::RecordDecl* RD = T->getDecl()->getDefinitionOrSelf(); // if this is an instantiation of a class template, // create a SpecializationType & extract the template arguments Optional> TArgs = std::nullopt; @@ -630,8 +632,12 @@ class TerminalTypeVisitor VisitInjectedClassNameType( clang::InjectedClassNameType const* T) { - getDerived().buildTerminal(T->getQualifier(), T->getOriginalDecl()->getDefinitionOrSelf(), - std::nullopt, Quals_, IsPack_); + getDerived().buildTerminal( + T->getQualifier(), + T->getDecl()->getDefinitionOrSelf(), + std::nullopt, + Quals_, + IsPack_); return true; } @@ -639,8 +645,12 @@ class TerminalTypeVisitor VisitEnumType( clang::EnumType const* T) { - getDerived().buildTerminal(T->getQualifier(), T->getOriginalDecl()->getDefinitionOrSelf(), - std::nullopt, Quals_, IsPack_); + getDerived().buildTerminal( + T->getQualifier(), + T->getDecl()->getDefinitionOrSelf(), + std::nullopt, + Quals_, + IsPack_); return true; } diff --git a/src/lib/CorpusImpl.cpp b/src/lib/CorpusImpl.cpp index e7cd73038..59808272a 100644 --- a/src/lib/CorpusImpl.cpp +++ b/src/lib/CorpusImpl.cpp @@ -746,10 +746,9 @@ lookupCacheSet( //------------------------------------------------ mrdocs::Expected> -CorpusImpl:: -build( +CorpusImpl::build( std::shared_ptr const& config, - clang::tooling::CompilationDatabase const& compilations) + MrDocsCompilationDatabase const& compilations) { using clock_type = std::chrono::steady_clock; auto start_time = clock_type::now(); @@ -769,6 +768,10 @@ build( // SymbolSet in the execution context. InfoExecutionContext context(*config); + // Identify if we should use "msvc/clang-cl" or "clang/gcc" format + // for options. + bool const is_clang_cl = compilations.isClangCL(); + // ------------------------------------------ // "Process file" task // ------------------------------------------ @@ -829,8 +832,10 @@ build( FSConcrete->addVirtualFile(shimPath, shimContent); Tool.appendArgumentsAdjuster( clang::tooling::combineAdjusters( - clang::tooling::getInsertArgumentAdjuster("-include"), - clang::tooling::getInsertArgumentAdjuster(shimPath.data()))); + clang::tooling::getInsertArgumentAdjuster( + is_clang_cl ? "/FI" : "-include"), + clang::tooling::getInsertArgumentAdjuster( + shimPath.data()))); } // Run the action diff --git a/src/lib/CorpusImpl.hpp b/src/lib/CorpusImpl.hpp index 1b6dd772c..571fb8fb3 100644 --- a/src/lib/CorpusImpl.hpp +++ b/src/lib/CorpusImpl.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -161,15 +162,14 @@ class CorpusImpl final : public Corpus not need to call this function directly. @param config A shared pointer to the configuration. - @param compilations A compilations database for the input files. + @param compilations A MrDocs compilations database for the input files. */ // MRDOCS_DECL [[nodiscard]] - static - mrdocs::Expected> + static mrdocs::Expected> build( std::shared_ptr const& config, - clang::tooling::CompilationDatabase const& compilations); + MrDocsCompilationDatabase const& compilations); void qualifiedName(Symbol const& I, diff --git a/src/lib/MrDocsCompilationDatabase.cpp b/src/lib/MrDocsCompilationDatabase.cpp index 7e6972746..61cd76aea 100644 --- a/src/lib/MrDocsCompilationDatabase.cpp +++ b/src/lib/MrDocsCompilationDatabase.cpp @@ -10,6 +10,7 @@ // #include "MrDocsCompilationDatabase.hpp" +#include #include #include #include @@ -270,13 +271,14 @@ isValidMrDocsOption( return true; } -static -std::vector +static std::vector adjustCommandLine( llvm::StringRef const workingDir, std::vector const& cmdline, + bool is_clang_cl, std::shared_ptr const& config, - std::unordered_map> const& implicitIncludeDirectories, + std::unordered_map> const& + implicitIncludeDirectories, std::string_view filename) { if (cmdline.empty()) @@ -300,92 +302,16 @@ adjustCommandLine( cmdLineCStrs.data(), cmdLineCStrs.data() + cmdLineCStrs.size()); - // ------------------------------------------------------ - // Get driver mode - // ------------------------------------------------------ - // The driver mode distinguishes between clang/gcc and msvc - // command line option formats. The value is deduced from - // the `-drive-mode` option or from `progName`. - // Common values are "gcc", "g++", "cpp", "cl" and "flang". - llvm::StringRef const driver_mode = clang::driver::getDriverMode(progName, cmdLineCStrs); - // Identify if we should use "msvc/clang-cl" or "clang/gcc" format - // for options. - bool const is_clang_cl = clang::driver::IsClangCL(driver_mode); + auto const systemIncludeFlag = is_clang_cl ? "-external:I" : "-isystem"; // ------------------------------------------------------ // Supress all warnings // ------------------------------------------------------ // Add flags to ignore all warnings. Any options that // affect warnings will be discarded later. - new_cmdline.emplace_back(is_clang_cl ? "/w" : "-w"); + new_cmdline.emplace_back("-w"); new_cmdline.emplace_back("-fsyntax-only"); - // ------------------------------------------------------ - // Target architecture - // ------------------------------------------------------ - constexpr auto is_target_option = [](std::string_view opt) { - return opt == "-target" || opt == "--target"; - }; - if (std::ranges::find_if(cmdline, is_target_option) == cmdline.end()) - { - auto getCommandCompilerTarget = [&]() -> std::string { - ScopedTempFile const outputPath("compiler-triple", "txt"); - if (!outputPath) { - return {}; - } - std::vector args = { - progName, "--print-target-triple" - }; - std::optional const redirects[] = { - llvm::StringRef(), - outputPath.path(), - llvm::StringRef() - }; - int const result = ExecuteAndWaitWithLogging( - progName, args, std::nullopt, redirects); - if (result != 0) - { - return {}; - } - - auto const bufferOrError = llvm::MemoryBuffer::getFile( - outputPath.path()); - if (!bufferOrError) { - return {}; - } - return bufferOrError.get()->getBuffer().trim().str(); - }; - - [&]() { - std::string target = llvm::sys::getDefaultTargetTriple(); - - if (target.empty()) - { - target = llvm::sys::getProcessTriple(); - } - - if (target.empty()) - { - target = getCommandCompilerTarget(); - } - -#if defined(__APPLE__) - if (target.empty()) - { - target = "arm64-apple-darwin24.0.0"; - } -#else - if (target.empty()) - { - return; - } -#endif - - new_cmdline.emplace_back("-target"); - new_cmdline.emplace_back(target); - }(); - } - // ------------------------------------------------------ // Language standard // ------------------------------------------------------ @@ -411,17 +337,23 @@ adjustCommandLine( isExplicitCCompileCommand || (!isExplicitCppCompileCommand && isImplicitCSourceFile); constexpr auto is_std_option = [](std::string_view const opt) { - return opt.starts_with("-std=") || opt.starts_with("--std=") || opt.starts_with("/std:"); + return opt.starts_with("-std=") || opt.starts_with("--std=") + || // clang options + opt.starts_with("-std:") + || opt.starts_with("/std:"); // clang-cl options }; - if (std::ranges::find_if(cmdline, is_std_option) == cmdline.end()) + // If the language standard wasn't specified, change the default to the + // latest one available. + if (std::ranges::none_of(cmdline, is_std_option)) { if (!isCCompileCommand) { - new_cmdline.emplace_back("-std=c++23"); + new_cmdline.emplace_back( + is_clang_cl ? "-std:c++latest" : "-std=c++26"); } else { - new_cmdline.emplace_back("-std=c23"); + new_cmdline.emplace_back(is_clang_cl ? "-std:clatest" : "-std=c23"); } } @@ -448,8 +380,9 @@ adjustCommandLine( it != implicitIncludeDirectories.end()) { for (auto const& inc : it->second) { - new_cmdline.emplace_back(std::format("-isystem{}", inc)); - } + new_cmdline.emplace_back(systemIncludeFlag); + new_cmdline.emplace_back(inc); + } } } @@ -463,11 +396,11 @@ adjustCommandLine( // implicit include paths and add the standard library // and system includes manually. That gives MrDocs // access to libc++ in a portable way. - new_cmdline.emplace_back("-nostdinc++"); - new_cmdline.emplace_back("-nostdlib++"); + new_cmdline.emplace_back(is_clang_cl ? "-X" : "-nostdinc++"); for (auto const& inc : (*config)->stdlibIncludes) { - new_cmdline.emplace_back(std::format("-isystem{}", inc)); + new_cmdline.emplace_back(systemIncludeFlag); + new_cmdline.emplace_back(inc); } } @@ -476,7 +409,8 @@ adjustCommandLine( new_cmdline.emplace_back("-nostdinc"); for (auto const& inc : (*config)->libcIncludes) { - new_cmdline.emplace_back(std::format("-isystem{}", inc)); + new_cmdline.emplace_back(systemIncludeFlag); + new_cmdline.emplace_back(inc); } } @@ -485,7 +419,8 @@ adjustCommandLine( // ------------------------------------------------------ for (auto const& inc : (*config)->systemIncludes) { - new_cmdline.emplace_back(std::format("-isystem{}", inc)); + new_cmdline.emplace_back(systemIncludeFlag); + new_cmdline.emplace_back(inc); } for (auto const& inc : (*config)->includes) { @@ -538,7 +473,7 @@ makeAbsoluteAndNative( else { temp = path; - llvm::sys::fs::make_absolute(workingDir, temp); + llvm::sys::path::make_absolute(workingDir, temp); llvm::sys::path::remove_dots(temp, true); } return static_cast(temp); @@ -556,6 +491,12 @@ MrDocsCompilationDatabase( using clang::tooling::CompileCommand; std::vector allCommands = inner.getAllCompileCommands(); + if (allCommands.empty()) + { + return; + } + + isClangCL_ = mrdocs::isClangCL(allCommands.front()); AllCommands_.reserve(allCommands.size()); SmallPathString temp; for (CompileCommand const& cmd0 : allCommands) @@ -567,6 +508,7 @@ MrDocsCompilationDatabase( cmd.CommandLine = adjustCommandLine( workingDir, cmd0.CommandLine, + isClangCL_, config, implicitIncludeDirectories, cmd0.Filename); diff --git a/src/lib/MrDocsCompilationDatabase.hpp b/src/lib/MrDocsCompilationDatabase.hpp index cb45862ea..8fd29414f 100644 --- a/src/lib/MrDocsCompilationDatabase.hpp +++ b/src/lib/MrDocsCompilationDatabase.hpp @@ -38,6 +38,7 @@ class MrDocsCompilationDatabase { std::vector AllCommands_; llvm::StringMap IndexByFile_; + bool isClangCL_{}; public: /** @@ -80,6 +81,13 @@ class MrDocsCompilationDatabase */ std::vector getAllCompileCommands() const override; + + /** Whether the driver mode for the compilation database is ClangCL */ + bool + isClangCL() const noexcept + { + return isClangCL_; + } }; } // mrdocs diff --git a/src/lib/SingleFileDB.hpp b/src/lib/SingleFileDB.hpp index 5139aba97..2146b7b8b 100644 --- a/src/lib/SingleFileDB.hpp +++ b/src/lib/SingleFileDB.hpp @@ -11,7 +11,6 @@ #ifndef MRDOCS_LIB_SINGLEFILEDB_HPP #define MRDOCS_LIB_SINGLEFILEDB_HPP -#include #include #include #include @@ -20,55 +19,39 @@ namespace mrdocs { -/** Compilation database for a single .cpp file. -*/ +/** Compilation database for a single file. + */ class SingleFileDB : public clang::tooling::CompilationDatabase { - std::vector cc_; + clang::tooling::CompileCommand cc_; public: - explicit - SingleFileDB( - llvm::StringRef pathName) - { - auto fileName = files::getFileName(pathName); - auto parentDir = files::getParentDir(pathName); - - std::vector cmds; - cmds.emplace_back("clang"); - cmds.emplace_back("-fsyntax-only"); - cmds.emplace_back("-std=c++23"); - cmds.emplace_back("-pedantic-errors"); - cmds.emplace_back("-Werror"); - cmds.emplace_back(fileName); - cc_.emplace_back( - parentDir, - fileName, - std::move(cmds), - parentDir); - cc_.back().Heuristic = "unit test"; - } + explicit SingleFileDB(clang::tooling::CompileCommand cc) + : cc_(std::move(cc)) + {} std::vector getCompileCommands( llvm::StringRef FilePath) const override { - if (FilePath != cc_.front().Filename) + if (FilePath != cc_.Filename) + { return {}; - return { cc_.front() }; + } + return { cc_ }; } std::vector getAllFiles() const override { - return { cc_.front().Filename }; + return { cc_.Filename }; } std::vector getAllCompileCommands() const override { - return { cc_.front() }; + return { cc_ }; } }; diff --git a/src/test/TestRunner.cpp b/src/test/TestRunner.cpp index 13de6b806..847e5825d 100644 --- a/src/test/TestRunner.cpp +++ b/src/test/TestRunner.cpp @@ -55,6 +55,7 @@ writeFile( return {}; } +namespace { void replaceCRLFWithLF(std::string &str) { @@ -65,6 +66,21 @@ replaceCRLFWithLF(std::string &str) } } +SingleFileDB +makeSingleFileDB(llvm::StringRef pathName, std::vector cmds) +{ + auto fileName = files::getFileName(pathName); + auto parentDir = files::getParentDir(pathName); + + cmds.push_back(std::string{ fileName }); + + clang::tooling::CompileCommand + cc(parentDir, fileName, std::move(cmds), parentDir); + cc.Heuristic = "unit test"; + return SingleFileDB(std::move(cc)); +} +} // namespace + void TestRunner:: handleFile( @@ -73,7 +89,6 @@ handleFile( { report::debug("Handling {}", filePath); - namespace fs = llvm::sys::fs; namespace path = llvm::sys::path; MRDOCS_ASSERT(path::extension(filePath).compare_insensitive(".cpp") == 0); @@ -108,16 +123,50 @@ handleFile( std::shared_ptr config = ConfigImpl::load(fileSettings, dirs_, threadPool_).value(); + auto parentDir = files::getParentDir(filePath); + std::unordered_map> + defaultIncludePaths; + + // Test normally + { + auto const db = makeSingleFileDB(filePath, { "clang", "-std=c++23" }); + + // Create an adjusted MrDocsDatabase + MrDocsCompilationDatabase compilations( + llvm::StringRef(parentDir), + db, + config, + defaultIncludePaths); + handleCompilationDatabase(filePath, compilations, config); + } + + // Test again in clang-cl mode + { + auto const db + = makeSingleFileDB(filePath, { "clang-cl", "/std:c++23preview" }); + + // Create an adjusted MrDocsDatabase + MrDocsCompilationDatabase compilations( + llvm::StringRef(parentDir), + db, + config, + defaultIncludePaths); + handleCompilationDatabase(filePath, compilations, config); + } +} + +void +TestRunner::handleCompilationDatabase( + llvm::StringRef filePath, + MrDocsCompilationDatabase const& compilations, + std::shared_ptr const& config) +{ + namespace path = llvm::sys::path; + // Path with the expected results SmallPathString expectedPath = filePath; path::replace_extension(expectedPath, gen_->fileExtension()); - // Create an adjusted MrDocsDatabase - auto parentDir = files::getParentDir(filePath); - std::unordered_map> defaultIncludePaths; - MrDocsCompilationDatabase compilations( - llvm::StringRef(parentDir), SingleFileDB(filePath), config, defaultIncludePaths); - report::debug("Building Corpus", filePath); auto corpus = CorpusImpl::build(config, compilations); if (!corpus) diff --git a/src/test/TestRunner.hpp b/src/test/TestRunner.hpp index 6e8974f17..f05964c72 100644 --- a/src/test/TestRunner.hpp +++ b/src/test/TestRunner.hpp @@ -12,8 +12,10 @@ #define MRDOCS_TEST_TESTRUNNER_HPP #include +#include #include #include +#include #include #include #include @@ -75,6 +77,12 @@ class TestRunner TestRunner(std::string_view generator); + void + handleCompilationDatabase( + llvm::StringRef filePath, + MrDocsCompilationDatabase const& compilations, + std::shared_ptr const& config); + /** Check a single file, or a directory recursively. This function checks the specified path diff --git a/src/test/lib/MrDocsCompilationDatabase.cpp b/src/test/lib/MrDocsCompilationDatabase.cpp new file mode 100644 index 000000000..8bfad9b9a --- /dev/null +++ b/src/test/lib/MrDocsCompilationDatabase.cpp @@ -0,0 +1,348 @@ +// +// 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) 2025 Agustin K-ballo Berge (agustinberge@gmail.com) +// Copyright (c) 2025 Matheus Izvekov (mizvekov@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/MrDocsCompilationDatabase.hpp" +#include "lib/ConfigImpl.hpp" +#include "lib/SingleFileDB.hpp" +#include +#include +#include +#include + +namespace mrdocs { + +namespace { +struct TestConfigImpl final : Config { + Settings settings_; + + ThreadPool& + threadPool() const noexcept final + { + MRDOCS_UNREACHABLE(); + } + + dom::Object const& + object() const final + { + MRDOCS_UNREACHABLE(); + } + + Settings const& + settings() const noexcept override + { + return settings_; + } +}; +} // namespace + +struct MrDocsCompilationDatabase_test { + auto + adjustCompileCommand( + std::vector commandLine, + std::shared_ptr config = std::make_shared< + TestConfigImpl>()) const + { + clang::tooling::CompileCommand cc; + cc.Directory = "."; + cc.Filename = "test.cpp"; // file does not exist + cc.CommandLine = std::move(commandLine); + cc.CommandLine.push_back(cc.Filename); + cc.Heuristic = "unit test"; + + // Create an adjusted MrDocsDatabase + std::unordered_map> + defaultIncludePaths; + MrDocsCompilationDatabase compilations( + "", + SingleFileDB(std::move(cc)), + config, + defaultIncludePaths); + return compilations.getAllCompileCommands().front().CommandLine; + } + + static bool + has(std::vector const& commandLine, std::string_view flag) + { + return mrdocs::contains(commandLine, flag); + } + static bool + has(std::vector const& commandLine, + std::initializer_list flags) + { + MRDOCS_ASSERT(flags.size() > 0); + return std::search( + commandLine.begin(), + commandLine.end(), + flags.begin(), + flags.end()) + != commandLine.end(); + } + + void + testClang_stdCxx() + { + std::string const programName = "clang"; + + { + auto adjusted = adjustCompileCommand({ programName }); + BOOST_TEST(has(adjusted, "-std=c++23")); + } + { + auto adjusted = adjustCompileCommand({ programName, "-std=c++11" }); + BOOST_TEST(has(adjusted, "-std=c++11")); + BOOST_TEST_NOT(has(adjusted, "-std=c++23")); + } + { + auto adjusted = adjustCompileCommand( + { programName, "--std=c++11" }); + BOOST_TEST(has(adjusted, "--std=c++11")); + BOOST_TEST_NOT(has(adjusted, "-std=c++23")); + } + } + + void + testClang_stdC() + { + std::string const programName = "clang"; + + { + auto adjusted = adjustCompileCommand({ programName, "-x", "c" }); + BOOST_TEST(has(adjusted, "-std=c23")); + } + { + auto adjusted = adjustCompileCommand( + { programName, "-x", "c", "-std=c11" }); + BOOST_TEST(has(adjusted, "-std=c11")); + BOOST_TEST_NOT(has(adjusted, "-std=c23")); + } + { + auto adjusted = adjustCompileCommand( + { programName, "-x", "c", "--std=c11" }); + BOOST_TEST(has(adjusted, "--std=c11")); + BOOST_TEST_NOT(has(adjusted, "-std=c23")); + } + } + + void + testClang_defines() + { + std::string const programName = "clang"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.defines = { "FOO", "BAR=1" }; + + auto adjusted + = adjustCompileCommand({ programName, "-DBAZ=2" }, config); + BOOST_TEST(has(adjusted, "-D__MRDOCS__")); + BOOST_TEST(has(adjusted, "-DFOO")); + BOOST_TEST(has(adjusted, "-DBAR=1")); + BOOST_TEST(has(adjusted, "-DBAZ=2")); + } + } + + void + testClang_stdlib() + { + std::string const programName = "clang"; + + { + std::shared_ptr const config = std::make_shared< + TestConfigImpl>(); + config->settings_.useSystemStdlib = false; + config->settings_.stdlibIncludes.push_back("stdlib-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, "-nostdinc++")); + BOOST_TEST(has(adjusted, { "-isystem", "stdlib-path" })); + } + } + + void + testClang_libc() + { + std::string const programName = "clang"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.useSystemLibc = false; + config->settings_.libcIncludes.push_back("libc-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, "-nostdinc")); + BOOST_TEST(has(adjusted, { "-isystem", "libc-path" })); + } + } + + void + testClang_systemIncludes() + { + std::string const programName = "clang"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.systemIncludes.push_back("system-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, { "-isystem", "system-path" })); + } + } + + void + testClang() + { + testClang_stdCxx(); + testClang_stdC(); + testClang_defines(); + testClang_stdlib(); + testClang_libc(); + testClang_systemIncludes(); + } + + void + testClangCL_stdCxx() + { + std::string const programName = "clang-cl"; + + { + auto adjusted = adjustCompileCommand({ programName }); + BOOST_TEST(has(adjusted, "-std:c++23preview")); + } + { + auto adjusted = adjustCompileCommand({ programName, "-std:c++11" }); + BOOST_TEST(has(adjusted, "-std:c++11")); + BOOST_TEST_NOT(has(adjusted, "-std:c++latest")); + } + { + auto adjusted = adjustCompileCommand({ programName, "/std:c++11" }); + BOOST_TEST(has(adjusted, "/std:c++11")); + BOOST_TEST_NOT(has(adjusted, "-std:c++latest")); + } + } + + void + testClangCL_stdC() + { + std::string const programName = "clang-cl"; + + { + auto adjusted = adjustCompileCommand({ programName, "-x", "c" }); + BOOST_TEST(has(adjusted, "-std:c17")); + } + { + auto adjusted = adjustCompileCommand( + { programName, "-x", "c", "-std:c11" }); + BOOST_TEST(has(adjusted, "-std:c11")); + BOOST_TEST_NOT(has(adjusted, "-std:clatest")); + } + { + auto adjusted = adjustCompileCommand( + { programName, "-x", "c", "/std:c11" }); + BOOST_TEST(has(adjusted, "/std:c11")); + BOOST_TEST_NOT(has(adjusted, "-std:clatest")); + } + } + + void + testClangCL_defines() + { + std::string const programName = "clang-cl"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.defines = { "FOO", "BAR=1" }; + + auto adjusted + = adjustCompileCommand({ programName, "-DBAZ=2" }, config); + BOOST_TEST(has(adjusted, "-D__MRDOCS__")); + BOOST_TEST(has(adjusted, "-DFOO")); + BOOST_TEST(has(adjusted, "-DBAR=1")); + BOOST_TEST(has(adjusted, "-DBAZ=2")); + } + } + + void + testClangCL_stdlib() + { + std::string const programName = "clang-cl"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.useSystemStdlib = false; + config->settings_.stdlibIncludes.push_back("stdlib-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, "-X")); + BOOST_TEST(has(adjusted, { "-external:I", "stdlib-path" })); + } + } + + void + testClangCL_libc() + { + std::string const programName = "clang-cl"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.useSystemLibc = false; + config->settings_.libcIncludes.push_back("libc-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, "-nostdinc")); + BOOST_TEST(has(adjusted, { "-external:I", "libc-path" })); + } + } + + void + testClangCL_systemIncludes() + { + std::string const programName = "clang-cl"; + + { + std::shared_ptr + config = std::make_shared(); + config->settings_.systemIncludes.push_back("system-path"); + + auto adjusted = adjustCompileCommand({ programName }, config); + BOOST_TEST(has(adjusted, { "-external:I", "system-path" })); + } + } + + void + testClangCL() + { + testClangCL_stdCxx(); + testClangCL_stdC(); + testClangCL_defines(); + testClangCL_stdlib(); + testClangCL_libc(); + testClangCL_systemIncludes(); + } + + void + run() + { + testClang(); + testClangCL(); + } +}; + +TEST_SUITE( + MrDocsCompilationDatabase_test, + "clang.mrdocs.MrDocsCompilationDatabase"); + +} // namespace mrdocs diff --git a/test-files/golden-tests/symbols/concept/requires-clause.adoc b/test-files/golden-tests/symbols/concept/requires-clause.adoc index 2edd8f71d..239e905cd 100644 --- a/test-files/golden-tests/symbols/concept/requires-clause.adoc +++ b/test-files/golden-tests/symbols/concept/requires-clause.adoc @@ -9,8 +9,8 @@ [cols=1] |=== | Name -| link:#A-08[`A`] -| link:#A-0b[`A`] +| link:#A-09[`A`] +| link:#A-01[`A`] |=== === Functions @@ -19,10 +19,10 @@ |=== | Name | link:#f[`f`] -| link:#g-0f[`g`] +| link:#g-0f49[`g`] |=== -[#A-08] +[#A-09] == A === Synopsis @@ -36,7 +36,7 @@ requires (sizeof(T) == 2) struct A; ---- -[#A-0b] +[#A-01] == A === Synopsis @@ -65,7 +65,7 @@ f() requires (sizeof(U) == 2); ---- -[#g-0f] +[#g-0f49] == g === Synopses @@ -78,10 +78,10 @@ Declared in `<requires‐clause.cpp>` template<typename T> requires (sizeof(T) == 2) void -link:#g-0e[g](); +link:#g-0d[g](); ---- -[.small]#link:#g-0e[_» more..._]# +[.small]#link:#g-0d[_» more..._]# [source,cpp,subs="verbatim,replacements,macros,-callouts"] @@ -89,10 +89,10 @@ link:#g-0e[g](); template<typename T> requires (sizeof(T) == 4) void -link:#g-02[g](); +link:#g-0f4d[g](); ---- -[.small]#link:#g-02[_» more..._]# +[.small]#link:#g-0f4d[_» more..._]# [source,cpp,subs="verbatim,replacements,macros,-callouts"] @@ -100,12 +100,12 @@ link:#g-02[g](); template<typename U> requires (sizeof(U) == 2) void -link:#g-08[g](); +link:#g-0b[g](); ---- -[.small]#link:#g-08[_» more..._]# +[.small]#link:#g-0b[_» more..._]# -[#g-0e] +[#g-0d] == g === Synopsis @@ -120,7 +120,7 @@ void g(); ---- -[#g-02] +[#g-0f4d] == g === Synopsis @@ -135,7 +135,7 @@ void g(); ---- -[#g-08] +[#g-0b] == g === Synopsis diff --git a/test-files/golden-tests/symbols/concept/requires-clause.html b/test-files/golden-tests/symbols/concept/requires-clause.html index 17813a4c7..528e81d3e 100644 --- a/test-files/golden-tests/symbols/concept/requires-clause.html +++ b/test-files/golden-tests/symbols/concept/requires-clause.html @@ -18,8 +18,8 @@

Types

-A -A +A +A @@ -33,14 +33,14 @@

Functions

f -g +g
-

A

+

A

Synopsis

@@ -57,7 +57,7 @@

Synopsis

-

A

+

A

Synopsis

@@ -90,7 +90,7 @@

Synopsis

-

g

+

g

Synopses

@@ -101,31 +101,31 @@

Synopses

template<typename T> requires (sizeof(T) == 2) void -g(); -» more... +g(); +» more...
 template<typename T>
 requires (sizeof(T) == 4)
 void
-g();
-
» more... +g(); +» more...
 template<typename U>
 requires (sizeof(U) == 2)
 void
-g();
-
» more... +g(); +» more...
-

g

+

g

Synopsis

@@ -141,7 +141,7 @@

Synopsis

-

g

+

g

Synopsis

@@ -157,7 +157,7 @@

Synopsis

-

g

+

g

Synopsis

diff --git a/test-files/golden-tests/symbols/concept/requires-clause.xml b/test-files/golden-tests/symbols/concept/requires-clause.xml index f433a13bc..ea8c88c38 100644 --- a/test-files/golden-tests/symbols/concept/requires-clause.xml +++ b/test-files/golden-tests/symbols/concept/requires-clause.xml @@ -4,13 +4,13 @@ @@ -24,19 +24,19 @@ diff --git a/test-files/golden-tests/symbols/record/dtor-overloads.adoc b/test-files/golden-tests/symbols/record/dtor-overloads.adoc index 4dabff5b6..191bbac16 100644 --- a/test-files/golden-tests/symbols/record/dtor-overloads.adoc +++ b/test-files/golden-tests/symbols/record/dtor-overloads.adoc @@ -60,11 +60,11 @@ Destructor [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- -link:#A-2destructor-07[~A]() +link:#A-2destructor-0b[~A]() requires (sizeof(T) <= 4); ---- -[.small]#link:#A-2destructor-07[_» more..._]# +[.small]#link:#A-2destructor-0b[_» more..._]# [#A-2destructor-0c] == link:#A[A]::~A @@ -81,7 +81,7 @@ Declared in `<dtor‐overloads.cpp>` requires (sizeof(T) > 4); ---- -[#A-2destructor-07] +[#A-2destructor-0b] == link:#A[A]::~A Destructor diff --git a/test-files/golden-tests/symbols/record/dtor-overloads.html b/test-files/golden-tests/symbols/record/dtor-overloads.html index fc3416561..56b0e810e 100644 --- a/test-files/golden-tests/symbols/record/dtor-overloads.html +++ b/test-files/golden-tests/symbols/record/dtor-overloads.html @@ -72,9 +72,9 @@

Synopses

Destructor

-~A()
+~A()
 requires (sizeof(T) <= 4);
-
» more... +» more...
@@ -98,7 +98,7 @@

Synopsis

-

A::~A

+

A::~A

Destructor

diff --git a/test-files/golden-tests/symbols/record/dtor-overloads.xml b/test-files/golden-tests/symbols/record/dtor-overloads.xml index fbb173113..a9f707c79 100644 --- a/test-files/golden-tests/symbols/record/dtor-overloads.xml +++ b/test-files/golden-tests/symbols/record/dtor-overloads.xml @@ -6,7 +6,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/test-files/golden-tests/templates/at_dtst.adoc b/test-files/golden-tests/templates/at_dtst.adoc new file mode 100644 index 000000000..252e32a23 --- /dev/null +++ b/test-files/golden-tests/templates/at_dtst.adoc @@ -0,0 +1,29 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Types + +[cols=1] +|=== +| Name +| link:#A[`A`] +|=== + +[#A] +== A + +=== Synopsis + +Declared in `<at_dtst.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<class T> +using A = T::XX; +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/templates/at_dtst.cpp b/test-files/golden-tests/templates/at_dtst.cpp new file mode 100644 index 000000000..7c8023dbd --- /dev/null +++ b/test-files/golden-tests/templates/at_dtst.cpp @@ -0,0 +1,2 @@ +template +using A = T::template XX<>; diff --git a/test-files/golden-tests/templates/at_dtst.html b/test-files/golden-tests/templates/at_dtst.html new file mode 100644 index 000000000..06820cdb2 --- /dev/null +++ b/test-files/golden-tests/templates/at_dtst.html @@ -0,0 +1,46 @@ + + +Reference + + +
+

Reference

+
+
+

+
+

Types

+ + + + + + + + + + +
Name
A
+ +
+
+
+

A

+
+
+

Synopsis

+
+Declared in <at_dtst.cpp>
+
+template<class T>
+using A = T::XX;
+
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/templates/at_dtst.xml b/test-files/golden-tests/templates/at_dtst.xml new file mode 100644 index 000000000..cd4b8e545 --- /dev/null +++ b/test-files/golden-tests/templates/at_dtst.xml @@ -0,0 +1,13 @@ + + + + + +